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