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    /// Returns the currently tracked heaviest tipset.
242    pub fn heaviest_tipset(&self) -> Tipset {
243        self.chain_store().heaviest_tipset()
244    }
245
246    /// Returns the currently tracked heaviest tipset and rewind to a most recent valid one if necessary.
247    /// A valid head has
248    ///     - state tree in the blockstore
249    ///     - actor bundle version in the state tree that matches chain configuration
250    pub async fn maybe_rewind_heaviest_tipset(&self) -> anyhow::Result<()> {
251        while self.maybe_rewind_heaviest_tipset_once().await? {}
252        Ok(())
253    }
254
255    async fn maybe_rewind_heaviest_tipset_once(&self) -> anyhow::Result<bool> {
256        let head = self.heaviest_tipset();
257        if let Some(info) = self
258            .chain_config()
259            .network_height_with_actor_bundle(head.epoch())
260        {
261            let expected_height_info = info.info;
262            let expected_bundle = info.manifest(self.db())?;
263            let expected_bundle_metadata = expected_bundle.metadata()?;
264            let state = self.get_state_tree(head.parent_state())?;
265            let bundle_metadata = state.get_actor_bundle_metadata()?;
266            if expected_bundle_metadata != bundle_metadata {
267                let current_epoch = head.epoch();
268                let target_head = self
269                    .chain_index()
270                    .load_required_tipset_by_height(
271                        (expected_height_info.epoch - 1).max(0),
272                        head,
273                        ResolveNullTipset::TakeOlder,
274                    )
275                    .await?;
276                let target_epoch = target_head.epoch();
277                let bundle_version = &bundle_metadata.version;
278                let expected_bundle_version = &expected_bundle_metadata.version;
279                if target_epoch < current_epoch {
280                    tracing::warn!(
281                        "rewinding chain head from {current_epoch} to {target_epoch}, actor bundle: {bundle_version}, expected: {expected_bundle_version}"
282                    );
283                    if self.db().has(target_head.parent_state())? {
284                        self.chain_store().set_heaviest_tipset(target_head)?;
285                        return Ok(true);
286                    } else {
287                        anyhow::bail!(
288                            "failed to rewind, state tree @ {target_epoch} is missing from blockstore: {}",
289                            target_head.parent_state()
290                        );
291                    }
292                }
293            }
294        }
295        Ok(false)
296    }
297
298    pub fn beacon_schedule(&self) -> &Arc<BeaconSchedule> {
299        &self.beacon
300    }
301
302    /// Returns network version for the given epoch.
303    pub fn get_network_version(&self, epoch: ChainEpoch) -> NetworkVersion {
304        self.chain_config().network_version(epoch)
305    }
306
307    /// Gets the state tree
308    pub fn get_state_tree(&self, state_cid: &Cid) -> anyhow::Result<StateTree<DbImpl>> {
309        StateTree::new_from_root(self.chain_index().db(), state_cid)
310    }
311
312    /// Gets actor from given [`Cid`], if it exists.
313    pub fn get_actor(&self, addr: &Address, state_cid: Cid) -> anyhow::Result<Option<ActorState>> {
314        let state = self.get_state_tree(&state_cid)?;
315        state.get_actor(addr)
316    }
317
318    /// Gets actor state from implicit actor address
319    pub fn get_actor_state<S: LoadActorStateFromBlockstore>(
320        &self,
321        ts: &Tipset,
322    ) -> anyhow::Result<S> {
323        let state_tree = self.get_state_tree(ts.parent_state())?;
324        state_tree.get_actor_state()
325    }
326
327    /// Gets actor state from explicit actor address
328    pub fn get_actor_state_from_address<S: LoadActorStateFromBlockstore>(
329        &self,
330        ts: &Tipset,
331        actor_address: &Address,
332    ) -> anyhow::Result<S> {
333        let state_tree = self.get_state_tree(ts.parent_state())?;
334        state_tree.get_actor_state_from_address(actor_address)
335    }
336
337    /// Gets required actor from given [`Cid`].
338    pub fn get_required_actor(&self, addr: &Address, state_cid: Cid) -> anyhow::Result<ActorState> {
339        let state = self.get_state_tree(&state_cid)?;
340        state.get_actor(addr)?.with_context(|| {
341            format!("Failed to load actor with addr={addr}, state_cid={state_cid}")
342        })
343    }
344
345    /// Returns a reference to the state manager's [`Blockstore`].
346    pub fn db(&self) -> &DbImpl {
347        self.cs.db()
348    }
349
350    pub fn db_owned(&self) -> DbImpl {
351        self.cs.db_owned()
352    }
353
354    /// Returns reference to the state manager's [`ChainStore`].
355    pub fn chain_store(&self) -> &ChainStore {
356        &self.cs
357    }
358
359    /// Returns reference to the state manager's [`ChainIndex`].
360    pub fn chain_index(&self) -> &ChainIndex {
361        self.cs.chain_index()
362    }
363
364    /// Returns reference to the state manager's [`ChainConfig`].
365    pub fn chain_config(&self) -> &Arc<ChainConfig> {
366        self.cs.chain_config()
367    }
368
369    pub fn chain_rand(&self, tipset: Tipset) -> ChainRand {
370        ChainRand::new(
371            self.chain_config().shallow_clone(),
372            tipset,
373            self.chain_index().shallow_clone(),
374            self.beacon.shallow_clone(),
375        )
376    }
377
378    /// Returns the internal, protocol-level network chain from the state.
379    pub fn get_network_state_name(
380        &self,
381        state_cid: Cid,
382    ) -> anyhow::Result<crate::networks::StateNetworkName> {
383        let init_act = self
384            .get_actor(&init::ADDRESS.into(), state_cid)?
385            .ok_or_else(|| Error::state("Init actor address could not be resolved"))?;
386        Ok(State::load(self.db(), init_act.code, init_act.state)?
387            .into_network_name()
388            .into())
389    }
390
391    /// Returns true if miner has been slashed or is considered invalid.
392    pub fn is_miner_slashed(&self, addr: &Address, state_cid: &Cid) -> anyhow::Result<bool, Error> {
393        let actor = self
394            .get_actor(&Address::POWER_ACTOR, *state_cid)?
395            .ok_or_else(|| Error::state("Power actor address could not be resolved"))?;
396
397        let spas = power::State::load(self.db(), actor.code, actor.state)?;
398
399        Ok(spas.miner_power(self.db(), addr)?.is_none())
400    }
401
402    /// Returns raw work address of a miner given the state root.
403    pub fn get_miner_work_addr(&self, state_cid: Cid, addr: &Address) -> Result<Address, Error> {
404        let state = StateTree::new_from_root(self.db(), &state_cid).map_err(Error::other)?;
405        let ms: miner::State = state.get_actor_state_from_address(addr)?;
406        let info = ms.info(self.db()).map_err(|e| e.to_string())?;
407        let addr = state.resolve_to_deterministic_address(self.db(), info.worker())?;
408        Ok(addr)
409    }
410
411    /// Returns specified actor's claimed power and total network power as a
412    /// tuple.
413    pub fn get_power(
414        &self,
415        state_cid: &Cid,
416        addr: Option<&Address>,
417    ) -> anyhow::Result<Option<(power::Claim, power::Claim)>, Error> {
418        let actor = self
419            .get_actor(&Address::POWER_ACTOR, *state_cid)?
420            .ok_or_else(|| Error::state("Power actor address could not be resolved"))?;
421
422        let spas = power::State::load(self.db(), actor.code, actor.state)?;
423
424        let t_pow = spas.total_power();
425
426        if let Some(maddr) = addr {
427            let m_pow = spas
428                .miner_power(self.db(), maddr)?
429                .ok_or_else(|| Error::state(format!("Miner for address {maddr} not found")))?;
430
431            let min_pow = spas.miner_nominal_power_meets_consensus_minimum(
432                &self.chain_config().policy,
433                self.db(),
434                maddr,
435            )?;
436            if min_pow {
437                return Ok(Some((m_pow, t_pow)));
438            }
439        }
440
441        Ok(None)
442    }
443
444    // Returns all sectors
445    pub fn get_all_sectors(
446        &self,
447        addr: &Address,
448        ts: &Tipset,
449    ) -> anyhow::Result<Vec<SectorOnChainInfo>> {
450        let actor = self
451            .get_actor(addr, *ts.parent_state())?
452            .ok_or_else(|| Error::state(format!("Miner actor {addr} not found")))?;
453        let state = miner::State::load(self.db(), actor.code, actor.state)?;
454        state.load_sectors_ext(self.db(), None)
455    }
456}