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