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