1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// Copyright 2019-2024 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use std::sync::{
    atomic::{self, AtomicBool},
    Arc,
};

use crate::networks::{ChainConfig, Height, NetworkChain};
use crate::shim::clock::ChainEpoch;
use crate::shim::state_tree::StateRoot;
use cid::Cid;
use fvm_ipld_blockstore::Blockstore;
use fvm_ipld_encoding::CborStore;

pub(in crate::state_migration) mod common;
mod nv17;
mod nv18;
mod nv19;
mod nv21;
mod nv21fix;
mod nv21fix2;
mod nv22;
mod nv22fix;
mod nv23;
mod type_migrations;

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

/// Run state migrations
pub fn run_state_migrations<DB>(
    epoch: ChainEpoch,
    chain_config: &ChainConfig,
    db: &Arc<DB>,
    parent_state: &Cid,
) -> anyhow::Result<Option<Cid>>
where
    DB: Blockstore + Send + Sync,
{
    let mappings: Vec<(_, RunMigration<DB>)> = match chain_config.network {
        NetworkChain::Mainnet => {
            vec![
                (Height::Shark, nv17::run_migration::<DB>),
                (Height::Hygge, nv18::run_migration::<DB>),
                (Height::Lightning, nv19::run_migration::<DB>),
                (Height::Watermelon, nv21::run_migration::<DB>),
                (Height::Dragon, nv22::run_migration::<DB>),
                (Height::Waffle, nv23::run_migration::<DB>),
            ]
        }
        NetworkChain::Calibnet => {
            vec![
                (Height::Shark, nv17::run_migration::<DB>),
                (Height::Hygge, nv18::run_migration::<DB>),
                (Height::Lightning, nv19::run_migration::<DB>),
                (Height::Watermelon, nv21::run_migration::<DB>),
                (Height::WatermelonFix, nv21fix::run_migration::<DB>),
                (Height::WatermelonFix2, nv21fix2::run_migration::<DB>),
                (Height::Dragon, nv22::run_migration::<DB>),
                (Height::DragonFix, nv22fix::run_migration::<DB>),
                (Height::Waffle, nv23::run_migration::<DB>),
            ]
        }
        NetworkChain::Butterflynet => {
            vec![
                (Height::Dragon, nv22::run_migration::<DB>),
                (Height::Waffle, nv23::run_migration::<DB>),
            ]
        }
        NetworkChain::Devnet(_) => {
            vec![
                (Height::Shark, nv17::run_migration::<DB>),
                (Height::Hygge, nv18::run_migration::<DB>),
                (Height::Lightning, nv19::run_migration::<DB>),
                (Height::Watermelon, nv21::run_migration::<DB>),
                (Height::Dragon, nv22::run_migration::<DB>),
                (Height::Waffle, nv23::run_migration::<DB>),
            ]
        }
    };

    // Make sure bundle is defined.
    static BUNDLE_CHECKED: AtomicBool = AtomicBool::new(false);
    if !BUNDLE_CHECKED.load(atomic::Ordering::Relaxed) {
        BUNDLE_CHECKED.store(true, atomic::Ordering::Relaxed);
        for (info_height, info) in chain_config.height_infos.iter() {
            for (height, _) in &mappings {
                if height == info_height {
                    assert!(
                        info.bundle.is_some(),
                        "Actor bundle info for height {height} needs to be defined in `src/networks/mod.rs` to run state migration"
                    );
                    break;
                }
            }
        }
    }

    for (height, migrate) in mappings {
        if epoch == chain_config.epoch(height) {
            tracing::info!("Running {height} migration at epoch {epoch}");
            let start_time = std::time::Instant::now();
            let new_state = migrate(chain_config, db, parent_state, epoch)?;
            let elapsed = start_time.elapsed().as_secs_f32();
            // `new_state_actors` is the Go state migration output, log for comparision
            let new_state_actors = db
                .get_cbor::<StateRoot>(&new_state)
                .ok()
                .flatten()
                .map(|sr| format!("{}", sr.actors))
                .unwrap_or_default();
            if new_state != *parent_state {
                crate::utils::misc::reveal_upgrade_logo(height.into());
                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.");
            } else {
                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.");
            }

            return Ok(Some(new_state));
        }
    }

    Ok(None)
}

#[cfg(test)]
mod tests;