1use 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
103pub 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 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 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 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;