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