use std::sync::atomic::{self, AtomicBool};
use crate::db::BlockstoreWithWriteBuffer;
use crate::networks::{ChainConfig, Height, NetworkChain};
use crate::prelude::*;
use crate::shim::clock::ChainEpoch;
use crate::shim::state_tree::StateRoot;
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 nv24;
mod nv25;
mod nv26fix;
mod nv27;
mod nv28;
mod type_migrations;
type RunMigration<DB> = fn(&ChainConfig, &DB, &Cid, ChainEpoch) -> anyhow::Result<Cid>;
pub fn get_migrations<DB>(chain: &NetworkChain) -> Vec<(Height, Option<RunMigration<DB>>)>
where
DB: Blockstore + ShallowClone + Send + Sync,
{
match chain {
NetworkChain::Mainnet => {
vec![
(Height::Assembly, None),
(Height::Trust, None),
(Height::Turbo, None),
(Height::Hyperdrive, None),
(Height::Chocolate, None),
(Height::OhSnap, None),
(Height::Skyr, None),
(Height::Shark, Some(nv17::run_migration::<DB>)),
(Height::Hygge, Some(nv18::run_migration::<DB>)),
(Height::Lightning, Some(nv19::run_migration::<DB>)),
(Height::Watermelon, Some(nv21::run_migration::<DB>)),
(Height::Dragon, Some(nv22::run_migration::<DB>)),
(Height::Waffle, Some(nv23::run_migration::<DB>)),
(Height::TukTuk, Some(nv24::run_migration::<DB>)),
(Height::Teep, Some(nv25::run_migration::<DB>)),
(Height::GoldenWeek, Some(nv27::run_migration::<DB>)),
(Height::FireHorse, Some(nv28::run_migration::<DB>)),
]
}
NetworkChain::Calibnet => {
vec![
(Height::Assembly, None),
(Height::Trust, None),
(Height::Turbo, None),
(Height::Hyperdrive, None),
(Height::Chocolate, None),
(Height::OhSnap, None),
(Height::Skyr, None),
(Height::Shark, Some(nv17::run_migration::<DB>)),
(Height::Hygge, Some(nv18::run_migration::<DB>)),
(Height::Lightning, Some(nv19::run_migration::<DB>)),
(Height::Watermelon, Some(nv21::run_migration::<DB>)),
(Height::WatermelonFix, Some(nv21fix::run_migration::<DB>)),
(Height::WatermelonFix2, Some(nv21fix2::run_migration::<DB>)),
(Height::Dragon, Some(nv22::run_migration::<DB>)),
(Height::DragonFix, Some(nv22fix::run_migration::<DB>)),
(Height::Waffle, Some(nv23::run_migration::<DB>)),
(Height::TukTuk, Some(nv24::run_migration::<DB>)),
(Height::Teep, Some(nv25::run_migration::<DB>)),
(Height::TockFix, Some(nv26fix::run_migration::<DB>)),
(Height::GoldenWeek, Some(nv27::run_migration::<DB>)),
(Height::FireHorse, Some(nv28::run_migration::<DB>)),
]
}
NetworkChain::Butterflynet => {
vec![(Height::FireHorse, Some(nv28::run_migration::<DB>))]
}
NetworkChain::Devnet(_) => {
vec![
(Height::Shark, Some(nv17::run_migration::<DB>)),
(Height::Hygge, Some(nv18::run_migration::<DB>)),
(Height::Lightning, Some(nv19::run_migration::<DB>)),
(Height::Watermelon, Some(nv21::run_migration::<DB>)),
(Height::Dragon, Some(nv22::run_migration::<DB>)),
(Height::Waffle, Some(nv23::run_migration::<DB>)),
(Height::TukTuk, Some(nv24::run_migration::<DB>)),
(Height::Teep, Some(nv25::run_migration::<DB>)),
(Height::GoldenWeek, Some(nv27::run_migration::<DB>)),
(Height::FireHorse, Some(nv28::run_migration::<DB>)),
]
}
}
}
pub fn run_state_migrations<DB>(
epoch: ChainEpoch,
chain_config: &ChainConfig,
db: &DB,
parent_state: &Cid,
) -> anyhow::Result<Option<Cid>>
where
DB: Blockstore + ShallowClone + Send + Sync,
{
let db_write_buffer = match std::env::var("FOREST_STATE_MIGRATION_DB_WRITE_BUFFER") {
Ok(v) => v.parse().ok(),
_ => None,
}
.unwrap_or(10000);
let mappings = get_migrations(&chain_config.network);
static BUNDLE_CHECKED: AtomicBool = AtomicBool::new(false);
if !BUNDLE_CHECKED.load(atomic::Ordering::Relaxed) {
BUNDLE_CHECKED.store(true, atomic::Ordering::Relaxed);
for height in mappings
.iter()
.filter_map(|(height, migrate)| migrate.as_ref().map(|_| height))
{
let Some(info) = chain_config.height_infos.get(height) else {
anyhow::bail!("Missing `HeightInfo` for migration height {height}");
};
anyhow::ensure!(
info.bundle.is_some(),
"Actor bundle info for height {height} needs to be defined in `src/networks/mod.rs` to run state migration"
);
}
}
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 db = Arc::new(BlockstoreWithWriteBuffer::new_with_capacity(
db.shallow_clone(),
db_write_buffer,
));
let migrate = migrate.ok_or_else(|| {
anyhow::anyhow!("Unimplemented state migration at height {height}")
})?;
let new_state = migrate(chain_config, &db, parent_state, epoch)?;
let elapsed = start_time.elapsed();
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}.",
elapsed = humantime::format_duration(elapsed)
);
} 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}.",
elapsed = humantime::format_duration(elapsed)
);
}
return Ok(Some(new_state));
}
}
Ok(None)
}
#[cfg(test)]
mod tests;