From f0fdbf641a257e2033b5f3a09e32f64d04ba69d7 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Fri, 15 Aug 2025 22:13:58 +0800 Subject: [PATCH 1/8] fix: add cache to improve tipset_by_height performance --- src/chain/store/index.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/chain/store/index.rs b/src/chain/store/index.rs index cd375b19e7ef..6e601ec5015c 100644 --- a/src/chain/store/index.rs +++ b/src/chain/store/index.rs @@ -1,6 +1,7 @@ // Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT +use std::sync::LazyLock; use std::{num::NonZeroUsize, sync::Arc}; use crate::beacon::{BeaconEntry, IGNORE_DRAND_VAR}; @@ -13,6 +14,7 @@ use crate::utils::misc::env::is_env_truthy; use fvm_ipld_blockstore::Blockstore; use itertools::Itertools; use nonzero_ext::nonzero; +use num::Integer; const DEFAULT_TIPSET_CACHE_SIZE: NonZeroUsize = nonzero!(131072_usize); @@ -120,6 +122,28 @@ impl ChainIndex { from: Arc, resolve: ResolveNullTipset, ) -> Result, Error> { + const CHECKPOINT_INTERVAL: ChainEpoch = 1000; + static CACHE: LazyLock>> = + LazyLock::new(|| { + SizeTrackingLruCache::new_with_default_metrics_registry( + "tipset_by_height".into(), + 4096.try_into().expect("infallible"), + ) + }); + + fn next_checkpoint(epoch: ChainEpoch) -> ChainEpoch { + let m = epoch.mod_floor(&CHECKPOINT_INTERVAL); + if m == 0 { + epoch + } else { + epoch - m + CHECKPOINT_INTERVAL + } + } + + let checkpoint_from_epoch = next_checkpoint(to); + let checkpoint_from = CACHE.get_cloned(&checkpoint_from_epoch); + let from = checkpoint_from.unwrap_or(from); + if to == 0 { return Ok(Arc::new(Tipset::from(from.genesis(&self.db)?))); } @@ -131,6 +155,10 @@ impl ChainIndex { } for (child, parent) in self.chain(from).tuple_windows() { + if child.epoch() % CHECKPOINT_INTERVAL == 0 { + CACHE.push(child.epoch(), child.clone()); + } + if to == child.epoch() { return Ok(child); } From 0500263d7493dfc280dcc8d0061691bfb7b0c496 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Fri, 15 Aug 2025 23:42:53 +0800 Subject: [PATCH 2/8] fix --- src/chain/store/chain_store.rs | 13 ++++++++++--- src/chain/store/index.rs | 7 +------ src/chain_sync/chain_follower.rs | 2 +- src/chain_sync/mod.rs | 2 +- src/chain_sync/validation.rs | 3 +-- src/libp2p/chain_exchange/provider.rs | 2 +- src/rpc/methods/chain.rs | 3 +-- src/rpc/methods/f3.rs | 2 +- src/rpc/methods/state.rs | 11 +++++++++++ 9 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/chain/store/chain_store.rs b/src/chain/store/chain_store.rs index 34b7636ce3d5..6a80e2174131 100644 --- a/src/chain/store/chain_store.rs +++ b/src/chain/store/chain_store.rs @@ -467,7 +467,7 @@ pub fn block_messages( where DB: Blockstore, { - let (bls_cids, secpk_cids) = read_msg_cids(db, &bh.messages)?; + let (bls_cids, secpk_cids) = read_msg_cids(db, bh)?; let bls_msgs: Vec = messages_from_cids(db, &bls_cids)?; let secp_msgs: Vec = messages_from_cids(db, &secpk_cids)?; @@ -491,17 +491,23 @@ where } /// Returns a tuple of CIDs for both unsigned and signed messages -pub fn read_msg_cids(db: &DB, msg_cid: &Cid) -> Result<(Vec, Vec), Error> +pub fn read_msg_cids( + db: &DB, + block_header: &CachingBlockHeader, +) -> Result<(Vec, Vec), Error> where DB: Blockstore, { + let msg_cid = &block_header.messages; if let Some(roots) = db.get_cbor::(msg_cid)? { let bls_cids = read_amt_cids(db, &roots.bls_message_root)?; let secpk_cids = read_amt_cids(db, &roots.secp_message_root)?; Ok((bls_cids, secpk_cids)) } else { Err(Error::UndefinedKey(format!( - "no msg root with cid {msg_cid}" + "no msg root with cid {msg_cid} at epoch {} in block {}", + block_header.epoch, + block_header.cid(), ))) } } @@ -635,6 +641,7 @@ where // message to get all messages for block_header into a single iterator let mut get_message_for_block_header = |b: &CachingBlockHeader| -> Result, Error> { + tracing::info!("block_messages({}): {}", b.epoch, b.cid()); let (unsigned, signed) = block_messages(&db, b)?; let mut messages = Vec::with_capacity(unsigned.len() + signed.len()); let unsigned_box = unsigned.into_iter().map(ChainMessage::Unsigned); diff --git a/src/chain/store/index.rs b/src/chain/store/index.rs index 6e601ec5015c..4d059c3ad20c 100644 --- a/src/chain/store/index.rs +++ b/src/chain/store/index.rs @@ -132,12 +132,7 @@ impl ChainIndex { }); fn next_checkpoint(epoch: ChainEpoch) -> ChainEpoch { - let m = epoch.mod_floor(&CHECKPOINT_INTERVAL); - if m == 0 { - epoch - } else { - epoch - m + CHECKPOINT_INTERVAL - } + epoch - epoch.mod_floor(&CHECKPOINT_INTERVAL) + CHECKPOINT_INTERVAL } let checkpoint_from_epoch = next_checkpoint(to); diff --git a/src/chain_sync/chain_follower.rs b/src/chain_sync/chain_follower.rs index ffa8a347b31a..c4022f850059 100644 --- a/src/chain_sync/chain_follower.rs +++ b/src/chain_sync/chain_follower.rs @@ -431,7 +431,7 @@ fn handle_peer_disconnected_event( network.peer_manager().unmark_peer_bad(&peer_id); } -async fn get_full_tipset( +pub async fn get_full_tipset( network: SyncNetworkContext, chain_store: Arc>, peer_id: Option, diff --git a/src/chain_sync/mod.rs b/src/chain_sync/mod.rs index 7647168c2268..2d7d52c3261e 100644 --- a/src/chain_sync/mod.rs +++ b/src/chain_sync/mod.rs @@ -13,7 +13,7 @@ mod validation; pub use self::{ bad_block_cache::BadBlockCache, - chain_follower::ChainFollower, + chain_follower::{ChainFollower, get_full_tipset}, chain_muxer::SyncConfig, consensus::collect_errs, sync_status::{ForkSyncInfo, ForkSyncStage, NodeSyncStatus, SyncStatusReport}, diff --git a/src/chain_sync/validation.rs b/src/chain_sync/validation.rs index b05100956101..560d84775099 100644 --- a/src/chain_sync/validation.rs +++ b/src/chain_sync/validation.rs @@ -73,7 +73,7 @@ impl TipsetValidator<'_> { // matches the mst root in the block header 2. Ensuring it has not // previously been seen in the bad blocks cache for block in self.0.blocks() { - self.validate_msg_root(&chainstore.db, block)?; + Self::validate_msg_root(&chainstore.db, block)?; if let Some(bad_block_cache) = bad_block_cache && bad_block_cache.peek(block.cid()).is_some() { @@ -104,7 +104,6 @@ impl TipsetValidator<'_> { } pub fn validate_msg_root( - &self, blockstore: &DB, block: &Block, ) -> Result<(), TipsetValidationError> { diff --git a/src/libp2p/chain_exchange/provider.rs b/src/libp2p/chain_exchange/provider.rs index 8d01e6c041f4..aa8329b3821f 100644 --- a/src/libp2p/chain_exchange/provider.rs +++ b/src/libp2p/chain_exchange/provider.rs @@ -96,7 +96,7 @@ where let mut secp_msg_includes: Vec> = vec![]; for block_header in tipset.block_headers().iter() { - let (bls_cids, secp_cids) = crate::chain::read_msg_cids(db, &block_header.messages)?; + let (bls_cids, secp_cids) = crate::chain::read_msg_cids(db, block_header)?; let mut bls_include = Vec::with_capacity(bls_cids.len()); for bls_cid in bls_cids.into_iter() { diff --git a/src/rpc/methods/chain.rs b/src/rpc/methods/chain.rs index 48558b345ab3..68a671718357 100644 --- a/src/rpc/methods/chain.rs +++ b/src/rpc/methods/chain.rs @@ -554,8 +554,7 @@ impl RpcMethod<1> for ChainGetBlockMessages { (block_cid,): Self::Params, ) -> Result { let blk: CachingBlockHeader = ctx.store().get_cbor_required(&block_cid)?; - let blk_msgs = &blk.messages; - let (unsigned_cids, signed_cids) = crate::chain::read_msg_cids(ctx.store(), blk_msgs)?; + let (unsigned_cids, signed_cids) = crate::chain::read_msg_cids(ctx.store(), &blk)?; let (bls_msg, secp_msg) = crate::chain::block_messages_from_cids(ctx.store(), &unsigned_cids, &signed_cids)?; let cids = unsigned_cids.into_iter().chain(signed_cids).collect(); diff --git a/src/rpc/methods/f3.rs b/src/rpc/methods/f3.rs index 16e24272b357..2a7bc1228ff3 100644 --- a/src/rpc/methods/f3.rs +++ b/src/rpc/methods/f3.rs @@ -165,7 +165,7 @@ impl GetPowerTable { const BLOCKSTORE_CACHE_CAP: usize = 65536; static BLOCKSTORE_CACHE: LazyLock = LazyLock::new(|| { LruBlockstoreReadCache::new_with_default_metrics_registry( - "get_powertable_cache".into(), + "get_powertable".into(), BLOCKSTORE_CACHE_CAP.try_into().expect("Infallible"), ) }); diff --git a/src/rpc/methods/state.rs b/src/rpc/methods/state.rs index d2a4a1fc482f..13e1c50447b5 100644 --- a/src/rpc/methods/state.rs +++ b/src/rpc/methods/state.rs @@ -6,6 +6,7 @@ pub use types::*; use crate::blocks::{Tipset, TipsetKey}; use crate::chain::index::ResolveNullTipset; +use crate::chain_sync::TipsetValidator; use crate::cid_collections::CidHashSet; use crate::eth::EthChainId; use crate::interpreter::{MessageCallbackCtx, VMTrace}; @@ -1437,6 +1438,16 @@ impl RpcMethod<1> for ForestStateCompute { ctx.chain_store().heaviest_tipset(), ResolveNullTipset::TakeOlder, )?; + let fts = crate::chain_sync::get_full_tipset( + ctx.sync_network_context.clone(), + ctx.chain_store().clone(), + None, + tipset.key().clone(), + ) + .await?; + for block in fts.blocks() { + TipsetValidator::validate_msg_root(ctx.store(), block)?; + } let StateOutput { state_root, .. } = ctx .state_manager .compute_tipset_state( From 8fb563f5685cc8098f0ddee14c42130c9fe04cf6 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Fri, 15 Aug 2025 23:49:12 +0800 Subject: [PATCH 3/8] fix --- src/chain_sync/chain_follower.rs | 1 + src/rpc/methods/state.rs | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/chain_sync/chain_follower.rs b/src/chain_sync/chain_follower.rs index c4022f850059..3f6ac9064b5b 100644 --- a/src/chain_sync/chain_follower.rs +++ b/src/chain_sync/chain_follower.rs @@ -449,6 +449,7 @@ pub async fn get_full_tipset( for block in tipset.blocks() { block.persist(&chain_store.db)?; + TipsetValidator::validate_msg_root(&chain_store.db, block)?; } Ok(tipset) diff --git a/src/rpc/methods/state.rs b/src/rpc/methods/state.rs index 13e1c50447b5..ddbc7ef94cda 100644 --- a/src/rpc/methods/state.rs +++ b/src/rpc/methods/state.rs @@ -6,7 +6,6 @@ pub use types::*; use crate::blocks::{Tipset, TipsetKey}; use crate::chain::index::ResolveNullTipset; -use crate::chain_sync::TipsetValidator; use crate::cid_collections::CidHashSet; use crate::eth::EthChainId; use crate::interpreter::{MessageCallbackCtx, VMTrace}; @@ -1438,16 +1437,13 @@ impl RpcMethod<1> for ForestStateCompute { ctx.chain_store().heaviest_tipset(), ResolveNullTipset::TakeOlder, )?; - let fts = crate::chain_sync::get_full_tipset( + crate::chain_sync::get_full_tipset( ctx.sync_network_context.clone(), ctx.chain_store().clone(), None, tipset.key().clone(), ) .await?; - for block in fts.blocks() { - TipsetValidator::validate_msg_root(ctx.store(), block)?; - } let StateOutput { state_root, .. } = ctx .state_manager .compute_tipset_state( From 518014fd6c2517f73055c357d044b5eb86d76240 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Mon, 18 Aug 2025 08:53:23 +0800 Subject: [PATCH 4/8] refactor --- src/blocks/tipset.rs | 10 ++++++++ src/chain/store/chain_store.rs | 1 - src/chain_sync/chain_follower.rs | 34 +++++++++++--------------- src/chain_sync/mod.rs | 2 +- src/chain_sync/network_context.rs | 36 +++++++++++++++++++++++++--- src/libp2p/chain_exchange/message.rs | 8 +++++-- src/rpc/methods/f3.rs | 2 +- src/rpc/methods/state.rs | 26 ++++++++++---------- 8 files changed, 78 insertions(+), 41 deletions(-) diff --git a/src/blocks/tipset.rs b/src/blocks/tipset.rs index b01964dfdfc0..71e981a2837e 100644 --- a/src/blocks/tipset.rs +++ b/src/blocks/tipset.rs @@ -8,6 +8,7 @@ use std::{ use super::{Block, CachingBlockHeader, RawBlockHeader, Ticket}; use crate::{ + chain_sync::TipsetValidator, cid_collections::SmallCidNonEmptyVec, networks::{calibnet, mainnet}, shim::clock::ChainEpoch, @@ -546,6 +547,15 @@ impl FullTipset { pub fn weight(&self) -> &BigInt { &self.first_block().header().weight } + /// Persists the tipset into the blockstore. + pub fn persist(&self, db: &impl Blockstore) -> anyhow::Result<()> { + for block in self.blocks() { + // To persist `TxMeta` that is required for loading tipset messages + TipsetValidator::validate_msg_root(db, block)?; + block.persist(db)?; + } + Ok(()) + } } fn verify_block_headers<'a>( diff --git a/src/chain/store/chain_store.rs b/src/chain/store/chain_store.rs index 6a80e2174131..96d019742bdf 100644 --- a/src/chain/store/chain_store.rs +++ b/src/chain/store/chain_store.rs @@ -641,7 +641,6 @@ where // message to get all messages for block_header into a single iterator let mut get_message_for_block_header = |b: &CachingBlockHeader| -> Result, Error> { - tracing::info!("block_messages({}): {}", b.epoch, b.cid()); let (unsigned, signed) = block_messages(&db, b)?; let mut messages = Vec::with_capacity(unsigned.len() + signed.len()); let unsigned_box = unsigned.into_iter().map(ChainMessage::Unsigned); diff --git a/src/chain_sync/chain_follower.rs b/src/chain_sync/chain_follower.rs index 3f6ac9064b5b..b59b5619caed 100644 --- a/src/chain_sync/chain_follower.rs +++ b/src/chain_sync/chain_follower.rs @@ -176,7 +176,7 @@ pub async fn chain_follower( network.clone(), state_manager.chain_store().clone(), Some(source), - tipset_keys, + &tipset_keys, ) .await .inspect_err(|e| debug!("Querying full tipset failed: {}", e)) @@ -188,7 +188,7 @@ pub async fn chain_follower( network.clone(), state_manager.chain_store().clone(), None, - key, + &key, ) .await } @@ -435,22 +435,18 @@ pub async fn get_full_tipset( network: SyncNetworkContext, chain_store: Arc>, peer_id: Option, - tipset_keys: TipsetKey, + tipset_keys: &TipsetKey, ) -> anyhow::Result { // Attempt to load from the store - if let Ok(full_tipset) = load_full_tipset(&chain_store, tipset_keys.clone()) { + if let Ok(full_tipset) = load_full_tipset(&chain_store, tipset_keys) { return Ok(full_tipset); } // Load from the network let tipset = network - .chain_exchange_fts(peer_id, &tipset_keys.clone()) + .chain_exchange_full_tipset(peer_id, tipset_keys) .await .map_err(|e| anyhow::anyhow!(e))?; - - for block in tipset.blocks() { - block.persist(&chain_store.db)?; - TipsetValidator::validate_msg_root(&chain_store.db, block)?; - } + tipset.persist(&chain_store.db)?; Ok(tipset) } @@ -459,15 +455,15 @@ async fn get_full_tipset_batch( network: SyncNetworkContext, chain_store: Arc>, peer_id: Option, - tipset_keys: TipsetKey, + tipset_keys: &TipsetKey, ) -> anyhow::Result> { // Attempt to load from the store - if let Ok(full_tipset) = load_full_tipset(&chain_store, tipset_keys.clone()) { + if let Ok(full_tipset) = load_full_tipset(&chain_store, tipset_keys) { return Ok(vec![full_tipset]); } // Load from the network let tipsets = network - .chain_exchange_full_tipsets(peer_id, &tipset_keys.clone()) + .chain_exchange_full_tipsets(peer_id, tipset_keys) .await .map_err(|e| anyhow::anyhow!(e))?; @@ -480,13 +476,12 @@ async fn get_full_tipset_batch( Ok(tipsets) } -fn load_full_tipset( +pub fn load_full_tipset( chain_store: &ChainStore, - tipset_keys: TipsetKey, + tipset_keys: &TipsetKey, ) -> anyhow::Result { // Retrieve tipset from store based on passed in TipsetKey - let ts = chain_store.chain_index.load_required_tipset(&tipset_keys)?; - + let ts = chain_store.chain_index.load_required_tipset(tipset_keys)?; let blocks: Vec<_> = ts .block_headers() .iter() @@ -500,7 +495,6 @@ fn load_full_tipset( }) }) .try_collect()?; - // Construct FullTipset let fts = FullTipset::new(blocks)?; Ok(fts) @@ -603,7 +597,7 @@ impl SyncStateMachine { if self.stateless_mode || tipset.key() == self.cs.genesis_tipset().key() { // Skip validation in stateless mode and for genesis tipset true - } else if let Ok(parent_ts) = load_full_tipset(&self.cs, tipset.parents().clone()) { + } else if let Ok(parent_ts) = load_full_tipset(&self.cs, tipset.parents()) { let head_ts = self.cs.heaviest_tipset(); // Treat post-head-epoch tipsets as not validated to fix // basically, the follow task should always start from the current head which could be manually set @@ -878,7 +872,7 @@ impl SyncTask { } SyncTask::FetchTipset(key, _epoch) => { if let Ok(parents) = - get_full_tipset_batch(network.clone(), cs.clone(), None, key).await + get_full_tipset_batch(network.clone(), cs.clone(), None, &key).await { Some(SyncEvent::NewFullTipsets( parents.into_iter().map(Arc::new).collect(), diff --git a/src/chain_sync/mod.rs b/src/chain_sync/mod.rs index 2d7d52c3261e..b1631b12fe53 100644 --- a/src/chain_sync/mod.rs +++ b/src/chain_sync/mod.rs @@ -13,7 +13,7 @@ mod validation; pub use self::{ bad_block_cache::BadBlockCache, - chain_follower::{ChainFollower, get_full_tipset}, + chain_follower::{ChainFollower, load_full_tipset}, chain_muxer::SyncConfig, consensus::collect_errs, sync_status::{ForkSyncInfo, ForkSyncStage, NodeSyncStatus, SyncStatusReport}, diff --git a/src/chain_sync/network_context.rs b/src/chain_sync/network_context.rs index 8def73d7e9eb..e3807a6186b1 100644 --- a/src/chain_sync/network_context.rs +++ b/src/chain_sync/network_context.rs @@ -159,10 +159,39 @@ where .await } + /// Send a `chain_exchange` request for messages to assemble a full tipset with a local tipset, + /// If `peer_id` is `None`, requests will be sent to a set of shuffled peers. + pub async fn chain_exchange_messages( + &self, + peer_id: Option, + ts: &Tipset, + ) -> Result { + let mut bundles: Vec = self + .handle_chain_exchange_request( + peer_id, + ts.key(), + NonZeroU64::new(1).expect("Infallible"), + MESSAGES, + |_| true, + ) + .await + .expect("infallible"); + + if bundles.len() != 1 { + return Err(format!( + "chain exchange request returned {} tipsets, 1 expected.", + bundles.len() + )); + } + let mut bundle = bundles.remove(0); + bundle.blocks = ts.block_headers().to_vec(); + bundle.try_into() + } + /// Send a `chain_exchange` request for a single full tipset (includes /// messages) If `peer_id` is `None`, requests will be sent to a set of /// shuffled peers. - pub async fn chain_exchange_fts( + pub async fn chain_exchange_full_tipset( &self, peer_id: Option, tsk: &TipsetKey, @@ -179,7 +208,7 @@ where if fts.len() != 1 { return Err(format!( - "Full tipset request returned {} tipsets", + "Full tipset request returned {} tipsets, 1 expected.", fts.len() )); } @@ -212,7 +241,8 @@ where validate: F, ) -> Result, String> where - T: TryFrom + Send + Sync + 'static, + T: TryFrom + Send + Sync + 'static, + >::Error: std::fmt::Display, F: Fn(&Vec) -> bool, { let request = ChainExchangeRequest { diff --git a/src/libp2p/chain_exchange/message.rs b/src/libp2p/chain_exchange/message.rs index 7c9955e9d913..bdd34f1a17c9 100644 --- a/src/libp2p/chain_exchange/message.rs +++ b/src/libp2p/chain_exchange/message.rs @@ -125,7 +125,8 @@ impl ChainExchangeResponse { /// implementation. pub fn into_result(self) -> Result, String> where - T: TryFrom, + T: TryFrom, + >::Error: std::fmt::Display, { if self.status != ChainExchangeResponseStatus::Success && self.status != ChainExchangeResponseStatus::PartialResponse @@ -133,7 +134,10 @@ impl ChainExchangeResponse { return Err(format!("Status {:?}: {}", self.status, self.message)); } - self.chain.into_iter().map(T::try_from).collect() + self.chain + .into_iter() + .map(|i| T::try_from(i).map_err(|e| e.to_string())) + .collect() } } /// Contains all BLS and SECP messages and their indexes per block diff --git a/src/rpc/methods/f3.rs b/src/rpc/methods/f3.rs index 2a7bc1228ff3..aecd711ffb69 100644 --- a/src/rpc/methods/f3.rs +++ b/src/rpc/methods/f3.rs @@ -583,7 +583,7 @@ impl RpcMethod<1> for Finalize { ); let fts = ctx .sync_network_context - .chain_exchange_fts(None, &tsk) + .chain_exchange_full_tipset(None, &tsk) .await?; for block in fts.blocks() { block.persist(ctx.store())?; diff --git a/src/rpc/methods/state.rs b/src/rpc/methods/state.rs index ddbc7ef94cda..9ba1580459e5 100644 --- a/src/rpc/methods/state.rs +++ b/src/rpc/methods/state.rs @@ -1432,25 +1432,25 @@ impl RpcMethod<1> for ForestStateCompute { ctx: Ctx, (epoch,): Self::Params, ) -> Result { - let tipset = ctx.chain_index().tipset_by_height( + let ts = ctx.chain_index().tipset_by_height( epoch, ctx.chain_store().heaviest_tipset(), ResolveNullTipset::TakeOlder, )?; - crate::chain_sync::get_full_tipset( - ctx.sync_network_context.clone(), - ctx.chain_store().clone(), - None, - tipset.key().clone(), - ) - .await?; + // Attempt to load full tipset from the store + if crate::chain_sync::load_full_tipset(ctx.chain_store(), ts.key()).is_err() { + // Load full tipset from the network + let fts = ctx + .sync_network_context + .chain_exchange_messages(None, &ts) + .await + .map_err(|e| anyhow::anyhow!(e))?; + fts.persist(ctx.store())?; + } + let StateOutput { state_root, .. } = ctx .state_manager - .compute_tipset_state( - tipset, - crate::state_manager::NO_CALLBACK, - VMTrace::NotTraced, - ) + .compute_tipset_state(ts, crate::state_manager::NO_CALLBACK, VMTrace::NotTraced) .await?; Ok(state_root) From 1e0a4dab01f84d96f1ca81f00633eb04db24a983 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Mon, 18 Aug 2025 10:25:58 +0800 Subject: [PATCH 5/8] resolve AI comments --- src/chain_sync/network_context.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/chain_sync/network_context.rs b/src/chain_sync/network_context.rs index e3807a6186b1..bb4a5a90820c 100644 --- a/src/chain_sync/network_context.rs +++ b/src/chain_sync/network_context.rs @@ -174,8 +174,7 @@ where MESSAGES, |_| true, ) - .await - .expect("infallible"); + .await?; if bundles.len() != 1 { return Err(format!( From b49d29b5fa006403ceed2b54cc6dc8e37b7710ff Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Mon, 18 Aug 2025 11:06:59 +0800 Subject: [PATCH 6/8] resolve AI comment --- src/chain/store/index.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/chain/store/index.rs b/src/chain/store/index.rs index 4d059c3ad20c..90f76617476d 100644 --- a/src/chain/store/index.rs +++ b/src/chain/store/index.rs @@ -119,7 +119,7 @@ impl ChainIndex { pub fn tipset_by_height( &self, to: ChainEpoch, - from: Arc, + mut from: Arc, resolve: ResolveNullTipset, ) -> Result, Error> { const CHECKPOINT_INTERVAL: ChainEpoch = 1000; @@ -135,9 +135,16 @@ impl ChainIndex { epoch - epoch.mod_floor(&CHECKPOINT_INTERVAL) + CHECKPOINT_INTERVAL } - let checkpoint_from_epoch = next_checkpoint(to); - let checkpoint_from = CACHE.get_cloned(&checkpoint_from_epoch); - let from = checkpoint_from.unwrap_or(from); + let from_epoch = from.epoch(); + + let mut checkpoint_from_epoch = to; + while checkpoint_from_epoch < from_epoch { + if let Some(checkpoint_from) = CACHE.get_cloned(&checkpoint_from_epoch) { + from = checkpoint_from; + break; + } + checkpoint_from_epoch = next_checkpoint(checkpoint_from_epoch); + } if to == 0 { return Ok(Arc::new(Tipset::from(from.genesis(&self.db)?))); @@ -150,7 +157,11 @@ impl ChainIndex { } for (child, parent) in self.chain(from).tuple_windows() { - if child.epoch() % CHECKPOINT_INTERVAL == 0 { + // use `child.epoch() + CHECKPOINT_INTERVAL <= from_epoch` where `CHECKPOINT_INTERVAL>=CHAIN_FINALITY && CHECKPOINT_INTERVAL>=F3_CHAIN_FINALITY` + // to ensure the cached child is finalized(not on a fork). + if child.epoch() % CHECKPOINT_INTERVAL == 0 + && child.epoch() + CHECKPOINT_INTERVAL <= from_epoch + { CACHE.push(child.epoch(), child.clone()); } From 54c67d681cf5312eb0c49d45538ef1ed181481f7 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Mon, 18 Aug 2025 17:37:49 +0800 Subject: [PATCH 7/8] resolve comments --- src/chain/store/index.rs | 33 +++++++++++++++++---------------- src/shim/mod.rs | 1 + src/shim/policy.rs | 4 ++++ 3 files changed, 22 insertions(+), 16 deletions(-) create mode 100644 src/shim/policy.rs diff --git a/src/chain/store/index.rs b/src/chain/store/index.rs index 90f76617476d..8ab7dcd5807e 100644 --- a/src/chain/store/index.rs +++ b/src/chain/store/index.rs @@ -122,25 +122,28 @@ impl ChainIndex { mut from: Arc, resolve: ResolveNullTipset, ) -> Result, Error> { - const CHECKPOINT_INTERVAL: ChainEpoch = 1000; - static CACHE: LazyLock>> = - LazyLock::new(|| { - SizeTrackingLruCache::new_with_default_metrics_registry( - "tipset_by_height".into(), - 4096.try_into().expect("infallible"), - ) - }); + use crate::shim::policy::policy_constants::CHAIN_FINALITY; + static CACHE: LazyLock> = LazyLock::new(|| { + SizeTrackingLruCache::new_with_default_metrics_registry( + "tipset_by_height".into(), + 4096.try_into().expect("infallible"), + ) + }); + + // use `CHAIN_FINALITY` as checkpoint interval fn next_checkpoint(epoch: ChainEpoch) -> ChainEpoch { - epoch - epoch.mod_floor(&CHECKPOINT_INTERVAL) + CHECKPOINT_INTERVAL + epoch - epoch.mod_floor(&CHAIN_FINALITY) + CHAIN_FINALITY } let from_epoch = from.epoch(); let mut checkpoint_from_epoch = to; while checkpoint_from_epoch < from_epoch { - if let Some(checkpoint_from) = CACHE.get_cloned(&checkpoint_from_epoch) { - from = checkpoint_from; + if let Some(checkpoint_from_key) = CACHE.get_cloned(&checkpoint_from_epoch) + && let Ok(Some(checkpoint_from)) = Tipset::load(&self.db, &checkpoint_from_key) + { + from = checkpoint_from.into(); break; } checkpoint_from_epoch = next_checkpoint(checkpoint_from_epoch); @@ -157,12 +160,10 @@ impl ChainIndex { } for (child, parent) in self.chain(from).tuple_windows() { - // use `child.epoch() + CHECKPOINT_INTERVAL <= from_epoch` where `CHECKPOINT_INTERVAL>=CHAIN_FINALITY && CHECKPOINT_INTERVAL>=F3_CHAIN_FINALITY` + // use `child.epoch() + CHAIN_FINALITY <= from_epoch` // to ensure the cached child is finalized(not on a fork). - if child.epoch() % CHECKPOINT_INTERVAL == 0 - && child.epoch() + CHECKPOINT_INTERVAL <= from_epoch - { - CACHE.push(child.epoch(), child.clone()); + if child.epoch() % CHAIN_FINALITY == 0 && child.epoch() + CHAIN_FINALITY <= from_epoch { + CACHE.push(child.epoch(), child.key().clone()); } if to == child.epoch() { diff --git a/src/shim/mod.rs b/src/shim/mod.rs index 5c8508cdce47..387cf2fe8c7a 100644 --- a/src/shim/mod.rs +++ b/src/shim/mod.rs @@ -16,6 +16,7 @@ pub mod kernel; pub mod machine; pub mod message; pub mod piece; +pub mod policy; pub mod randomness; pub mod sector; pub mod state_tree; diff --git a/src/shim/policy.rs b/src/shim/policy.rs new file mode 100644 index 000000000000..5eb4320f2467 --- /dev/null +++ b/src/shim/policy.rs @@ -0,0 +1,4 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + +pub use fil_actors_shared::v16::runtime::policy_constants; From cbf06fc0da5f9811e5331f1249456eb49c0d8ddd Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Mon, 18 Aug 2025 18:03:24 +0800 Subject: [PATCH 8/8] fix --- src/chain/store/index.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/chain/store/index.rs b/src/chain/store/index.rs index 8ab7dcd5807e..4f0ef0cb530b 100644 --- a/src/chain/store/index.rs +++ b/src/chain/store/index.rs @@ -141,9 +141,9 @@ impl ChainIndex { let mut checkpoint_from_epoch = to; while checkpoint_from_epoch < from_epoch { if let Some(checkpoint_from_key) = CACHE.get_cloned(&checkpoint_from_epoch) - && let Ok(Some(checkpoint_from)) = Tipset::load(&self.db, &checkpoint_from_key) + && let Ok(Some(checkpoint_from)) = self.load_tipset(&checkpoint_from_key) { - from = checkpoint_from.into(); + from = checkpoint_from; break; } checkpoint_from_epoch = next_checkpoint(checkpoint_from_epoch);