Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions chain/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[dependencies]
anyhow = { workspace = true }
bcs-ext = { workspace = true }
dashmap = { workspace = true }
starcoin-crypto = { workspace = true }
starcoin-logger = { workspace = true }
proptest = { optional = true, workspace = true }
Expand Down
22 changes: 20 additions & 2 deletions chain/open-block/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ pub struct OpenedBlock {
version: Version,
pruning_point: HashValue,
parents_hash: Vec<HashValue>,
// Cached outputs for reuse during block execution
cached_vm1_outputs: Vec<TransactionOutput>,
cached_vm2_outputs: Vec<starcoin_vm2_types::transaction::TransactionOutput>,
}

impl OpenedBlock {
Expand Down Expand Up @@ -122,6 +125,8 @@ impl OpenedBlock {
version,
pruning_point,
parents_hash: tips_hash.clone(),
cached_vm1_outputs: vec![],
cached_vm2_outputs: vec![],
};

opened_block.initialize()?;
Expand Down Expand Up @@ -225,6 +230,7 @@ impl OpenedBlock {
debug!("txn {:?} execute error: {:?}", txn_hash, status);
}
let gas_used = output.gas_used();
self.cached_vm1_outputs.push(output.clone());
self.push_txn_and_state(txn_hash, output, index == last_index)?;
self.gas_used += gas_used;
self.included_user_txns
Expand Down Expand Up @@ -265,6 +271,8 @@ impl OpenedBlock {
);
}
TransactionStatus::Keep(_) => {
// Cache BlockMetadata output for reuse during block execution
self.cached_vm1_outputs.push(output.clone());
let _ = self.push_txn_and_state(block_meta_txn_hash, output, true)?;
}
TransactionStatus::Retry => {
Expand Down Expand Up @@ -331,7 +339,13 @@ impl OpenedBlock {
}

/// Construct a block template for mining.
pub fn finalize(self) -> Result<BlockTemplate> {
pub fn finalize(
self,
) -> Result<(
BlockTemplate,
Vec<TransactionOutput>,
Vec<starcoin_vm2_types::transaction::TransactionOutput>,
)> {
let accumulator_root = self.txn_accumulator.root_hash();
// update state_root accumulator, state_root order is important
let (state_root, state_root1, state_root2) = {
Expand Down Expand Up @@ -367,7 +381,11 @@ impl OpenedBlock {
self.pruning_point,
self.parents_hash.clone(),
);
Ok(block_template)
Ok((
block_template,
self.cached_vm1_outputs,
self.cached_vm2_outputs,
))
}
}

Expand Down
5 changes: 5 additions & 0 deletions chain/open-block/src/vm2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ impl OpenedBlock {
);
}
TransactionStatus2::Keep(_) => {
// Cache BlockMetadata output for reuse during block execution
self.cached_vm2_outputs.push(output.clone());
self.push_txn_and_state2(block_meta_txn_hash, output, true)?;
}
TransactionStatus2::Retry => {
Expand Down Expand Up @@ -102,6 +104,7 @@ impl OpenedBlock {
debug!("txn {:?} execute error: {:?}", txn_hash, status);
}
let gas_used = output.gas_used();
self.cached_vm2_outputs.push(output.clone());
self.push_txn_and_state2(txn_hash, output, false)?;
self.gas_used += gas_used;
self.included_user_txns2
Expand Down Expand Up @@ -148,6 +151,8 @@ impl OpenedBlock {
);
}
TransactionStatus2::Keep(_) => {
// Cache BlockEpilogue output for reuse during block execution
self.cached_vm2_outputs.push(output.clone());
self.push_txn_and_state2(block_epilogue_txn_hash, output, true)?;
}
TransactionStatus2::Retry => {
Expand Down
89 changes: 71 additions & 18 deletions chain/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use crate::{
fixed_blocks::MAIN_DIRECT_SAVE_BLOCK_HASH_MAP,
get_merge_bound_hash,
txn_output_cache::global_txn_output_cache,
verifier::{BlockVerifier, FullVerifier},
};
use anyhow::{bail, ensure, format_err, Result};
Expand Down Expand Up @@ -449,7 +450,7 @@ impl BlockChain {
if vm2_contains_user_transaction {
opened_block.finalize_block_epilogue()?;
}
let template = opened_block.finalize()?;
let (template, _vm1_outputs, _vm2_outputs) = opened_block.finalize()?;

Ok((template, excluded_txns.absorb(excluded_txns2)))
}
Expand Down Expand Up @@ -2218,22 +2219,47 @@ impl BlockChain {
format_err!("Can not find block info for parent {:?}", selected_parent)
})?;

let multi_state = self.storage.0.get_vm_multi_state(selected_parent)?;

let statedb = self.statedb.0.fork_at(multi_state.state_root1());
let statedb2 = self.statedb.1.fork_at(multi_state.state_root2());
let statedb = &self.statedb.0;
let statedb2 = &self.statedb.1;

// Get epoch from forked statedb (read from VM2's statedb)
let epoch = get_epoch_from_statedb(&statedb2)?;
let epoch = get_epoch_from_statedb(statedb2)?;

// Check cache for pre-executed outputs
// Use txn_accumulator_root as key since it uniquely identifies the transaction list
let cache = global_txn_output_cache();
let cached_outputs = cache.get(header.txn_accumulator_root());

// Execute VM1 transactions
let executed_data = if !transactions.is_empty() {
starcoin_executor::block_execute(
&statedb,
transactions.clone(),
epoch.block_gas_limit(),
self.vm_metrics.clone(),
)?
if let Some(ref cached) = cached_outputs {
if let Some(ref vm1_outputs) = cached.vm1_outputs {
info!(
"Using cached VM1 outputs for block {:?}, outputs count: {}",
block_id,
vm1_outputs.len()
);
starcoin_executor::block_execute_with_outputs(
statedb,
transactions.clone(),
vm1_outputs.as_ref().clone(),
)?
} else {
starcoin_executor::block_execute(
statedb,
transactions.clone(),
epoch.block_gas_limit(),
self.vm_metrics.clone(),
)?
}
} else {
starcoin_executor::block_execute(
statedb,
transactions.clone(),
epoch.block_gas_limit(),
self.vm_metrics.clone(),
)?
}
} else {
BlockExecutedData {
state_root: statedb.state_root(),
Expand Down Expand Up @@ -2261,12 +2287,39 @@ impl BlockChain {
.iter()
.fold(0u64, |acc, info| acc.saturating_add(info.gas_used()));

let executed_data2 = starcoin_vm2_chain::execute_transactions(
&statedb2,
transactions2.clone(),
epoch.block_gas_limit() - vm1_gas_used,
self.vm_metrics.clone(),
)?;
let executed_data2 = if let Some(ref cached) = cached_outputs {
if let Some(ref vm2_outputs) = cached.vm2_outputs {
info!(
"Using cached VM2 outputs for block {:?}, outputs count: {}",
block_id,
vm2_outputs.len()
);
starcoin_vm2_chain::execute_transactions_with_outputs(
statedb2,
transactions2.clone(),
vm2_outputs.as_ref().clone(),
)?
} else {
starcoin_vm2_chain::execute_transactions(
statedb2,
transactions2.clone(),
epoch.block_gas_limit() - vm1_gas_used,
self.vm_metrics.clone(),
)?
}
} else {
starcoin_vm2_chain::execute_transactions(
statedb2,
transactions2.clone(),
epoch.block_gas_limit() - vm1_gas_used,
self.vm_metrics.clone(),
)?
};

// Remove cached outputs after use
if cached_outputs.is_some() {
cache.remove(header.txn_accumulator_root());
}

// Update state roots
let (state_root, multi_state, vm_state_accumulator) = {
Expand Down
2 changes: 2 additions & 0 deletions chain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
#![allow(clippy::arithmetic_side_effects)]
mod chain;
mod fixed_blocks;
pub mod txn_output_cache;
pub mod verifier;
use std::sync::Arc;

pub use chain::BlockChain;
use starcoin_accumulator::{node::AccumulatorStoreType, Accumulator, MerkleAccumulator};
pub use starcoin_chain_api::{ChainReader, ChainWriter};
pub use txn_output_cache::{global_txn_output_cache, CachedBlockOutputs, TransactionOutputCache};

pub use starcoin_data_migration::*;

Expand Down
151 changes: 151 additions & 0 deletions chain/src/txn_output_cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright (c) The Starcoin Core Contributors
// SPDX-License-Identifier: Apache-2.0

use dashmap::DashMap;
use once_cell::sync::Lazy;
use starcoin_crypto::HashValue;
use starcoin_vm2_vm_types::transaction::TransactionOutput as TransactionOutput2;
use starcoin_vm_types::transaction::TransactionOutput as TransactionOutput1;
use std::sync::Arc;

/// Cached block outputs containing VM1 and VM2 transaction outputs
#[derive(Debug, Clone)]
pub struct CachedBlockOutputs {
pub vm1_outputs: Option<Arc<Vec<TransactionOutput1>>>,
pub vm2_outputs: Option<Arc<Vec<TransactionOutput2>>>,
}

impl CachedBlockOutputs {
pub fn new(
vm1_outputs: Option<Vec<TransactionOutput1>>,
vm2_outputs: Option<Vec<TransactionOutput2>>,
) -> Self {
Self {
vm1_outputs: vm1_outputs.map(Arc::new),
vm2_outputs: vm2_outputs.map(Arc::new),
}
}
}

/// Cache for transaction outputs, keyed by txn_accumulator_root
///
/// The txn_accumulator_root uniquely identifies a transaction list,
/// so it can be used as a cache key even before the block_id is known.
pub struct TransactionOutputCache {
cache: DashMap<HashValue, CachedBlockOutputs>,
}

impl TransactionOutputCache {
pub fn new() -> Self {
Self {
cache: DashMap::new(),
}
}

/// Insert outputs using txn_accumulator_root as key
pub fn insert_outputs(
&self,
txn_accumulator_root: HashValue,
vm1_outputs: Option<Vec<TransactionOutput1>>,
vm2_outputs: Option<Vec<TransactionOutput2>>,
) {
let cached = CachedBlockOutputs::new(vm1_outputs, vm2_outputs);
self.cache.insert(txn_accumulator_root, cached);
}

/// Get outputs using txn_accumulator_root as key
pub fn get(&self, txn_accumulator_root: HashValue) -> Option<CachedBlockOutputs> {
self.cache.get(&txn_accumulator_root).map(|v| v.clone())
}

/// Remove outputs using txn_accumulator_root as key
pub fn remove(&self, txn_accumulator_root: HashValue) {
self.cache.remove(&txn_accumulator_root);
}

pub fn len(&self) -> usize {
self.cache.len()
}

pub fn is_empty(&self) -> bool {
self.cache.is_empty()
}

pub fn clear(&self) {
self.cache.clear();
}
}

impl Default for TransactionOutputCache {
fn default() -> Self {
Self::new()
}
}

static GLOBAL_TXN_OUTPUT_CACHE: Lazy<TransactionOutputCache> =
Lazy::new(TransactionOutputCache::new);

pub fn global_txn_output_cache() -> &'static TransactionOutputCache {
&GLOBAL_TXN_OUTPUT_CACHE
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_cache_insert_and_get() {
let cache = TransactionOutputCache::new();
let txn_root = HashValue::random();

assert!(cache.is_empty());
assert!(cache.get(txn_root).is_none());

cache.insert_outputs(txn_root, None, None);
assert_eq!(cache.len(), 1);

let result = cache.get(txn_root);
assert!(result.is_some());
}

#[test]
fn test_cache_remove() {
let cache = TransactionOutputCache::new();
let txn_root = HashValue::random();

cache.insert_outputs(txn_root, None, None);
assert_eq!(cache.len(), 1);

cache.remove(txn_root);
assert!(cache.is_empty());
assert!(cache.get(txn_root).is_none());
}

#[test]
fn test_cache_clear() {
let cache = TransactionOutputCache::new();

for _ in 0..5 {
let txn_root = HashValue::random();
cache.insert_outputs(txn_root, None, None);
}

assert_eq!(cache.len(), 5);
cache.clear();
assert!(cache.is_empty());
}

#[test]
fn test_global_cache() {
let cache = global_txn_output_cache();
let initial_len = cache.len();

let txn_root = HashValue::random();
cache.insert_outputs(txn_root, None, None);

assert_eq!(cache.len(), initial_len + 1);

cache.remove(txn_root);
assert_eq!(cache.len(), initial_len);
}
}
Loading
Loading