Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

State migrations nv18 #2666

Merged
merged 18 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .config/forest.dic
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ daemonize
DB/S
DNS
Enum
Ethereum
LesnyRumcajs marked this conversation as resolved.
Show resolved Hide resolved
Filecoin
FVM
GC
Expand Down
33 changes: 30 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ multihash = { version = "0.16", default-features = false }
nonempty = "0.8.0"
num = "0.4.0"
num-bigint = "0.4"
num-derive = "0.3"
num-rational = "0.4"
num-traits = "0.2"
num_cpus = "1.14"
Expand Down Expand Up @@ -171,6 +172,7 @@ forest_rpc-api = { path = "./node/rpc-api" }
forest_rpc-client = { path = "./node/rpc-client" }
forest_shim = { path = "./utils/forest_shim" }
forest_state_manager = { path = "./blockchain/state_manager" }
forest_state_migration = { path = "./vm/state_migration" }
forest_statediff = { path = "./utils/statediff" }
forest_test_utils = { path = "./utils/test_utils" }
forest_utils = { path = "./utils/forest_utils" }
Expand Down
1 change: 1 addition & 0 deletions blockchain/state_manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ forest_message = { workspace = true, features = ["blst"] }
forest_metrics.workspace = true
forest_networks.workspace = true
forest_shim.workspace = true
forest_state_migration.workspace = true
forest_utils.workspace = true
futures.workspace = true
fvm.workspace = true
Expand Down
40 changes: 36 additions & 4 deletions blockchain/state_manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod vm_circ_supply;
use std::{num::NonZeroUsize, sync::Arc};

use ahash::{HashMap, HashMapExt};
use anyhow::bail;
use chain_rand::ChainRand;
use cid::Cid;
use fil_actor_interface::*;
Expand Down Expand Up @@ -45,7 +46,10 @@ use num_traits::identities::Zero;
use once_cell::unsync::Lazy;
use parking_lot::Mutex as SyncMutex;
use serde::{Deserialize, Serialize};
use tokio::sync::{broadcast::error::RecvError, Mutex as TokioMutex, RwLock};
use tokio::{
sync::{broadcast::error::RecvError, Mutex as TokioMutex, RwLock},
time,
};
use tracing::{debug, error, info, instrument, trace, warn};
use vm_circ_supply::GenesisInfo;

Expand Down Expand Up @@ -379,7 +383,6 @@ where

let db = self.blockstore().clone();

let turbo_height = self.chain_config.epoch(Height::Turbo);
let create_vm = |state_root, epoch, timestamp| {
VM::new(
state_root,
Expand Down Expand Up @@ -418,8 +421,8 @@ where
parent_state = vm.flush()?;
}

if epoch_i == turbo_height {
todo!("cannot migrate state when using FVM - see https://github.com/ChainSafe/forest/issues/1454 for updates");
if let Some(new_state) = self.handle_state_migrations(&parent_state, epoch_i)? {
parent_state = new_state;
}
}

Expand All @@ -437,6 +440,35 @@ where
Ok((state_root, receipt_root))
}

/// Handles state migrations. Returns the new state root if a migration was
/// run.
fn handle_state_migrations(
&self,
parent_state: &Cid,
epoch: ChainEpoch,
) -> anyhow::Result<Option<Cid>> {
match epoch {
x if x == self.chain_config.epoch(Height::Hygge) => {
info!("Running Hygge migration at epoch {epoch}");
let start_time = time::Instant::now();
let new_state = forest_state_migration::run_nv18_migration(
&self.chain_config,
self.blockstore(),
parent_state,
epoch,
)?;
let elapsed = start_time.elapsed().as_secs_f32();
LesnyRumcajs marked this conversation as resolved.
Show resolved Hide resolved
if new_state != *parent_state {
info!("state migration successful, took: {elapsed}s");
} else {
bail!("State post migration must not match. Previous state: {parent_state}, new state: {new_state}. Took {elapsed}s");
}
Ok(Some(new_state))
}
_ => Ok(None),
}
}

/// Returns the pair of (parent state root, message receipt root). This will
/// either be cached or will be calculated and fill the cache. Tipset
/// state for a given tipset is guaranteed not to be computed twice.
Expand Down
1 change: 1 addition & 0 deletions forest/daemon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ shared_memory = "0.12"
tempfile.workspace = true
time.workspace = true
tokio = { workspace = true, features = ["sync", "macros", "rt", "signal"] }
tokio-util = { workspace = true, features = ["compat"] }

[dev-dependencies]
assert_cmd.workspace = true
Expand Down
76 changes: 76 additions & 0 deletions forest/daemon/src/bundle/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2019-2023 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use forest_cli_shared::cli::Config;
use forest_genesis::forest_load_car;
use forest_networks::Height;
use forest_shim::clock::ChainEpoch;
use forest_utils::net::FetchProgress;
use fvm_ipld_blockstore::Blockstore;
use log::info;
use tokio::{
fs::File,
io::{BufReader, BufWriter},
};
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};

pub async fn load_bundles<DB>(epoch: ChainEpoch, config: &Config, db: DB) -> anyhow::Result<()>
where
DB: Blockstore + Send + Sync + Clone + 'static,
{
// collect bundles to load into the database.
let mut bundles = Vec::new();
if epoch < config.chain.epoch(Height::Hygge) {
bundles.push(get_actors_bundle(config, Height::Hygge).await?);
}

for bundle in bundles {
let result = forest_load_car(db.clone(), bundle.compat()).await?;
assert_eq!(
result.len(),
1,
"expected one root when loading actors bundle"
);
info!("Loaded actors bundle with CID: {}", result[0]);
}
Ok(())
}

/// Downloads the actors bundle (if not already downloaded) and returns a reader
/// to it.
// TODO Get it from IPFS instead of GitHub.
pub async fn get_actors_bundle(config: &Config, height: Height) -> anyhow::Result<BufReader<File>> {
let bundle_info = config.chain.height_infos[height as usize]
.bundle
.as_ref()
.ok_or_else(|| anyhow::anyhow!("no bundle for epoch {}", config.chain.epoch(height)))?;

// This is the path where the actors bundle will be stored.
let bundle_path_dir = config
.client
.data_dir
.join("bundle")
LesnyRumcajs marked this conversation as resolved.
Show resolved Hide resolved
.join(&config.chain.name);

tokio::fs::create_dir_all(&bundle_path_dir).await?;
let bundle_path = bundle_path_dir.join(format!("bundle_{height}.car"));

// If the bundle already exists, return a reader to it.
if bundle_path.exists() {
let file = tokio::fs::File::open(bundle_path).await?;
return Ok(BufReader::new(file));
}

// Otherwise, download it.
info!("Downloading actors bundle...");
let reader = FetchProgress::fetch_from_url(bundle_info.url.clone())
.await?
.inner;

let file = File::create(&bundle_path).await?;
let mut writer = BufWriter::new(file);
tokio::io::copy(&mut reader.compat(), &mut writer).await?;

let file = tokio::fs::File::open(bundle_path).await?;
Ok(BufReader::new(file))
}
3 changes: 3 additions & 0 deletions forest/daemon/src/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use forest_cli_shared::{
to_size_string, CliOpts, Client, Config, SnapshotServer, FOREST_VERSION_STRING,
},
};
use forest_daemon::bundle::load_bundles;
use forest_db::{
db_engine::{db_root, open_proxy_db},
rolling::{DbGarbageCollector, RollingDB},
Expand Down Expand Up @@ -255,6 +256,8 @@ pub(super) async fn start(opts: CliOpts, config: Config) -> anyhow::Result<Rolli
false
};

load_bundles(epoch, &config, db.clone()).await?;
LesnyRumcajs marked this conversation as resolved.
Show resolved Hide resolved

let peer_manager = Arc::new(PeerManager::default());
services.spawn(peer_manager.clone().peer_operation_event_loop_task());
let genesis_cid = *genesis_header.cid();
Expand Down
1 change: 1 addition & 0 deletions forest/daemon/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Copyright 2019-2023 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT
pub mod bundle;
pub mod cli;
7 changes: 4 additions & 3 deletions networks/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ edition = "2021"

[dependencies]
anyhow.workspace = true
cid.workspace = true
fil_actors_runtime_v10.workspace = true
forest_beacon.workspace = true
forest_shim.workspace = true
fvm_shared = { workspace = true, default-features = false }
lazy_static.workspace = true
serde = { workspace = true, features = ["derive"] }

[dev-dependencies]
toml = "0.7"
strum_macros = "0.24"
url.workspace = true
Loading