forest/tool/subcommands/
state_compute_cmd.rs

1// Copyright 2019-2025 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use crate::{
5    chain::{ChainStore, index::ResolveNullTipset},
6    cli_shared::{chain_path, read_config},
7    db::{
8        MemoryDB, SettingsStoreExt,
9        car::{AnyCar, ManyCar},
10        db_engine::db_root,
11    },
12    genesis::read_genesis_header,
13    interpreter::VMTrace,
14    networks::{ChainConfig, NetworkChain},
15    shim::clock::ChainEpoch,
16    state_manager::{StateManager, StateOutput},
17};
18use std::{num::NonZeroUsize, path::PathBuf, sync::Arc, time::Instant};
19
20/// Interact with Filecoin chain state
21#[derive(Debug, clap::Subcommand)]
22pub enum StateCommand {
23    Compute(ComputeCommand),
24    ReplayCompute(ReplayComputeCommand),
25}
26
27impl StateCommand {
28    pub async fn run(self) -> anyhow::Result<()> {
29        match self {
30            Self::Compute(cmd) => cmd.run().await,
31            Self::ReplayCompute(cmd) => cmd.run().await,
32        }
33    }
34}
35
36/// Compute state tree for an epoch
37#[derive(Debug, clap::Args)]
38pub struct ComputeCommand {
39    /// Which epoch to compute the state transition for
40    #[arg(long, required = true)]
41    epoch: ChainEpoch,
42    /// Filecoin network chain
43    #[arg(long, required = true)]
44    chain: NetworkChain,
45    /// Optional path to the database folder
46    #[arg(long)]
47    db: Option<PathBuf>,
48    /// Optional path to the database snapshot `CAR` file to write to for reproducing the computation
49    #[arg(long)]
50    export_db_to: Option<PathBuf>,
51}
52
53impl ComputeCommand {
54    pub async fn run(self) -> anyhow::Result<()> {
55        let Self {
56            epoch,
57            chain,
58            db,
59            export_db_to,
60        } = self;
61        let db_root_path = if let Some(db) = db {
62            db
63        } else {
64            let (_, config) = read_config(None, Some(chain.clone()))?;
65            db_root(&chain_path(&config))?
66        };
67        let db = super::api_cmd::generate_test_snapshot::load_db(&db_root_path)?;
68        let chain_config = Arc::new(ChainConfig::from_chain(&chain));
69        let genesis_header =
70            read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db)
71                .await?;
72        let chain_store = Arc::new(ChainStore::new(
73            db.clone(),
74            db.clone(),
75            db.clone(),
76            db.clone(),
77            chain_config,
78            genesis_header,
79        )?);
80        let ts = chain_store.chain_index().tipset_by_height(
81            epoch,
82            chain_store.heaviest_tipset(),
83            ResolveNullTipset::TakeOlder,
84        )?;
85        let epoch = ts.epoch();
86        SettingsStoreExt::write_obj(&db.tracker, crate::db::setting_keys::HEAD_KEY, ts.key())?;
87        let state_manager = Arc::new(StateManager::new(chain_store.clone())?);
88
89        let StateOutput {
90            state_root,
91            receipt_root,
92            ..
93        } = state_manager
94            .compute_tipset_state(ts, crate::state_manager::NO_CALLBACK, VMTrace::NotTraced)
95            .await?;
96        let mut db_snapshot = vec![];
97        db.export_forest_car(&mut db_snapshot).await?;
98        println!(
99            "epoch: {epoch}, state_root: {state_root}, receipt_root: {receipt_root}, db_snapshot_size: {}",
100            human_bytes::human_bytes(db_snapshot.len() as f64)
101        );
102        if let Some(export_db_to) = export_db_to {
103            std::fs::write(export_db_to, db_snapshot)?;
104        }
105        Ok(())
106    }
107}
108
109/// Replay state computation with a db snapshot
110/// To be used in conjunction with `forest-tool state compute`.
111#[derive(Debug, clap::Args)]
112pub struct ReplayComputeCommand {
113    /// Path to the database snapshot `CAR` file generated by `forest-tool state compute`
114    snapshot: PathBuf,
115    /// Filecoin network chain
116    #[arg(long, required = true)]
117    chain: NetworkChain,
118    /// Number of times to repeat the state computation
119    #[arg(short, long, default_value_t = NonZeroUsize::new(1).unwrap())]
120    n: NonZeroUsize,
121}
122
123impl ReplayComputeCommand {
124    pub async fn run(self) -> anyhow::Result<()> {
125        let Self { snapshot, chain, n } = self;
126        let snap_car = AnyCar::try_from(&snapshot)?;
127        let ts = Arc::new(snap_car.heaviest_tipset()?);
128        let epoch = ts.epoch();
129        let db = Arc::new(ManyCar::new(MemoryDB::default()).with_read_only(snap_car)?);
130        let chain_config = Arc::new(ChainConfig::from_chain(&chain));
131        let genesis_header =
132            read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db)
133                .await?;
134        let chain_store = Arc::new(ChainStore::new(
135            db.clone(),
136            db.clone(),
137            db.clone(),
138            db.clone(),
139            chain_config,
140            genesis_header,
141        )?);
142        let state_manager = Arc::new(StateManager::new(chain_store.clone())?);
143        for _ in 0..n.get() {
144            let start = Instant::now();
145            let StateOutput {
146                state_root,
147                receipt_root,
148                ..
149            } = state_manager
150                .compute_tipset_state(
151                    ts.clone(),
152                    crate::state_manager::NO_CALLBACK,
153                    VMTrace::NotTraced,
154                )
155                .await?;
156            println!(
157                "epoch: {epoch}, state_root: {state_root}, receipt_root: {receipt_root}, took {}.",
158                humantime::format_duration(start.elapsed())
159            );
160        }
161        Ok(())
162    }
163}