From 6c22b7735c86bba40a14490f5966109fcd68c425 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Fri, 22 May 2026 19:52:10 +0800 Subject: [PATCH 1/7] warmup tipset_by_height lookup --- src/chain/store/chain_store.rs | 45 +++++++++++----- src/daemon/mod.rs | 39 ++++++++------ src/rpc/methods/chain.rs | 94 +++++++++++++++++++++++++--------- 3 files changed, 125 insertions(+), 53 deletions(-) diff --git a/src/chain/store/chain_store.rs b/src/chain/store/chain_store.rs index 7c3e08284c9f..f2002b48490e 100644 --- a/src/chain/store/chain_store.rs +++ b/src/chain/store/chain_store.rs @@ -8,7 +8,10 @@ use super::{ }; use crate::networks::{ChainConfig, Height}; use crate::prelude::*; -use crate::rpc::eth::{eth_tx_from_signed_eth_message, types::EthHash}; +use crate::rpc::{ + chain::ChainGetTipSetFinalityStatus, + 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::ExecutedTipset; @@ -60,6 +63,9 @@ pub struct ChainStore { /// F3 finalized tipset cache f3_finalized_tipset: Arc>>, + /// EC calculator finalized epoch cache + ec_calculator_finalized_epoch: Arc>, + /// Used as a cache for tipset `lookbacks`. chain_index: ChainIndex, @@ -84,6 +90,7 @@ impl ShallowClone for ChainStore { head_changes_tx: self.head_changes_tx.clone(), heaviest_tipset: self.heaviest_tipset.shallow_clone(), f3_finalized_tipset: self.f3_finalized_tipset.shallow_clone(), + ec_calculator_finalized_epoch: self.ec_calculator_finalized_epoch.shallow_clone(), chain_index: self.chain_index.shallow_clone(), tipset_tracker: self.tipset_tracker.shallow_clone(), genesis_block_header: self.genesis_block_header.shallow_clone(), @@ -111,35 +118,37 @@ impl ChainStore { } else { Tipset::from(&genesis_block_header) }; - let heaviest_tipset = Arc::new(RwLock::new(head)); + let heaviest_tipset = Arc::new(RwLock::new(head.shallow_clone())); let f3_finalized_tipset: Arc>> = Default::default(); - let chain_index = ChainIndex::new(db.shallow_clone()).with_is_tipset_finalized(Arc::new({ - let chain_finality = chain_config.policy.chain_finality; - let heaviest_tipset = heaviest_tipset.clone(); - let f3_finalized_tipset = f3_finalized_tipset.clone(); + let chain_index = ChainIndex::new(db.shallow_clone()); + let ec_calculator_finalized_epoch = Arc::new(RwLock::new( + ChainGetTipSetFinalityStatus::get_ec_finality_epoch(&chain_index, &chain_config, &head), + )); + let chain_index = chain_index.with_is_tipset_finalized(Arc::new({ + let f3_finalized_tipset = f3_finalized_tipset.shallow_clone(); + let ec_calculator_finalized_epoch = ec_calculator_finalized_epoch.shallow_clone(); move |ts| { let finalized = f3_finalized_tipset .read() .as_ref() .map(|ts| ts.epoch()) .unwrap_or_default() - .max(heaviest_tipset.read().epoch() - chain_finality); + .max(*ec_calculator_finalized_epoch.read()); ts.epoch() <= finalized } })); - let cs = Self { + Ok(Self { head_changes_tx: publisher, chain_index, tipset_tracker: TipsetTracker::new(db, chain_config.clone()), heaviest_tipset, f3_finalized_tipset, + ec_calculator_finalized_epoch, genesis_block_header: genesis_block_header.into(), validated_blocks: Default::default(), chain_config, messages_in_tipset_cache: Default::default(), - }; - - Ok(cs) + }) } /// Sets F3 finalized tipset @@ -152,6 +161,11 @@ impl ChainStore { self.f3_finalized_tipset.read().clone() } + /// Gets the EC calculator finalized epoch + pub fn ec_calculator_finalized_epoch(&self) -> ChainEpoch { + *self.ec_calculator_finalized_epoch.read() + } + /// Cache for messages in tipsets, keyed by tipset key. pub fn messages_in_tipset_cache(&self) -> &MessagesInTipsetCache { &self.messages_in_tipset_cache @@ -161,8 +175,13 @@ impl ChainStore { pub fn set_heaviest_tipset(&self, head: Tipset) -> Result<(), Error> { head.key().save(self.db())?; self.db().set_heaviest_tipset_key(head.key())?; - let old_head = std::mem::replace(&mut *self.heaviest_tipset.write(), head.clone()); - + let old_head = std::mem::replace(&mut *self.heaviest_tipset.write(), head.shallow_clone()); + *self.ec_calculator_finalized_epoch.write() = + ChainGetTipSetFinalityStatus::get_ec_finality_epoch( + self.chain_index(), + self.chain_config(), + &head, + ); if crate::utils::broadcast::has_subscribers(&self.head_changes_tx) { let changes = match crate::rpc::chain::chain_get_path(self, old_head.key(), head.key()) { diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index cfeea5d6a6c6..82947731b3f1 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -812,22 +812,31 @@ fn warmup_in_background(ctx: &AppContext) { let cs = ctx.chain_store().shallow_clone(); tokio::task::spawn_blocking(move || { let start = Instant::now(); - match cs.chain_index().tipset_by_height( - // 0 would short-circuit the cache - 1, - cs.heaviest_tipset(), - ResolveNullTipset::TakeOlder, - ) { - Ok(_) => { - tracing::info!( - "Successfully populated tipset_by_height cache, took {}", - humantime::format_duration(start.elapsed()) - ); - } - Err(e) => { - tracing::warn!("Failed to populate tipset_by_height cache: {e}"); - } + let mut from = cs.heaviest_tipset(); + let ec_calculator_finalized_epoch = cs.ec_calculator_finalized_epoch(); + let warmup_epochs = (19..ec_calculator_finalized_epoch).step_by(20).collect_vec(); + for epoch in warmup_epochs.into_iter().rev() { + tracing::info!("warming up tipset_by_height@{epoch}"); + from = match cs.chain_index().tipset_by_height( + epoch, + from.shallow_clone(), + ResolveNullTipset::TakeOlder, + ) { + Ok(Some(ts)) => ts, + Ok(None) => { + tracing::error!("tipset@{epoch} not found"); + continue; + } + Err(e) => { + tracing::error!("failed to load tipset@{epoch}: {e}"); + continue; + } + }; } + tracing::info!( + "Populated tipset_by_height cache, took {}", + humantime::format_duration(start.elapsed()) + ); }); } diff --git a/src/rpc/methods/chain.rs b/src/rpc/methods/chain.rs index f9f8f6d23bbd..d3e764b58f14 100644 --- a/src/rpc/methods/chain.rs +++ b/src/rpc/methods/chain.rs @@ -7,7 +7,7 @@ use types::*; #[cfg(test)] use crate::blocks::RawBlockHeader; use crate::blocks::{Block, CachingBlockHeader, Tipset, TipsetKey}; -use crate::chain::index::ResolveNullTipset; +use crate::chain::index::{ChainIndex, ResolveNullTipset}; use crate::chain::{ChainStore, ExportOptions, FilecoinSnapshotVersion, HeadChange}; use crate::chain_sync::{get_full_tipset, load_full_tipset}; use crate::cid_collections::{CidHashSet, FileBackedCidHashSet}; @@ -17,6 +17,7 @@ use crate::lotus_json::{HasLotusJson, LotusJson, lotus_json_with_self}; #[cfg(test)] use crate::lotus_json::{assert_all_snapshots, assert_unchanged_via_json}; use crate::message::{ChainMessage, SignedMessage}; +use crate::networks::ChainConfig; use crate::prelude::*; use crate::rpc::eth::{ Block as EthBlock, EthLog, TxInfo, eth_logs_with_filter, types::ApiHeaders, @@ -1167,10 +1168,51 @@ impl ChainGetTipSetFinalityStatus { } } - fn get_ec_finality_threshold_depth_and_tipset( - ctx: &Ctx, - head: Tipset, - ) -> anyhow::Result<(i64, Option)> { + pub fn get_ec_finality_epoch( + chain_index: &ChainIndex, + chain_config: &ChainConfig, + head: &Tipset, + ) -> i64 { + let depth = + Self::get_ec_finality_threshold_depth_with_cache(chain_index, chain_config, head); + Self::get_ec_finality_epoch_by_depth(chain_config, head, depth) + } + + fn get_ec_finality_epoch_by_depth( + chain_config: &ChainConfig, + head: &Tipset, + depth: i64, + ) -> i64 { + if depth >= 0 { + (head.epoch() - depth).max(0) + } else { + (head.epoch() - chain_config.policy.chain_finality).max(0) + } + } + + fn get_ec_finality_threshold_depth_with_cache( + chain_index: &ChainIndex, + chain_config: &ChainConfig, + head: &Tipset, + ) -> i64 { + static CACHE: parking_lot::Mutex> = parking_lot::Mutex::new(None); + let mut cache = CACHE.lock(); + if let Some((cached_head, cached_threshold)) = &*cache + && cached_head == head + { + *cached_threshold + } else { + let threshold = Self::get_ec_finality_threshold_depth(chain_index, chain_config, head); + *cache = Some((head.shallow_clone(), threshold)); + threshold + } + } + + fn get_ec_finality_threshold_depth( + chain_index: &ChainIndex, + chain_config: &ChainConfig, + head: &Tipset, + ) -> i64 { use crate::chain::ec_finality::calculator::{ DEFAULT_BLOCKS_PER_EPOCH, DEFAULT_BYZANTINE_FRACTION, DEFAULT_GUARANTEE, find_threshold_depth, @@ -1184,13 +1226,13 @@ impl ChainGetTipSetFinalityStatus { /// they consume array slots without advancing the meaningful epoch count. const FINALITY_CHAIN_EXTRA_EPOCHS: usize = 5; - let finality = ctx.chain_config().policy.chain_finality; + let finality = chain_config.policy.chain_finality; let chain_len = finality as usize + FINALITY_CHAIN_EXTRA_EPOCHS; let mut chain = Vec::with_capacity(chain_len); let mut ts = head.shallow_clone(); while chain.len() < chain_len { chain.push(ts.len() as i64); - if let Ok(parent) = ctx.chain_index().load_required_tipset(ts.parents()) { + if let Ok(parent) = chain_index.load_required_tipset(ts.parents()) { // insert 0 for null rounds if let Ok(n_null_tipsets_to_pad) = usize::try_from(ts.epoch() - parent.epoch() - 1) && n_null_tipsets_to_pad > 0 @@ -1206,7 +1248,7 @@ impl ChainGetTipSetFinalityStatus { } // Reverse to chronological order (oldest first). chain.reverse(); - let depth = match find_threshold_depth( + match find_threshold_depth( &chain, finality, DEFAULT_BLOCKS_PER_EPOCH, @@ -1220,23 +1262,25 @@ impl ChainGetTipSetFinalityStatus { ); -1 } - }; - let finalized = if depth >= 0 - && let Ok(Some(ts)) = ctx.chain_index().tipset_by_height( - (head.epoch() - depth).max(0), - head.shallow_clone(), - ResolveNullTipset::TakeOlder, - ) { - Some(ts) - } else { - let ec_finality_epoch = - (head.epoch() - ctx.chain_config().policy.chain_finality).max(0); - ctx.chain_index().tipset_by_height( - ec_finality_epoch, - head, - ResolveNullTipset::TakeOlder, - )? - }; + } + } + + fn get_ec_finality_threshold_depth_and_tipset( + ctx: &Ctx, + head: Tipset, + ) -> anyhow::Result<(i64, Option)> { + let depth = Self::get_ec_finality_threshold_depth_with_cache( + ctx.chain_index(), + ctx.chain_config(), + &head, + ); + let ec_finality_epoch = + Self::get_ec_finality_epoch_by_depth(ctx.chain_config(), &head, depth); + let finalized = ctx.chain_index().tipset_by_height( + ec_finality_epoch, + head, + ResolveNullTipset::TakeOlder, + )?; Ok((depth, finalized)) } } From 0c18225291ad0647ba20e2a8f7c566bd5683af77 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Wed, 27 May 2026 16:13:56 +0800 Subject: [PATCH 2/7] revert debugging change --- src/daemon/mod.rs | 41 +++++++++++++++-------------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index c11f10139a28..f9206c1d7d91 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -816,33 +816,22 @@ fn warmup_in_background(ctx: &AppContext) { let cs = ctx.chain_store().shallow_clone(); tokio::task::spawn_blocking(move || { let start = Instant::now(); - let mut from = cs.heaviest_tipset(); - let ec_calculator_finalized_epoch = cs.ec_calculator_finalized_epoch(); - let warmup_epochs = (19..ec_calculator_finalized_epoch) - .step_by(20) - .collect_vec(); - for epoch in warmup_epochs.into_iter().rev() { - tracing::info!("warming up tipset_by_height@{epoch}"); - from = match cs.chain_index().tipset_by_height( - epoch, - from.shallow_clone(), - ResolveNullTipset::TakeOlder, - ) { - Ok(Some(ts)) => ts, - Ok(None) => { - tracing::error!("tipset@{epoch} not found"); - continue; - } - Err(e) => { - tracing::error!("failed to load tipset@{epoch}: {e}"); - continue; - } - }; + match cs.chain_index().tipset_by_height( + // 0 would short-circuit the cache + 1, + cs.heaviest_tipset(), + ResolveNullTipset::TakeOlder, + ) { + Ok(_) => { + tracing::info!( + "Successfully populated tipset_by_height cache, took {}", + humantime::format_duration(start.elapsed()) + ); + } + Err(e) => { + tracing::warn!("Failed to populate tipset_by_height cache: {e}"); + } } - tracing::info!( - "Populated tipset_by_height cache, took {}", - humantime::format_duration(start.elapsed()) - ); }); } From 2da49d64a66e27dd6bc609426f8f588f92cb00f9 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Wed, 27 May 2026 16:47:17 +0800 Subject: [PATCH 3/7] fix clippy --- src/chain/store/chain_store.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/chain/store/chain_store.rs b/src/chain/store/chain_store.rs index f2002b48490e..66d769186a33 100644 --- a/src/chain/store/chain_store.rs +++ b/src/chain/store/chain_store.rs @@ -162,6 +162,7 @@ impl ChainStore { } /// Gets the EC calculator finalized epoch + #[allow(dead_code)] pub fn ec_calculator_finalized_epoch(&self) -> ChainEpoch { *self.ec_calculator_finalized_epoch.read() } From 8eef8d9d97eb25349c17bc49912a6dec96fad19d Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Wed, 27 May 2026 17:46:49 +0800 Subject: [PATCH 4/7] Prefill tipset_by_height cache --- src/chain/store/chain_store.rs | 1 - src/chain/store/index.rs | 12 ++++++++++++ src/daemon/mod.rs | 25 +++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/chain/store/chain_store.rs b/src/chain/store/chain_store.rs index 66d769186a33..f2002b48490e 100644 --- a/src/chain/store/chain_store.rs +++ b/src/chain/store/chain_store.rs @@ -162,7 +162,6 @@ impl ChainStore { } /// Gets the EC calculator finalized epoch - #[allow(dead_code)] pub fn ec_calculator_finalized_epoch(&self) -> ChainEpoch { *self.ec_calculator_finalized_epoch.read() } diff --git a/src/chain/store/index.rs b/src/chain/store/index.rs index 4072d5a3814e..3ee6a483c767 100644 --- a/src/chain/store/index.rs +++ b/src/chain/store/index.rs @@ -225,6 +225,18 @@ impl ChainIndex { Ok(None) } + pub async fn tipset_by_height_async( + &self, + to: ChainEpoch, + from: Tipset, + resolve: ResolveNullTipset, + ) -> Result, Error> { + let this = self.shallow_clone(); + tokio::task::spawn_blocking(move || this.tipset_by_height(to, from, resolve)) + .await + .map_err(|e| Error::Other(e.to_string()))? + } + /// Same as [`Self::tipset_by_height`], but errors if that would return `None`. pub fn load_required_tipset_by_height( &self, diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index f9206c1d7d91..6b3d4580ecb5 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -405,6 +405,31 @@ async fn prefill_rpc_caches_for_tipset(state_manager: StateManager, tsk: TipsetK warn!("failed to call `StateManager::execution_trace` for cache warmup: {e:#}"); } } + { + let finalized_epoch = state_manager + .chain_store() + .ec_calculator_finalized_epoch() + .max( + state_manager + .chain_store() + .f3_finalized_tipset() + .map(|ts| ts.epoch()) + .unwrap_or(0), + ); + if let Err(e) = state_manager + .chain_index() + .tipset_by_height_async( + finalized_epoch, + ts.shallow_clone(), + ResolveNullTipset::TakeOlder, + ) + .await + { + warn!( + "failed to call `ChainIndex::tipset_by_height` at finalized epoch {finalized_epoch} for cache warmup: {e:#}" + ); + } + } { use crate::rpc::eth::filter::{Matcher, SkipEvent}; struct CollectEventsCachePrefillingMatcher; From db1faa8854547ed849daf9cc1a363bbba16a318c Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Wed, 27 May 2026 18:34:05 +0800 Subject: [PATCH 5/7] refine some logic --- src/chain/store/chain_store.rs | 20 +++++++++++++------- src/chain/store/index.rs | 9 ++++++--- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/chain/store/chain_store.rs b/src/chain/store/chain_store.rs index f2002b48490e..159d2c795371 100644 --- a/src/chain/store/chain_store.rs +++ b/src/chain/store/chain_store.rs @@ -33,7 +33,10 @@ use fvm_ipld_encoding::CborStore; use nonzero_ext::nonzero; use parking_lot::{Mutex, RwLock}; use serde::{Serialize, de::DeserializeOwned}; -use std::num::NonZeroUsize; +use std::{ + num::NonZeroUsize, + sync::atomic::{self, AtomicI64}, +}; use tokio::sync::broadcast; use tracing::{debug, error, trace, warn}; @@ -64,7 +67,7 @@ pub struct ChainStore { f3_finalized_tipset: Arc>>, /// EC calculator finalized epoch cache - ec_calculator_finalized_epoch: Arc>, + ec_calculator_finalized_epoch: Arc, /// Used as a cache for tipset `lookbacks`. chain_index: ChainIndex, @@ -121,7 +124,7 @@ impl ChainStore { let heaviest_tipset = Arc::new(RwLock::new(head.shallow_clone())); let f3_finalized_tipset: Arc>> = Default::default(); let chain_index = ChainIndex::new(db.shallow_clone()); - let ec_calculator_finalized_epoch = Arc::new(RwLock::new( + let ec_calculator_finalized_epoch = Arc::new(AtomicI64::new( ChainGetTipSetFinalityStatus::get_ec_finality_epoch(&chain_index, &chain_config, &head), )); let chain_index = chain_index.with_is_tipset_finalized(Arc::new({ @@ -133,7 +136,7 @@ impl ChainStore { .as_ref() .map(|ts| ts.epoch()) .unwrap_or_default() - .max(*ec_calculator_finalized_epoch.read()); + .max(ec_calculator_finalized_epoch.load(atomic::Ordering::Relaxed)); ts.epoch() <= finalized } })); @@ -163,7 +166,8 @@ impl ChainStore { /// Gets the EC calculator finalized epoch pub fn ec_calculator_finalized_epoch(&self) -> ChainEpoch { - *self.ec_calculator_finalized_epoch.read() + self.ec_calculator_finalized_epoch + .load(atomic::Ordering::Relaxed) } /// Cache for messages in tipsets, keyed by tipset key. @@ -176,12 +180,14 @@ impl ChainStore { head.key().save(self.db())?; self.db().set_heaviest_tipset_key(head.key())?; let old_head = std::mem::replace(&mut *self.heaviest_tipset.write(), head.shallow_clone()); - *self.ec_calculator_finalized_epoch.write() = + self.ec_calculator_finalized_epoch.store( ChainGetTipSetFinalityStatus::get_ec_finality_epoch( self.chain_index(), self.chain_config(), &head, - ); + ), + atomic::Ordering::Relaxed, + ); if crate::utils::broadcast::has_subscribers(&self.head_changes_tx) { let changes = match crate::rpc::chain::chain_get_path(self, old_head.key(), head.key()) { diff --git a/src/chain/store/index.rs b/src/chain/store/index.rs index 3ee6a483c767..170a8a76479a 100644 --- a/src/chain/store/index.rs +++ b/src/chain/store/index.rs @@ -166,6 +166,10 @@ impl ChainIndex { epoch.mod_floor(&CHECKPOINT_INTERVAL) == 0 } + if to == 0 { + return Ok(Some(Tipset::from(from.genesis(&self.db)?))); + } + let from_epoch = from.epoch(); let mut checkpoint_from_epoch = to; @@ -180,14 +184,13 @@ impl ChainIndex { checkpoint_from_epoch = next_checkpoint(checkpoint_from_epoch); } - if to == 0 { - return Ok(Some(Tipset::from(from.genesis(&self.db)?))); - } if to > from.epoch() { return Err(Error::Other(format!( "looking for tipset with height greater than start point, req: {to}, head: {from}", from = from.epoch() ))); + } else if to == from.epoch() { + return Ok(Some(from)); } let from_epoch = from.epoch(); From e8e976e3933c224739a70230a537a24306fbe5b5 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Wed, 27 May 2026 18:59:03 +0800 Subject: [PATCH 6/7] adjust atomic::Ordering --- src/chain/store/chain_store.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/chain/store/chain_store.rs b/src/chain/store/chain_store.rs index 159d2c795371..d81a57b6c6da 100644 --- a/src/chain/store/chain_store.rs +++ b/src/chain/store/chain_store.rs @@ -136,7 +136,7 @@ impl ChainStore { .as_ref() .map(|ts| ts.epoch()) .unwrap_or_default() - .max(ec_calculator_finalized_epoch.load(atomic::Ordering::Relaxed)); + .max(ec_calculator_finalized_epoch.load(atomic::Ordering::Acquire)); ts.epoch() <= finalized } })); @@ -167,7 +167,7 @@ impl ChainStore { /// Gets the EC calculator finalized epoch pub fn ec_calculator_finalized_epoch(&self) -> ChainEpoch { self.ec_calculator_finalized_epoch - .load(atomic::Ordering::Relaxed) + .load(atomic::Ordering::Acquire) } /// Cache for messages in tipsets, keyed by tipset key. @@ -186,7 +186,7 @@ impl ChainStore { self.chain_config(), &head, ), - atomic::Ordering::Relaxed, + atomic::Ordering::Release, ); if crate::utils::broadcast::has_subscribers(&self.head_changes_tx) { let changes = match crate::rpc::chain::chain_get_path(self, old_head.key(), head.key()) From 7bd0392ab9461f1220ef3a431afad8f92baadd3d Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Wed, 27 May 2026 19:16:47 +0800 Subject: [PATCH 7/7] use quick_cache in ChainGetTipSetFinalityStatus --- src/rpc/methods/chain.rs | 42 +++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/src/rpc/methods/chain.rs b/src/rpc/methods/chain.rs index d3e764b58f14..9a03161f3c4c 100644 --- a/src/rpc/methods/chain.rs +++ b/src/rpc/methods/chain.rs @@ -44,6 +44,7 @@ use num::BigInt; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use sha2::Sha256; +use std::convert::Infallible; use std::fs::File; use std::{collections::VecDeque, path::PathBuf, sync::LazyLock}; use tokio::sync::{ @@ -1122,6 +1123,7 @@ impl RpcMethod<1> for ChainGetTipSetV2 { pub enum ChainGetTipSetFinalityStatus {} +const EC_CALCULATOR_FINALITY_CACHE_SIZE: usize = 4; impl ChainGetTipSetFinalityStatus { pub fn get_finality_status(ctx: &Ctx) -> anyhow::Result { let head = ctx.chain_store().heaviest_tipset(); @@ -1153,19 +1155,11 @@ impl ChainGetTipSetFinalityStatus { ctx: &Ctx, head: Tipset, ) -> anyhow::Result<(i64, Option)> { - static CACHE: parking_lot::Mutex)>> = - parking_lot::Mutex::new(None); - let mut cache = CACHE.lock(); - if let Some((cached_head, cached_threshold, cached_tipset)) = &*cache - && cached_head == &head - { - Ok((*cached_threshold, cached_tipset.shallow_clone())) - } else { - let (threshold, tipset) = - Self::get_ec_finality_threshold_depth_and_tipset(ctx, head.shallow_clone())?; - *cache = Some((head, threshold, tipset.shallow_clone())); - Ok((threshold, tipset)) - } + static CACHE: LazyLock)>> = + LazyLock::new(|| quick_cache::sync::Cache::new(EC_CALCULATOR_FINALITY_CACHE_SIZE)); + CACHE.get_or_insert_with(head.shallow_clone().key(), move || { + Self::get_ec_finality_threshold_depth_and_tipset(ctx, head) + }) } pub fn get_ec_finality_epoch( @@ -1195,17 +1189,17 @@ impl ChainGetTipSetFinalityStatus { chain_config: &ChainConfig, head: &Tipset, ) -> i64 { - static CACHE: parking_lot::Mutex> = parking_lot::Mutex::new(None); - let mut cache = CACHE.lock(); - if let Some((cached_head, cached_threshold)) = &*cache - && cached_head == head - { - *cached_threshold - } else { - let threshold = Self::get_ec_finality_threshold_depth(chain_index, chain_config, head); - *cache = Some((head.shallow_clone(), threshold)); - threshold - } + static CACHE: LazyLock> = + LazyLock::new(|| quick_cache::sync::Cache::new(EC_CALCULATOR_FINALITY_CACHE_SIZE)); + CACHE + .get_or_insert_with(head.key(), move || -> Result { + Ok(Self::get_ec_finality_threshold_depth( + chain_index, + chain_config, + head, + )) + }) + .expect("infallible") } fn get_ec_finality_threshold_depth(