Skip to content

Commit 0173743

Browse files
authored
feat(tool): add forest-tool shed migrate state command (#5461)
1 parent 4ce9752 commit 0173743

File tree

8 files changed

+138
-15
lines changed

8 files changed

+138
-15
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131

3232
### Added
3333

34+
- [#5461](https://github.com/ChainSafe/forest/pull/5461) Add `forest-tool shed migrate-state` command.
35+
3436
### Changed
3537

3638
- [#5452](https://github.com/ChainSafe/forest/pull/5452) Speed up void database migration.

docs/docs/developers/guides/network_upgrades.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ This provides the base for the state migrations and network-aware node changes.
4646
State migrations are described in detail in the relevant FIPs, including the steps required to perform them. Note that naive state migrations might take a significant amount of time and resources. It is up to the implementation team to decide whether to optimize them.
4747

4848
:::note
49-
Testing the state migration on a relevant network is crucial before the upgrade epoch. This is done by changing the upgrade epoch in both Lotus and Forest and ensuring both migrations produce the same state root. This is done locally, but it might be facilitated in the future.
49+
Testing the state migration on a relevant network is crucial before the upgrade epoch. This could be done by either changing the upgrade epoch in both Lotus and Forest and ensuring both migrations produce the same state root, or comparing the output of `forest-tool shed migrate-state` and `lotus-shed migrate-state` commands.
5050

5151
This also allows for assessing the duration of the state migration and determining whether it is feasible to perform it on the mainnet.
5252
:::

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)