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    /// Bounds concurrent RPC-triggered tipset replays, see [`Self::replay_concurrency`].
184    replay_semaphore: Arc<tokio::sync::Semaphore>,
185}
186
187impl ShallowClone for StateManager {
188    fn shallow_clone(&self) -> Self {
189        Self {
190            cs: self.cs.shallow_clone(),
191            cache: self.cache.shallow_clone(),
192            trace_cache: self.trace_cache.shallow_clone(),
193            id_to_deterministic_address_cache: self
194                .id_to_deterministic_address_cache
195                .as_ref()
196                .map(ShallowClone::shallow_clone),
197            beacon: self.beacon.shallow_clone(),
198            engine: self.engine.shallow_clone(),
199            replay_semaphore: self.replay_semaphore.shallow_clone(),
200        }
201    }
202}
203
204#[allow(clippy::type_complexity)]
205pub const NO_CALLBACK: Option<fn(MessageCallbackCtx<'_>) -> anyhow::Result<()>> = None;
206
207/// Controls whether the VM should flush its state after execution
208#[derive(Debug, Copy, Clone, Default)]
209pub enum VMFlush {
210    Flush,
211    #[default]
212    Skip,
213}
214
215impl StateManager {
216    pub fn new(cs: ChainStore) -> anyhow::Result<Self> {
217        Self::new_with_engine(cs, GLOBAL_MULTI_ENGINE.clone())
218    }
219
220    pub fn new_with_engine(cs: ChainStore, engine: Arc<MultiEngine>) -> anyhow::Result<Self> {
221        let genesis = cs.genesis_block_header();
222        let beacon = Arc::new(cs.chain_config().get_beacon_schedule(genesis.timestamp));
223
224        Ok(Self {
225            cs,
226            cache: ForestCache::new("tipset_state_executed_tipset"), // For StateOutput
227            trace_cache: ForestCache::with_size("tipset_trace", DEFAULT_TRACE_CACHE_SIZE),
228            beacon,
229            engine,
230            id_to_deterministic_address_cache: Some(SizeTrackingCache::new_with_metrics(
231                "id_to_deterministic_address",
232                DEFAULT_ID_TO_DETERMINISTIC_ADDRESS_CACHE_SIZE,
233            )),
234            replay_semaphore: Arc::new(tokio::sync::Semaphore::new(Self::replay_concurrency())),
235        })
236    }
237
238    /// Maximum concurrent RPC-triggered tipset replays (`StateReplay`, `trace_*` and
239    /// `debug_trace*` methods). Each replay is a full VM execution of a tipset, so
240    /// unbounded concurrency lets a burst of such requests starve the whole node.
241    /// Configurable via `FOREST_RPC_REPLAY_CONCURRENCY`; defaults to half the
242    /// available CPUs.
243    fn replay_concurrency() -> usize {
244        static VALUE: std::sync::LazyLock<NonZeroUsize> = std::sync::LazyLock::new(|| {
245            let default = std::thread::available_parallelism()
246                .ok()
247                .and_then(|n| NonZeroUsize::new(n.get() / 2))
248                .unwrap_or(nonzero!(1usize));
249            crate::utils::misc::env::env_or_default("FOREST_RPC_REPLAY_CONCURRENCY", default)
250        });
251        VALUE.get()
252    }
253
254    /// The returned permit is owned so it can be moved into the `spawn_blocking`
255    /// closure doing the actual execution: if the requesting future is cancelled
256    /// (RPC timeout, disconnect), the permit stays held until the orphaned
257    /// blocking task finishes, keeping the concurrency bound accurate.
258    async fn replay_permit(&self) -> tokio::sync::OwnedSemaphorePermit {
259        self.replay_semaphore
260            .clone()
261            .acquire_owned()
262            .await
263            .expect("replay semaphore is never closed")
264    }
265
266    /// Disables caching of ID -> deterministic-address resolution. To be used strictly
267    /// by the RPC test-snapshot generator and replay harness
268    pub fn with_id_address_cache_disabled(mut self) -> Self {
269        self.id_to_deterministic_address_cache = None;
270        self
271    }
272
273    /// Test-only view of the ID -> deterministic-address cache.
274    #[cfg(test)]
275    pub(crate) fn id_to_deterministic_address_cache(&self) -> Option<&IdToAddressCache> {
276        self.id_to_deterministic_address_cache.as_ref()
277    }
278
279    /// Returns the currently tracked heaviest tipset.
280    pub fn heaviest_tipset(&self) -> Tipset {
281        self.chain_store().heaviest_tipset()
282    }
283
284    /// Returns the currently tracked heaviest tipset and rewind to a most recent valid one if necessary.
285    /// A valid head has
286    ///     - state tree in the blockstore
287    ///     - actor bundle version in the state tree that matches chain configuration
288    pub async fn maybe_rewind_heaviest_tipset(&self) -> anyhow::Result<()> {
289        while self.maybe_rewind_heaviest_tipset_once().await? {}
290        Ok(())
291    }
292
293    async fn maybe_rewind_heaviest_tipset_once(&self) -> anyhow::Result<bool> {
294        let head = self.heaviest_tipset();
295        if let Some(info) = self
296            .chain_config()
297            .network_height_with_actor_bundle(head.epoch())
298        {
299            let expected_height_info = info.info;
300            let expected_bundle = info.manifest(self.db())?;
301            let expected_bundle_metadata = expected_bundle.metadata()?;
302            let state = self.get_state_tree(head.parent_state())?;
303            let bundle_metadata = state.get_actor_bundle_metadata()?;
304            if expected_bundle_metadata != bundle_metadata {
305                let current_epoch = head.epoch();
306                let target_head = self
307                    .chain_index()
308                    .load_required_tipset_by_height(
309                        (expected_height_info.epoch - 1).max(0),
310                        head,
311                        ResolveNullTipset::TakeOlder,
312                    )
313                    .await?;
314                let target_epoch = target_head.epoch();
315                let bundle_version = &bundle_metadata.version;
316                let expected_bundle_version = &expected_bundle_metadata.version;
317                if target_epoch < current_epoch {
318                    tracing::warn!(
319                        "rewinding chain head from {current_epoch} to {target_epoch}, actor bundle: {bundle_version}, expected: {expected_bundle_version}"
320                    );
321                    if self.db().has(target_head.parent_state())? {
322                        self.chain_store().set_heaviest_tipset(target_head)?;
323                        return Ok(true);
324                    } else {
325                        anyhow::bail!(
326                            "failed to rewind, state tree @ {target_epoch} is missing from blockstore: {}",
327                            target_head.parent_state()
328                        );
329                    }
330                }
331            }
332        }
333        Ok(false)
334    }
335
336    pub fn beacon_schedule(&self) -> &Arc<BeaconSchedule> {
337        &self.beacon
338    }
339
340    /// Returns network version for the given epoch.
341    pub fn get_network_version(&self, epoch: ChainEpoch) -> NetworkVersion {
342        self.chain_config().network_version(epoch)
343    }
344
345    /// Gets the state tree
346    pub fn get_state_tree(&self, state_cid: &Cid) -> anyhow::Result<StateTree<DbImpl>> {
347        StateTree::new_from_root(self.chain_index().db(), state_cid)
348    }
349
350    /// Gets actor from given [`Cid`], if it exists.
351    pub fn get_actor(&self, addr: &Address, state_cid: Cid) -> anyhow::Result<Option<ActorState>> {
352        let state = self.get_state_tree(&state_cid)?;
353        state.get_actor(addr)
354    }
355
356    /// Gets actor state from implicit actor address
357    pub fn get_actor_state<S: LoadActorStateFromBlockstore>(
358        &self,
359        ts: &Tipset,
360    ) -> anyhow::Result<S> {
361        let state_tree = self.get_state_tree(ts.parent_state())?;
362        state_tree.get_actor_state()
363    }
364
365    /// Gets actor state from explicit actor address
366    pub fn get_actor_state_from_address<S: LoadActorStateFromBlockstore>(
367        &self,
368        ts: &Tipset,
369        actor_address: &Address,
370    ) -> anyhow::Result<S> {
371        let state_tree = self.get_state_tree(ts.parent_state())?;
372        state_tree.get_actor_state_from_address(actor_address)
373    }
374
375    /// Gets required actor from given [`Cid`].
376    pub fn get_required_actor(&self, addr: &Address, state_cid: Cid) -> anyhow::Result<ActorState> {
377        let state = self.get_state_tree(&state_cid)?;
378        state.get_actor(addr)?.with_context(|| {
379            format!("Failed to load actor with addr={addr}, state_cid={state_cid}")
380        })
381    }
382
383    /// Returns a reference to the state manager's [`Blockstore`].
384    pub fn db(&self) -> &DbImpl {
385        self.cs.db()
386    }
387
388    pub fn db_owned(&self) -> DbImpl {
389        self.cs.db_owned()
390    }
391
392    /// Returns reference to the state manager's [`ChainStore`].
393    pub fn chain_store(&self) -> &ChainStore {
394        &self.cs
395    }
396
397    /// Returns reference to the state manager's [`ChainIndex`].
398    pub fn chain_index(&self) -> &ChainIndex {
399        self.cs.chain_index()
400    }
401
402    /// Returns reference to the state manager's [`ChainConfig`].
403    pub fn chain_config(&self) -> &Arc<ChainConfig> {
404        self.cs.chain_config()
405    }
406
407    pub fn chain_rand(&self, tipset: Tipset) -> ChainRand {
408        ChainRand::new(
409            self.chain_config().shallow_clone(),
410            tipset,
411            self.chain_index().shallow_clone(),
412            self.beacon.shallow_clone(),
413        )
414    }
415
416    /// Returns the internal, protocol-level network chain from the state.
417    pub fn get_network_state_name(
418        &self,
419        state_cid: Cid,
420    ) -> anyhow::Result<crate::networks::StateNetworkName> {
421        let init_act = self
422            .get_actor(&init::ADDRESS.into(), state_cid)?
423            .ok_or_else(|| Error::state("Init actor address could not be resolved"))?;
424        Ok(State::load(self.db(), init_act.code, init_act.state)?
425            .into_network_name()
426            .into())
427    }
428
429    /// Returns true if miner has been slashed or is considered invalid.
430    pub fn is_miner_slashed(&self, addr: &Address, state_cid: &Cid) -> anyhow::Result<bool, Error> {
431        let actor = self
432            .get_actor(&Address::POWER_ACTOR, *state_cid)?
433            .ok_or_else(|| Error::state("Power actor address could not be resolved"))?;
434
435        let spas = power::State::load(self.db(), actor.code, actor.state)?;
436
437        Ok(spas.miner_power(self.db(), addr)?.is_none())
438    }
439
440    /// Returns raw work address of a miner given the state root.
441    pub fn get_miner_work_addr(&self, state_cid: Cid, addr: &Address) -> Result<Address, Error> {
442        let state = StateTree::new_from_root(self.db(), &state_cid).map_err(Error::other)?;
443        let ms: miner::State = state.get_actor_state_from_address(addr)?;
444        let info = ms.info(self.db()).map_err(|e| e.to_string())?;
445        let addr = state.resolve_to_deterministic_address(self.db(), info.worker())?;
446        Ok(addr)
447    }
448
449    /// Returns specified actor's claimed power and total network power as a
450    /// tuple.
451    pub fn get_power(
452        &self,
453        state_cid: &Cid,
454        addr: Option<&Address>,
455    ) -> anyhow::Result<Option<(power::Claim, power::Claim)>, Error> {
456        let actor = self
457            .get_actor(&Address::POWER_ACTOR, *state_cid)?
458            .ok_or_else(|| Error::state("Power actor address could not be resolved"))?;
459
460        let spas = power::State::load(self.db(), actor.code, actor.state)?;
461
462        let t_pow = spas.total_power();
463
464        if let Some(maddr) = addr {
465            let m_pow = spas
466                .miner_power(self.db(), maddr)?
467                .ok_or_else(|| Error::state(format!("Miner for address {maddr} not found")))?;
468
469            let min_pow = spas.miner_nominal_power_meets_consensus_minimum(
470                &self.chain_config().policy,
471                self.db(),
472                maddr,
473            )?;
474            if min_pow {
475                return Ok(Some((m_pow, t_pow)));
476            }
477        }
478
479        Ok(None)
480    }
481
482    // Returns all sectors
483    pub fn get_all_sectors(
484        &self,
485        addr: &Address,
486        ts: &Tipset,
487    ) -> anyhow::Result<Vec<SectorOnChainInfo>> {
488        let actor = self
489            .get_actor(addr, *ts.parent_state())?
490            .ok_or_else(|| Error::state(format!("Miner actor {addr} not found")))?;
491        let state = miner::State::load(self.db(), actor.code, actor.state)?;
492        state.load_sectors_ext(self.db(), None)
493    }
494}