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 human_repr::HumanCount as _;
18use nonzero_ext::nonzero;
19use std::{num::NonZeroUsize, path::PathBuf, sync::Arc, 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 = Arc::new(ChainStore::new(
79 db.clone(),
80 db.clone(),
81 db.clone(),
82 chain_config,
83 genesis_header,
84 )?);
85 let (ts, ts_next) = {
86 db.pause_tracking();
88 let ts = chain_store.chain_index().tipset_by_height(
89 epoch,
90 chain_store.heaviest_tipset(),
91 ResolveNullTipset::TakeOlder,
92 )?;
93 let ts_next = chain_store.chain_index().tipset_by_height(
94 epoch + 1,
95 chain_store.heaviest_tipset(),
96 ResolveNullTipset::TakeNewer,
97 )?;
98 db.resume_tracking();
99 SettingsStoreExt::write_obj(
100 &db.tracker,
101 crate::db::setting_keys::HEAD_KEY,
102 ts_next.key(),
103 )?;
104 (
106 Tipset::load_required(&db, ts.key())?,
107 Tipset::load_required(&db, ts_next.key())?,
108 )
109 };
110 let epoch = ts.epoch();
111 let state_manager = Arc::new(StateManager::new(chain_store)?);
112
113 let StateOutput {
114 state_root,
115 receipt_root,
116 ..
117 } = state_manager
118 .compute_tipset_state(ts, crate::state_manager::NO_CALLBACK, VMTrace::NotTraced)
119 .await?;
120 let mut db_snapshot = vec![];
121 db.export_forest_car(&mut db_snapshot).await?;
122 println!(
123 "epoch: {epoch}, state_root: {state_root}, receipt_root: {receipt_root}, db_snapshot_size: {}",
124 db_snapshot.len().human_count_bytes()
125 );
126 let expected_state_root = *ts_next.parent_state();
127 let expected_receipt_root = *ts_next.parent_message_receipts();
128 anyhow::ensure!(
129 state_root == expected_state_root,
130 "state root mismatch, state_root: {state_root}, expected_state_root: {expected_state_root}"
131 );
132 anyhow::ensure!(
133 receipt_root == expected_receipt_root,
134 "receipt root mismatch, receipt_root: {receipt_root}, expected_receipt_root: {expected_receipt_root}"
135 );
136 if let Some(export_db_to) = export_db_to {
137 std::fs::write(export_db_to, db_snapshot)?;
138 }
139 Ok(())
140 }
141}
142
143#[derive(Debug, clap::Args)]
146pub struct ReplayComputeCommand {
147 snapshot: PathBuf,
149 #[arg(long, required = true)]
151 chain: NetworkChain,
152 #[arg(short, long, default_value_t = nonzero!(1usize))]
154 n: NonZeroUsize,
155}
156
157impl ReplayComputeCommand {
158 pub async fn run(self) -> anyhow::Result<()> {
159 let Self { snapshot, chain, n } = self;
160 let (sm, ts, ts_next) =
161 crate::state_manager::utils::state_compute::prepare_state_compute(&chain, &snapshot)
162 .await?;
163 for _ in 0..n.get() {
164 crate::state_manager::utils::state_compute::state_compute(&sm, ts.clone(), &ts_next)
165 .await?;
166 }
167 Ok(())
168 }
169}
170
171#[derive(Debug, clap::Args)]
173pub struct ValidateCommand {
174 #[arg(long, required = true)]
176 epoch: ChainEpoch,
177 #[arg(long, required = true)]
179 chain: NetworkChain,
180 #[arg(long)]
182 db: Option<PathBuf>,
183 #[arg(long)]
185 export_db_to: Option<PathBuf>,
186}
187
188impl ValidateCommand {
189 pub async fn run(self) -> anyhow::Result<()> {
190 let Self {
191 epoch,
192 chain,
193 db,
194 export_db_to,
195 } = self;
196 disable_tipset_cache();
197 let db_root_path = if let Some(db) = db {
198 db
199 } else {
200 let (_, config) = read_config(None, Some(chain.clone()))?;
201 db_root(&chain_path(&config))?
202 };
203 let db = generate_test_snapshot::load_db(&db_root_path, Some(&chain)).await?;
204 let chain_config = Arc::new(ChainConfig::from_chain(&chain));
205 let genesis_header =
206 read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db)
207 .await?;
208 let chain_store = Arc::new(ChainStore::new(
209 db.clone(),
210 db.clone(),
211 db.clone(),
212 chain_config,
213 genesis_header,
214 )?);
215 let ts = {
216 db.pause_tracking();
218 let ts = chain_store.chain_index().tipset_by_height(
219 epoch,
220 chain_store.heaviest_tipset(),
221 ResolveNullTipset::TakeOlder,
222 )?;
223 db.resume_tracking();
224 SettingsStoreExt::write_obj(&db.tracker, crate::db::setting_keys::HEAD_KEY, ts.key())?;
225 Tipset::load_required(&db, ts.key())?
227 };
228 let epoch = ts.epoch();
229 let fts = load_full_tipset(&chain_store, ts.key())?;
230 let state_manager = Arc::new(StateManager::new(chain_store)?);
231 validate_tipset(&state_manager, fts, None).await?;
232 let mut db_snapshot = vec![];
233 db.export_forest_car(&mut db_snapshot).await?;
234 println!(
235 "epoch: {epoch}, db_snapshot_size: {}",
236 db_snapshot.len().human_count_bytes()
237 );
238 if let Some(export_db_to) = export_db_to {
239 std::fs::write(export_db_to, db_snapshot)?;
240 }
241 Ok(())
242 }
243}
244
245#[derive(Debug, clap::Args)]
248pub struct ReplayValidateCommand {
249 snapshot: PathBuf,
251 #[arg(long, required = true)]
253 chain: NetworkChain,
254 #[arg(short, long, default_value_t = nonzero!(1usize))]
256 n: NonZeroUsize,
257}
258
259impl ReplayValidateCommand {
260 pub async fn run(self) -> anyhow::Result<()> {
261 let Self { snapshot, chain, n } = self;
262 let (sm, fts) =
263 crate::state_manager::utils::state_compute::prepare_state_validate(&chain, &snapshot)
264 .await?;
265 let epoch = fts.epoch();
266 for _ in 0..n.get() {
267 let fts = fts.clone();
268 let start = Instant::now();
269 validate_tipset(&sm, fts, None).await?;
270 println!(
271 "epoch: {epoch}, took {}.",
272 humantime::format_duration(start.elapsed())
273 );
274 }
275 Ok(())
276 }
277}
278
279fn disable_tipset_cache() {
280 unsafe {
281 std::env::set_var("FOREST_TIPSET_CACHE_DISABLED", "1");
282 }
283}