Skip to content

Commit 36271ad

Browse files
committed
feat(tool): add forest-tool shed migrate state command
1 parent 4341111 commit 36271ad

File tree

6 files changed

+135
-14
lines changed

6 files changed

+135
-14
lines changed

src/blocks/tipset.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ impl FullTipset {
504504
pub fn parent_state(&self) -> &Cid {
505505
&self.first_block().header().state_root
506506
}
507-
/// Returns the state root for the tipset parent.
507+
/// Returns the keys of the parents of the blocks in the tipset.
508508
pub fn parents(&self) -> &TipsetKey {
509509
&self.first_block().header().parents
510510
}

src/shim/version.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright 2019-2025 ChainSafe Systems
22
// SPDX-License-Identifier: Apache-2.0, MIT
3+
use std::fmt;
34
use std::ops::{Deref, DerefMut};
5+
use std::str::FromStr;
46

57
use crate::lotus_json::lotus_json_with_self;
68

@@ -121,6 +123,21 @@ impl From<NetworkVersion> for NetworkVersion_v4 {
121123
}
122124
}
123125

126+
impl FromStr for NetworkVersion {
127+
type Err = <u32 as FromStr>::Err;
128+
129+
fn from_str(s: &str) -> Result<Self, Self::Err> {
130+
let v: u32 = s.parse()?;
131+
Ok(v.into())
132+
}
133+
}
134+
135+
impl fmt::Display for NetworkVersion {
136+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
137+
self.0.fmt(f)
138+
}
139+
}
140+
124141
#[cfg(test)]
125142
impl quickcheck::Arbitrary for NetworkVersion {
126143
fn arbitrary(g: &mut quickcheck::Gen) -> Self {

src/state_migration/mod.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,11 @@ mod type_migrations;
2929

3030
type RunMigration<DB> = fn(&ChainConfig, &Arc<DB>, &Cid, ChainEpoch) -> anyhow::Result<Cid>;
3131

32-
/// Run state migrations
33-
pub fn run_state_migrations<DB>(
34-
epoch: ChainEpoch,
35-
chain_config: &ChainConfig,
36-
db: &Arc<DB>,
37-
parent_state: &Cid,
38-
) -> anyhow::Result<Option<Cid>>
32+
pub fn get_migrations<DB>(chain: &NetworkChain) -> Vec<(Height, RunMigration<DB>)>
3933
where
4034
DB: Blockstore + Send + Sync,
4135
{
42-
let mappings: Vec<(_, RunMigration<DB>)> = match chain_config.network {
36+
match chain {
4337
NetworkChain::Mainnet => {
4438
vec![
4539
(Height::Shark, nv17::run_migration::<DB>),
@@ -82,7 +76,20 @@ where
8276
(Height::Teep, nv25::run_migration::<DB>),
8377
]
8478
}
85-
};
79+
}
80+
}
81+
82+
/// Run state migrations
83+
pub fn run_state_migrations<DB>(
84+
epoch: ChainEpoch,
85+
chain_config: &ChainConfig,
86+
db: &Arc<DB>,
87+
parent_state: &Cid,
88+
) -> anyhow::Result<Option<Cid>>
89+
where
90+
DB: Blockstore + Send + Sync,
91+
{
92+
let mappings = get_migrations(&chain_config.network);
8693

8794
// Make sure bundle is defined.
8895
static BUNDLE_CHECKED: AtomicBool = AtomicBool::new(false);
@@ -106,7 +113,7 @@ where
106113
tracing::info!("Running {height} migration at epoch {epoch}");
107114
let start_time = std::time::Instant::now();
108115
let new_state = migrate(chain_config, db, parent_state, epoch)?;
109-
let elapsed = start_time.elapsed().as_secs_f32();
116+
let elapsed = start_time.elapsed();
110117
// `new_state_actors` is the Go state migration output, log for comparision
111118
let new_state_actors = db
112119
.get_cbor::<StateRoot>(&new_state)
@@ -116,9 +123,9 @@ where
116123
.unwrap_or_default();
117124
if new_state != *parent_state {
118125
crate::utils::misc::reveal_upgrade_logo(height.into());
119-
tracing::info!("State migration at height {height}(epoch {epoch}) was successful, Previous state: {parent_state}, new state: {new_state}, new state actors: {new_state_actors}. Took: {elapsed}s.");
126+
tracing::info!("State migration at height {height}(epoch {epoch}) was successful, Previous state: {parent_state}, new state: {new_state}, new state actors: {new_state_actors}. Took: {elapsed}.", elapsed = humantime::format_duration(elapsed));
120127
} else {
121-
anyhow:: bail!("State post migration at height {height} must not match. Previous state: {parent_state}, new state: {new_state}, new state actors: {new_state_actors}. Took {elapsed}s.");
128+
anyhow:: bail!("State post migration at height {height} must not match. Previous state: {parent_state}, new state: {new_state}, new state actors: {new_state_actors}. Took {elapsed}.", elapsed = humantime::format_duration(elapsed));
122129
}
123130

124131
return Ok(Some(new_state));

src/state_migration/nv25/migration.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ impl<BS: Blockstore> StateMigration<BS> {
6666
}
6767

6868
/// Runs the migration for `NV25`. Returns the new state root.
69-
#[allow(dead_code)]
7069
pub fn run_migration<DB>(
7170
chain_config: &ChainConfig,
7271
blockstore: &Arc<DB>,

src/tool/subcommands/shed_cmd.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
mod f3;
55
use f3::*;
6+
mod migration;
7+
use migration::*;
68

79
use crate::{
810
libp2p::keypair::get_keypair,
@@ -67,6 +69,8 @@ pub enum ShedCommands {
6769
/// F3 related commands.
6870
#[command(subcommand)]
6971
F3(F3Commands),
72+
/// Run a network upgrade migration
73+
MigrateState(MigrateStateCommand),
7074
}
7175

7276
#[derive(Debug, Clone, ValueEnum, PartialEq)]
@@ -171,6 +175,7 @@ impl ShedCommands {
171175
println!("{}", serde_json::to_string_pretty(&openrpc_doc)?);
172176
}
173177
ShedCommands::F3(cmd) => cmd.run(client).await?,
178+
ShedCommands::MigrateState(cmd) => cmd.run(client).await?,
174179
}
175180
Ok(())
176181
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2019-2025 ChainSafe Systems
2+
// SPDX-License-Identifier: Apache-2.0, MIT
3+
4+
use std::path::{Path, PathBuf};
5+
use std::sync::Arc;
6+
use std::time::Instant;
7+
8+
use cid::Cid;
9+
use clap::Args;
10+
use itertools::Itertools;
11+
12+
use crate::utils::db::CborStoreExt;
13+
use crate::{
14+
blocks::CachingBlockHeader,
15+
cli_shared::{chain_path, read_config},
16+
daemon::db_util::load_all_forest_cars,
17+
db::{
18+
car::ManyCar,
19+
db_engine::{db_root, open_db},
20+
parity_db::ParityDb,
21+
CAR_DB_DIR_NAME,
22+
},
23+
networks::{ChainConfig, NetworkChain},
24+
shim::version::NetworkVersion,
25+
};
26+
27+
#[derive(Debug, Args)]
28+
pub struct MigrateStateCommand {
29+
/// Target network version
30+
network_version: NetworkVersion,
31+
/// Block to look back from
32+
block_to_look_back: Cid,
33+
/// Path to the Forest database folder
34+
#[arg(long)]
35+
db: Option<PathBuf>,
36+
/// Filecoin network chain
37+
#[arg(long, required = true)]
38+
chain: NetworkChain,
39+
}
40+
41+
impl MigrateStateCommand {
42+
pub async fn run(self, _: crate::rpc::Client) -> anyhow::Result<()> {
43+
let Self {
44+
network_version,
45+
block_to_look_back,
46+
db,
47+
chain,
48+
} = self;
49+
let db = {
50+
let db = if let Some(db) = db {
51+
db
52+
} else {
53+
let (_, config) = read_config(None, Some(chain.clone()))?;
54+
db_root(&chain_path(&config))?
55+
};
56+
load_db(&db)?
57+
};
58+
let block: CachingBlockHeader = db.get_cbor_required(&block_to_look_back)?;
59+
let chain_config = Arc::new(ChainConfig::from_chain(&chain));
60+
let mut state_root = block.state_root;
61+
let epoch = block.epoch - 1;
62+
let migrations = crate::state_migration::get_migrations(&chain)
63+
.into_iter()
64+
.filter(|(h, _)| {
65+
let nv: NetworkVersion = (*h).into();
66+
network_version == nv
67+
})
68+
.collect_vec();
69+
anyhow::ensure!(
70+
!migrations.is_empty(),
71+
"No migration found for network version {network_version} on {chain}"
72+
);
73+
for (_, migrate) in migrations {
74+
println!("Migrating... state_root: {state_root}, epoch: {epoch}");
75+
let start = Instant::now();
76+
let new_state = migrate(&chain_config, &db, &state_root, epoch)?;
77+
println!(
78+
"Done. old_state: {state_root}, new_state: {new_state}, took: {}",
79+
humantime::format_duration(start.elapsed())
80+
);
81+
state_root = new_state;
82+
}
83+
Ok(())
84+
}
85+
}
86+
87+
pub(super) fn load_db(db_root: &Path) -> anyhow::Result<Arc<ManyCar<ParityDb>>> {
88+
let db_writer = open_db(db_root.into(), Default::default())?;
89+
let db = ManyCar::new(db_writer);
90+
let forest_car_db_dir = db_root.join(CAR_DB_DIR_NAME);
91+
load_all_forest_cars(&db, &forest_car_db_dir)?;
92+
Ok(Arc::new(db))
93+
}

0 commit comments

Comments
 (0)