1use crate::{
5 blocks::Tipset,
6 chain::{ChainStore, index::ResolveNullTipset},
7 chain_sync::{load_full_tipset, tipset_syncer::validate_tipset},
8 cli_shared::{chain_path, read_config},
9 db::{SettingsStoreExt, db_engine::db_root},
10 genesis::read_genesis_header,
11 interpreter::VMTrace,
12 networks::{ChainConfig, NetworkChain},
13 shim::clock::ChainEpoch,
14 state_manager::{StateManager, StateOutput},
15 tool::subcommands::api_cmd::generate_test_snapshot,
16};
17use nonzero_ext::nonzero;
18use std::{num::NonZeroUsize, path::PathBuf, sync::Arc, time::Instant};
19
20#[derive(Debug, clap::Subcommand)]
22pub enum StateCommand {
23 Compute(ComputeCommand),
24 ReplayCompute(ReplayComputeCommand),
25 Validate(ValidateCommand),
26 ReplayValidate(ReplayValidateCommand),
27}
28
29impl StateCommand {
30 pub async fn run(self) -> anyhow::Result<()> {
31 match self {
32 Self::Compute(cmd) => cmd.run().await,
33 Self::ReplayCompute(cmd) => cmd.run().await,
34 Self::Validate(cmd) => cmd.run().await,
35 Self::ReplayValidate(cmd) => cmd.run().await,
36 }
37 }
38}
39
40#[derive(Debug, clap::Args)]
42pub struct ComputeCommand {
43 #[arg(long, required = true)]
45 epoch: ChainEpoch,
46 #[arg(long, required = true)]
48 chain: NetworkChain,
49 #[arg(long)]
51 db: Option<PathBuf>,
52 #[arg(long)]
54 export_db_to: Option<PathBuf>,
55}
56
57impl ComputeCommand {
58 pub async fn run(self) -> anyhow::Result<()> {
59 let Self {
60 epoch,
61 chain,
62 db,
63 export_db_to,
64 } = self;
65 disable_tipset_cache();
66 let db_root_path = if let Some(db) = db {
67 db
68 } else {
69 let (_, config) = read_config(None, Some(chain.clone()))?;
70 db_root(&chain_path(&config))?
71 };
72 let db = generate_test_snapshot::load_db(&db_root_path, Some(&chain)).await?;
73 let chain_config = Arc::new(ChainConfig::from_chain(&chain));
74 let genesis_header =
75 read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db)
76 .await?;
77 let chain_store = Arc::new(ChainStore::new(
78 db.clone(),
79 db.clone(),
80 db.clone(),
81 chain_config,
82 genesis_header,
83 )?);
84 let (ts, ts_next) = {
85 db.pause_tracking();
87 let ts = chain_store.chain_index().tipset_by_height(
88 epoch,
89 chain_store.heaviest_tipset(),
90 ResolveNullTipset::TakeOlder,
91 )?;
92 let ts_next = chain_store.chain_index().tipset_by_height(
93 epoch + 1,
94 chain_store.heaviest_tipset(),
95 ResolveNullTipset::TakeNewer,
96 )?;
97 db.resume_tracking();
98 SettingsStoreExt::write_obj(
99 &db.tracker,
100 crate::db::setting_keys::HEAD_KEY,
101 ts_next.key(),
102 )?;
103 (
105 Tipset::load_required(&db, ts.key())?,
106 Tipset::load_required(&db, ts_next.key())?,
107 )
108 };
109 let epoch = ts.epoch();
110 let state_manager = Arc::new(StateManager::new(chain_store)?);
111
112 let StateOutput {
113 state_root,
114 receipt_root,
115 ..
116 } = state_manager
117 .compute_tipset_state(ts, crate::state_manager::NO_CALLBACK, VMTrace::NotTraced)
118 .await?;
119 let mut db_snapshot = vec![];
120 db.export_forest_car(&mut db_snapshot).await?;
121 println!(
122 "epoch: {epoch}, state_root: {state_root}, receipt_root: {receipt_root}, db_snapshot_size: {}",
123 human_bytes::human_bytes(db_snapshot.len() as f64)
124 );
125 let expected_state_root = *ts_next.parent_state();
126 let expected_receipt_root = *ts_next.parent_message_receipts();
127 anyhow::ensure!(
128 state_root == expected_state_root,
129 "state root mismatch, state_root: {state_root}, expected_state_root: {expected_state_root}"
130 );
131 anyhow::ensure!(
132 receipt_root == expected_receipt_root,
133 "receipt root mismatch, receipt_root: {receipt_root}, expected_receipt_root: {expected_receipt_root}"
134 );
135 if let Some(export_db_to) = export_db_to {
136 std::fs::write(export_db_to, db_snapshot)?;
137 }
138 Ok(())
139 }
140}
141
142#[derive(Debug, clap::Args)]
145pub struct ReplayComputeCommand {
146 snapshot: PathBuf,
148 #[arg(long, required = true)]
150 chain: NetworkChain,
151 #[arg(short, long, default_value_t = nonzero!(1usize))]
153 n: NonZeroUsize,
154}
155
156impl ReplayComputeCommand {
157 pub async fn run(self) -> anyhow::Result<()> {
158 let Self { snapshot, chain, n } = self;
159 let (sm, ts, ts_next) =
160 crate::state_manager::utils::state_compute::prepare_state_compute(&chain, &snapshot)
161 .await?;
162 for _ in 0..n.get() {
163 crate::state_manager::utils::state_compute::state_compute(&sm, ts.clone(), &ts_next)
164 .await?;
165 }
166 Ok(())
167 }
168}
169
170#[derive(Debug, clap::Args)]
172pub struct ValidateCommand {
173 #[arg(long, required = true)]
175 epoch: ChainEpoch,
176 #[arg(long, required = true)]
178 chain: NetworkChain,
179 #[arg(long)]
181 db: Option<PathBuf>,
182 #[arg(long)]
184 export_db_to: Option<PathBuf>,
185}
186
187impl ValidateCommand {
188 pub async fn run(self) -> anyhow::Result<()> {
189 let Self {
190 epoch,
191 chain,
192 db,
193 export_db_to,
194 } = self;
195 disable_tipset_cache();
196 let db_root_path = if let Some(db) = db {
197 db
198 } else {
199 let (_, config) = read_config(None, Some(chain.clone()))?;
200 db_root(&chain_path(&config))?
201 };
202 let db = generate_test_snapshot::load_db(&db_root_path, Some(&chain)).await?;
203 let chain_config = Arc::new(ChainConfig::from_chain(&chain));
204 let genesis_header =
205 read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db)
206 .await?;
207 let chain_store = Arc::new(ChainStore::new(
208 db.clone(),
209 db.clone(),
210 db.clone(),
211 chain_config,
212 genesis_header,
213 )?);
214 let ts = {
215 db.pause_tracking();
217 let ts = chain_store.chain_index().tipset_by_height(
218 epoch,
219 chain_store.heaviest_tipset(),
220 ResolveNullTipset::TakeOlder,
221 )?;
222 db.resume_tracking();
223 SettingsStoreExt::write_obj(&db.tracker, crate::db::setting_keys::HEAD_KEY, ts.key())?;
224 Tipset::load_required(&db, ts.key())?
226 };
227 let epoch = ts.epoch();
228 let fts = load_full_tipset(&chain_store, ts.key())?;
229 let state_manager = Arc::new(StateManager::new(chain_store)?);
230 validate_tipset(&state_manager, fts, None).await?;
231 let mut db_snapshot = vec![];
232 db.export_forest_car(&mut db_snapshot).await?;
233 println!(
234 "epoch: {epoch}, db_snapshot_size: {}",
235 human_bytes::human_bytes(db_snapshot.len() as f64)
236 );
237 if let Some(export_db_to) = export_db_to {
238 std::fs::write(export_db_to, db_snapshot)?;
239 }
240 Ok(())
241 }
242}
243
244#[derive(Debug, clap::Args)]
247pub struct ReplayValidateCommand {
248 snapshot: PathBuf,
250 #[arg(long, required = true)]
252 chain: NetworkChain,
253 #[arg(short, long, default_value_t = nonzero!(1usize))]
255 n: NonZeroUsize,
256}
257
258impl ReplayValidateCommand {
259 pub async fn run(self) -> anyhow::Result<()> {
260 let Self { snapshot, chain, n } = self;
261 let (sm, fts) =
262 crate::state_manager::utils::state_compute::prepare_state_validate(&chain, &snapshot)
263 .await?;
264 let epoch = fts.epoch();
265 for _ in 0..n.get() {
266 let fts = fts.clone();
267 let start = Instant::now();
268 validate_tipset(&sm, fts, None).await?;
269 println!(
270 "epoch: {epoch}, took {}.",
271 humantime::format_duration(start.elapsed())
272 );
273 }
274 Ok(())
275 }
276}
277
278fn disable_tipset_cache() {
279 unsafe {
280 std::env::set_var("FOREST_TIPSET_CACHE_DISABLED", "1");
281 }
282}