diff --git a/src/chain/store/chain_store.rs b/src/chain/store/chain_store.rs index 1aa5f057d45a..d17bc6771e95 100644 --- a/src/chain/store/chain_store.rs +++ b/src/chain/store/chain_store.rs @@ -6,13 +6,11 @@ use super::{ index::{ChainIndex, ResolveNullTipset}, tipset_tracker::TipsetTracker, }; -use crate::libp2p_bitswap::{BitswapStoreRead, BitswapStoreReadWrite}; use crate::message::{ChainMessage, SignedMessage}; use crate::networks::{ChainConfig, Height}; use crate::rpc::eth::{eth_tx_from_signed_eth_message, types::EthHash}; use crate::shim::clock::ChainEpoch; use crate::shim::{executor::Receipt, message::Message, version::NetworkVersion}; -use crate::state_manager::StateOutput; use crate::utils::db::{BlockstoreExt, CborStoreExt}; use crate::{ blocks::{CachingBlockHeader, Tipset, TipsetKey, TxMeta}, @@ -27,6 +25,10 @@ use crate::{ interpreter::{BlockMessages, VMTrace}, rpc::chain::PathChanges, }; +use crate::{ + libp2p_bitswap::{BitswapStoreRead, BitswapStoreReadWrite}, + state_manager::ExecutedTipset, +}; use ahash::{HashMap, HashSet}; use anyhow::Context as _; use cid::Cid; @@ -384,7 +386,7 @@ where // state-root without caching. let genesis_timestamp = heaviest_tipset.genesis(chain_index.db())?.timestamp; let beacon = Arc::new(chain_config.get_beacon_schedule(genesis_timestamp)); - let StateOutput { state_root, .. } = crate::state_manager::apply_block_messages( + let ExecutedTipset { state_root, .. } = crate::state_manager::apply_block_messages( genesis_timestamp, Arc::clone(chain_index), Arc::clone(chain_config), diff --git a/src/chain_sync/tipset_syncer.rs b/src/chain_sync/tipset_syncer.rs index 1c6993eeda90..04f647c1734e 100644 --- a/src/chain_sync/tipset_syncer.rs +++ b/src/chain_sync/tipset_syncer.rs @@ -11,7 +11,7 @@ use crate::shim::{ address::Address, crypto::verify_bls_aggregate, econ::BLOCK_GAS_LIMIT, gas::price_list_by_network_version, message::Message, state_tree::StateTree, }; -use crate::state_manager::StateLookupPolicy; +use crate::state_manager::ExecutedTipset; use crate::state_manager::{Error as StateManagerError, StateManager, utils::is_valid_for_sending}; use crate::{ blocks::{Block, CachingBlockHeader, Error as ForestBlockError, FullTipset, Tipset}, @@ -280,8 +280,12 @@ async fn validate_block( let block = block.clone(); async move { let header = block.header(); - let (state_root, receipt_root) = state_manager - .tipset_state(&base_tipset, StateLookupPolicy::Disabled) + let ExecutedTipset { + state_root, + receipt_root, + .. + } = state_manager + .load_executed_tipset(&base_tipset) .await .map_err(|e| { TipsetSyncerError::Calculation(format!("Failed to calculate state: {e}")) @@ -441,8 +445,8 @@ async fn check_block_messages( }; let mut account_sequences: HashMap = HashMap::default(); - let (state_root, _) = state_manager - .tipset_state(&base_tipset, StateLookupPolicy::Disabled) + let ExecutedTipset { state_root, .. } = state_manager + .load_executed_tipset(&base_tipset) .await .map_err(|e| TipsetSyncerError::Calculation(format!("Could not update state: {e}")))?; let tree = diff --git a/src/daemon/db_util.rs b/src/daemon/db_util.rs index 1ef93db810a2..8261d0f380f0 100644 --- a/src/daemon/db_util.rs +++ b/src/daemon/db_util.rs @@ -6,11 +6,10 @@ use crate::db::car::forest::{ FOREST_CAR_FILE_EXTENSION, TEMP_FOREST_CAR_FILE_EXTENSION, new_forest_car_temp_path_in, }; use crate::db::car::{ForestCar, ManyCar}; -use crate::interpreter::VMTrace; use crate::networks::ChainConfig; use crate::rpc::sync::SnapshotProgressTracker; use crate::shim::clock::ChainEpoch; -use crate::state_manager::{NO_CALLBACK, StateManager}; +use crate::state_manager::StateManager; use crate::utils::db::car_stream::CarStream; use crate::utils::io::EitherMmapOrRandomAccessFile; use crate::utils::net::{DownloadFileOption, download_to}; @@ -339,9 +338,7 @@ where let epoch = ts.epoch(); let tsk = ts.key().clone(); - state_manager - .compute_tipset_state(ts.clone(), NO_CALLBACK, VMTrace::NotTraced) - .await?; + state_manager.load_executed_tipset(ts).await?; delegated_messages.append( &mut state_manager diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index a83a86cfc7a0..8dbb4709f0a0 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -624,7 +624,6 @@ pub(super) async fn start_services( } on_app_context_and_db_initialized(&ctx, chain_follower.sync_status.clone()); warmup_in_background(&ctx); - ctx.state_manager.populate_cache(); maybe_start_metrics_service(&mut services, &config, &ctx).await?; maybe_start_f3_service(opts, &config, &ctx)?; maybe_start_health_check_service(&mut services, &config, &p2p_service, &chain_follower, &ctx) diff --git a/src/dev/subcommands/state_cmd.rs b/src/dev/subcommands/state_cmd.rs index b8b4ef79b4d5..ed122c77d517 100644 --- a/src/dev/subcommands/state_cmd.rs +++ b/src/dev/subcommands/state_cmd.rs @@ -10,7 +10,7 @@ use crate::{ interpreter::VMTrace, networks::{ChainConfig, NetworkChain}, shim::clock::ChainEpoch, - state_manager::{StateManager, StateOutput}, + state_manager::{ExecutedTipset, StateManager}, tool::subcommands::api_cmd::generate_test_snapshot, }; use human_repr::HumanCount as _; @@ -106,9 +106,10 @@ impl ComputeCommand { let epoch = ts.epoch(); let state_manager = Arc::new(StateManager::new(chain_store)?); - let StateOutput { + let ExecutedTipset { state_root, receipt_root, + .. } = state_manager .compute_tipset_state(ts, crate::state_manager::NO_CALLBACK, VMTrace::NotTraced) .await?; diff --git a/src/interpreter/vm.rs b/src/interpreter/vm.rs index 9dc3e26939bb..f2e2baf334be 100644 --- a/src/interpreter/vm.rs +++ b/src/interpreter/vm.rs @@ -77,8 +77,11 @@ type ForestExecutorV4 = DefaultExecutor_v4>; pub type ApplyResult = anyhow::Result<(ApplyRet, Duration)>; -pub type ApplyBlockResult = - anyhow::Result<(Vec, Vec>, Vec>), anyhow::Error>; +pub type ApplyBlockResult = anyhow::Result<( + Vec, + Vec>>, + Vec>, +)>; /// Comes from pub const IMPLICIT_MESSAGE_GAS_LIMIT: i64 = i64::MAX / 2; @@ -381,7 +384,11 @@ where receipts.push(msg_receipt.clone()); events_roots.push(ret.msg_receipt().events_root()); - events.push(ret.events()); + if ret.msg_receipt().events_root().is_some() { + events.push(Some(ret.events())); + } else { + events.push(None); + } // Add processed Cid to set of processed messages processed.insert(cid); diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 7fea00380ce8..9f09bd2c63d0 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -25,7 +25,6 @@ use crate::eth::{ EAMMethod, EVMMethod, EthChainId as EthChainIdType, EthEip1559TxArgs, EthLegacyEip155TxArgs, EthLegacyHomesteadTxArgs, parse_eth_transaction, }; -use crate::interpreter::VMTrace; use crate::lotus_json::{HasLotusJson, lotus_json_with_self}; use crate::message::{ChainMessage, Message as _, MessageRead as _, SignedMessage}; use crate::rpc::{ @@ -52,7 +51,7 @@ use crate::shim::gas::GasOutputs; use crate::shim::message::Message; use crate::shim::trace::{CallReturn, ExecutionEvent}; use crate::shim::{clock::ChainEpoch, state_tree::StateTree}; -use crate::state_manager::{ExecutedMessage, ExecutedTipset, StateLookupPolicy, VMFlush}; +use crate::state_manager::{ExecutedMessage, ExecutedTipset, TipsetState, VMFlush}; use crate::utils::cache::SizeTrackingLruCache; use crate::utils::db::BlockstoreExt as _; use crate::utils::encoding::from_slice_with_fallback; @@ -500,10 +499,8 @@ impl Block { let ExecutedTipset { state_root, executed_messages, - } = ctx - .state_manager - .load_executed_tipset_without_events(&tipset) - .await?; + .. + } = ctx.state_manager.load_executed_tipset(&tipset).await?; let has_transactions = !executed_messages.is_empty(); let state_tree = ctx.state_manager.get_state_tree(&state_root)?; @@ -514,19 +511,19 @@ impl Block { ExecutedMessage { message, receipt, .. }, - ) in executed_messages.into_iter().enumerate() + ) in executed_messages.iter().enumerate() { let ti = EthUint64(i as u64); gas_used += receipt.gas_used(); let mut tx = match message { ChainMessage::Signed(smsg) => new_eth_tx_from_signed_message( - &smsg, + smsg, &state_tree, ctx.chain_config().eth_chain_id, )?, ChainMessage::Unsigned(msg) => { let tx = eth_tx_from_native_message( - &msg, + msg, &state_tree, ctx.chain_config().eth_chain_id, )?; @@ -920,11 +917,8 @@ async fn eth_get_balance( ts: &Tipset, ) -> Result { let fil_addr = address.to_filecoin_address()?; - let (state_cid, _) = ctx - .state_manager - .tipset_state(ts, StateLookupPolicy::Enabled) - .await?; - let state_tree = ctx.state_manager.get_state_tree(&state_cid)?; + let TipsetState { state_root, .. } = ctx.state_manager.load_tipset_state(ts).await?; + let state_tree = ctx.state_manager.get_state_tree(&state_root)?; match state_tree.get_actor(&fil_addr)? { Some(actor) => Ok(EthBigInt(actor.balance.atto().clone())), None => Ok(EthBigInt::default()), // Balance is 0 if the actor doesn't exist @@ -1424,10 +1418,8 @@ async fn get_block_receipts( let ExecutedTipset { state_root, executed_messages, - } = ctx - .state_manager - .load_executed_tipset_without_events(&ts_ref) - .await?; + .. + } = ctx.state_manager.load_executed_tipset(&ts_ref).await?; // Load the state tree let state_tree = ctx.state_manager.get_state_tree(&state_root)?; @@ -1438,7 +1430,7 @@ async fn get_block_receipts( ExecutedMessage { message, receipt, .. }, - ) in executed_messages.into_iter().enumerate() + ) in executed_messages.iter().enumerate() { let tx = new_eth_tx( ctx, @@ -1449,7 +1441,7 @@ async fn get_block_receipts( i as u64, )?; - let receipt = new_eth_tx_receipt(ctx, &ts_ref, &tx, &receipt).await?; + let receipt = new_eth_tx_receipt(ctx, &ts_ref, &tx, receipt).await?; eth_receipts.push(receipt); } Ok(eth_receipts) @@ -1756,7 +1748,7 @@ where { let (invoc_res, _) = ctx .state_manager - .apply_on_state_with_gas(tipset, msg, StateLookupPolicy::Enabled, VMFlush::Skip) + .apply_on_state_with_gas(tipset, msg, VMFlush::Skip) .await .map_err(|e| anyhow::anyhow!("failed to apply on state with gas: {e}"))?; @@ -1790,8 +1782,7 @@ where DB: Blockstore + Send + Sync + 'static, { let (_invoc_res, apply_ret, prior_messages, ts) = - gas::GasEstimateGasLimit::estimate_call_with_gas(data, msg.clone(), tsk, VMTrace::Traced) - .await?; + gas::GasEstimateGasLimit::estimate_call_with_gas(data, msg.clone(), tsk).await?; if apply_ret.msg_receipt().exit_code().is_success() { return Ok(msg.gas_limit()); } @@ -1847,14 +1838,7 @@ where msg.gas_limit = limit; let (_invoc_res, apply_ret, _, _) = data .state_manager - .call_with_gas( - &mut msg.into(), - prior_messages, - Some(ts), - VMTrace::NotTraced, - StateLookupPolicy::Enabled, - VMFlush::Skip, - ) + .call_with_gas(&mut msg.into(), prior_messages, Some(ts), VMFlush::Skip) .await?; Ok(apply_ret.msg_receipt().exit_code().is_success()) } @@ -1938,14 +1922,11 @@ async fn eth_fee_history( let base_fee = &ts.block_headers().first().parent_base_fee; let ExecutedTipset { executed_messages, .. - } = ctx - .state_manager - .load_executed_tipset_without_events(&ts) - .await?; + } = ctx.state_manager.load_executed_tipset(&ts).await?; let mut tx_gas_rewards = Vec::with_capacity(executed_messages.len()); for ExecutedMessage { message, receipt, .. - } in executed_messages + } in executed_messages.iter() { let premium = message.effective_gas_premium(base_fee); tx_gas_rewards.push(GasReward { @@ -2068,11 +2049,8 @@ where DB: Blockstore + Send + Sync + 'static, { let to_address = FilecoinAddress::try_from(eth_address)?; - let (state, _) = ctx - .state_manager - .tipset_state(ts, StateLookupPolicy::Enabled) - .await?; - let state_tree = ctx.state_manager.get_state_tree(&state)?; + let TipsetState { state_root, .. } = ctx.state_manager.load_tipset_state(ts).await?; + let state_tree = ctx.state_manager.get_state_tree(&state_root)?; let Some(actor) = state_tree .get_actor(&to_address) .with_context(|| format!("failed to lookup contract {}", eth_address.0))? @@ -2096,7 +2074,10 @@ where let api_invoc_result = 'invoc: { for ts in ts.clone().chain(ctx.store()) { - match ctx.state_manager.call_on_state(state, &message, Some(ts)) { + match ctx + .state_manager + .call_on_state(state_root, &message, Some(ts)) + { Ok(res) => { break 'invoc res; } @@ -2161,14 +2142,11 @@ async fn get_storage_at( position: EthBytes, ) -> Result { let to_address = FilecoinAddress::try_from(ð_address)?; - let (state, _) = ctx - .state_manager - .tipset_state(&ts, StateLookupPolicy::Enabled) - .await?; + let TipsetState { state_root, .. } = ctx.state_manager.load_tipset_state(&ts).await?; let make_empty_result = || EthBytes(vec![0; EVM_WORD_LENGTH]); let Some(actor) = ctx .state_manager - .get_actor(&to_address, state) + .get_actor(&to_address, state_root) .with_context(|| format!("failed to lookup contract {}", eth_address.0))? else { return Ok(make_empty_result()); @@ -2189,7 +2167,10 @@ async fn get_storage_at( }; let api_invoc_result = 'invoc: { for ts in ts.chain(ctx.store()) { - match ctx.state_manager.call_on_state(state, &message, Some(ts)) { + match ctx + .state_manager + .call_on_state(state_root, &message, Some(ts)) + { Ok(res) => { break 'invoc res; } @@ -2259,12 +2240,9 @@ async fn eth_get_transaction_count( where B: Blockstore + Send + Sync + 'static, { - let (state_cid, _) = ctx - .state_manager - .tipset_state(ts, StateLookupPolicy::Enabled) - .await?; + let TipsetState { state_root, .. } = ctx.state_manager.load_tipset_state(ts).await?; - let state_tree = ctx.state_manager.get_state_tree(&state_cid)?; + let state_tree = ctx.state_manager.get_state_tree(&state_root)?; let actor = match state_tree.get_actor(&addr)? { Some(actor) => actor, None => return Ok(EthUint64(0)), @@ -3474,21 +3452,19 @@ impl RpcMethod<3> for EthTraceCall { .tipset_by_block_number_or_hash(block_param, ResolveNullTipset::TakeOlder) .await?; - let (pre_state_root, _) = ctx + let TipsetState { + state_root: pre_state_root, + .. + } = ctx .state_manager - .tipset_state(&ts, StateLookupPolicy::Enabled) + .load_tipset_state(&ts) .await .map_err(|e| anyhow::anyhow!("failed to get tipset state: {e}"))?; let pre_state = StateTree::new_from_root(ctx.store_owned(), &pre_state_root)?; let (invoke_result, post_state_root) = ctx .state_manager - .apply_on_state_with_gas( - Some(ts.clone()), - msg.clone(), - StateLookupPolicy::Enabled, - VMFlush::Flush, - ) + .apply_on_state_with_gas(Some(ts.clone()), msg.clone(), VMFlush::Flush) .await .map_err(|e| anyhow::anyhow!("failed to apply message: {e}"))?; let post_state_root = diff --git a/src/rpc/methods/eth/filter/mod.rs b/src/rpc/methods/eth/filter/mod.rs index 327d7643a480..7fd1e2695965 100644 --- a/src/rpc/methods/eth/filter/mod.rs +++ b/src/rpc/methods/eth/filter/mod.rs @@ -38,7 +38,7 @@ use crate::rpc::types::{Event, EventEntry}; use crate::shim::address::Address; use crate::shim::clock::ChainEpoch; use crate::shim::executor::{Entry, StampedEvent}; -use crate::state_manager::ExecutedMessage; +use crate::state_manager::{ExecutedMessage, ExecutedTipset}; use crate::utils::misc::env::env_or_default; use ahash::AHashMap as HashMap; use anyhow::{Context, Error, anyhow, bail, ensure}; @@ -269,14 +269,16 @@ impl EthEventHandler { ) -> anyhow::Result<()> { let height = tipset.epoch(); let tipset_key = tipset.key(); - let executed_tipset = ctx.state_manager.load_executed_tipset(tipset).await?; + let ExecutedTipset { + executed_messages, .. + } = ctx.state_manager.load_executed_tipset(tipset).await?; let mut event_count = 0; for ( msg_idx, ExecutedMessage { message, events, .. }, - ) in executed_tipset.executed_messages.into_iter().enumerate() + ) in executed_messages.iter().enumerate() { if let Some(events) = events { let event_idx_base = u64::try_from(event_count)?; diff --git a/src/rpc/methods/gas.rs b/src/rpc/methods/gas.rs index e70f7ae95f3c..ce071348f70d 100644 --- a/src/rpc/methods/gas.rs +++ b/src/rpc/methods/gas.rs @@ -4,7 +4,6 @@ use super::state::InvocResult; use crate::blocks::Tipset; use crate::chain::{BASE_FEE_MAX_CHANGE_DENOM, BLOCK_GAS_TARGET}; -use crate::interpreter::VMTrace; use crate::message::{ChainMessage, Message as _, MessageRead as _, SignedMessage}; use crate::rpc::chain::FlattenedApiMessage; use crate::rpc::{ApiPaths, Ctx, Permission, RpcMethod, error::ServerError, types::*}; @@ -15,7 +14,7 @@ use crate::shim::{ econ::{BLOCK_GAS_LIMIT, TokenAmount}, message::Message, }; -use crate::state_manager::{StateLookupPolicy, VMFlush}; +use crate::state_manager::VMFlush; use anyhow::{Context, Result}; use enumflags2::BitFlags; use fvm_ipld_blockstore::Blockstore; @@ -218,7 +217,6 @@ impl GasEstimateGasLimit { data: &Ctx, mut msg: Message, ApiTipsetKey(tsk): &ApiTipsetKey, - trace_config: VMTrace, ) -> anyhow::Result<(InvocResult, ApplyRet, Vec, Tipset)> where DB: Blockstore + Send + Sync + 'static, @@ -263,8 +261,6 @@ impl GasEstimateGasLimit { &mut chain_msg, &prior_messages, Some(ts.clone()), - trace_config, - StateLookupPolicy::Enabled, VMFlush::Skip, ) .await?; @@ -279,7 +275,7 @@ impl GasEstimateGasLimit { where DB: Blockstore + Send + Sync + 'static, { - let (res, ..) = Self::estimate_call_with_gas(data, msg, tsk, VMTrace::NotTraced) + let (res, ..) = Self::estimate_call_with_gas(data, msg, tsk) .await .map_err(|e| anyhow::anyhow!("gas estimation failed: {e}"))?; match res.msg_rct { diff --git a/src/rpc/methods/miner.rs b/src/rpc/methods/miner.rs index 0dfd40251510..fba9d8b41edf 100644 --- a/src/rpc/methods/miner.rs +++ b/src/rpc/methods/miner.rs @@ -8,6 +8,7 @@ use crate::blocks::{ElectionProof, RawBlockHeader}; use crate::chain::{ChainStore, compute_base_fee}; use crate::fil_cns::weight; +use crate::interpreter::VMTrace; use crate::key_management::{Key, KeyStore}; use crate::lotus_json::lotus_json_with_self; @@ -21,7 +22,7 @@ use crate::rpc::{ApiPaths, Ctx, RpcMethod, ServerError}; use crate::shim::address::Address; use crate::shim::clock::ChainEpoch; use crate::shim::crypto::{Signature, SignatureType}; -use crate::state_manager::StateLookupPolicy; +use crate::state_manager::ExecutedTipset; use enumflags2::BitFlags; use crate::shim::sector::PoStProof; @@ -147,9 +148,17 @@ impl RpcMethod<1> for MinerCreateBlock { .context("Missing Xxx height")? .epoch, )?; - let (state, receipts) = ctx + let ExecutedTipset { + state_root, + receipt_root, + .. + } = ctx .state_manager - .tipset_state(&parent_tipset, StateLookupPolicy::Disabled) + .compute_tipset_state( + parent_tipset, + crate::state_manager::NO_CALLBACK, + VMTrace::NotTraced, + ) .await?; let network_version = ctx.state_manager.get_network_version(block_template.epoch); @@ -211,8 +220,8 @@ impl RpcMethod<1> for MinerCreateBlock { parents: block_template.parents, weight: parent_weight, epoch: block_template.epoch, - state_root: state, - message_receipts: receipts, + state_root, + message_receipts: receipt_root, messages: message_meta_cid, bls_aggregate: bls_aggregate.into(), timestamp: block_template.timestamp, diff --git a/src/rpc/methods/state.rs b/src/rpc/methods/state.rs index 2c05838fa774..c8fd0c9e73f8 100644 --- a/src/rpc/methods/state.rs +++ b/src/rpc/methods/state.rs @@ -41,8 +41,9 @@ use crate::shim::{ address::Address, clock::ChainEpoch, deal::DealID, econ::TokenAmount, executor::Receipt, state_tree::ActorState, version::NetworkVersion, }; +use crate::state_manager::ExecutedTipset; use crate::state_manager::{ - MarketBalance, StateManager, StateOutput, circulating_supply::GenesisInfo, utils::structured, + MarketBalance, StateManager, circulating_supply::GenesisInfo, utils::structured, }; use crate::utils::db::car_stream::{CarBlock, CarWriter}; use crate::{ @@ -1587,7 +1588,7 @@ impl RpcMethod<2> for ForestStateCompute { while let Some(ts) = futures.try_next().await? { let epoch = ts.epoch(); let tipset_key = ts.key().clone(); - let StateOutput { state_root, .. } = ctx + let ExecutedTipset { state_root, .. } = ctx .state_manager .compute_tipset_state(ts, crate::state_manager::NO_CALLBACK, VMTrace::NotTraced) .await?; @@ -1634,7 +1635,7 @@ impl RpcMethod<3> for StateCompute { })?; Ok(()) }; - let StateOutput { state_root, .. } = ctx + let ExecutedTipset { state_root, .. } = ctx .state_manager .compute_state(height, messages, ts, Some(callback), VMTrace::Traced) .await?; diff --git a/src/state_manager/cache.rs b/src/state_manager/cache.rs index 8415a45ca186..f911204ace0a 100644 --- a/src/state_manager/cache.rs +++ b/src/state_manager/cache.rs @@ -121,8 +121,12 @@ impl TipsetStateCache { } } + pub fn get_map(&self, key: &TipsetKey, mapper: impl Fn(&V) -> T) -> Option { + self.with_inner(|inner| inner.values.get_map(key, mapper)) + } + pub fn get(&self, key: &TipsetKey) -> Option { - self.with_inner(|inner| inner.values.get_cloned(key)) + self.get_map(key, Clone::clone) } pub fn insert(&self, key: TipsetKey, value: V) { diff --git a/src/state_manager/mod.rs b/src/state_manager/mod.rs index 5cb17dae45f4..2dd2477a2ffa 100644 --- a/src/state_manager/mod.rs +++ b/src/state_manager/mod.rs @@ -54,7 +54,7 @@ use crate::shim::{ use crate::state_manager::cache::TipsetStateCache; use crate::state_manager::chain_rand::draw_randomness; use crate::state_migration::run_state_migrations; -use crate::utils::get_size::GetSize; +use crate::utils::get_size::{GetSize, vec_heap_size_helper}; use ahash::{HashMap, HashMapExt}; use anyhow::{Context as _, bail, ensure}; use bls_signatures::{PublicKey as BlsPublicKey, Serialize as _}; @@ -81,48 +81,90 @@ use std::ops::RangeInclusive; use std::time::Duration; use std::{num::NonZeroUsize, sync::Arc}; use tokio::sync::{RwLock, broadcast::error::RecvError}; -use tracing::{error, info, instrument, trace, warn}; +use tracing::{error, info, instrument, warn}; const DEFAULT_TIPSET_CACHE_SIZE: NonZeroUsize = nonzero!(1024usize); pub const EVENTS_AMT_BITWIDTH: u32 = 5; -/// Intermediary for retrieving state objects and updating actor states. -type CidPair = (Cid, Cid); - /// Result of executing an individual chain message in a tipset. /// /// Includes the executed message itself, the execution receipt, and /// optional events emitted by the actor during execution. +#[derive(Debug, Clone)] pub struct ExecutedMessage { pub message: ChainMessage, pub receipt: Receipt, pub events: Option>, } +impl GetSize for ExecutedMessage { + fn get_heap_size(&self) -> usize { + self.message.get_heap_size() + + self.receipt.get_heap_size() + + self + .events + .as_ref() + .map(vec_heap_size_helper) + .unwrap_or_default() + } +} + /// Aggregated execution result for a tipset. -/// -/// `state_root` is the resulting state tree root after message execution -/// and `executed_messages` contains per-message execution details. +#[derive(Debug, Clone, GetSize)] pub struct ExecutedTipset { + /// Resulting state tree root after message execution + #[get_size(ignore)] pub state_root: Cid, - pub executed_messages: Vec, -} - -/// Options controlling how `load_executed_tipset` fetches extra execution data. -/// -/// `include_events` toggles whether event logs are loaded from receipts. -pub struct LoadExecutedTipsetOptions { - pub include_events: bool, + /// Resulting message receipts root after message execution + #[get_size(ignore)] + pub receipt_root: Cid, + /// Per-message execution details. + /// Wrapped in an `Arc` to reduce cloning cost, as this can be quite large. + pub executed_messages: Arc>, } -#[derive(Debug, Default, Clone, GetSize)] -pub struct StateOutput { +/// Basic execution result for a tipset. +#[derive(Debug, Clone, GetSize)] +pub struct TipsetState { + /// Resulting state tree root after message execution #[get_size(ignore)] pub state_root: Cid, + /// Resulting message receipts root after message execution + #[allow(dead_code)] #[get_size(ignore)] pub receipt_root: Cid, } +impl From for TipsetState { + fn from( + ExecutedTipset { + state_root, + receipt_root, + .. + }: ExecutedTipset, + ) -> Self { + Self { + state_root, + receipt_root, + } + } +} + +impl From<&ExecutedTipset> for TipsetState { + fn from( + ExecutedTipset { + state_root, + receipt_root, + .. + }: &ExecutedTipset, + ) -> Self { + Self { + state_root: *state_root, + receipt_root: *receipt_root, + } + } +} + /// External format for returning market balance from state. #[derive( Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, JsonSchema, @@ -147,7 +189,7 @@ pub struct StateManager { /// Chain store cs: Arc>, /// This is a cache which indexes tipsets to their calculated state output (state root, receipt root). - cache: TipsetStateCache, + cache: TipsetStateCache, beacon: Arc, engine: Arc, } @@ -172,7 +214,7 @@ where Ok(Self { cs, - cache: TipsetStateCache::new("state_output"), // For StateOutput + cache: TipsetStateCache::new("executed_tipset"), // For StateOutput beacon, engine, }) @@ -232,30 +274,6 @@ where Ok(false) } - // Given the assumption that the heaviest tipset must always be validated, - // we can populate our state cache by walking backwards through the - // block-chain. A warm cache cuts 10-20 seconds from the first state - // validation, and it prevents duplicate migrations. - pub fn populate_cache(&self) { - for (child, parent) in self - .heaviest_tipset() - .chain(self.blockstore()) - .tuple_windows() - .take(DEFAULT_TIPSET_CACHE_SIZE.into()) - { - let key = parent.key(); - let state_root = child.min_ticket_block().state_root; - let receipt_root = child.min_ticket_block().message_receipts; - self.cache.insert( - key.clone(), - StateOutput { - state_root, - receipt_root, - }, - ); - } - } - pub fn beacon_schedule(&self) -> &Arc { &self.beacon } @@ -423,86 +441,32 @@ impl StateManager where DB: Blockstore + Send + Sync + 'static, { - /// Returns the pair of (state root, message receipt root). This will - /// either be cached or will be calculated and fill the cache. Tipset - /// state for a given tipset is guaranteed not to be computed twice. - pub async fn tipset_state( - self: &Arc, - tipset: &Tipset, - state_lookup: StateLookupPolicy, - ) -> anyhow::Result { - let StateOutput { - state_root, - receipt_root, - } = self.tipset_state_output(tipset, state_lookup).await?; - Ok((state_root, receipt_root)) - } - - pub async fn tipset_state_output( - self: &Arc, - tipset: &Tipset, - state_lookup: StateLookupPolicy, - ) -> anyhow::Result { - let key = tipset.key(); - self.cache - .get_or_else(key, || async move { - info!( - "Evaluating tipset: EPOCH={}, blocks={}, tsk={}", - tipset.epoch(), - tipset.len(), - tipset.key(), - ); - - // First, try to look up the state and receipt if not found in the blockstore - // compute it - if matches!(state_lookup, StateLookupPolicy::Enabled) - && let Some(state_from_child) = self.try_lookup_state_from_next_tipset(tipset) - { - return Ok(state_from_child); - } - - trace!("Computing state for tipset at epoch {}", tipset.epoch()); - let state_output = self - .compute_tipset_state(tipset.clone(), NO_CALLBACK, VMTrace::NotTraced) - .await?; - - Ok(state_output) + /// Load the state of a tipset, including state root, message receipts + pub async fn load_tipset_state(self: &Arc, ts: &Tipset) -> anyhow::Result { + if let Some(state) = self.cache.get_map(ts.key(), |et| et.into()) { + Ok(state) + } else if let Ok(receipt_ts) = self.chain_store().load_child_tipset(ts) { + Ok(TipsetState { + state_root: *receipt_ts.parent_state(), + receipt_root: *receipt_ts.parent_message_receipts(), }) - .await - } - - /// Load an executed tipset, including message receipts and state root, - /// without loading event logs from receipts. - pub async fn load_executed_tipset_without_events( - self: &Arc, - ts: &Tipset, - ) -> anyhow::Result { - let receipt_ts = self.chain_store().load_child_tipset(ts).ok(); - self.load_executed_tipset_inner( - ts, - receipt_ts.as_ref(), - LoadExecutedTipsetOptions { - include_events: false, - }, - ) - .await + } else { + Ok(self.load_executed_tipset(ts).await?.into()) + } } - /// Load an executed tipset, including message receipts and state root, - /// with event logs loaded when available. + /// Load an executed tipset, including state root, message receipts and events with caching. pub async fn load_executed_tipset( self: &Arc, ts: &Tipset, ) -> anyhow::Result { - let receipt_ts = self.chain_store().load_child_tipset(ts).ok(); - self.load_executed_tipset_inner( - ts, - receipt_ts.as_ref(), - LoadExecutedTipsetOptions { - include_events: true, - }, - ) - .await + self.cache + .get_or_else(ts.key(), || async move { + let receipt_ts = self.chain_store().load_child_tipset(ts).ok(); + self.load_executed_tipset_inner(ts, receipt_ts.as_ref()) + .await + }) + .await } async fn load_executed_tipset_inner( @@ -510,23 +474,21 @@ where msg_ts: &Tipset, // when `msg_ts` is the current head, `receipt_ts` is `None` receipt_ts: Option<&Tipset>, - options: LoadExecutedTipsetOptions, ) -> anyhow::Result { - let LoadExecutedTipsetOptions { include_events } = options; if let Some(receipt_ts) = receipt_ts { anyhow::ensure!( msg_ts.key() == receipt_ts.parents(), "message tipset should be the parent of message receipt tipset" ); } - let messages = self.chain_store().messages_for_tipset(msg_ts)?; let mut recomputed = false; - let (state_root, receipts) = match receipt_ts.and_then(|ts| { - Receipt::get_receipts(self.cs.blockstore(), *ts.parent_message_receipts()) + let (state_root, receipt_root, receipts) = match receipt_ts.and_then(|ts| { + let receipt_root = *ts.parent_message_receipts(); + Receipt::get_receipts(self.cs.blockstore(), receipt_root) .ok() - .map(|r| (*ts.parent_state(), r)) + .map(|r| (*ts.parent_state(), receipt_root, r)) }) { - Some((state_root, receipts)) => (state_root, receipts), + Some((state_root, receipt_root, receipts)) => (state_root, receipt_root, receipts), None => { let state_output = self .compute_tipset_state(msg_ts.clone(), NO_CALLBACK, VMTrace::NotTraced) @@ -534,10 +496,13 @@ where recomputed = true; ( state_output.state_root, + state_output.receipt_root, Receipt::get_receipts(self.cs.blockstore(), state_output.receipt_root)?, ) } }; + + let messages = self.chain_store().messages_for_tipset(msg_ts)?; anyhow::ensure!( messages.len() == receipts.len(), "mismatching message and receipt counts ({} messages, {} receipts)", @@ -546,7 +511,7 @@ where ); let mut executed_messages = Vec::with_capacity(messages.len()); for (message, receipt) in messages.iter().cloned().zip(receipts.into_iter()) { - let events = if include_events && let Some(events_root) = receipt.events_root() { + let events = if let Some(events_root) = receipt.events_root() { Some( match StampedEvent::get_events(self.cs.blockstore(), &events_root) { Ok(events) => events, @@ -574,7 +539,8 @@ where } Ok(ExecutedTipset { state_root, - executed_messages, + receipt_root, + executed_messages: Arc::new(executed_messages), }) } @@ -680,7 +646,6 @@ where self: &Arc, tipset: Option, msg: Message, - state_lookup: StateLookupPolicy, vm_flush: VMFlush, ) -> anyhow::Result<(ApiInvocResult, Option)> { let ts = tipset.unwrap_or_else(|| self.heaviest_tipset()); @@ -707,14 +672,7 @@ where }; let (_invoc_res, apply_ret, duration, state_root) = self - .call_with_gas( - &mut chain_msg, - &[], - Some(ts), - VMTrace::Traced, - state_lookup, - vm_flush, - ) + .call_with_gas(&mut chain_msg, &[], Some(ts), vm_flush) .await?; Ok(( @@ -739,13 +697,11 @@ where message: &mut ChainMessage, prior_messages: &[ChainMessage], tipset: Option, - trace_config: VMTrace, - state_lookup: StateLookupPolicy, vm_flush: VMFlush, ) -> Result<(InvocResult, ApplyRet, Duration, Option), Error> { let ts = tipset.unwrap_or_else(|| self.heaviest_tipset()); - let (st, _) = self - .tipset_state(&ts, state_lookup) + let TipsetState { state_root, .. } = self + .load_tipset_state(&ts) .await .map_err(|e| Error::Other(format!("Could not load tipset state: {e}")))?; let chain_rand = self.chain_rand(ts.clone()); @@ -760,21 +716,21 @@ where let mut vm = VM::new( ExecutionContext { heaviest_tipset: ts.clone(), - state_tree_root: st, + state_tree_root: state_root, epoch, rand: Box::new(chain_rand), base_fee: ts.block_headers().first().parent_base_fee.clone(), circ_supply: genesis_info.get_vm_circulating_supply( epoch, self.blockstore(), - &st, + &state_root, )?, chain_config: self.chain_config().clone(), chain_index: self.chain_index().clone(), timestamp: ts.min_timestamp(), }, &self.engine, - trace_config, + VMTrace::NotTraced, )?; for msg in prior_messages { @@ -926,13 +882,12 @@ where /// /// For details, see the documentation for [`apply_block_messages`]. /// - #[instrument(skip_all)] pub async fn compute_tipset_state( self: &Arc, tipset: Tipset, callback: Option) -> anyhow::Result<()> + Send + 'static>, enable_tracing: VMTrace, - ) -> Result { + ) -> Result { let this = Arc::clone(self); tokio::task::spawn_blocking(move || { this.compute_tipset_state_blocking(tipset, callback, enable_tracing) @@ -941,15 +896,19 @@ where } /// Blocking version of `compute_tipset_state` - #[tracing::instrument(skip_all)] pub fn compute_tipset_state_blocking( &self, tipset: Tipset, callback: Option) -> anyhow::Result<()>>, enable_tracing: VMTrace, - ) -> Result { + ) -> Result { let epoch = tipset.epoch(); let has_callback = callback.is_some(); + info!( + "Evaluating tipset: EPOCH={epoch}, blocks={}, tsk={}", + tipset.len(), + tipset.key(), + ); Ok(apply_block_messages( self.chain_store().genesis_block_header().timestamp, Arc::clone(self.chain_index()), @@ -977,7 +936,7 @@ where tipset: Tipset, callback: Option) -> anyhow::Result<()> + Send + 'static>, enable_tracing: VMTrace, - ) -> Result { + ) -> Result { let this = Arc::clone(self); tokio::task::spawn_blocking(move || { this.compute_state_blocking(height, messages, tipset, callback, enable_tracing) @@ -994,7 +953,7 @@ where tipset: Tipset, callback: Option) -> anyhow::Result<()>>, enable_tracing: VMTrace, - ) -> Result { + ) -> Result { Ok(compute_state( height, messages, @@ -1470,8 +1429,8 @@ where } // If that fails, compute the tip-set and try again. - let (st, _) = self.tipset_state(ts, StateLookupPolicy::Enabled).await?; - let state = StateTree::new_from_root(self.blockstore_owned(), &st)?; + let TipsetState { state_root, .. } = self.load_tipset_state(ts).await?; + let state = StateTree::new_from_root(self.blockstore_owned(), &state_root)?; resolve_to_key_addr(&state, self.blockstore(), addr) } @@ -1727,7 +1686,7 @@ where } // If that fails, compute the tip-set and try again. - let (state_root, _) = self.tipset_state(ts, StateLookupPolicy::Enabled).await?; + let TipsetState { state_root, .. } = self.load_tipset_state(ts).await?; let state = StateTree::new_from_root(self.blockstore_owned(), &state_root)?; state.resolve_to_deterministic_addr(self.chain_store().blockstore(), address) } @@ -1758,7 +1717,7 @@ where } }; - let StateOutput { state_root, .. } = apply_block_messages( + let ExecutedTipset { state_root, .. } = apply_block_messages( genesis_timestamp, self.chain_index().clone(), self.chain_config().clone(), @@ -1771,27 +1730,6 @@ where Ok((state_root, invoc_trace)) } - - /// Attempts to lookup the state and receipt root of the next tipset. - /// This is a performance optimization to avoid recomputing the state and receipt root by checking the blockstore. - /// It only checks the immediate next epoch, as this is the most likely place to find a child. - fn try_lookup_state_from_next_tipset(&self, ts: &Tipset) -> Option { - // Check if the next tipset has the same parent - if let Ok(child_ts) = self.chain_store().load_child_tipset(ts) { - let state_root = *child_ts.parent_state(); - let receipt_root = *child_ts.parent_message_receipts(); - if self.blockstore().has(&state_root).unwrap_or(false) - && self.blockstore().has(&receipt_root).unwrap_or(false) - { - return Some(StateOutput { - state_root, - receipt_root, - }); - } - } - - None - } } pub fn validate_tipsets( @@ -1812,9 +1750,10 @@ where .par_bridge() .try_for_each(|(child, parent)| { info!(height = parent.epoch(), "compute parent state"); - let StateOutput { + let ExecutedTipset { state_root: actual_state, receipt_root: actual_receipt, + .. } = apply_block_messages( genesis_timestamp, chain_index.clone(), @@ -2046,7 +1985,7 @@ pub fn apply_block_messages( tipset: Tipset, mut callback: Option) -> anyhow::Result<()>>, enable_tracing: VMTrace, -) -> anyhow::Result +) -> anyhow::Result where DB: Blockstore + Send + Sync + 'static, { @@ -2064,9 +2003,10 @@ where // magical genesis miner, this won't work properly, so we short circuit here // This avoids the question of 'who gets paid the genesis block reward' let message_receipts = tipset.min_ticket_block().message_receipts; - return Ok(StateOutput { + return Ok(ExecutedTipset { state_root: *tipset.parent_state(), receipt_root: message_receipts, + executed_messages: vec![].into(), }); } @@ -2085,7 +2025,7 @@ where // FVM requires a stack size of 64MiB. The alternative is to use `ThreadedExecutor` from // FVM, but that introduces some constraints, and possible deadlocks. - stacker::grow(64 << 20, || -> anyhow::Result { + stacker::grow(64 << 20, || -> anyhow::Result { let mut vm = exec.create_vm(parent_state, epoch, tipset.min_timestamp(), enable_tracing)?; // step 4: apply tipset messages @@ -2093,22 +2033,24 @@ where vm.apply_block_messages(&block_messages, epoch, callback)?; // step 5: construct receipt root from receipts - let receipt_root = Amtv0::new_from_iter(chain_index.db(), receipts)?; + let receipt_root = Amtv0::new_from_iter(chain_index.db(), receipts.iter())?; // step 6: store events AMTs in the blockstore - for (msg_events, events_root) in events.iter().zip(events_roots.iter()) { - if let Some(event_root) = events_root { + for (events, events_root) in events.iter().zip(events_roots.iter()) { + if let Some(events) = events { + let event_root = + events_root.context("events root should be present when events present")?; // Store the events AMT - the root CID should match the one computed by FVM let derived_event_root = Amt::new_from_iter_with_bit_width( chain_index.db(), EVENTS_AMT_BITWIDTH, - msg_events.iter(), + events.iter(), ) .map_err(|e| Error::Other(format!("failed to store events AMT: {e}")))?; // Verify the stored root matches the FVM-computed root ensure!( - derived_event_root.eq(event_root), + derived_event_root == event_root, "Events AMT root mismatch: derived={derived_event_root}, actual={event_root}." ); } @@ -2116,9 +2058,29 @@ where let state_root = vm.flush()?; - Ok(StateOutput { + // Update executed tipset cache + let messages: Vec = block_messages + .into_iter() + .flat_map(|bm| bm.messages) + .collect_vec(); + anyhow::ensure!( + messages.len() == receipts.len() && messages.len() == events.len(), + "length of messages, receipts, and events should match", + ); + Ok(ExecutedTipset { state_root, receipt_root, + executed_messages: messages + .into_iter() + .zip(receipts) + .zip(events) + .map(|((message, receipt), events)| ExecutedMessage { + message, + receipt, + events, + }) + .collect_vec() + .into(), }) }) } @@ -2135,7 +2097,7 @@ pub fn compute_state( engine: &MultiEngine, callback: Option) -> anyhow::Result<()>>, enable_tracing: VMTrace, -) -> anyhow::Result +) -> anyhow::Result where DB: Blockstore + Send + Sync + 'static, { @@ -2157,14 +2119,6 @@ where Ok(output) } -/// Whether or not to lookup the state output from the next tipset before computing a state -#[derive(Debug, Copy, Clone, Default)] -pub enum StateLookupPolicy { - #[default] - Enabled, - Disabled, -} - /// Controls whether the VM should flush its state after execution #[derive(Debug, Copy, Clone, Default)] pub enum VMFlush { diff --git a/src/state_manager/tests.rs b/src/state_manager/tests.rs index 1692e8e21aad..d7393d9cc767 100644 --- a/src/state_manager/tests.rs +++ b/src/state_manager/tests.rs @@ -2,309 +2,9 @@ // SPDX-License-Identifier: Apache-2.0, MIT use super::*; -use crate::blocks::{Chain4U, HeaderBuilder, chain4u}; -use crate::chain::ChainStore; use crate::db::MemoryDB; -use crate::networks::ChainConfig; -use crate::shim::clock::ChainEpoch; use crate::shim::executor::StampedEvent; -use crate::utils::db::CborStoreExt; -use crate::utils::multihash::MultihashCode; -use cid::Cid; use fil_actors_shared::fvm_ipld_amt::Amt; -use fvm_ipld_blockstore::Blockstore; -use fvm_ipld_encoding::DAG_CBOR; -use multihash_derive::MultihashDigest; -use num_bigint::BigInt; -use std::sync::Arc; - -fn create_dummy_cid(i: u64) -> Cid { - let bytes = i.to_le_bytes().to_vec(); - Cid::new_v1(DAG_CBOR, MultihashCode::Blake2b256.digest(&bytes)) -} - -fn dummy_state(db: impl Blockstore, i: ChainEpoch) -> Cid { - db.put_cbor_default(&i).unwrap() -} - -fn dummy_node(db: impl Blockstore, i: ChainEpoch) -> HeaderBuilder { - HeaderBuilder { - state_root: dummy_state(db, i).into(), - weight: BigInt::from(i).into(), - epoch: i.into(), - timestamp: 100.into(), - ..Default::default() - } -} - -/// Structure to hold the setup components for chain tests -struct TestChainSetup { - chain_store: Arc>, - chain_builder: Chain4U>, - state_root: Cid, - receipt_root: Cid, -} - -fn setup_chain_with_tipsets() -> TestChainSetup { - let db = Arc::new(MemoryDB::default()); - let chain_config = Arc::new(ChainConfig::default()); - - let chain_builder = Chain4U::with_blockstore(db.clone()); - chain4u! { - in chain_builder; - [genesis_header = dummy_node(&db, 0)] - } - - let chain_store = Arc::new( - ChainStore::new( - db.clone(), - db.clone(), - db.clone(), - chain_config.clone(), - genesis_header.clone().into(), - ) - .expect("should create chain store"), - ); - - // Create dummy state and receipt roots and store them in blockstore - let state_root = create_dummy_cid(1); - let receipt_root = create_dummy_cid(2); - - db.put_keyed(&state_root, "dummy_state".as_bytes()).unwrap(); - db.put_keyed(&receipt_root, "dummy_receipt".as_bytes()) - .unwrap(); - - chain_store - .set_heaviest_tipset(chain_store.genesis_tipset()) - .unwrap(); - - TestChainSetup { - chain_store, - chain_builder, // Assign c4u to the named field - state_root, - receipt_root, - } -} - -#[test] -fn test_try_lookup_state_from_next_tipset_success() { - let TestChainSetup { - chain_store, - chain_builder, - state_root, - receipt_root, - .. - } = setup_chain_with_tipsets(); - - // Build a chain with parent and child tipsets - chain4u! { - in chain_builder; - parent_ts @ [ - a = HeaderBuilder::new() - .with_epoch(10) - .with_timestamp(101) - ]-> - child_ts @ [ - child_block = HeaderBuilder::new() - .with_epoch(11) - .with_parents(parent_ts.key().clone()) - .with_state_root(state_root) - .with_message_receipts(receipt_root) - .with_timestamp(102) - ] - } - - assert_eq!(a.epoch, 10); - // parent state root is not set, so it should be empty - assert_eq!(a.state_root, Cid::default()); - assert_eq!(child_block.epoch, 11); - assert_eq!(child_block.state_root, state_root); - - chain_store.set_heaviest_tipset(child_ts.clone()).unwrap(); - - let state_manager = Arc::new(StateManager::new(chain_store).unwrap()); - - let result = state_manager.try_lookup_state_from_next_tipset(parent_ts); - - assert!(result.is_some()); - let state_output = result.unwrap(); - assert_eq!(state_output.state_root, state_root); - assert_eq!(state_output.receipt_root, receipt_root); -} - -#[test] -fn test_try_lookup_state_from_next_tipset_no_next_tipset() { - let TestChainSetup { - chain_store, - chain_builder, - .. - } = setup_chain_with_tipsets(); - - // Build a chain with just one tipset - chain4u! { - in chain_builder; - a_ts @ [ - a = HeaderBuilder::new() - .with_epoch(10) - ] - } - - assert_eq!(a.epoch, 10); - - chain_store.set_heaviest_tipset(a_ts.clone()).unwrap(); - - let state_manager = Arc::new(StateManager::new(chain_store).unwrap()); - - let result = state_manager.try_lookup_state_from_next_tipset(a_ts); - - // Should return None since there's no next tipset - assert!(result.is_none()); -} - -#[test] -fn test_try_lookup_state_from_next_tipset_different_parent() { - let TestChainSetup { - chain_store, - chain_builder, - state_root, - receipt_root, - .. - } = setup_chain_with_tipsets(); - - // genesis -> a - chain4u! { - in chain_builder; - a_ts @ [ - a = HeaderBuilder::new() - .with_epoch(10) - .with_timestamp(101) // genesis timestamp(100) + 1 - ] - } - - // Build a chain with parent and child tipsets, but child has different parent - // genesis -> a -> b - // \a1 --> b - chain4u! { - in chain_builder; - // Different parent (a1) - a1_ts @ [ - a1 = HeaderBuilder::new() - .with_epoch(10) - .with_timestamp(102) // genesis timestamp(100) + 2 - ]-> - b_ts @ [ - b = HeaderBuilder::new() - .with_epoch(11) - .with_parents(a1_ts.key().clone()) - .with_state_root(state_root) - .with_message_receipts(receipt_root) - ] - } - - assert_eq!(a.epoch, 10); - assert_eq!(a1.epoch, 10); - assert_eq!(b.epoch, 11); - - // a tipset key should be different from `a1` tipset key - assert_ne!(a_ts.key(), a1_ts.key()); - - chain_store.set_heaviest_tipset(b_ts.clone()).unwrap(); - - let state_manager = Arc::new(StateManager::new(chain_store).unwrap()); - - let result = state_manager.try_lookup_state_from_next_tipset(a_ts); - - // Should return None since the child tipset (b_ts) has a different parent (a1_ts) - assert!(result.is_none()); -} - -#[test] -fn test_try_lookup_state_from_next_tipset_missing_receipt_root() { - let TestChainSetup { - chain_store, - chain_builder, - state_root, - .. - } = setup_chain_with_tipsets(); - - // Create a new receipt root that isn't stored in the blockstore - let missing_receipt_root = create_dummy_cid(999); - - // Build a chain with parent and child tipsets - chain4u! { - in chain_builder; - a_ts @ [ - a = HeaderBuilder::new() - .with_epoch(10) - ]-> - b_ts @ [ - b = HeaderBuilder::new() - .with_epoch(11) - .with_parents(a_ts.key().clone()) - .with_state_root(state_root) - .with_message_receipts(missing_receipt_root) - ] - } - - assert_eq!(a.epoch, 10); - assert_eq!(b.epoch, 11); - - chain_store.set_heaviest_tipset(b_ts.clone()).unwrap(); - - let state_manager = Arc::new(StateManager::new(chain_store).unwrap()); - - let result = state_manager.try_lookup_state_from_next_tipset(a_ts); - - // Should return None since the receipt root is missing - assert!(result.is_none()); -} - -#[test] -fn test_try_lookup_state_from_next_tipset_missing_state_root() { - let TestChainSetup { - chain_store, - chain_builder, - receipt_root, - .. - } = setup_chain_with_tipsets(); - - // Create a new state root that is not stored in the blockstore - let missing_state_root = create_dummy_cid(999); - - // Build a chain with parent and child tipsets - chain4u! { - in chain_builder; - a_ts @ [ - a = HeaderBuilder::new() - .with_epoch(10) - ]-> - b_ts @ [ - b = HeaderBuilder::new() - .with_epoch(11) - .with_parents(a_ts.key().clone()) - .with_message_receipts(receipt_root) - .with_state_root(missing_state_root) - ] - } - - assert_eq!(a.epoch, 10); - assert_eq!(b.epoch, 11); - - chain_store.set_heaviest_tipset(b_ts.clone()).unwrap(); - - let state_manager = Arc::new(StateManager::new(chain_store).unwrap()); - - let result = state_manager.try_lookup_state_from_next_tipset(a_ts); - - // Should return None since the state root is missing - assert!(result.is_none()); -} - -#[test] -fn test_state_output_get_size() { - let s = StateOutput::default(); - assert_eq!(s.get_size(), std::mem::size_of_val(&s)); -} fn create_raw_event_v4(emitter: u64, key: &str) -> fvm_shared4::event::StampedEvent { fvm_shared4::event::StampedEvent { diff --git a/src/state_manager/utils.rs b/src/state_manager/utils.rs index a812507014a5..23997b80920e 100644 --- a/src/state_manager/utils.rs +++ b/src/state_manager/utils.rs @@ -190,7 +190,7 @@ pub mod state_compute { genesis::read_genesis_header, interpreter::VMTrace, networks::{ChainConfig, NetworkChain}, - state_manager::{StateManager, StateOutput}, + state_manager::{ExecutedTipset, StateManager}, utils::net::{DownloadFileOption, download_file_with_cache}, }; use directories::ProjectDirs; @@ -310,9 +310,10 @@ pub mod state_compute { let expected_state_root = *ts_next.parent_state(); let expected_receipt_root = *ts_next.parent_message_receipts(); let start = Instant::now(); - let StateOutput { + let ExecutedTipset { state_root, receipt_root, + .. } = state_manager .compute_tipset_state(ts, crate::state_manager::NO_CALLBACK, VMTrace::NotTraced) .await?; diff --git a/src/tool/subcommands/archive_cmd.rs b/src/tool/subcommands/archive_cmd.rs index 9f275c1eeca6..e6386413e76c 100644 --- a/src/tool/subcommands/archive_cmd.rs +++ b/src/tool/subcommands/archive_cmd.rs @@ -44,7 +44,7 @@ use crate::shim::clock::{ChainEpoch, EPOCH_DURATION_SECONDS, EPOCHS_IN_DAY}; use crate::shim::executor::{Receipt, StampedEvent}; use crate::shim::fvm_shared_latest::address::Network; use crate::shim::machine::GLOBAL_MULTI_ENGINE; -use crate::state_manager::{NO_CALLBACK, StateOutput, apply_block_messages}; +use crate::state_manager::{ExecutedTipset, NO_CALLBACK, apply_block_messages}; use crate::utils::db::car_stream::{CarBlock, CarBlockWrite as _, CarStream}; use crate::utils::multihash::MultihashCode; use anyhow::{Context as _, bail}; @@ -825,7 +825,7 @@ async fn show_tipset_diff( ResolveNullTipset::TakeNewer, )?; - let StateOutput { state_root, .. } = apply_block_messages( + let ExecutedTipset { state_root, .. } = apply_block_messages( timestamp, Arc::new(chain_index), Arc::new(chain_config), diff --git a/src/tool/subcommands/snapshot_cmd.rs b/src/tool/subcommands/snapshot_cmd.rs index bcabde060acb..f0b2913221a3 100644 --- a/src/tool/subcommands/snapshot_cmd.rs +++ b/src/tool/subcommands/snapshot_cmd.rs @@ -16,7 +16,7 @@ use crate::shim::address::CurrentNetwork; use crate::shim::clock::ChainEpoch; use crate::shim::fvm_shared_latest::address::Network; use crate::shim::machine::GLOBAL_MULTI_ENGINE; -use crate::state_manager::{StateOutput, apply_block_messages}; +use crate::state_manager::{ExecutedTipset, apply_block_messages}; use crate::utils::db::car_stream::CarStream; use crate::utils::proofs_api::ensure_proof_params_downloaded; use anyhow::{Context as _, bail}; @@ -486,7 +486,7 @@ fn print_computed_state(snapshot: PathBuf, epoch: ChainEpoch, json: bool) -> any let mut message_calls = vec![]; - let StateOutput { state_root, .. } = apply_block_messages( + let ExecutedTipset { state_root, .. } = apply_block_messages( timestamp, Arc::new(chain_index), Arc::new(chain_config), diff --git a/src/utils/cache/lru.rs b/src/utils/cache/lru.rs index 9e265cc88e19..2686be68d3d0 100644 --- a/src/utils/cache/lru.rs +++ b/src/utils/cache/lru.rs @@ -104,12 +104,20 @@ where self.cache.write().insert(k, v) } + pub fn get_map(&self, k: &Q, mapper: impl Fn(&V) -> T) -> Option + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.cache.write().get(k).map(mapper) + } + pub fn get_cloned(&self, k: &Q) -> Option where K: Borrow, Q: Hash + Eq + ?Sized, { - self.cache.write().get(k).cloned() + self.get_map(k, Clone::clone) } pub fn peek_cloned(&self, k: &Q) -> Option diff --git a/src/utils/get_size/mod.rs b/src/utils/get_size/mod.rs index 6c35b3f2714a..b5a696a3a1eb 100644 --- a/src/utils/get_size/mod.rs +++ b/src/utils/get_size/mod.rs @@ -35,6 +35,10 @@ macro_rules! impl_vec_alike_heap_size_helper { }; } +pub fn vec_heap_size_helper(v: &Vec) -> usize { + impl_vec_alike_heap_size_helper!(v, T) +} + pub fn vec_heap_size_with_fn_helper(v: &Vec, get_heap_size: impl Fn(&T) -> usize) -> usize { impl_vec_alike_heap_size_with_fn_helper!(v, T, std::mem::size_of::, get_heap_size) }