1use crate::{
5 chain::{ChainStore, index::ResolveNullTipset},
6 chain_sync::{load_full_tipset, tipset_syncer::validate_tipset},
7 cli_shared::{chain_path, read_config},
8 db::{SettingsStoreExt, db_engine::db_root},
9 genesis::read_genesis_header,
10 interpreter::VMTrace,
11 networks::{ChainConfig, NetworkChain},
12 prelude::*,
13 shim::clock::ChainEpoch,
14 state_manager::{ExecutedTipset, StateManager},
15 tool::subcommands::api_cmd::generate_test_snapshot,
16};
17use human_repr::HumanCount as _;
18use nonzero_ext::nonzero;
19use std::{num::NonZeroUsize, path::PathBuf, time::Instant};
20
21#[derive(Debug, clap::Subcommand)]
23pub enum StateCommand {
24 Compute(ComputeCommand),
25 ReplayCompute(ReplayComputeCommand),
26 Validate(ValidateCommand),
27 ReplayValidate(ReplayValidateCommand),
28}
29
30impl StateCommand {
31 pub async fn run(self) -> anyhow::Result<()> {
32 match self {
33 Self::Compute(cmd) => cmd.run().await,
34 Self::ReplayCompute(cmd) => cmd.run().await,
35 Self::Validate(cmd) => cmd.run().await,
36 Self::ReplayValidate(cmd) => cmd.run().await,
37 }
38 }
39}
40
41#[derive(Debug, clap::Args)]
43pub struct ComputeCommand {
44 #[arg(long, required = true)]
46 epoch: ChainEpoch,
47 #[arg(long, required = true)]
49 chain: NetworkChain,
50 #[arg(long)]
52 db: Option<PathBuf>,
53 #[arg(long)]
55 export_db_to: Option<PathBuf>,
56}
57
58impl ComputeCommand {
59 pub async fn run(self) -> anyhow::Result<()> {
60 let Self {
61 epoch,
62 chain,
63 db,
64 export_db_to,
65 } = self;
66 disable_tipset_cache();
67 let db_root_path = if let Some(db) = db {
68 db
69 } else {
70 let (_, config) = read_config(None, Some(chain.clone()))?;
71 db_root(&chain_path(&config))?
72 };
73 let db = generate_test_snapshot::load_db(&db_root_path, Some(&chain)).await?;
74 let chain_config = Arc::new(ChainConfig::from_chain(&chain));
75 let genesis_header =
76 read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db)
77 .await?;
78 let chain_store = ChainStore::new(db.clone(), chain_config, genesis_header)?;
79 let chain_index = chain_store.chain_index();
80 let (ts, ts_next) = {
81 db.pause_tracking();
83 let ts = chain_index
84 .load_required_tipset_by_height(
85 epoch,
86 chain_store.heaviest_tipset(),
87 ResolveNullTipset::TakeOlder,
88 )
89 .await?;
90 let ts_next = chain_store.load_child_tipset(&ts).await?.with_context(|| {
91 format!(
92 "no child tipset for epoch {} (may be chain head)",
93 ts.epoch()
94 )
95 })?;
96 db.resume_tracking();
97 SettingsStoreExt::write_obj(
98 &db.tracker,
99 crate::db::setting_keys::HEAD_KEY,
100 ts_next.key(),
101 )?;
102 (
104 chain_index.load_required_tipset(ts.key())?,
105 chain_index.load_required_tipset(ts_next.key())?,
106 )
107 };
108 let epoch = ts.epoch();
109 let state_manager = StateManager::new(chain_store)?;
110
111 let ExecutedTipset {
112 state_root,
113 receipt_root,
114 ..
115 } = state_manager
116 .compute_tipset_state(ts, crate::state_manager::NO_CALLBACK, VMTrace::NotTraced)
117 .await?;
118 let mut db_snapshot = vec![];
119 db.export_forest_car(&mut db_snapshot).await?;
120 println!(
121 "epoch: {epoch}, state_root: {state_root}, receipt_root: {receipt_root}, db_snapshot_size: {}",
122 db_snapshot.len().human_count_bytes()
123 );
124 if let Some(export_db_to) = export_db_to {
125 std::fs::write(export_db_to, db_snapshot)?;
126 }
127 let expected_state_root = *ts_next.parent_state();
128 let expected_receipt_root = *ts_next.parent_message_receipts();
129 anyhow::ensure!(
130 state_root == expected_state_root,
131 "state root mismatch, state_root: {state_root}, expected_state_root: {expected_state_root}"
132 );
133 anyhow::ensure!(
134 receipt_root == expected_receipt_root,
135 "receipt root mismatch, receipt_root: {receipt_root}, expected_receipt_root: {expected_receipt_root}"
136 );
137 Ok(())
138 }
139}
140
141#[derive(Debug, clap::Args)]
144pub struct ReplayComputeCommand {
145 snapshot: PathBuf,
147 #[arg(long, required = true)]
149 chain: NetworkChain,
150 #[arg(short, long, default_value_t = nonzero!(1usize))]
152 n: NonZeroUsize,
153}
154
155impl ReplayComputeCommand {
156 pub async fn run(self) -> anyhow::Result<()> {
157 let Self { snapshot, chain, n } = self;
158 let (sm, ts, ts_next) =
159 crate::state_manager::utils::state_compute::prepare_state_compute(&chain, &snapshot)
160 .await?;
161 for _ in 0..n.get() {
162 crate::state_manager::utils::state_compute::state_compute(
163 &sm,
164 ts.shallow_clone(),
165 &ts_next,
166 )
167 .await?;
168 }
169 Ok(())
170 }
171}
172
173#[derive(Debug, clap::Args)]
175pub struct ValidateCommand {
176 #[arg(long, required = true)]
178 epoch: ChainEpoch,
179 #[arg(long, required = true)]
181 chain: NetworkChain,
182 #[arg(long)]
184 db: Option<PathBuf>,
185 #[arg(long)]
187 export_db_to: Option<PathBuf>,
188}
189
190impl ValidateCommand {
191 pub async fn run(self) -> anyhow::Result<()> {
192 let Self {
193 epoch,
194 chain,
195 db,
196 export_db_to,
197 } = self;
198 disable_tipset_cache();
199 let db_root_path = if let Some(db) = db {
200 db
201 } else {
202 let (_, config) = read_config(None, Some(chain.clone()))?;
203 db_root(&chain_path(&config))?
204 };
205 let db = generate_test_snapshot::load_db(&db_root_path, Some(&chain)).await?;
206 let chain_config = Arc::new(ChainConfig::from_chain(&chain));
207 let genesis_header =
208 read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db)
209 .await?;
210 let chain_store = ChainStore::new(db.clone(), chain_config, genesis_header)?;
211 let chain_index = chain_store.chain_index();
212 let ts = {
213 db.pause_tracking();
215 let ts = chain_index
216 .load_required_tipset_by_height(
217 epoch,
218 chain_store.heaviest_tipset(),
219 ResolveNullTipset::TakeOlder,
220 )
221 .await?;
222 db.resume_tracking();
223 SettingsStoreExt::write_obj(&db.tracker, crate::db::setting_keys::HEAD_KEY, ts.key())?;
224 chain_index.load_required_tipset(ts.key())?
226 };
227 let epoch = ts.epoch();
228 let fts = load_full_tipset(&chain_store, ts.key())?;
229 let state_manager = 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 db_snapshot.len().human_count_bytes()
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 std::env::set_var("FOREST_TIPSET_LOOKUP_TABLE_DISABLED", "1");
282 }
283}