diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 6879cbb89990..d501747ca4cb 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -54,6 +54,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::Error as StateManagerError; use crate::utils::cache::SizeTrackingLruCache; use crate::utils::db::BlockstoreExt as _; use crate::utils::encoding::from_slice_with_fallback; @@ -2017,6 +2018,18 @@ async fn apply_message( where DB: Blockstore + Send + Sync + 'static, { + if let Some(tipset) = tipset.as_ref() + && tipset.epoch() > 0 + { + let parent_ts = Tipset::load_required(ctx.store(), tipset.parents()) + .context("failed to load parent tipset")?; + + ensure!( + !ctx.state_manager + .has_expensive_fork_between(parent_ts.epoch(), tipset.epoch() + 1), + StateManagerError::ExpensiveFork + ); + } let invoc_res = ctx .state_manager .apply_on_state_with_gas(tipset, msg) diff --git a/src/state_manager/errors.rs b/src/state_manager/errors.rs index 0d70eb8f4f12..41445e6c8628 100644 --- a/src/state_manager/errors.rs +++ b/src/state_manager/errors.rs @@ -15,6 +15,8 @@ pub enum Error { /// Other state manager error #[error("{0}")] Other(String), + #[error("refusing explicit call due to state fork at epoch")] + ExpensiveFork, } impl Error { diff --git a/src/state_manager/mod.rs b/src/state_manager/mod.rs index 1e151052e724..bc9cfd24b198 100644 --- a/src/state_manager/mod.rs +++ b/src/state_manager/mod.rs @@ -56,11 +56,12 @@ use crate::state_manager::cache::{ TipsetStateCache, }; use crate::state_manager::chain_rand::draw_randomness; +use crate::state_migration::get_expensive_migration_heights; use crate::state_migration::run_state_migrations; use crate::utils::get_size::{ GetSize, vec_heap_size_helper, vec_with_stack_only_item_heap_size_helper, }; -use ahash::{HashMap, HashMapExt}; +use ahash::{HashMap, HashMapExt, HashSet}; use anyhow::{Context as _, bail}; use bls_signatures::{PublicKey as BlsPublicKey, Serialize as _}; use chain_rand::ChainRand; @@ -167,6 +168,7 @@ pub struct StateManager { engine: Arc, /// Handler for caching/retrieving tipset events and receipts. receipt_event_cache_handler: Box, + expensive_migration_epochs: HashSet, } #[allow(clippy::type_complexity)] @@ -186,20 +188,28 @@ where ) -> Result { let genesis = cs.genesis_block_header(); let beacon = Arc::new(cs.chain_config().get_beacon_schedule(genesis.timestamp)); + let chain_config = cs.chain_config(); let cache_handler: Box = - if cs.chain_config().enable_receipt_event_caching { + if chain_config.enable_receipt_event_caching { Box::new(EnabledTipsetDataCache::new()) } else { Box::new(DisabledTipsetDataCache::new()) }; + let expensive_migration_epochs: HashSet<_> = + get_expensive_migration_heights(&chain_config.network) + .iter() + .map(|&h| chain_config.epoch(h)) + .collect(); + Ok(Self { cs, cache: TipsetStateCache::new("state_output"), // For StateOutputValue beacon, engine, receipt_event_cache_handler: cache_handler, + expensive_migration_epochs, }) } @@ -448,6 +458,17 @@ where let state = miner::State::load(self.blockstore(), actor.code, actor.state)?; state.load_sectors_ext(self.blockstore(), None) } + + /// Returns true where executing tipsets between the specified heights would trigger an expensive migration. + /// Note: migrations occurring at the target height are not included, as they're executed after the target height. + pub fn has_expensive_fork_between(&self, parent: ChainEpoch, height: ChainEpoch) -> bool { + for epoch in parent..height { + if self.expensive_migration_epochs.contains(&epoch) { + return true; + } + } + false + } } impl StateManager @@ -588,6 +609,15 @@ where ) -> Result { let mut msg = msg.clone(); + if tipset.epoch() > 0 { + let parent_ts = Tipset::load_required(self.blockstore(), tipset.parents()) + .map_err(|e| Error::Other(format!("failed to load parent tipset: {e}")))?; + + if self.has_expensive_fork_between(parent_ts.epoch(), tipset.epoch() + 1) { + return Err(Error::ExpensiveFork); + } + } + let state_cid = state_cid.unwrap_or(*tipset.parent_state()); let tipset_messages = self diff --git a/src/state_manager/tests.rs b/src/state_manager/tests.rs index b260c127eebb..2a074defcabd 100644 --- a/src/state_manager/tests.rs +++ b/src/state_manager/tests.rs @@ -8,6 +8,7 @@ use crate::db::MemoryDB; use crate::networks::ChainConfig; use crate::shim::clock::ChainEpoch; use crate::shim::executor::{Receipt, StampedEvent}; +use crate::state_migration::get_expensive_migration_heights; use crate::utils::db::CborStoreExt; use crate::utils::multihash::MultihashCode; use cid::Cid; @@ -433,3 +434,19 @@ fn test_state_output_get_size() { let s = StateOutputValue::default(); assert_eq!(s.get_size(), std::mem::size_of_val(&s)); } + +#[test] +fn test_has_expensive_fork_between() { + let TestChainSetup { state_manager, .. } = setup_chain_with_tipsets(); + let chain_config = state_manager.chain_config(); + + let expensive_epoch = get_expensive_migration_heights(&chain_config.network) + .iter() + .find_map(|height| { + let epoch = chain_config.epoch(*height); + (epoch > 0).then_some(epoch) + }) + .expect("expected at least one expensive migration epoch > 0"); + + assert!(state_manager.has_expensive_fork_between(expensive_epoch, expensive_epoch + 1)); +} diff --git a/src/state_migration/mod.rs b/src/state_migration/mod.rs index a94345e81d3d..da9526361d41 100644 --- a/src/state_migration/mod.rs +++ b/src/state_migration/mod.rs @@ -89,6 +89,25 @@ where } } +/// Returns the heights at which expensive migrations occur. +pub fn get_expensive_migration_heights(chain: &NetworkChain) -> &'static [Height] { + match chain { + NetworkChain::Mainnet | NetworkChain::Calibnet | NetworkChain::Devnet(_) => &[ + Height::Shark, + Height::Hygge, + Height::Lightning, + Height::Watermelon, + Height::Dragon, + Height::Waffle, + Height::TukTuk, + Height::Teep, + Height::GoldenWeek, + ], + + NetworkChain::Butterflynet => &[Height::Teep, Height::GoldenWeek], + } +} + /// Run state migrations pub fn run_state_migrations( epoch: ChainEpoch,