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