-
Notifications
You must be signed in to change notification settings - Fork 189
feat: forest-tool state compute
#6167
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4323528
6c85f38
365cdef
e207be1
2d20452
6359429
4559c41
544c005
3b5e775
78dff80
fff6e7b
d0c08c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,163 @@ | ||||||||||||||
| // Copyright 2019-2025 ChainSafe Systems | ||||||||||||||
| // SPDX-License-Identifier: Apache-2.0, MIT | ||||||||||||||
|
|
||||||||||||||
| use crate::{ | ||||||||||||||
| chain::{ChainStore, index::ResolveNullTipset}, | ||||||||||||||
| cli_shared::{chain_path, read_config}, | ||||||||||||||
| db::{ | ||||||||||||||
| MemoryDB, SettingsStoreExt, | ||||||||||||||
| car::{AnyCar, ManyCar}, | ||||||||||||||
| db_engine::db_root, | ||||||||||||||
| }, | ||||||||||||||
| genesis::read_genesis_header, | ||||||||||||||
| interpreter::VMTrace, | ||||||||||||||
| networks::{ChainConfig, NetworkChain}, | ||||||||||||||
| shim::clock::ChainEpoch, | ||||||||||||||
| state_manager::{StateManager, StateOutput}, | ||||||||||||||
| }; | ||||||||||||||
| use std::{num::NonZeroUsize, path::PathBuf, sync::Arc, time::Instant}; | ||||||||||||||
|
|
||||||||||||||
| /// Interact with Filecoin chain state | ||||||||||||||
| #[derive(Debug, clap::Subcommand)] | ||||||||||||||
| pub enum StateCommand { | ||||||||||||||
| Compute(ComputeCommand), | ||||||||||||||
| ReplayCompute(ReplayComputeCommand), | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| impl StateCommand { | ||||||||||||||
| pub async fn run(self) -> anyhow::Result<()> { | ||||||||||||||
| match self { | ||||||||||||||
| Self::Compute(cmd) => cmd.run().await, | ||||||||||||||
| Self::ReplayCompute(cmd) => cmd.run().await, | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /// Compute state tree for an epoch | ||||||||||||||
| #[derive(Debug, clap::Args)] | ||||||||||||||
| pub struct ComputeCommand { | ||||||||||||||
| /// Which epoch to compute the state transition for | ||||||||||||||
| #[arg(long, required = true)] | ||||||||||||||
| epoch: ChainEpoch, | ||||||||||||||
| /// Filecoin network chain | ||||||||||||||
| #[arg(long, required = true)] | ||||||||||||||
| chain: NetworkChain, | ||||||||||||||
| /// Optional path to the database folder | ||||||||||||||
| #[arg(long)] | ||||||||||||||
| db: Option<PathBuf>, | ||||||||||||||
| /// Optional path to the database snapshot `CAR` file to write to for reproducing the computation | ||||||||||||||
| #[arg(long)] | ||||||||||||||
| export_db_to: Option<PathBuf>, | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| impl ComputeCommand { | ||||||||||||||
| pub async fn run(self) -> anyhow::Result<()> { | ||||||||||||||
| let Self { | ||||||||||||||
| epoch, | ||||||||||||||
| chain, | ||||||||||||||
| db, | ||||||||||||||
| export_db_to, | ||||||||||||||
| } = self; | ||||||||||||||
| let db_root_path = if let Some(db) = db { | ||||||||||||||
| db | ||||||||||||||
| } else { | ||||||||||||||
| let (_, config) = read_config(None, Some(chain.clone()))?; | ||||||||||||||
| db_root(&chain_path(&config))? | ||||||||||||||
| }; | ||||||||||||||
| let db = super::api_cmd::generate_test_snapshot::load_db(&db_root_path)?; | ||||||||||||||
| let chain_config = Arc::new(ChainConfig::from_chain(&chain)); | ||||||||||||||
| let genesis_header = | ||||||||||||||
| read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db) | ||||||||||||||
| .await?; | ||||||||||||||
| let chain_store = Arc::new(ChainStore::new( | ||||||||||||||
| db.clone(), | ||||||||||||||
| db.clone(), | ||||||||||||||
| db.clone(), | ||||||||||||||
| db.clone(), | ||||||||||||||
| chain_config, | ||||||||||||||
| genesis_header, | ||||||||||||||
| )?); | ||||||||||||||
| let ts = chain_store.chain_index().tipset_by_height( | ||||||||||||||
| epoch, | ||||||||||||||
| chain_store.heaviest_tipset(), | ||||||||||||||
| ResolveNullTipset::TakeOlder, | ||||||||||||||
| )?; | ||||||||||||||
| let epoch = ts.epoch(); | ||||||||||||||
| SettingsStoreExt::write_obj(&db.tracker, crate::db::setting_keys::HEAD_KEY, ts.key())?; | ||||||||||||||
| let state_manager = Arc::new(StateManager::new(chain_store.clone())?); | ||||||||||||||
|
|
||||||||||||||
|
Comment on lines
+86
to
+88
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrong store used when writing HEAD; likely compile error and unintended DB mutation
- SettingsStoreExt::write_obj(&db.tracker, crate::db::setting_keys::HEAD_KEY, ts.key())?;
+ // Optional: this write is not strictly necessary for compute; remove if you want no side-effects.
+ SettingsStoreExt::write_obj(&*db, crate::db::setting_keys::HEAD_KEY, &ts.key())?;If you prefer method syntax: - SettingsStoreExt::write_obj(&db.tracker, crate::db::setting_keys::HEAD_KEY, ts.key())?;
+ db.write_obj(crate::db::setting_keys::HEAD_KEY, &ts.key())?;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is intended. Setting the head for exporting the snapshot from the tracker
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 19887 🏁 Script executed: Length of output: 17067 🏁 Script executed: Length of output: 7646 @hanabi1224, thank you for the clarification! You're absolutely right. I now understand the architecture: The Writing I apologize for the incorrect initial assessment. The code is working as designed. 🐰 ✏️ Learnings added
|
||||||||||||||
| let StateOutput { | ||||||||||||||
| state_root, | ||||||||||||||
| receipt_root, | ||||||||||||||
| .. | ||||||||||||||
| } = state_manager | ||||||||||||||
| .compute_tipset_state(ts, crate::state_manager::NO_CALLBACK, VMTrace::NotTraced) | ||||||||||||||
| .await?; | ||||||||||||||
| let mut db_snapshot = vec![]; | ||||||||||||||
| db.export_forest_car(&mut db_snapshot).await?; | ||||||||||||||
| println!( | ||||||||||||||
| "epoch: {epoch}, state_root: {state_root}, receipt_root: {receipt_root}, db_snapshot_size: {}", | ||||||||||||||
| human_bytes::human_bytes(db_snapshot.len() as f64) | ||||||||||||||
| ); | ||||||||||||||
| if let Some(export_db_to) = export_db_to { | ||||||||||||||
| std::fs::write(export_db_to, db_snapshot)?; | ||||||||||||||
| } | ||||||||||||||
| Ok(()) | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /// Replay state computation with a db snapshot | ||||||||||||||
|
hanabi1224 marked this conversation as resolved.
|
||||||||||||||
| /// To be used in conjunction with `forest-tool state compute`. | ||||||||||||||
| #[derive(Debug, clap::Args)] | ||||||||||||||
| pub struct ReplayComputeCommand { | ||||||||||||||
| /// Path to the database snapshot `CAR` file generated by `forest-tool state compute` | ||||||||||||||
| snapshot: PathBuf, | ||||||||||||||
| /// Filecoin network chain | ||||||||||||||
| #[arg(long, required = true)] | ||||||||||||||
| chain: NetworkChain, | ||||||||||||||
| /// Number of times to repeat the state computation | ||||||||||||||
| #[arg(short, long, default_value_t = NonZeroUsize::new(1).unwrap())] | ||||||||||||||
| n: NonZeroUsize, | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| impl ReplayComputeCommand { | ||||||||||||||
| pub async fn run(self) -> anyhow::Result<()> { | ||||||||||||||
| let Self { snapshot, chain, n } = self; | ||||||||||||||
| let snap_car = AnyCar::try_from(&snapshot)?; | ||||||||||||||
| let ts = Arc::new(snap_car.heaviest_tipset()?); | ||||||||||||||
| let epoch = ts.epoch(); | ||||||||||||||
| let db = Arc::new(ManyCar::new(MemoryDB::default()).with_read_only(snap_car)?); | ||||||||||||||
| let chain_config = Arc::new(ChainConfig::from_chain(&chain)); | ||||||||||||||
| let genesis_header = | ||||||||||||||
| read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db) | ||||||||||||||
| .await?; | ||||||||||||||
| let chain_store = Arc::new(ChainStore::new( | ||||||||||||||
| db.clone(), | ||||||||||||||
| db.clone(), | ||||||||||||||
| db.clone(), | ||||||||||||||
| db.clone(), | ||||||||||||||
| chain_config, | ||||||||||||||
| genesis_header, | ||||||||||||||
| )?); | ||||||||||||||
| let state_manager = Arc::new(StateManager::new(chain_store.clone())?); | ||||||||||||||
| for _ in 0..n.get() { | ||||||||||||||
| let start = Instant::now(); | ||||||||||||||
| let StateOutput { | ||||||||||||||
| state_root, | ||||||||||||||
| receipt_root, | ||||||||||||||
| .. | ||||||||||||||
| } = state_manager | ||||||||||||||
| .compute_tipset_state( | ||||||||||||||
| ts.clone(), | ||||||||||||||
| crate::state_manager::NO_CALLBACK, | ||||||||||||||
| VMTrace::NotTraced, | ||||||||||||||
| ) | ||||||||||||||
| .await?; | ||||||||||||||
| println!( | ||||||||||||||
| "epoch: {epoch}, state_root: {state_root}, receipt_root: {receipt_root}, took {}.", | ||||||||||||||
| humantime::format_duration(start.elapsed()) | ||||||||||||||
| ); | ||||||||||||||
| } | ||||||||||||||
| Ok(()) | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix “private type in public interface” for Subcommand::State
Subcommandispub, but it exposesstate_compute_cmd::StateCommandfrom a private module, which breaks visibility (E0446). Re-export the type and reference the re-export, or make the module public. Minimal fix:Also applies to: 85-87
🤖 Prompt for AI Agents