Skip to main content

forest/state_manager/
mod.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4#[cfg(test)]
5mod tests;
6
7mod actor_queries;
8mod address_resolution;
9pub mod cache;
10pub mod chain_rand;
11pub mod circulating_supply;
12mod errors;
13mod execution;
14mod message_search;
15mod message_simulation;
16mod mining;
17mod state_computation;
18pub mod utils;
19
20use self::circulating_supply::GenesisInfo;
21pub use self::errors::*;
22pub use self::state_computation::{apply_block_messages_blocking, validate_tipsets_blocking};
23use crate::beacon::BeaconSchedule;
24use crate::blocks::{Tipset, TipsetKey};
25use crate::chain::{
26    ChainStore,
27    index::{ChainIndex, ResolveNullTipset},
28};
29use crate::db::DbImpl;
30use crate::interpreter::MessageCallbackCtx;
31use crate::lotus_json::{LotusJson, lotus_json_with_self};
32use crate::message::ChainMessage;
33use crate::networks::ChainConfig;
34use crate::prelude::*;
35use crate::rpc::state::ApiInvocResult;
36use crate::rpc::types::SectorOnChainInfo;
37use crate::shim::actors::init::{self, State};
38use crate::shim::actors::*;
39use crate::shim::address::AddressId;
40use crate::shim::{
41    actors::LoadActorStateFromBlockstore,
42    executor::{Receipt, StampedEvent},
43};
44use crate::shim::{
45    address::Address,
46    clock::ChainEpoch,
47    econ::TokenAmount,
48    machine::{GLOBAL_MULTI_ENGINE, MultiEngine},
49    state_tree::{ActorState, StateTree},
50    version::NetworkVersion,
51};
52use crate::state_manager::cache::ForestCache;
53use crate::utils::cache::SizeTrackingCache;
54use crate::utils::get_size::GetSize;
55use anyhow::Context as _;
56use chain_rand::ChainRand;
57use nonzero_ext::nonzero;
58use schemars::JsonSchema;
59use serde::{Deserialize, Serialize};
60use std::num::NonZeroUsize;
61use tracing::warn;
62
63const DEFAULT_TIPSET_CACHE_SIZE: NonZeroUsize = nonzero!(8192usize); // maximum ~150MiB on mainnet
64const DEFAULT_ID_TO_DETERMINISTIC_ADDRESS_CACHE_SIZE: NonZeroUsize = nonzero!(8192usize); // maximum ~0.7MiB on mainnet
65const DEFAULT_TRACE_CACHE_SIZE: NonZeroUsize = nonzero!(16usize); // maximum ~70MiB on mainnet
66pub const EVENTS_AMT_BITWIDTH: u32 = 5;
67pub type IdToAddressCache = SizeTrackingCache<AddressId, Address>;
68
69/// Result of executing an individual chain message in a tipset.
70///
71/// Includes the executed message itself, the execution receipt, and
72/// optional events emitted by the actor during execution.
73#[derive(Debug, Clone)]
74pub struct ExecutedMessage {
75    pub message: ChainMessage,
76    pub receipt: Receipt,
77    pub events: Option<Vec<StampedEvent>>,
78}
79
80impl GetSize for ExecutedMessage {
81    fn get_heap_size_with_tracker<T: get_size2::GetSizeTracker>(
82        &self,
83        mut tracker: T,
84    ) -> (usize, T) {
85        (
86            self.message.get_heap_size_with_tracker(&mut tracker).0
87                + self.receipt.get_heap_size_with_tracker(&mut tracker).0
88                + self.events.get_heap_size_with_tracker(&mut tracker).0,
89            tracker,
90        )
91    }
92}
93
94/// Aggregated execution result for a tipset.
95#[derive(Debug, Clone, GetSize)]
96pub struct ExecutedTipset {
97    /// Resulting state tree root after message execution
98    #[get_size(ignore)]
99    pub state_root: Cid,
100    /// Resulting message receipts root after message execution
101    #[get_size(ignore)]
102    pub receipt_root: Cid,
103    /// Per-message execution details.
104    /// Wrapped in an `Arc` to reduce cloning cost, as this can be quite large.
105    pub executed_messages: Arc<Vec<ExecutedMessage>>,
106}
107
108/// Basic execution result for a tipset.
109#[derive(Debug, Clone, GetSize)]
110pub struct TipsetState {
111    /// Resulting state tree root after message execution
112    #[get_size(ignore)]
113    pub state_root: Cid,
114    /// Resulting message receipts root after message execution
115    #[allow(dead_code)]
116    #[get_size(ignore)]
117    pub receipt_root: Cid,
118}
119
120impl From<ExecutedTipset> for TipsetState {
121    fn from(
122        ExecutedTipset {
123            state_root,
124            receipt_root,
125            ..
126        }: ExecutedTipset,
127    ) -> Self {
128        Self {
129            state_root,
130            receipt_root,
131        }
132    }
133}
134
135impl From<&ExecutedTipset> for TipsetState {
136    fn from(
137        ExecutedTipset {
138            state_root,
139            receipt_root,
140            ..
141        }: &ExecutedTipset,
142    ) -> Self {
143        Self {
144            state_root: *state_root,
145            receipt_root: *receipt_root,
146        }
147    }
148}
149
150/// External format for returning market balance from state.
151#[derive(
152    Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, JsonSchema,
153)]
154#[serde(rename_all = "PascalCase")]
155pub struct MarketBalance {
156    #[schemars(with = "LotusJson<TokenAmount>")]
157    #[serde(with = "crate::lotus_json")]
158    pub escrow: TokenAmount,
159    #[schemars(with = "LotusJson<TokenAmount>")]
160    #[serde(with = "crate::lotus_json")]
161    pub locked: TokenAmount,
162}
163lotus_json_with_self!(MarketBalance);
164
165/// State manager handles all interactions with the internal Filecoin actors
166/// state. This encapsulates the [`ChainStore`] functionality, which only
167/// handles chain data, to allow for interactions with the underlying state of
168/// the chain. The state manager not only allows interfacing with state, but
169/// also is used when performing state transitions.
170pub struct StateManager {
171    /// Chain store
172    cs: ChainStore,
173    /// This is a cache which indexes tipsets to their calculated state output (state root, receipt root).
174    cache: ForestCache<TipsetKey, ExecutedTipset>,
175    /// This is a cache which indexes tipsets to their traces.
176    trace_cache: ForestCache<TipsetKey, (CidWrapper, Vec<Arc<ApiInvocResult>>)>,
177    /// `None` disables caching of ID -> deterministic-address resolution.
178    /// Used by the RPC test-snapshot generator and replay harness so every
179    /// `(id, tipset)` pair resolves independently, making recorded snapshots
180    /// read-complete and replay order-independent.
181    id_to_deterministic_address_cache: Option<IdToAddressCache>,
182    beacon: Arc<crate::beacon::BeaconSchedule>,
183    engine: Arc<MultiEngine>,
184    genesis_info: Arc<GenesisInfo>,
185    /// Bounds concurrent RPC-triggered tipset replays, see [`Self::replay_concurrency`].
186    replay_semaphore: Arc<tokio::sync::Semaphore>,
187}
188
189impl ShallowClone for StateManager {
190    fn shallow_clone(&self) -> Self {
191        Self {
192            cs: self.cs.shallow_clone(),
193            cache: self.cache.shallow_clone(),
194            trace_cache: self.trace_cache.shallow_clone(),
195            id_to_deterministic_address_cache: self
196                .id_to_deterministic_address_cache
197                .as_ref()
198                .map(ShallowClone::shallow_clone),
199            beacon: self.beacon.shallow_clone(),
200            engine: self.engine.shallow_clone(),
201            genesis_info: self.genesis_info.shallow_clone(),
202            replay_semaphore: self.replay_semaphore.shallow_clone(),
203        }
204    }
205}
206
207#[allow(clippy::type_complexity)]
208pub const NO_CALLBACK: Option<fn(MessageCallbackCtx<'_>) -> anyhow::Result<()>> = None;
209
210/// Controls whether the VM should flush its state after execution
211#[derive(Debug, Copy, Clone, Default)]
212pub enum VMFlush {
213    Flush,
214    #[default]
215    Skip,
216}
217
218impl StateManager {
219    pub fn new(cs: ChainStore) -> anyhow::Result<Self> {
220        Self::new_with_engine(cs, GLOBAL_MULTI_ENGINE.clone())
221    }
222
223    pub fn new_with_engine(cs: ChainStore, engine: Arc<MultiEngine>) -> anyhow::Result<Self> {
224        let genesis = cs.genesis_block_header();
225        let beacon = Arc::new(cs.chain_config().get_beacon_schedule(genesis.timestamp));
226        let genesis_info = Arc::new(GenesisInfo::from_chain_config(
227            cs.chain_config().shallow_clone(),
228        ));
229
230        Ok(Self {
231            cs,
232            cache: ForestCache::new("tipset_state_executed_tipset"), // For StateOutput
233            trace_cache: ForestCache::with_size("tipset_trace", DEFAULT_TRACE_CACHE_SIZE),
234            beacon,
235            engine,
236            genesis_info,
237            id_to_deterministic_address_cache: Some(SizeTrackingCache::new_with_metrics(
238                "id_to_deterministic_address",
239                DEFAULT_ID_TO_DETERMINISTIC_ADDRESS_CACHE_SIZE,
240            )),
241            replay_semaphore: Arc::new(tokio::sync::Semaphore::new(Self::replay_concurrency())),
242        })
243    }
244
245    /// Maximum concurrent RPC-triggered tipset replays (`StateReplay`, `trace_*` and
246    /// `debug_trace*` methods). Each replay is a full VM execution of a tipset, so
247    /// unbounded concurrency lets a burst of such requests starve the whole node.
248    /// Configurable via `FOREST_RPC_REPLAY_CONCURRENCY`; defaults to half the
249    /// available CPUs.
250    fn replay_concurrency() -> usize {
251        static VALUE: std::sync::LazyLock<NonZeroUsize> = std::sync::LazyLock::new(|| {
252            let default = std::thread::available_parallelism()
253                .ok()
254                .and_then(|n| NonZeroUsize::new(n.get() / 2))
255                .unwrap_or(nonzero!(1usize));
256            crate::utils::misc::env::env_or_default("FOREST_RPC_REPLAY_CONCURRENCY", default)
257        });
258        VALUE.get()
259    }
260
261    /// The returned permit is owned so it can be moved into the `spawn_blocking`
262    /// closure doing the actual execution: if the requesting future is cancelled
263    /// (RPC timeout, disconnect), the permit stays held until the orphaned
264    /// blocking task finishes, keeping the concurrency bound accurate.
265    async fn replay_permit(&self) -> tokio::sync::OwnedSemaphorePermit {
266        self.replay_semaphore
267            .clone()
268            .acquire_owned()
269            .await
270            .expect("replay semaphore is never closed")
271    }
272
273    /// Disables caching of ID -> deterministic-address resolution. To be used strictly
274    /// by the RPC test-snapshot generator and replay harness
275    pub fn with_id_address_cache_disabled(mut self) -> Self {
276        self.id_to_deterministic_address_cache = None;
277        self
278    }
279
280    /// Test-only view of the ID -> deterministic-address cache.
281    #[cfg(test)]
282    pub(crate) fn id_to_deterministic_address_cache(&self) -> Option<&IdToAddressCache> {
283        self.id_to_deterministic_address_cache.as_ref()
284    }
285
286    /// Returns the currently tracked heaviest tipset.
287    pub fn heaviest_tipset(&self) -> Tipset {
288        self.chain_store().heaviest_tipset()
289    }
290
291    /// Returns the currently tracked heaviest tipset and rewind to a most recent valid one if necessary.
292    /// A valid head has
293    ///     - state tree in the blockstore
294    ///     - actor bundle version in the state tree that matches chain configuration
295    pub async fn maybe_rewind_heaviest_tipset(&self) -> anyhow::Result<()> {
296        while self.maybe_rewind_heaviest_tipset_once().await? {}
297        Ok(())
298    }
299
300    async fn maybe_rewind_heaviest_tipset_once(&self) -> anyhow::Result<bool> {
301        let head = self.heaviest_tipset();
302        if let Some(info) = self
303            .chain_config()
304            .network_height_with_actor_bundle(head.epoch())
305        {
306            let expected_height_info = info.info;
307            let expected_bundle = info.manifest(self.db())?;
308            let expected_bundle_metadata = expected_bundle.metadata()?;
309            let state = self.get_state_tree(head.parent_state())?;
310            let bundle_metadata = state.get_actor_bundle_metadata()?;
311            if expected_bundle_metadata != bundle_metadata {
312                let current_epoch = head.epoch();
313                let target_head = self
314                    .chain_index()
315                    .load_required_tipset_by_height(
316                        (expected_height_info.epoch - 1).max(0),
317                        head,
318                        ResolveNullTipset::TakeOlder,
319                    )
320                    .await?;
321                let target_epoch = target_head.epoch();
322                let bundle_version = &bundle_metadata.version;
323                let expected_bundle_version = &expected_bundle_metadata.version;
324                if target_epoch < current_epoch {
325                    tracing::warn!(
326                        "rewinding chain head from {current_epoch} to {target_epoch}, actor bundle: {bundle_version}, expected: {expected_bundle_version}"
327                    );
328                    if self.db().has(target_head.parent_state())? {
329                        self.chain_store().set_heaviest_tipset(target_head)?;
330                        return Ok(true);
331                    } else {
332                        anyhow::bail!(
333                            "failed to rewind, state tree @ {target_epoch} is missing from blockstore: {}",
334                            target_head.parent_state()
335                        );
336                    }
337                }
338            }
339        }
340        Ok(false)
341    }
342
343    pub fn beacon_schedule(&self) -> &Arc<BeaconSchedule> {
344        &self.beacon
345    }
346
347    /// Returns network version for the given epoch.
348    pub fn get_network_version(&self, epoch: ChainEpoch) -> NetworkVersion {
349        self.chain_config().network_version(epoch)
350    }
351
352    /// Gets the state tree
353    pub fn get_state_tree(&self, state_cid: &Cid) -> anyhow::Result<StateTree<DbImpl>> {
354        StateTree::new_from_root(self.chain_index().db(), state_cid)
355    }
356
357    /// Gets actor from given [`Cid`], if it exists.
358    pub fn get_actor(&self, addr: &Address, state_cid: Cid) -> anyhow::Result<Option<ActorState>> {
359        let state = self.get_state_tree(&state_cid)?;
360        state.get_actor(addr)
361    }
362
363    /// Gets actor state from implicit actor address
364    pub fn get_actor_state<S: LoadActorStateFromBlockstore>(
365        &self,
366        ts: &Tipset,
367    ) -> anyhow::Result<S> {
368        let state_tree = self.get_state_tree(ts.parent_state())?;
369        state_tree.get_actor_state()
370    }
371
372    /// Gets actor state from explicit actor address
373    pub fn get_actor_state_from_address<S: LoadActorStateFromBlockstore>(
374        &self,
375        ts: &Tipset,
376        actor_address: &Address,
377    ) -> anyhow::Result<S> {
378        let state_tree = self.get_state_tree(ts.parent_state())?;
379        state_tree.get_actor_state_from_address(actor_address)
380    }
381
382    /// Gets required actor from given [`Cid`].
383    pub fn get_required_actor(&self, addr: &Address, state_cid: Cid) -> anyhow::Result<ActorState> {
384        let state = self.get_state_tree(&state_cid)?;
385        state.get_actor(addr)?.with_context(|| {
386            format!("Failed to load actor with addr={addr}, state_cid={state_cid}")
387        })
388    }
389
390    /// Returns a reference to the state manager's [`Blockstore`].
391    pub fn db(&self) -> &DbImpl {
392        self.cs.db()
393    }
394
395    pub fn db_owned(&self) -> DbImpl {
396        self.cs.db_owned()
397    }
398
399    /// Returns reference to the state manager's [`ChainStore`].
400    pub fn chain_store(&self) -> &ChainStore {
401        &self.cs
402    }
403
404    /// Returns reference to the state manager's [`ChainIndex`].
405    pub fn chain_index(&self) -> &ChainIndex {
406        self.cs.chain_index()
407    }
408
409    /// Returns reference to the state manager's [`ChainConfig`].
410    pub fn chain_config(&self) -> &Arc<ChainConfig> {
411        self.cs.chain_config()
412    }
413
414    pub fn genesis_info(&self) -> &Arc<GenesisInfo> {
415        &self.genesis_info
416    }
417
418    pub fn chain_rand(&self, tipset: Tipset) -> ChainRand {
419        ChainRand::new(
420            self.chain_config().shallow_clone(),
421            tipset,
422            self.chain_index().shallow_clone(),
423            self.beacon.shallow_clone(),
424        )
425    }
426
427    /// Returns the internal, protocol-level network chain from the state.
428    pub fn get_network_state_name(
429        &self,
430        state_cid: Cid,
431    ) -> anyhow::Result<crate::networks::StateNetworkName> {
432        let init_act = self
433            .get_actor(&init::ADDRESS.into(), state_cid)?
434            .ok_or_else(|| Error::state("Init actor address could not be resolved"))?;
435        Ok(State::load(self.db(), init_act.code, init_act.state)?
436            .into_network_name()
437            .into())
438    }
439
440    /// Returns true if miner has been slashed or is considered invalid.
441    pub fn is_miner_slashed(&self, addr: &Address, state_cid: &Cid) -> anyhow::Result<bool, Error> {
442        let actor = self
443            .get_actor(&Address::POWER_ACTOR, *state_cid)?
444            .ok_or_else(|| Error::state("Power actor address could not be resolved"))?;
445
446        let spas = power::State::load(self.db(), actor.code, actor.state)?;
447
448        Ok(spas.miner_power(self.db(), addr)?.is_none())
449    }
450
451    /// Returns raw work address of a miner given the state root.
452    pub fn get_miner_work_addr(&self, state_cid: Cid, addr: &Address) -> Result<Address, Error> {
453        let state = StateTree::new_from_root(self.db(), &state_cid).map_err(Error::other)?;
454        let ms: miner::State = state.get_actor_state_from_address(addr)?;
455        let info = ms.info(self.db()).map_err(|e| e.to_string())?;
456        let addr = state.resolve_to_deterministic_address(self.db(), info.worker())?;
457        Ok(addr)
458    }
459
460    /// Returns specified actor's claimed power and total network power as a
461    /// tuple.
462    pub fn get_power(
463        &self,
464        state_cid: &Cid,
465        addr: Option<&Address>,
466    ) -> anyhow::Result<Option<(power::Claim, power::Claim)>, Error> {
467        let actor = self
468            .get_actor(&Address::POWER_ACTOR, *state_cid)?
469            .ok_or_else(|| Error::state("Power actor address could not be resolved"))?;
470
471        let spas = power::State::load(self.db(), actor.code, actor.state)?;
472
473        let t_pow = spas.total_power();
474
475        if let Some(maddr) = addr {
476            let m_pow = spas
477                .miner_power(self.db(), maddr)?
478                .ok_or_else(|| Error::state(format!("Miner for address {maddr} not found")))?;
479
480            let min_pow = spas.miner_nominal_power_meets_consensus_minimum(
481                &self.chain_config().policy,
482                self.db(),
483                maddr,
484            )?;
485            if min_pow {
486                return Ok(Some((m_pow, t_pow)));
487            }
488        }
489
490        Ok(None)
491    }
492
493    /// Single-sector lookup via a direct AMT `get`, avoiding a full sector-set scan.
494    pub fn get_sector_info(
495        &self,
496        addr: &Address,
497        sector_number: u64,
498        ts: &Tipset,
499    ) -> anyhow::Result<Option<SectorOnChainInfo>> {
500        let actor = self
501            .get_actor(addr, *ts.parent_state())?
502            .ok_or_else(|| Error::state(format!("Miner actor {addr} not found")))?;
503        let state = miner::State::load(self.db(), actor.code, actor.state)?;
504        state.get_sector(self.db(), sector_number)
505    }
506}