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