Skip to main content

forest/state_migration/
mod.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use std::sync::{
5    Arc,
6    atomic::{self, AtomicBool},
7};
8
9use crate::db::BlockstoreWithWriteBuffer;
10use crate::networks::{ChainConfig, Height, NetworkChain};
11use crate::shim::clock::ChainEpoch;
12use crate::shim::state_tree::StateRoot;
13use cid::Cid;
14use fvm_ipld_blockstore::Blockstore;
15use fvm_ipld_encoding::CborStore;
16
17pub(in crate::state_migration) mod common;
18mod nv17;
19mod nv18;
20mod nv19;
21mod nv21;
22mod nv21fix;
23mod nv21fix2;
24mod nv22;
25mod nv22fix;
26mod nv23;
27mod nv24;
28mod nv25;
29mod nv26fix;
30mod nv27;
31mod type_migrations;
32
33type RunMigration<DB> = fn(&ChainConfig, &Arc<DB>, &Cid, ChainEpoch) -> anyhow::Result<Cid>;
34
35pub fn get_migrations<DB>(chain: &NetworkChain) -> Vec<(Height, RunMigration<DB>)>
36where
37    DB: Blockstore + Send + Sync,
38{
39    match chain {
40        NetworkChain::Mainnet => {
41            vec![
42                (Height::Shark, nv17::run_migration::<DB>),
43                (Height::Hygge, nv18::run_migration::<DB>),
44                (Height::Lightning, nv19::run_migration::<DB>),
45                (Height::Watermelon, nv21::run_migration::<DB>),
46                (Height::Dragon, nv22::run_migration::<DB>),
47                (Height::Waffle, nv23::run_migration::<DB>),
48                (Height::TukTuk, nv24::run_migration::<DB>),
49                (Height::Teep, nv25::run_migration::<DB>),
50                (Height::GoldenWeek, nv27::run_migration::<DB>),
51            ]
52        }
53        NetworkChain::Calibnet => {
54            vec![
55                (Height::Shark, nv17::run_migration::<DB>),
56                (Height::Hygge, nv18::run_migration::<DB>),
57                (Height::Lightning, nv19::run_migration::<DB>),
58                (Height::Watermelon, nv21::run_migration::<DB>),
59                (Height::WatermelonFix, nv21fix::run_migration::<DB>),
60                (Height::WatermelonFix2, nv21fix2::run_migration::<DB>),
61                (Height::Dragon, nv22::run_migration::<DB>),
62                (Height::DragonFix, nv22fix::run_migration::<DB>),
63                (Height::Waffle, nv23::run_migration::<DB>),
64                (Height::TukTuk, nv24::run_migration::<DB>),
65                (Height::Teep, nv25::run_migration::<DB>),
66                (Height::TockFix, nv26fix::run_migration::<DB>),
67                (Height::GoldenWeek, nv27::run_migration::<DB>),
68            ]
69        }
70        NetworkChain::Butterflynet => {
71            vec![
72                (Height::Teep, nv25::run_migration::<DB>),
73                (Height::GoldenWeek, nv27::run_migration::<DB>),
74            ]
75        }
76        NetworkChain::Devnet(_) => {
77            vec![
78                (Height::Shark, nv17::run_migration::<DB>),
79                (Height::Hygge, nv18::run_migration::<DB>),
80                (Height::Lightning, nv19::run_migration::<DB>),
81                (Height::Watermelon, nv21::run_migration::<DB>),
82                (Height::Dragon, nv22::run_migration::<DB>),
83                (Height::Waffle, nv23::run_migration::<DB>),
84                (Height::TukTuk, nv24::run_migration::<DB>),
85                (Height::Teep, nv25::run_migration::<DB>),
86                (Height::GoldenWeek, nv27::run_migration::<DB>),
87            ]
88        }
89    }
90}
91
92/// Run state migrations
93pub fn run_state_migrations<DB>(
94    epoch: ChainEpoch,
95    chain_config: &ChainConfig,
96    db: &Arc<DB>,
97    parent_state: &Cid,
98) -> anyhow::Result<Option<Cid>>
99where
100    DB: Blockstore + Send + Sync,
101{
102    // ~10MB RAM per 10k buffer
103    let db_write_buffer = match std::env::var("FOREST_STATE_MIGRATION_DB_WRITE_BUFFER") {
104        Ok(v) => v.parse().ok(),
105        _ => None,
106    }
107    .unwrap_or(10000);
108    let mappings = get_migrations(&chain_config.network);
109
110    // Make sure bundle is defined.
111    static BUNDLE_CHECKED: AtomicBool = AtomicBool::new(false);
112    if !BUNDLE_CHECKED.load(atomic::Ordering::Relaxed) {
113        BUNDLE_CHECKED.store(true, atomic::Ordering::Relaxed);
114        for (info_height, info) in chain_config.height_infos.iter() {
115            for (height, _) in &mappings {
116                if height == info_height {
117                    assert!(
118                        info.bundle.is_some(),
119                        "Actor bundle info for height {height} needs to be defined in `src/networks/mod.rs` to run state migration"
120                    );
121                    break;
122                }
123            }
124        }
125    }
126
127    for (height, migrate) in mappings {
128        if epoch == chain_config.epoch(height) {
129            tracing::info!("Running {height} migration at epoch {epoch}");
130            let start_time = std::time::Instant::now();
131            let db = Arc::new(BlockstoreWithWriteBuffer::new_with_capacity(
132                db.clone(),
133                db_write_buffer,
134            ));
135            let new_state = migrate(chain_config, &db, parent_state, epoch)?;
136            let elapsed = start_time.elapsed();
137            // `new_state_actors` is the Go state migration output, log for comparision
138            let new_state_actors = db
139                .get_cbor::<StateRoot>(&new_state)
140                .ok()
141                .flatten()
142                .map(|sr| format!("{}", sr.actors))
143                .unwrap_or_default();
144            if new_state != *parent_state {
145                crate::utils::misc::reveal_upgrade_logo(height.into());
146                tracing::info!(
147                    "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}.",
148                    elapsed = humantime::format_duration(elapsed)
149                );
150            } else {
151                anyhow::bail!(
152                    "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}.",
153                    elapsed = humantime::format_duration(elapsed)
154                );
155            }
156
157            return Ok(Some(new_state));
158        }
159    }
160
161    Ok(None)
162}
163
164#[cfg(test)]
165mod tests;