diff --git a/src/chain/store/chain_store.rs b/src/chain/store/chain_store.rs index 714c1f596ae6..b8a3beca6fb1 100644 --- a/src/chain/store/chain_store.rs +++ b/src/chain/store/chain_store.rs @@ -319,16 +319,16 @@ impl ChainStore { /// Returns [`None`] when `ts` has no known child on the current heaviest chain /// (e.g. `ts` is the chain head). Blockstore errors are returned as [`Err`]. - pub fn load_child_tipset(&self, ts: &Tipset) -> Result, Error> { + pub async fn load_child_tipset(&self, ts: &Tipset) -> Result, Error> { let head = self.heaviest_tipset(); if head.parents() == ts.key() { Ok(Some(head)) } else if head.epoch() > ts.epoch() { - match self.chain_index().tipset_by_height( - ts.epoch() + 1, - head, - ResolveNullTipset::TakeNewer, - )? { + match self + .chain_index() + .tipset_by_height_async(ts.epoch() + 1, head, ResolveNullTipset::TakeNewer) + .await? + { Some(maybe_child) if maybe_child.parents() == ts.key() => Ok(Some(maybe_child)), _ => Ok(None), } @@ -431,7 +431,7 @@ impl ChainStore { } let next_ts = chain_index - .load_required_tipset_by_height( + .load_required_tipset_by_height_blocking( lbr + 1, heaviest_tipset.clone(), ResolveNullTipset::TakeNewer, diff --git a/src/chain/store/index.rs b/src/chain/store/index.rs index 0c839cc2e56e..f533facca875 100644 --- a/src/chain/store/index.rs +++ b/src/chain/store/index.rs @@ -240,6 +240,7 @@ impl ChainIndex { Ok(None) } + /// Non-blocking version of [`Self::tipset_by_height`] pub async fn tipset_by_height_async( &self, to: ChainEpoch, @@ -247,13 +248,13 @@ impl ChainIndex { 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()))? + tokio::task::spawn_blocking(move || this.tipset_by_height(to, from, resolve)).await? } /// Same as [`Self::tipset_by_height`], but errors if that would return `None`. - pub fn load_required_tipset_by_height( + /// This call can be expensive and blocking, use [`Self::load_required_tipset_by_height`] + /// in async contexts to avoid exhausting Tokio worker threads. + pub fn load_required_tipset_by_height_blocking( &self, to: ChainEpoch, from: Tipset, @@ -263,6 +264,18 @@ impl ChainIndex { .ok_or_else(|| Error::NotFound(format!("tipset at epoch {to}").into())) } + /// Same as [`Self::tipset_by_height_async`], but errors if that would return `None`. + pub async fn load_required_tipset_by_height( + &self, + to: ChainEpoch, + from: Tipset, + resolve: ResolveNullTipset, + ) -> Result { + self.tipset_by_height_async(to, from, resolve) + .await? + .ok_or_else(|| Error::NotFound(format!("tipset at epoch {to}").into())) + } + /// Finds the latest beacon entry given a tipset up to 20 tipsets behind pub fn latest_beacon_entry(&self, tipset: Tipset) -> Result { for ts in tipset.chain(&self.db).take(20) { diff --git a/src/chain_sync/sync_status.rs b/src/chain_sync/sync_status.rs index a93f9ccea871..d946b7e49968 100644 --- a/src/chain_sync/sync_status.rs +++ b/src/chain_sync/sync_status.rs @@ -13,7 +13,7 @@ use std::sync::Arc; use tracing::log; // Node considered synced if the head is within this threshold. -const SYNCED_EPOCH_THRESHOLD: u64 = 10; +const SYNCED_EPOCH_THRESHOLD: u64 = 2; /// Represents the overall synchronization status of the Forest node. #[derive( diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index 5063d78ea926..1f04bcb3c508 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -825,7 +825,7 @@ pub(super) async fn start_services( } if !opts.stateless && !opts.skip_load_actors - && let Err(e) = ctx.state_manager.maybe_rewind_heaviest_tipset() + && let Err(e) = ctx.state_manager.maybe_rewind_heaviest_tipset().await { tracing::warn!("error in maybe_rewind_heaviest_tipset: {e:#}"); } diff --git a/src/dev/subcommands/export_state_tree_cmd.rs b/src/dev/subcommands/export_state_tree_cmd.rs index b133ba78495a..df7b2670aabb 100644 --- a/src/dev/subcommands/export_state_tree_cmd.rs +++ b/src/dev/subcommands/export_state_tree_cmd.rs @@ -77,11 +77,14 @@ impl ExportStateTreeCommand { .await?; let chain_store = ChainStore::new(db.clone(), chain_config, genesis_header)?; - let start_ts = chain_store.chain_index().load_required_tipset_by_height( - from, - chain_store.heaviest_tipset(), - ResolveNullTipset::TakeNewer, - )?; + let start_ts = chain_store + .chain_index() + .load_required_tipset_by_height( + from, + chain_store.heaviest_tipset(), + ResolveNullTipset::TakeNewer, + ) + .await?; let mut ipld_roots = vec![]; for (child, ts) in start_ts diff --git a/src/dev/subcommands/state_cmd.rs b/src/dev/subcommands/state_cmd.rs index 932f1d5a7dd6..5b1c07569ef5 100644 --- a/src/dev/subcommands/state_cmd.rs +++ b/src/dev/subcommands/state_cmd.rs @@ -80,12 +80,14 @@ impl ComputeCommand { let (ts, ts_next) = { // We don't want to track all entries that are visited by `tipset_by_height` db.pause_tracking(); - let ts = chain_index.load_required_tipset_by_height( - epoch, - chain_store.heaviest_tipset(), - ResolveNullTipset::TakeOlder, - )?; - let ts_next = chain_store.load_child_tipset(&ts)?.with_context(|| { + let ts = chain_index + .load_required_tipset_by_height( + epoch, + chain_store.heaviest_tipset(), + ResolveNullTipset::TakeOlder, + ) + .await?; + let ts_next = chain_store.load_child_tipset(&ts).await?.with_context(|| { format!( "no child tipset for epoch {} (may be chain head)", ts.epoch() @@ -210,11 +212,13 @@ impl ValidateCommand { let ts = { // We don't want to track all entries that are visited by `tipset_by_height` db.pause_tracking(); - let ts = chain_index.load_required_tipset_by_height( - epoch, - chain_store.heaviest_tipset(), - ResolveNullTipset::TakeOlder, - )?; + let ts = chain_index + .load_required_tipset_by_height( + epoch, + chain_store.heaviest_tipset(), + ResolveNullTipset::TakeOlder, + ) + .await?; db.resume_tracking(); SettingsStoreExt::write_obj(&db.tracker, crate::db::setting_keys::HEAD_KEY, ts.key())?; // Only track the desired tipset diff --git a/src/interpreter/externs.rs b/src/interpreter/externs.rs index d8ed23655d3c..d2607950678f 100644 --- a/src/interpreter/externs.rs +++ b/src/interpreter/externs.rs @@ -261,7 +261,7 @@ impl ForestExterns { fn get_tipset_cid_impl(&self, epoch: ChainEpoch) -> anyhow::Result { let ts = self .chain_index - .load_required_tipset_by_height( + .load_required_tipset_by_height_blocking( epoch, self.heaviest_tipset.clone(), ResolveNullTipset::TakeOlder, diff --git a/src/message_pool/msgpool/provider.rs b/src/message_pool/msgpool/provider.rs index 71c860bd9440..8f90a45272bd 100644 --- a/src/message_pool/msgpool/provider.rs +++ b/src/message_pool/msgpool/provider.rs @@ -10,6 +10,7 @@ use crate::message_pool::msg_pool::{ MAX_ACTOR_PENDING_MESSAGES, MAX_UNTRUSTED_ACTOR_PENDING_MESSAGES, }; use crate::networks::Height; +use crate::prelude::*; use crate::shim::{ address::{Address, Protocol::*}, econ::TokenAmount, @@ -18,8 +19,6 @@ use crate::shim::{ }; use crate::utils::db::CborStoreExt; use auto_impl::auto_impl; -use cid::Cid; -use std::sync::Arc; use tokio::sync::broadcast; /// Provider Trait. This trait will be used by the message pool to interact with @@ -120,7 +119,7 @@ impl Provider for ChainStore { _ => { let lookback_ts = if ts.epoch() > self.chain_config().policy.chain_finality { self.chain_index() - .load_required_tipset_by_height( + .load_required_tipset_by_height_blocking( ts.epoch() - self.chain_config().policy.chain_finality, ts.clone(), ResolveNullTipset::TakeOlder, @@ -144,3 +143,31 @@ impl Provider for ChainStore { ChainStore::messages_for_tipset(self, ts).map_err(Into::into) } } + +#[allow(dead_code)] +pub trait ProviderExt { + /// Non-blocking version of [`Provider::resolve_to_deterministic_address_at_finality`] + async fn resolve_to_deterministic_address_at_finality_async( + &self, + addr: Address, + ts: Tipset, + ) -> Result; +} + +impl ProviderExt for T +where + T: Provider + ShallowClone + Send + Sync + 'static, +{ + async fn resolve_to_deterministic_address_at_finality_async( + &self, + addr: Address, + ts: Tipset, + ) -> Result { + let this = self.shallow_clone(); + tokio::task::spawn_blocking(move || { + this.resolve_to_deterministic_address_at_finality(&addr, &ts) + }) + .await + .context("tokio join error")? + } +} diff --git a/src/rpc/methods/chain.rs b/src/rpc/methods/chain.rs index f4087231a512..5a47b1a12849 100644 --- a/src/rpc/methods/chain.rs +++ b/src/rpc/methods/chain.rs @@ -296,11 +296,10 @@ impl RpcMethod<1> for ForestChainExport { let chain_export_guard = ChainExportGuard::try_start_export()?; let head = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?; - let start_ts = ctx.chain_index().load_required_tipset_by_height( - epoch, - head, - ResolveNullTipset::TakeOlder, - )?; + let start_ts = ctx + .chain_index() + .load_required_tipset_by_height(epoch, head, ResolveNullTipset::TakeOlder) + .await?; let options = ExportOptions { skip_checksum, @@ -498,11 +497,10 @@ impl RpcMethod<1> for ForestChainExportDiff { } let head = ctx.chain_store().heaviest_tipset(); - let start_ts = ctx.chain_index().load_required_tipset_by_height( - from, - head, - ResolveNullTipset::TakeOlder, - )?; + let start_ts = ctx + .chain_index() + .load_required_tipset_by_height(from, head, ResolveNullTipset::TakeOlder) + .await?; crate::tool::subcommands::archive_cmd::do_export( ctx.chain_index().db(), @@ -798,11 +796,10 @@ impl RpcMethod<2> for ChainGetTipSetByHeight { let ts = ctx .chain_store() .load_required_tipset_or_heaviest(&tipset_key)?; - let tss = ctx.chain_index().load_required_tipset_by_height( - height, - ts, - ResolveNullTipset::TakeOlder, - )?; + let tss = ctx + .chain_index() + .load_required_tipset_by_height(height, ts, ResolveNullTipset::TakeOlder) + .await?; Ok(tss) } } @@ -828,11 +825,10 @@ impl RpcMethod<2> for ChainGetTipSetAfterHeight { let ts = ctx .chain_store() .load_required_tipset_or_heaviest(&tipset_key)?; - let tss = ctx.chain_index().load_required_tipset_by_height( - height, - ts, - ResolveNullTipset::TakeNewer, - )?; + let tss = ctx + .chain_index() + .load_required_tipset_by_height(height, ts, ResolveNullTipset::TakeNewer) + .await?; Ok(tss) } } @@ -969,16 +965,16 @@ impl ChainGetTipSetV2 { if finalized.epoch() >= safe_height { Ok(finalized) } else { - Ok(ctx.chain_index().load_required_tipset_by_height( - safe_height, - head, - ResolveNullTipset::TakeOlder, - )?) + Ok(ctx + .chain_index() + .load_required_tipset_by_height(safe_height, head, ResolveNullTipset::TakeOlder) + .await?) } } pub async fn get_latest_finalized_tipset(ctx: &Ctx) -> anyhow::Result { - ChainGetTipSetFinalityStatus::get_finality_status(ctx)? + ChainGetTipSetFinalityStatus::get_finality_status(ctx) + .await? .finalized_tip_set .context("failed to resolve finalized tipset") } @@ -993,11 +989,14 @@ impl ChainGetTipSetV2 { // Get tipset by height. if let Some(height) = &selector.height { let anchor = Self::get_tipset_by_anchor(ctx, height.anchor.as_ref()).await?; - let ts = ctx.chain_index().load_required_tipset_by_height( - height.at, - anchor, - height.resolve_null_tipset_policy(), - )?; + let ts = ctx + .chain_index() + .load_required_tipset_by_height( + height.at, + anchor, + height.resolve_null_tipset_policy(), + ) + .await?; return Ok(ts); } // Get tipset by tag, either latest or finalized. @@ -1032,10 +1031,11 @@ pub enum ChainGetTipSetFinalityStatus {} const EC_CALCULATOR_FINALITY_CACHE_SIZE: usize = 4; impl ChainGetTipSetFinalityStatus { - pub fn get_finality_status(ctx: &Ctx) -> anyhow::Result { + pub async fn get_finality_status(ctx: &Ctx) -> anyhow::Result { let head = ctx.chain_store().heaviest_tipset(); let (ec_finality_threshold_depth, ec_finalized_tip_set) = - Self::get_ec_finality_threshold_depth_and_tipset_with_cache(ctx, head.shallow_clone())?; + Self::get_ec_finality_threshold_depth_and_tipset_with_cache(ctx, head.shallow_clone()) + .await?; let f3_finalized_tip_set = ctx.chain_store().f3_finalized_tipset(); let finalized_tip_set = match (&ec_finalized_tip_set, &f3_finalized_tip_set) { (Some(ec), Some(f3)) => { @@ -1058,15 +1058,18 @@ impl ChainGetTipSetFinalityStatus { }) } - pub fn get_ec_finality_threshold_depth_and_tipset_with_cache( + pub async fn get_ec_finality_threshold_depth_and_tipset_with_cache( ctx: &Ctx, head: Tipset, ) -> anyhow::Result<(i64, Option)> { 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) - }) + CACHE + .get_or_insert_async( + head.shallow_clone().key(), + Self::get_ec_finality_threshold_depth_and_tipset(ctx, head), + ) + .await } pub fn get_ec_finality_epoch( @@ -1166,7 +1169,7 @@ impl ChainGetTipSetFinalityStatus { } } - fn get_ec_finality_threshold_depth_and_tipset( + async fn get_ec_finality_threshold_depth_and_tipset( ctx: &Ctx, head: Tipset, ) -> anyhow::Result<(i64, Option)> { @@ -1177,11 +1180,10 @@ impl ChainGetTipSetFinalityStatus { ); 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, - )?; + let finalized = ctx + .chain_index() + .tipset_by_height_async(ec_finality_epoch, head, ResolveNullTipset::TakeOlder) + .await?; Ok((depth, finalized)) } } @@ -1202,7 +1204,7 @@ impl RpcMethod<0> for ChainGetTipSetFinalityStatus { (): Self::Params, _: &http::Extensions, ) -> Result { - Ok(Self::get_finality_status(&ctx)?) + Ok(Self::get_finality_status(&ctx).await?) } } diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 1697ae57b879..b1a094aa73c9 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -924,11 +924,14 @@ impl RpcMethod<1> for BaseFeeByHeight { (height,): Self::Params, _: &http::Extensions, ) -> Result { - let ts = ctx.chain_index().load_required_tipset_by_height( - height, - ctx.chain_store().heaviest_tipset(), - ResolveNullTipset::TakeOlder, - )?; + let ts = ctx + .chain_index() + .load_required_tipset_by_height( + height, + ctx.chain_store().heaviest_tipset(), + ResolveNullTipset::TakeOlder, + ) + .await?; let base_fee = EthBaseFee::get_base_fee(&ctx, &ts)?; Ok(base_fee.atto().into()) } @@ -1068,7 +1071,7 @@ fn get_tipset_from_hash(chain_store: &ChainStore, block_hash: &EthHash) -> anyho Ok(chain_store.chain_index().load_required_tipset(&tsk)?) } -fn resolve_block_number_tipset( +async fn resolve_block_number_tipset( chain: &ChainStore, block_number: EthInt64, resolve: ResolveNullTipset, @@ -1081,13 +1084,14 @@ fn resolve_block_number_tipset( chain .chain_index() .load_required_tipset_by_height(height, head, resolve) + .await .map_err(|e| match e { crate::chain::store::Error::NullRound(epoch) => EthErrors::null_round(epoch).into(), e => e.into(), }) } -fn resolve_block_hash_tipset( +async fn resolve_block_hash_tipset( chain: &ChainStore, block_hash: &EthHash, require_canonical: bool, @@ -1097,11 +1101,10 @@ fn resolve_block_hash_tipset( // verify that the tipset is in the canonical chain if require_canonical { // walk up the current chain (our head) until we reach ts.epoch() - let walk_ts = chain.chain_index().load_required_tipset_by_height( - ts.epoch(), - chain.heaviest_tipset(), - resolve, - )?; + let walk_ts = chain + .chain_index() + .load_required_tipset_by_height(ts.epoch(), chain.heaviest_tipset(), resolve) + .await?; // verify that it equals the expected tipset if walk_ts != ts { bail!("tipset is not canonical"); diff --git a/src/rpc/methods/eth/filter/mod.rs b/src/rpc/methods/eth/filter/mod.rs index dc4d69072d43..ee3489a7d498 100644 --- a/src/rpc/methods/eth/filter/mod.rs +++ b/src/rpc/methods/eth/filter/mod.rs @@ -526,11 +526,14 @@ impl EthEventHandler { } else { *range.end() }; - let max_tipset = ctx.chain_index().load_required_tipset_by_height( - max_height, - ctx.chain_store().heaviest_tipset(), - ResolveNullTipset::TakeOlder, - )?; + let max_tipset = ctx + .chain_index() + .load_required_tipset_by_height( + max_height, + ctx.chain_store().heaviest_tipset(), + ResolveNullTipset::TakeOlder, + ) + .await?; let tipsets = max_tipset .chain(ctx.db()) .take_while(|ts| ts.epoch() >= *range.start()); diff --git a/src/rpc/methods/eth/tipset_resolver.rs b/src/rpc/methods/eth/tipset_resolver.rs index 4fc6b64c1bfd..54c1c391d08d 100644 --- a/src/rpc/methods/eth/tipset_resolver.rs +++ b/src/rpc/methods/eth/tipset_resolver.rs @@ -40,20 +40,23 @@ impl<'a> TipsetResolver<'a> { BlockNumberOrHash::PredefinedBlock(tag) => self.resolve_predefined_tipset(tag).await, BlockNumberOrHash::BlockNumber(block_number) | BlockNumberOrHash::BlockNumberObject(BlockNumber { block_number }) => { - resolve_block_number_tipset(self.ctx.chain_store(), block_number, resolve) + resolve_block_number_tipset(self.ctx.chain_store(), block_number, resolve).await } BlockNumberOrHash::BlockHash(block_hash) => { - resolve_block_hash_tipset(self.ctx.chain_store(), &block_hash, false, resolve) + resolve_block_hash_tipset(self.ctx.chain_store(), &block_hash, false, resolve).await } BlockNumberOrHash::BlockHashObject(BlockHash { block_hash, require_canonical, - }) => resolve_block_hash_tipset( - self.ctx.chain_store(), - &block_hash, - require_canonical, - resolve, - ), + }) => { + resolve_block_hash_tipset( + self.ctx.chain_store(), + &block_hash, + require_canonical, + resolve, + ) + .await + } } } @@ -95,8 +98,8 @@ impl<'a> TipsetResolver<'a> { Ok(ts) } else { match tag { - Predefined::Safe => self.get_ec_safe_tipset(), - Predefined::Finalized => self.get_ec_finalized_tipset(), + Predefined::Safe => self.get_ec_safe_tipset().await, + Predefined::Finalized => self.get_ec_finalized_tipset().await, tag => anyhow::bail!("unknown block tag: {tag}"), } } @@ -152,24 +155,25 @@ impl<'a> TipsetResolver<'a> { /// Returns the tipset considered "safe" relative to the current heaviest tipset. /// /// The safe tipset is the tipset at height `max(head.epoch() - SAFE_HEIGHT_DISTANCE, 0)`. - pub fn get_ec_safe_tipset(&self) -> anyhow::Result { + pub async fn get_ec_safe_tipset(&self) -> anyhow::Result { let head = self.ctx.chain_store().heaviest_tipset(); let safe_height = (head.epoch() - SAFE_HEIGHT_DISTANCE).max(0); - Ok(self.ctx.chain_index().load_required_tipset_by_height( - safe_height, - head, - ResolveNullTipset::TakeOlder, - )?) + Ok(self + .ctx + .chain_index() + .load_required_tipset_by_height(safe_height, head, ResolveNullTipset::TakeOlder) + .await?) } /// Returns the tipset considered finalized by the expected-consensus finality calculator(`FRC-0089`). - pub fn get_ec_finalized_tipset(&self) -> anyhow::Result { + pub async fn get_ec_finalized_tipset(&self) -> anyhow::Result { let head = self.ctx.chain_store().heaviest_tipset(); let (_, ec_finalized_tipset) = ChainGetTipSetFinalityStatus::get_ec_finality_threshold_depth_and_tipset_with_cache( self.ctx, head.clone(), - )?; + ) + .await?; ec_finalized_tipset.context("failed to resolve EC finalized tipset") } } diff --git a/src/rpc/methods/f3.rs b/src/rpc/methods/f3.rs index 2c3413f06d5b..899e9af0c066 100644 --- a/src/rpc/methods/f3.rs +++ b/src/rpc/methods/f3.rs @@ -96,11 +96,14 @@ impl RpcMethod<1> for GetTipsetByEpoch { (epoch,): Self::Params, _: &http::Extensions, ) -> Result { - let ts = ctx.chain_index().load_required_tipset_by_height( - epoch, - ctx.chain_store().heaviest_tipset(), - ResolveNullTipset::TakeOlder, - )?; + let ts = ctx + .chain_index() + .load_required_tipset_by_height( + epoch, + ctx.chain_store().heaviest_tipset(), + ResolveNullTipset::TakeOlder, + ) + .await?; Ok(ts.into()) } } diff --git a/src/rpc/methods/state.rs b/src/rpc/methods/state.rs index d4920f746336..53121815c185 100644 --- a/src/rpc/methods/state.rs +++ b/src/rpc/methods/state.rs @@ -1600,21 +1600,26 @@ impl RpcMethod<3> for ForestStateCompute { let force_recompute = force_recompute.unwrap_or_default(); let n_epochs = n_epochs.map(|n| n.get()).unwrap_or(1) as ChainEpoch; let to_epoch = from_epoch + n_epochs - 1; - let to_ts = ctx.chain_index().load_required_tipset_by_height( - to_epoch, - ctx.chain_store().heaviest_tipset(), - ResolveNullTipset::TakeOlder, - )?; + let to_ts = ctx + .chain_index() + .load_required_tipset_by_height( + to_epoch, + ctx.chain_store().heaviest_tipset(), + ResolveNullTipset::TakeOlder, + ) + .await?; let from_ts = if from_epoch >= to_ts.epoch() { // When `from_epoch` is a null epoch or `n_epochs` is 1, // `to_ts.epoch()` could be less than or equal to `from_epoch` to_ts.shallow_clone() } else { - ctx.chain_index().load_required_tipset_by_height( - from_epoch, - to_ts.shallow_clone(), - ResolveNullTipset::TakeOlder, - )? + ctx.chain_index() + .load_required_tipset_by_height( + from_epoch, + to_ts.shallow_clone(), + ResolveNullTipset::TakeOlder, + ) + .await? }; let mut futures = FuturesOrdered::new(); @@ -1753,7 +1758,7 @@ impl RpcMethod<4> for StateGetRandomnessFromTickets { ) -> Result { let tipset = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?; let chain_rand = ctx.state_manager.chain_rand(tipset); - let digest = chain_rand.get_chain_randomness(rand_epoch, false)?; + let digest = chain_rand.get_chain_randomness(rand_epoch, false).await?; let value = crate::state_manager::chain_rand::draw_randomness_from_digest( &digest, personalization, @@ -1783,7 +1788,7 @@ impl RpcMethod<2> for StateGetRandomnessDigestFromTickets { ) -> Result { let tipset = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?; let chain_rand = ctx.state_manager.chain_rand(tipset); - let digest = chain_rand.get_chain_randomness(rand_epoch, false)?; + let digest = chain_rand.get_chain_randomness(rand_epoch, false).await?; Ok(digest.to_vec()) } } @@ -1808,7 +1813,7 @@ impl RpcMethod<4> for StateGetRandomnessFromBeacon { ) -> Result { let tipset = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?; let chain_rand = ctx.state_manager.chain_rand(tipset); - let digest = chain_rand.get_beacon_randomness_v3(rand_epoch)?; + let digest = chain_rand.get_beacon_randomness_v3(rand_epoch).await?; let value = crate::state_manager::chain_rand::draw_randomness_from_digest( &digest, personalization, @@ -1838,7 +1843,7 @@ impl RpcMethod<2> for StateGetRandomnessDigestFromBeacon { ) -> Result { let tipset = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?; let chain_rand = ctx.state_manager.chain_rand(tipset); - let digest = chain_rand.get_beacon_randomness_v3(rand_epoch)?; + let digest = chain_rand.get_beacon_randomness_v3(rand_epoch).await?; Ok(digest.to_vec()) } } diff --git a/src/state_manager/chain_rand.rs b/src/state_manager/chain_rand.rs index 9bad37d489a2..277e0f04b770 100644 --- a/src/state_manager/chain_rand.rs +++ b/src/state_manager/chain_rand.rs @@ -38,7 +38,21 @@ impl ShallowClone for ChainRand { impl ChainRand { /// Gets 32 bytes of randomness for `ChainRand` parameterized by the /// `DomainSeparationTag`, `ChainEpoch`, Entropy from the ticket chain. - pub fn get_chain_randomness( + pub async fn get_chain_randomness( + &self, + round: ChainEpoch, + lookback: bool, + ) -> anyhow::Result<[u8; 32]> { + let this = self.shallow_clone(); + tokio::task::spawn_blocking(move || this.get_chain_randomness_blocking(round, lookback)) + .await? + } + + /// Gets 32 bytes of randomness for `ChainRand` parameterized by the + /// `DomainSeparationTag`, `ChainEpoch`, Entropy from the ticket chain. + /// This call can be expensive and blocking, use [`Self::get_chain_randomness`] + /// in async contexts to avoid exhausting Tokio worker threads. + pub fn get_chain_randomness_blocking( &self, round: ChainEpoch, lookback: bool, @@ -58,7 +72,7 @@ impl ChainRand { }; let rand_ts = self.chain_index - .load_required_tipset_by_height(search_height, ts, resolve)?; + .load_required_tipset_by_height_blocking(search_height, ts, resolve)?; Ok(digest( rand_ts @@ -70,40 +84,46 @@ impl ChainRand { } /// network version 13 onward - pub fn get_chain_randomness_v2(&self, round: ChainEpoch) -> anyhow::Result<[u8; 32]> { - self.get_chain_randomness(round, false) + pub fn get_chain_randomness_v2_blocking(&self, round: ChainEpoch) -> anyhow::Result<[u8; 32]> { + self.get_chain_randomness_blocking(round, false) } /// network version 13; without look-back - pub fn get_beacon_randomness_v2(&self, round: ChainEpoch) -> anyhow::Result<[u8; 32]> { - self.get_beacon_randomness(round, false) + pub fn get_beacon_randomness_v2_blocking(&self, round: ChainEpoch) -> anyhow::Result<[u8; 32]> { + self.get_beacon_randomness_blocking(round, false) } /// network version 14 onward - pub fn get_beacon_randomness_v3(&self, round: ChainEpoch) -> anyhow::Result<[u8; 32]> { + pub fn get_beacon_randomness_v3_blocking(&self, round: ChainEpoch) -> anyhow::Result<[u8; 32]> { if round < 0 { - return self.get_beacon_randomness_v2(round); + return self.get_beacon_randomness_v2_blocking(round); } let beacon_entry = self.extract_beacon_entry_for_epoch(round)?; Ok(digest(beacon_entry.signature())) } + /// Non-blocking version of [`Self::get_beacon_randomness_v3_blocking`] + pub async fn get_beacon_randomness_v3(&self, round: ChainEpoch) -> anyhow::Result<[u8; 32]> { + let this = self.shallow_clone(); + tokio::task::spawn_blocking(move || this.get_beacon_randomness_v3_blocking(round)).await? + } + /// Gets 32 bytes of randomness for `ChainRand` parameterized by the /// `DomainSeparationTag`, `ChainEpoch`, Entropy from the latest beacon /// entry. - pub fn get_beacon_randomness( + pub fn get_beacon_randomness_blocking( &self, round: ChainEpoch, lookback: bool, ) -> anyhow::Result<[u8; 32]> { - let rand_ts: Tipset = self.get_beacon_randomness_tipset(round, lookback)?; + let rand_ts: Tipset = self.get_beacon_randomness_tipset_blocking(round, lookback)?; let be = self.chain_index.latest_beacon_entry(rand_ts)?; Ok(digest(be.signature())) } pub fn extract_beacon_entry_for_epoch(&self, epoch: ChainEpoch) -> anyhow::Result { - let mut rand_ts: Tipset = self.get_beacon_randomness_tipset(epoch, false)?; + let mut rand_ts: Tipset = self.get_beacon_randomness_tipset_blocking(epoch, false)?; let (_, beacon) = self.beacon.beacon_for_epoch(epoch)?; let round = beacon.max_beacon_round_for_epoch(self.chain_config.network_version(epoch), epoch); @@ -126,7 +146,7 @@ impl ChainRand { ) } - pub fn get_beacon_randomness_tipset( + pub fn get_beacon_randomness_tipset_blocking( &self, round: ChainEpoch, lookback: bool, @@ -146,7 +166,7 @@ impl ChainRand { }; self.chain_index - .load_required_tipset_by_height(search_height, ts, resolve) + .load_required_tipset_by_height_blocking(search_height, ts, resolve) .map_err(|e| e.into()) } } @@ -154,24 +174,26 @@ impl ChainRand { impl Rand for ChainRand { fn get_chain_randomness(&self, round: ChainEpoch) -> anyhow::Result<[u8; 32]> { // Inspect and log errors as this is only called in `FVM` and errors are not propagated to the caller - self.get_chain_randomness_v2(round).inspect_err(|e| { - tracing::warn!( - "get_chain_randomness failed, round: {round}, ts@{}: {}, error: {e:#?}", - self.tipset.epoch(), - self.tipset.key() - ); - }) + self.get_chain_randomness_v2_blocking(round) + .inspect_err(|e| { + tracing::warn!( + "get_chain_randomness failed, round: {round}, ts@{}: {}, error: {e:#?}", + self.tipset.epoch(), + self.tipset.key() + ); + }) } fn get_beacon_randomness(&self, round: ChainEpoch) -> anyhow::Result<[u8; 32]> { // Inspect and log errors as this is only called in `FVM` and errors are not propagated to the caller - self.get_beacon_randomness_v3(round).inspect_err(|e| { - tracing::warn!( - "get_beacon_randomness failed, round: {round}, ts@{}: {}, error: {e:#?}", - self.tipset.epoch(), - self.tipset.key() - ); - }) + self.get_beacon_randomness_v3_blocking(round) + .inspect_err(|e| { + tracing::warn!( + "get_beacon_randomness failed, round: {round}, ts@{}: {}, error: {e:#?}", + self.tipset.epoch(), + self.tipset.key() + ); + }) } } diff --git a/src/state_manager/execution.rs b/src/state_manager/execution.rs index 50b4f27ffdb5..6986e3f43edc 100644 --- a/src/state_manager/execution.rs +++ b/src/state_manager/execution.rs @@ -189,7 +189,7 @@ impl StateManager { pub fn validate_range_blocking(&self, epochs: RangeInclusive) -> anyhow::Result<()> { let heaviest = self.heaviest_tipset(); let heaviest_epoch = heaviest.epoch(); - let end = self.chain_index().load_required_tipset_by_height( + let end = self.chain_index().load_required_tipset_by_height_blocking( *epochs.end(), heaviest, ResolveNullTipset::TakeOlder, diff --git a/src/state_manager/mod.rs b/src/state_manager/mod.rs index 1b45d25a85ad..0a6dcb6b4e59 100644 --- a/src/state_manager/mod.rs +++ b/src/state_manager/mod.rs @@ -235,12 +235,12 @@ impl StateManager { /// A valid head has /// - state tree in the blockstore /// - actor bundle version in the state tree that matches chain configuration - pub fn maybe_rewind_heaviest_tipset(&self) -> anyhow::Result<()> { - while self.maybe_rewind_heaviest_tipset_once()? {} + pub async fn maybe_rewind_heaviest_tipset(&self) -> anyhow::Result<()> { + while self.maybe_rewind_heaviest_tipset_once().await? {} Ok(()) } - fn maybe_rewind_heaviest_tipset_once(&self) -> anyhow::Result { + async fn maybe_rewind_heaviest_tipset_once(&self) -> anyhow::Result { let head = self.heaviest_tipset(); if let Some(info) = self .chain_config() @@ -253,11 +253,14 @@ impl StateManager { let bundle_metadata = state.get_actor_bundle_metadata()?; if expected_bundle_metadata != bundle_metadata { let current_epoch = head.epoch(); - let target_head = self.chain_index().load_required_tipset_by_height( - (expected_height_info.epoch - 1).max(0), - head, - ResolveNullTipset::TakeOlder, - )?; + let target_head = self + .chain_index() + .load_required_tipset_by_height( + (expected_height_info.epoch - 1).max(0), + head, + ResolveNullTipset::TakeOlder, + ) + .await?; let target_epoch = target_head.epoch(); let bundle_version = &bundle_metadata.version; let expected_bundle_version = &expected_bundle_metadata.version; diff --git a/src/state_manager/state_computation.rs b/src/state_manager/state_computation.rs index bb8decd3ab2d..bdf72ed7f653 100644 --- a/src/state_manager/state_computation.rs +++ b/src/state_manager/state_computation.rs @@ -22,7 +22,7 @@ impl StateManager { if let Some(state) = self.cache.get_map(ts.key(), |et| et.into()) { Ok(state) } else { - match self.chain_store().load_child_tipset(ts)? { + match self.chain_store().load_child_tipset(ts).await? { Some(receipt_ts) => Ok(TipsetState { state_root: *receipt_ts.parent_state(), receipt_root: *receipt_ts.parent_message_receipts(), @@ -99,7 +99,7 @@ impl StateManager { } self.cache .get_or_insert_async(ts.key(), async move { - let receipt_ts = self.chain_store().load_child_tipset(ts)?; + let receipt_ts = self.chain_store().load_child_tipset(ts).await?; self.load_executed_tipset_inner(ts, receipt_ts.as_ref(), policy) .await }) diff --git a/src/tool/subcommands/archive_cmd.rs b/src/tool/subcommands/archive_cmd.rs index 1da1596914a7..6c56f5062be9 100644 --- a/src/tool/subcommands/archive_cmd.rs +++ b/src/tool/subcommands/archive_cmd.rs @@ -591,11 +591,13 @@ where let ts = index .load_required_tipset_by_height(epoch, ts, ResolveNullTipset::TakeOlder) + .await .context("unable to get a tipset at given height")?; let seen = if let Some(diff) = diff { let diff_ts: Tipset = index .load_required_tipset_by_height(diff, ts.shallow_clone(), ResolveNullTipset::TakeOlder) + .await .context("diff epoch must be smaller than target epoch")?; let diff_ts: &Tipset = &diff_ts; let diff_limit = diff_depth.map(|depth| diff_ts.epoch() - depth).unwrap_or(0); @@ -855,17 +857,21 @@ async fn show_tipset_diff( CurrentNetwork::set_global(Network::Testnet); } let beacon = Arc::new(chain_config.get_beacon_schedule(genesis_timestamp)); - let tipset = chain_index.load_required_tipset_by_height( - epoch, - heaviest_tipset.clone(), - ResolveNullTipset::TakeOlder, - )?; + let tipset = chain_index + .load_required_tipset_by_height( + epoch, + heaviest_tipset.clone(), + ResolveNullTipset::TakeOlder, + ) + .await?; - let child_tipset = chain_index.load_required_tipset_by_height( - epoch + 1, - heaviest_tipset.clone(), - ResolveNullTipset::TakeNewer, - )?; + let child_tipset = chain_index + .load_required_tipset_by_height( + epoch + 1, + heaviest_tipset.clone(), + ResolveNullTipset::TakeNewer, + ) + .await?; let ExecutedTipset { state_root, .. } = apply_block_messages_blocking( chain_index, diff --git a/src/tool/subcommands/benchmark_cmd.rs b/src/tool/subcommands/benchmark_cmd.rs index d7e9ad33ae8f..5c29c404a8bc 100644 --- a/src/tool/subcommands/benchmark_cmd.rs +++ b/src/tool/subcommands/benchmark_cmd.rs @@ -223,11 +223,13 @@ async fn benchmark_exporting( store.shallow_clone(), heaviest.genesis(store.shallow_clone()).await?, ); - let ts = idx.load_required_tipset_by_height( - epoch.unwrap_or(heaviest.epoch()), - heaviest, - ResolveNullTipset::TakeOlder, - )?; + let ts = idx + .load_required_tipset_by_height( + epoch.unwrap_or(heaviest.epoch()), + heaviest, + ResolveNullTipset::TakeOlder, + ) + .await?; // We don't do any sanity checking for 'depth'. The output is discarded so // there's no need. let stateroot_lookup_limit = ts.epoch() - depth; diff --git a/src/tool/subcommands/index_cmd.rs b/src/tool/subcommands/index_cmd.rs index 1d6d2c4cde3c..8ba0ef5d2f4a 100644 --- a/src/tool/subcommands/index_cmd.rs +++ b/src/tool/subcommands/index_cmd.rs @@ -96,11 +96,10 @@ impl IndexCommands { // ensure from epoch is not greater than head epoch. This can happen if the // assumed head is actually a null tipset. let from = std::cmp::min(*from, head_ts.epoch()); - chain_store.chain_index().load_required_tipset_by_height( - from, - head_ts, - ResolveNullTipset::TakeOlder, - )? + chain_store + .chain_index() + .load_required_tipset_by_height(from, head_ts, ResolveNullTipset::TakeOlder) + .await? } else { head_ts }; diff --git a/src/tool/subcommands/snapshot_cmd.rs b/src/tool/subcommands/snapshot_cmd.rs index ab0044df0934..4c2b5093eb28 100644 --- a/src/tool/subcommands/snapshot_cmd.rs +++ b/src/tool/subcommands/snapshot_cmd.rs @@ -266,7 +266,7 @@ impl SnapshotCommands { snapshot, epoch, json, - } => print_computed_state(snapshot, epoch, json), + } => print_computed_state(snapshot, epoch, json).await, } } } @@ -463,7 +463,11 @@ fn validation_spinner(prefix: &'static str) -> indicatif::ProgressBar { pb } -fn print_computed_state(snapshot: PathBuf, epoch: ChainEpoch, json: bool) -> anyhow::Result<()> { +async fn print_computed_state( + snapshot: PathBuf, + epoch: ChainEpoch, + json: bool, +) -> anyhow::Result<()> { // Initialize Blockstore let store: Arc = Arc::new(AnyCar::try_from(snapshot.as_path())?.try_into()?); @@ -483,6 +487,7 @@ fn print_computed_state(snapshot: PathBuf, epoch: ChainEpoch, json: bool) -> any let beacon = Arc::new(chain_config.get_beacon_schedule(genesis_timestamp)); let tipset = chain_index .load_required_tipset_by_height(epoch, ts, ResolveNullTipset::TakeOlder) + .await .with_context(|| format!("couldn't get a tipset at height {epoch}"))?; let mut message_calls = vec![];