diff --git a/Cargo.lock b/Cargo.lock index 6c6cf1391..f50cd9396 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3075,6 +3075,7 @@ dependencies = [ "fendermint_vm_core", "fendermint_vm_encoding", "fendermint_vm_genesis", + "fendermint_vm_interpreter", "fendermint_vm_message", "futures", "fvm_shared", @@ -3299,6 +3300,7 @@ dependencies = [ "arbitrary", "async-stm", "async-trait", + "base64 0.21.7", "cid", "ethers", "fendermint_actor_chainmetadata", @@ -3340,6 +3342,7 @@ dependencies = [ "serde", "serde_json", "serde_with 2.3.3", + "snap", "strum", "tempfile", "tendermint 0.31.1", @@ -8775,6 +8778,12 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + [[package]] name = "snow" version = "0.9.6" diff --git a/Cargo.toml b/Cargo.toml index a33c320cc..ccc3db09b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -144,6 +144,7 @@ serde_yaml = { version = "0.9" } serde_tuple = "0.5" serde_with = "2.3" serial_test = "3.0" +snap = "1.1.0" strum = { version = "0.26.1", features = ["derive"] } tempfile = "3.7" thiserror = "1" diff --git a/fendermint/app/options/src/genesis.rs b/fendermint/app/options/src/genesis.rs index 3da1794bc..740384758 100644 --- a/fendermint/app/options/src/genesis.rs +++ b/fendermint/app/options/src/genesis.rs @@ -133,6 +133,9 @@ pub struct GenesisAddValidatorArgs { #[derive(Args, Debug)] pub struct GenesisIntoTendermintArgs { + /// The initial app bytes path for cometbft + #[arg(long, short)] + pub app_state: Option, /// Output file name for the Tendermint genesis JSON file. #[arg(long, short)] pub out: PathBuf, @@ -147,6 +150,28 @@ pub enum GenesisIpcCommands { Gateway(GenesisIpcGatewayArgs), /// Fetch the genesis parameters of a subnet from the parent. FromParent(Box), + /// Seal the genesis state from the genesis parameter file + SealGenesis(SealGenesisArgs), +} + +#[derive(Args, Debug, Clone)] +pub struct SealGenesisArgs { + /// The built in actors bundle path + #[arg(long, short)] + pub builtin_actors_path: PathBuf, + + /// The custom actors bundle path + #[arg(long, short)] + pub custom_actors_path: PathBuf, + + /// The solidity artifacts output path. If you are using ipc-monorepo, it should be the `out` folder + /// of `make build` + #[arg(long, short)] + pub artifacts_path: Option, + + /// The sealed genesis state output path, i.e. finalized genesis state CAR file dump path + #[arg(long, short)] + pub output_path: PathBuf, } #[derive(Args, Debug, Clone)] diff --git a/fendermint/app/settings/src/lib.rs b/fendermint/app/settings/src/lib.rs index 695014b20..fa33f8a61 100644 --- a/fendermint/app/settings/src/lib.rs +++ b/fendermint/app/settings/src/lib.rs @@ -263,10 +263,6 @@ pub struct Settings { snapshots_dir: PathBuf, /// Solidity contracts. contracts_dir: PathBuf, - /// Builtin-actors CAR file. - builtin_actors_bundle: PathBuf, - /// Custom actors CAR file. - custom_actors_bundle: PathBuf, /// Where to reach CometBFT for queries or broadcasting transactions. tendermint_rpc_url: Url, @@ -291,13 +287,7 @@ pub struct Settings { } impl Settings { - home_relative!( - data_dir, - snapshots_dir, - contracts_dir, - builtin_actors_bundle, - custom_actors_bundle - ); + home_relative!(data_dir, snapshots_dir, contracts_dir); /// Load the default configuration from a directory, /// then potential overrides specific to the run mode, diff --git a/fendermint/app/src/app.rs b/fendermint/app/src/app.rs index e87677483..c44b9addd 100644 --- a/fendermint/app/src/app.rs +++ b/fendermint/app/src/app.rs @@ -1,7 +1,6 @@ // Copyright 2022-2024 Protocol Labs // SPDX-License-Identifier: Apache-2.0, MIT use std::future::Future; -use std::path::PathBuf; use std::sync::Arc; use anyhow::{anyhow, Context, Result}; @@ -19,14 +18,15 @@ use fendermint_vm_interpreter::bytes::{ }; use fendermint_vm_interpreter::chain::{ChainEnv, ChainMessageApplyRet, IllegalMessage}; use fendermint_vm_interpreter::fvm::state::{ - empty_state_tree, CheckStateRef, FvmExecState, FvmGenesisState, FvmQueryState, FvmStateParams, + empty_state_tree, CheckStateRef, FvmExecState, FvmQueryState, FvmStateParams, FvmUpdatableParams, }; use fendermint_vm_interpreter::fvm::store::ReadOnlyBlockstore; -use fendermint_vm_interpreter::fvm::{FvmApplyRet, FvmGenesisOutput, PowerUpdates}; +use fendermint_vm_interpreter::fvm::{FvmApplyRet, PowerUpdates}; +use fendermint_vm_interpreter::genesis::{read_genesis_car, GenesisAppState}; use fendermint_vm_interpreter::signed::InvalidSignature; use fendermint_vm_interpreter::{ - CheckInterpreter, ExecInterpreter, GenesisInterpreter, ProposalInterpreter, QueryInterpreter, + CheckInterpreter, ExecInterpreter, ProposalInterpreter, QueryInterpreter, }; use fendermint_vm_message::query::FvmQueryHeight; use fendermint_vm_snapshot::{SnapshotClient, SnapshotError}; @@ -109,12 +109,6 @@ pub struct AppConfig { pub state_hist_namespace: S::Namespace, /// Size of state history to keep; 0 means unlimited. pub state_hist_size: u64, - /// Path to the Wasm bundle. - /// - /// Only loaded once during genesis; later comes from the [`StateTree`]. - pub builtin_actors_bundle: PathBuf, - /// Path to the custom actor WASM bundle. - pub custom_actors_bundle: PathBuf, /// Block height where we should gracefully stop the node pub halt_height: i64, } @@ -136,12 +130,6 @@ where state_store: Arc, /// Wasm engine cache. multi_engine: Arc, - /// Path to the Wasm bundle. - /// - /// Only loaded once during genesis; later comes from the [`StateTree`]. - builtin_actors_bundle: PathBuf, - /// Path to the custom actor WASM bundle. - custom_actors_bundle: PathBuf, /// Block height where we should gracefully stop the node halt_height: i64, /// Namespace to store app state. @@ -195,8 +183,6 @@ where db: Arc::new(db), state_store: Arc::new(state_store), multi_engine: Arc::new(MultiEngine::new(1)), - builtin_actors_bundle: config.builtin_actors_bundle, - custom_actors_bundle: config.custom_actors_bundle, halt_height: config.halt_height, namespace: config.app_namespace, state_hist: KVCollection::new(config.state_hist_namespace), @@ -390,6 +376,14 @@ where // It's really the empty state tree that would be the best indicator. !(height == 0 && params.timestamp.0 == 0 && params.network_version == NetworkVersion::V0) } + + fn parse_genesis_app_bytes(bytes: &[u8]) -> Result> { + // cometbft serves data in json format, convert from json string + match serde_json::from_slice(bytes)? { + serde_json::Value::String(s) => Ok(GenesisAppState::decode_and_decompress(&s)?), + _ => Err(anyhow!("invalid app state json")), + } + } } // NOTE: The `Application` interface doesn't allow failures at the moment. The protobuf @@ -408,11 +402,6 @@ where S::Namespace: Sync + Send, DB: KVWritable + KVReadable + Clone + Send + Sync + 'static, SS: Blockstore + Clone + Send + Sync + 'static, - I: GenesisInterpreter< - State = FvmGenesisState, - Genesis = Vec, - Output = FvmGenesisOutput, - >, I: ProposalInterpreter>, I: ExecInterpreter< State = (ChainEnv, FvmExecState), @@ -450,45 +439,18 @@ where /// Called once upon genesis. async fn init_chain(&self, request: request::InitChain) -> AbciResult { - let bundle = &self.builtin_actors_bundle; - let bundle = std::fs::read(bundle) - .map_err(|e| anyhow!("failed to load builtin bundle CAR from {bundle:?}: {e}"))?; - - let custom_actors_bundle = &self.custom_actors_bundle; - let custom_actors_bundle = std::fs::read(custom_actors_bundle).map_err(|e| { - anyhow!("failed to load custom actor bundle CAR from {custom_actors_bundle:?}: {e}") - })?; - - let state = FvmGenesisState::new( - self.state_store_clone(), - self.multi_engine.clone(), - &bundle, - &custom_actors_bundle, - ) - .await - .context("failed to create genesis state")?; - - tracing::info!( - manifest_root = format!("{}", state.manifest_data_cid), - "pre-genesis state created" - ); - - let genesis_bytes = request.app_state_bytes.to_vec(); + let genesis_bytes = Self::parse_genesis_app_bytes(&request.app_state_bytes)?; let genesis_hash = fendermint_vm_message::cid(&genesis_bytes).context("failed to compute genesis CID")?; // Make it easy to spot any discrepancies between nodes. tracing::info!(genesis_hash = genesis_hash.to_string(), "genesis"); - let (state, out) = self - .interpreter - .init(state, genesis_bytes) - .await - .context("failed to init from genesis")?; - - let state_root = state.commit().context("failed to commit genesis state")?; + let (validators, state_params) = read_genesis_car(genesis_bytes, &self.state_store).await?; let validators = - to_validator_updates(out.validators).context("failed to convert validators")?; + to_validator_updates(validators).context("failed to convert validators")?; + + tracing::info!(state_params = serde_json::to_string(&state_params)?); // Let's pretend that the genesis state is that of a fictive block at height 0. // The record will be stored under height 1, and the record after the application @@ -505,16 +467,7 @@ where let app_state = AppState { block_height: height, oldest_state_height: height, - state_params: FvmStateParams { - state_root, - timestamp: out.timestamp, - network_version: out.network_version, - base_fee: out.base_fee, - circ_supply: out.circ_supply, - chain_id: out.chain_id.into(), - power_scale: out.power_scale, - app_version: 0, - }, + state_params, }; let response = response::InitChain { diff --git a/fendermint/app/src/cmd/genesis.rs b/fendermint/app/src/cmd/genesis.rs index c7d1c6a21..134c9ad4d 100644 --- a/fendermint/app/src/cmd/genesis.rs +++ b/fendermint/app/src/cmd/genesis.rs @@ -14,6 +14,7 @@ use fendermint_vm_genesis::{ ipc, Account, Actor, ActorMeta, Collateral, Genesis, Multisig, PermissionMode, SignerAddr, Validator, ValidatorKey, }; +use fendermint_vm_interpreter::genesis::{GenesisAppState, GenesisBuilder}; use crate::cmd; use crate::options::genesis::*; @@ -94,6 +95,8 @@ cmd! { set_ipc_gateway(&genesis_file, args), GenesisIpcCommands::FromParent(args) => new_genesis_from_parent(&genesis_file, args).await, + GenesisIpcCommands::SealGenesis(args) => + seal_genesis(&genesis_file, args).await, } } } @@ -213,7 +216,12 @@ fn set_eam_permissions( fn into_tendermint(genesis_file: &PathBuf, args: &GenesisIntoTendermintArgs) -> anyhow::Result<()> { let genesis = read_genesis(genesis_file)?; - let genesis_json = serde_json::to_value(&genesis)?; + let app_state: Option = match args.app_state { + Some(ref path) if path.exists() => { + Some(GenesisAppState::v1(std::fs::read(path)?).compress_and_encode()?) + } + _ => None, + }; let chain_id: u64 = chainid::from_str_hashed(&genesis.chain_name)?.into(); let chain_id = chain_id.to_string(); @@ -248,7 +256,8 @@ fn into_tendermint(genesis_file: &PathBuf, args: &GenesisIntoTendermintArgs) -> // Hopefully leaving this empty will skip validation, // otherwise we have to run the genesis in memory here and now. app_hash: tendermint::AppHash::default(), - app_state: genesis_json, + // cometbft serves data in json format, convert to string to be specific + app_state, }; let tmg_json = serde_json::to_string_pretty(&tmg)?; std::fs::write(&args.out, tmg_json)?; @@ -280,6 +289,22 @@ fn set_ipc_gateway(genesis_file: &PathBuf, args: &GenesisIpcGatewayArgs) -> anyh }) } +async fn seal_genesis(genesis_file: &PathBuf, args: &SealGenesisArgs) -> anyhow::Result<()> { + let genesis_params = read_genesis(genesis_file)?; + + let mut builder = GenesisBuilder::new( + args.builtin_actors_path.clone(), + args.custom_actors_path.clone(), + genesis_params, + ); + + if let Some(ref ipc_system_artifacts) = args.artifacts_path { + builder = builder.with_ipc_system_contracts(ipc_system_artifacts.clone()); + } + + builder.write_to(args.output_path.clone()).await +} + async fn new_genesis_from_parent( genesis_file: &PathBuf, args: &GenesisFromParentArgs, diff --git a/fendermint/app/src/cmd/run.rs b/fendermint/app/src/cmd/run.rs index 2dac3ef6a..dca099b1f 100644 --- a/fendermint/app/src/cmd/run.rs +++ b/fendermint/app/src/cmd/run.rs @@ -137,7 +137,6 @@ async fn run(settings: Settings) -> anyhow::Result<()> { let interpreter = FvmMessageInterpreter::::new( tendermint_client.clone(), validator_ctx, - settings.contracts_dir(), settings.fvm.gas_overestimation_rate, settings.fvm.gas_search_step, settings.fvm.exec_in_check, @@ -300,8 +299,6 @@ async fn run(settings: Settings) -> anyhow::Result<()> { app_namespace: ns.app, state_hist_namespace: ns.state_hist, state_hist_size: settings.db.state_hist_size, - builtin_actors_bundle: settings.builtin_actors_bundle(), - custom_actors_bundle: settings.custom_actors_bundle(), halt_height: settings.halt_height, }, db, diff --git a/fendermint/app/src/lib.rs b/fendermint/app/src/lib.rs index 0b529a9f2..f9a45b9a5 100644 --- a/fendermint/app/src/lib.rs +++ b/fendermint/app/src/lib.rs @@ -1,3 +1,5 @@ +extern crate core; + // Copyright 2022-2024 Protocol Labs // SPDX-License-Identifier: Apache-2.0, MIT mod app; diff --git a/fendermint/testing/contract-test/Cargo.toml b/fendermint/testing/contract-test/Cargo.toml index 0c186f8b6..109e5fae1 100644 --- a/fendermint/testing/contract-test/Cargo.toml +++ b/fendermint/testing/contract-test/Cargo.toml @@ -31,7 +31,7 @@ fendermint_vm_core = { path = "../../vm/core" } fendermint_vm_genesis = { path = "../../vm/genesis" } fendermint_vm_message = { path = "../../vm/message" } fendermint_vm_interpreter = { path = "../../vm/interpreter", features = [ - "bundle", + "bundle", "test-util" ] } [dev-dependencies] diff --git a/fendermint/testing/contract-test/src/lib.rs b/fendermint/testing/contract-test/src/lib.rs index a6290d665..65a4ce8ad 100644 --- a/fendermint/testing/contract-test/src/lib.rs +++ b/fendermint/testing/contract-test/src/lib.rs @@ -3,72 +3,52 @@ use anyhow::{anyhow, Context, Result}; use byteorder::{BigEndian, WriteBytesExt}; -use cid::Cid; use fendermint_vm_core::Timestamp; use fendermint_vm_interpreter::fvm::PowerUpdates; -use fvm_shared::{bigint::Zero, clock::ChainEpoch, econ::TokenAmount, version::NetworkVersion}; +use fvm_shared::clock::ChainEpoch; use std::{future::Future, sync::Arc}; use fendermint_vm_genesis::Genesis; +use fendermint_vm_interpreter::genesis::{create_test_genesis_state, GenesisOutput}; use fendermint_vm_interpreter::{ fvm::{ bundle::{bundle_path, contracts_path, custom_actors_bundle_path}, - state::{FvmExecState, FvmGenesisState, FvmStateParams, FvmUpdatableParams}, + state::{FvmExecState, FvmStateParams, FvmUpdatableParams}, store::memory::MemoryBlockstore, - upgrades::UpgradeScheduler, - FvmApplyRet, FvmGenesisOutput, FvmMessage, FvmMessageInterpreter, + FvmApplyRet, FvmMessage, }, - ExecInterpreter, GenesisInterpreter, + ExecInterpreter, }; use fvm::engine::MultiEngine; pub mod ipc; -pub async fn init_exec_state( - multi_engine: Arc, +pub async fn create_test_exec_state( genesis: Genesis, -) -> anyhow::Result<(FvmExecState, FvmGenesisOutput)> { +) -> Result<( + FvmExecState, + GenesisOutput, + MemoryBlockstore, +)> { let bundle_path = bundle_path(); - let bundle = std::fs::read(&bundle_path) - .with_context(|| format!("failed to read bundle: {}", bundle_path.to_string_lossy()))?; - let custom_actors_bundle_path = custom_actors_bundle_path(); - let custom_actors_bundle = std::fs::read(&custom_actors_bundle_path).with_context(|| { - format!( - "failed to read custom actors_bundle: {}", - custom_actors_bundle_path.to_string_lossy() - ) - })?; - - let store = MemoryBlockstore::new(); - - let state = FvmGenesisState::new(store, multi_engine, &bundle, &custom_actors_bundle) - .await - .context("failed to create state")?; - - let (client, _) = - tendermint_rpc::MockClient::new(tendermint_rpc::MockRequestMethodMatcher::default()); - - let interpreter = FvmMessageInterpreter::new( - client, - None, - contracts_path(), - 1.05, - 1.05, - false, - UpgradeScheduler::new(), - ); - - let (state, out) = interpreter - .init(state, genesis) - .await - .context("failed to create actors")?; - - let state = state - .into_exec_state() - .map_err(|_| anyhow!("should be in exec stage"))?; - - Ok((state, out)) + let maybe_contract_path = genesis.ipc.as_ref().map(|_| contracts_path()); + + let (state, out) = create_test_genesis_state( + bundle_path, + custom_actors_bundle_path, + genesis, + maybe_contract_path, + ) + .await?; + let store = state.store().clone(); + Ok(( + state + .into_exec_state() + .map_err(|_| anyhow!("cannot parse state"))?, + out, + store, + )) } pub struct Tester { @@ -81,11 +61,6 @@ pub struct Tester { impl Tester where - I: GenesisInterpreter< - State = FvmGenesisState, - Genesis = Genesis, - Output = FvmGenesisOutput, - >, I: ExecInterpreter< State = FvmExecState, Message = FvmMessage, @@ -94,61 +69,13 @@ where EndOutput = PowerUpdates, >, { - fn state_store_clone(&self) -> MemoryBlockstore { - self.state_store.as_ref().clone() - } + pub async fn new(interpreter: I, genesis: Genesis) -> anyhow::Result { + let (exec_state, out, store) = create_test_exec_state(genesis).await?; + let (state_root, _, _) = exec_state + .commit() + .context("failed to commit genesis state")?; - pub fn new(interpreter: I, state_store: MemoryBlockstore) -> Self { - Self { - interpreter: Arc::new(interpreter), - state_store: Arc::new(state_store), - multi_engine: Arc::new(MultiEngine::new(1)), - exec_state: Arc::new(tokio::sync::Mutex::new(None)), - state_params: FvmStateParams { - timestamp: Timestamp(0), - state_root: Cid::default(), - network_version: NetworkVersion::V21, - base_fee: TokenAmount::zero(), - circ_supply: TokenAmount::zero(), - chain_id: 0, - power_scale: 0, - app_version: 0, - }, - } - } - - pub async fn init(&mut self, genesis: Genesis) -> anyhow::Result<()> { - let bundle_path = bundle_path(); - let bundle = std::fs::read(&bundle_path) - .with_context(|| format!("failed to read bundle: {}", bundle_path.to_string_lossy()))?; - - let custom_actors_bundle_path = custom_actors_bundle_path(); - let custom_actors_bundle = - std::fs::read(&custom_actors_bundle_path).with_context(|| { - format!( - "failed to read custom actors_bundle: {}", - custom_actors_bundle_path.to_string_lossy() - ) - })?; - - let state = FvmGenesisState::new( - self.state_store_clone(), - self.multi_engine.clone(), - &bundle, - &custom_actors_bundle, - ) - .await - .context("failed to create genesis state")?; - - let (state, out) = self - .interpreter - .init(state, genesis) - .await - .context("failed to init from genesis")?; - - let state_root = state.commit().context("failed to commit genesis state")?; - - self.state_params = FvmStateParams { + let state_params = FvmStateParams { state_root, timestamp: out.timestamp, network_version: out.network_version, @@ -159,7 +86,13 @@ where app_version: 0, }; - Ok(()) + Ok(Self { + interpreter: Arc::new(interpreter), + state_store: Arc::new(store), + multi_engine: Arc::new(MultiEngine::new(1)), + exec_state: Arc::new(tokio::sync::Mutex::new(None)), + state_params, + }) } /// Take the execution state, update it, put it back, return the output. diff --git a/fendermint/testing/contract-test/tests/run_upgrades.rs b/fendermint/testing/contract-test/tests/run_upgrades.rs index 00b3f4a4d..532c2f71f 100644 --- a/fendermint/testing/contract-test/tests/run_upgrades.rs +++ b/fendermint/testing/contract-test/tests/run_upgrades.rs @@ -26,7 +26,7 @@ use fendermint_vm_core::Timestamp; use fendermint_vm_genesis::{Account, Actor, ActorMeta, Genesis, PermissionMode, SignerAddr}; use fendermint_vm_interpreter::fvm::store::memory::MemoryBlockstore; use fendermint_vm_interpreter::fvm::upgrades::{Upgrade, UpgradeScheduler}; -use fendermint_vm_interpreter::fvm::{bundle::contracts_path, FvmMessageInterpreter}; +use fendermint_vm_interpreter::fvm::FvmMessageInterpreter; // returns a seeded secret key which is guaranteed to be the same every time fn my_secret_key() -> SecretKey { @@ -194,17 +194,8 @@ async fn test_applying_upgrades() { ) .unwrap(); - let interpreter: FvmMessageInterpreter = FvmMessageInterpreter::new( - NeverCallClient, - None, - contracts_path(), - 1.05, - 1.05, - false, - upgrade_scheduler, - ); - - let mut tester = Tester::new(interpreter, MemoryBlockstore::new()); + let interpreter: FvmMessageInterpreter = + FvmMessageInterpreter::new(NeverCallClient, None, 1.05, 1.05, false, upgrade_scheduler); let genesis = Genesis { chain_name: CHAIN_NAME.to_string(), @@ -223,7 +214,7 @@ async fn test_applying_upgrades() { ipc: None, }; - tester.init(genesis).await.unwrap(); + let mut tester = Tester::new(interpreter, genesis).await.unwrap(); // check that the app version is 0 assert_eq!(tester.state_params().app_version, 0); diff --git a/fendermint/testing/contract-test/tests/staking/machine.rs b/fendermint/testing/contract-test/tests/staking/machine.rs index 76adc405f..99ecb95fa 100644 --- a/fendermint/testing/contract-test/tests/staking/machine.rs +++ b/fendermint/testing/contract-test/tests/staking/machine.rs @@ -1,6 +1,6 @@ // Copyright 2022-2024 Protocol Labs // SPDX-License-Identifier: Apache-2.0, MIT -use std::{cell::RefCell, collections::HashSet, sync::Arc}; +use std::{cell::RefCell, collections::HashSet}; use arbitrary::{Arbitrary, Unstructured}; use fendermint_contract_test::ipc::{registry::RegistryCaller, subnet::SubnetCaller}; @@ -19,7 +19,6 @@ use fendermint_vm_message::{ conv::from_fvm::{self, to_eth_tokens}, signed::sign_secp256k1, }; -use fvm::engine::MultiEngine; use fvm_ipld_blockstore::Blockstore; use fvm_shared::bigint::Integer; use fvm_shared::econ::TokenAmount; @@ -65,9 +64,7 @@ pub enum StakingCommand { } #[derive(Default)] -pub struct StakingMachine { - multi_engine: Arc, -} +pub struct StakingMachine {} impl StateMachine for StakingMachine { type System = StakingSystem; @@ -86,9 +83,8 @@ impl StateMachine for StakingMachine { fn new_system(&self, state: &Self::State) -> Self::System { let rt = tokio::runtime::Runtime::new().expect("create tokio runtime for init"); - let (mut exec_state, _) = rt - .block_on(fendermint_contract_test::init_exec_state( - self.multi_engine.clone(), + let (mut exec_state, _, _) = rt + .block_on(fendermint_contract_test::create_test_exec_state( state.parent_genesis.clone(), )) .expect("failed to init parent"); diff --git a/fendermint/testing/graph-test/scripts/init.sh b/fendermint/testing/graph-test/scripts/init.sh index 461ccfa3e..2e76741ee 100755 --- a/fendermint/testing/graph-test/scripts/init.sh +++ b/fendermint/testing/graph-test/scripts/init.sh @@ -7,6 +7,7 @@ set -e KEYS_DIR=/data/keys CMT_DIR=/data/${NODE_NAME}/cometbft GENESIS_FILE=/data/genesis.json +SEALED_GENESIS_FILE=/data/sealed.car # Create a genesis file fendermint \ @@ -47,10 +48,20 @@ fendermint \ genesis --genesis-file $GENESIS_FILE \ add-validator --public-key $KEYS_DIR/$VALIDATOR_NAME.pk --power 1 +# Seal the genesis state +fendermint \ + genesis --genesis-file $GENESIS_FILE \ + ipc \ + seal-genesis \ + --builtin-actors-path /fendermint/bundle.car \ + --custom-actors-path /fendermint/custom_actors_bundle.car \ + --artifacts-path /fendermint/contracts \ + --output-path "${SEALED_GENESIS_FILE}" + # Convert FM genesis to CMT fendermint \ genesis --genesis-file $GENESIS_FILE \ - into-tendermint --out $CMT_DIR/config/genesis.json + into-tendermint --out $CMT_DIR/config/genesis.json --app-state "${SEALED_GENESIS_FILE}" # Copy the default validator key cp $KEYS_DIR/$VALIDATOR_NAME.priv_validator_key.json \ diff --git a/fendermint/testing/materializer/Cargo.toml b/fendermint/testing/materializer/Cargo.toml index b43b09702..fabce48d2 100644 --- a/fendermint/testing/materializer/Cargo.toml +++ b/fendermint/testing/materializer/Cargo.toml @@ -44,6 +44,7 @@ fendermint_vm_core = { path = "../../vm/core" } fendermint_vm_genesis = { path = "../../vm/genesis" } fendermint_vm_encoding = { path = "../../vm/encoding" } fendermint_vm_message = { path = "../../vm/message" } +fendermint_vm_interpreter = { path = "../../vm/interpreter" } fendermint_testing = { path = "..", optional = true } diff --git a/fendermint/testing/materializer/src/docker/node.rs b/fendermint/testing/materializer/src/docker/node.rs index f64035a72..128ab78ff 100644 --- a/fendermint/testing/materializer/src/docker/node.rs +++ b/fendermint/testing/materializer/src/docker/node.rs @@ -205,6 +205,21 @@ impl DockerNode { export_file(keys_dir.join(COMETBFT_NODE_ID), cometbft_node_id)?; + fendermint_runner + .run_cmd( + "genesis \ + --genesis-file /fendermint/genesis.json \ + ipc \ + seal-genesis \ + --builtin-actors-path /fendermint/bundle.car \ + --custom-actors-path /fendermint/custom_actors_bundle.car \ + --artifacts-path /fendermint/contracts \ + --output-path /cometbft/config/sealed.json \ + ", + ) + .await + .context("failed to seal genesis state")?; + // Convert fendermint genesis to cometbft. fendermint_runner .run_cmd( @@ -212,6 +227,7 @@ impl DockerNode { --genesis-file /fendermint/genesis.json \ into-tendermint \ --out /cometbft/config/genesis.json \ + --app-state /cometbft/config/sealed.json \ ", ) .await @@ -511,7 +527,7 @@ impl DockerNode { } if let Some(client) = self.ethapi_http_provider()? { - if let Err(e) = client.get_chainid().await { + if let Err(e) = client.get_block(1).await { continue; } } diff --git a/fendermint/testing/materializer/tests/docker.rs b/fendermint/testing/materializer/tests/docker.rs index c6910f8cc..04038a183 100644 --- a/fendermint/testing/materializer/tests/docker.rs +++ b/fendermint/testing/materializer/tests/docker.rs @@ -166,7 +166,7 @@ async fn wait_for_startup(testnet: &DockerTestnet) -> anyhow::Result { } if let Some(client) = dnode.ethapi_http_provider()? { - if let Err(e) = client.get_chainid().await { + if let Err(e) = client.get_block(1).await { eprintln!("EthAPI on {name} still fails: {e}"); continue 'startup; } diff --git a/fendermint/testing/materializer/tests/docker_tests/standalone.rs b/fendermint/testing/materializer/tests/docker_tests/standalone.rs index 38b1fd05d..805943889 100644 --- a/fendermint/testing/materializer/tests/docker_tests/standalone.rs +++ b/fendermint/testing/materializer/tests/docker_tests/standalone.rs @@ -66,6 +66,8 @@ async fn test_sent_tx_found_in_mempool() { .await .context("failed to set up middleware")?; + eprintln!("middleware ready, pending tests"); + // Create the simplest transaction possible: send tokens between accounts. let to: H160 = charlie.eth_addr().into(); let transfer = Eip1559TransactionRequest::new().to(to).value(1); @@ -77,6 +79,8 @@ async fn test_sent_tx_found_in_mempool() { let tx_hash = pending.tx_hash(); + eprintln!("sent pending txn {:?}", tx_hash); + // We expect that the transaction is pending, however it should not return an error. match middleware.get_transaction(tx_hash).await { Ok(Some(_)) => {} diff --git a/fendermint/testing/smoke-test/scripts/init.sh b/fendermint/testing/smoke-test/scripts/init.sh index 1ccbcc77d..0728e6eab 100755 --- a/fendermint/testing/smoke-test/scripts/init.sh +++ b/fendermint/testing/smoke-test/scripts/init.sh @@ -7,6 +7,7 @@ set -e KEYS_DIR=/data/keys CMT_DIR=/data/${NODE_NAME}/cometbft GENESIS_FILE=/data/genesis.json +SEALED_GENESIS_FILE=/data/sealed.car # Create a genesis file fendermint \ @@ -63,10 +64,20 @@ fendermint \ --msg-fee 10 \ --majority-percentage 66 +# Seal the genesis state +fendermint \ + genesis --genesis-file $GENESIS_FILE \ + ipc \ + seal-genesis \ + --builtin-actors-path /fendermint/bundle.car \ + --custom-actors-path /fendermint/custom_actors_bundle.car \ + --artifacts-path /fendermint/contracts \ + --output-path "${SEALED_GENESIS_FILE}" + # Convert FM genesis to CMT fendermint \ genesis --genesis-file $GENESIS_FILE \ - into-tendermint --out $CMT_DIR/config/genesis.json + into-tendermint --out $CMT_DIR/config/genesis.json --app-state "${SEALED_GENESIS_FILE}" # Convert FM validator key to CMT fendermint \ diff --git a/fendermint/testing/snapshot-test/scripts/init.sh b/fendermint/testing/snapshot-test/scripts/init.sh index 011bbfa75..1639e36f7 100755 --- a/fendermint/testing/snapshot-test/scripts/init.sh +++ b/fendermint/testing/snapshot-test/scripts/init.sh @@ -7,6 +7,7 @@ set -e KEYS_DIR=/data/keys CMT_DIR=/data/${NODE_NAME}/cometbft GENESIS_FILE=/data/genesis.json +SEALED_GENESIS_FILE=/data/sealed.car # Create a genesis file fendermint \ @@ -42,10 +43,20 @@ fendermint \ genesis --genesis-file $GENESIS_FILE \ add-validator --public-key $KEYS_DIR/$VALIDATOR_NAME.pk --power 1 +# Seal the genesis state +fendermint \ + genesis --genesis-file $GENESIS_FILE \ + ipc \ + seal-genesis \ + --builtin-actors-path /fendermint/bundle.car \ + --custom-actors-path /fendermint/custom_actors_bundle.car \ + --artifacts-path /fendermint/contracts \ + --output-path "${SEALED_GENESIS_FILE}" + # Convert FM genesis to CMT fendermint \ genesis --genesis-file $GENESIS_FILE \ - into-tendermint --out $CMT_DIR/config/genesis.json + into-tendermint --out $CMT_DIR/config/genesis.json --app-state "${SEALED_GENESIS_FILE}" # Copy the default validator key cp $KEYS_DIR/$VALIDATOR_NAME.priv_validator_key.json \ diff --git a/fendermint/vm/interpreter/Cargo.toml b/fendermint/vm/interpreter/Cargo.toml index 63e591da8..8e40ffed5 100644 --- a/fendermint/vm/interpreter/Cargo.toml +++ b/fendermint/vm/interpreter/Cargo.toml @@ -33,6 +33,7 @@ ipc-observability = { workspace = true } async-trait = { workspace = true } async-stm = { workspace = true } anyhow = { workspace = true } +base64 = { workspace = true } ethers = { workspace = true } hex = { workspace = true } num-traits = { workspace = true } @@ -58,6 +59,7 @@ futures-util = { workspace = true } libipld = { workspace = true } tokio = { workspace = true } pin-project = { workspace = true } +snap = { workspace = true } tokio-stream = { workspace = true } tokio-util = { workspace = true } @@ -86,3 +88,4 @@ arb = [ "fendermint_testing/arb", "rand", ] +test-util = [] diff --git a/fendermint/vm/interpreter/src/bytes.rs b/fendermint/vm/interpreter/src/bytes.rs index 1af84dd85..ca5b54909 100644 --- a/fendermint/vm/interpreter/src/bytes.rs +++ b/fendermint/vm/interpreter/src/bytes.rs @@ -1,16 +1,15 @@ // Copyright 2022-2024 Protocol Labs // SPDX-License-Identifier: Apache-2.0, MIT -use anyhow::{anyhow, Context}; +use anyhow::Context; use async_trait::async_trait; use cid::Cid; -use fendermint_vm_genesis::Genesis; use fendermint_vm_message::chain::ChainMessage; use fvm_ipld_encoding::Error as IpldError; use crate::{ chain::{ChainMessageApplyRet, ChainMessageCheckRes}, fvm::{FvmQuery, FvmQueryRet}, - CheckInterpreter, ExecInterpreter, GenesisInterpreter, ProposalInterpreter, QueryInterpreter, + CheckInterpreter, ExecInterpreter, ProposalInterpreter, QueryInterpreter, }; pub type BytesMessageApplyRes = Result; @@ -276,42 +275,3 @@ where Ok((state, Ok(ret))) } } - -#[async_trait] -impl GenesisInterpreter for BytesMessageInterpreter -where - I: GenesisInterpreter, -{ - type State = I::State; - type Genesis = Vec; - type Output = I::Output; - - async fn init( - &self, - state: Self::State, - genesis: Self::Genesis, - ) -> anyhow::Result<(Self::State, Self::Output)> { - // TODO (IPC-44): Handle the serialized application state as well as `Genesis`. - let genesis: Genesis = parse_genesis(&genesis)?; - self.inner.init(state, genesis).await - } -} - -/// Parse the initial genesis either as JSON or CBOR. -fn parse_genesis(bytes: &[u8]) -> anyhow::Result { - try_parse_genesis_json(bytes).or_else(|e1| { - try_parse_genesis_cbor(bytes) - .map_err(|e2| anyhow!("failed to deserialize genesis as JSON or CBOR: {e1}; {e2}")) - }) -} - -fn try_parse_genesis_json(bytes: &[u8]) -> anyhow::Result { - let json = String::from_utf8(bytes.to_vec())?; - let genesis = serde_json::from_str(&json)?; - Ok(genesis) -} - -fn try_parse_genesis_cbor(bytes: &[u8]) -> anyhow::Result { - let genesis = fvm_ipld_encoding::from_slice(bytes)?; - Ok(genesis) -} diff --git a/fendermint/vm/interpreter/src/chain.rs b/fendermint/vm/interpreter/src/chain.rs index 79136138f..ae4d6af24 100644 --- a/fendermint/vm/interpreter/src/chain.rs +++ b/fendermint/vm/interpreter/src/chain.rs @@ -6,7 +6,7 @@ use crate::{ fvm::state::FvmExecState, fvm::FvmMessage, signed::{SignedMessageApplyRes, SignedMessageCheckRes, SyntheticMessage, VerifiableMessage}, - CheckInterpreter, ExecInterpreter, GenesisInterpreter, ProposalInterpreter, QueryInterpreter, + CheckInterpreter, ExecInterpreter, ProposalInterpreter, QueryInterpreter, }; use anyhow::{bail, Context}; use async_stm::atomically; @@ -497,25 +497,6 @@ where } } -#[async_trait] -impl GenesisInterpreter for ChainMessageInterpreter -where - DB: Blockstore + Clone + 'static + Send + Sync, - I: GenesisInterpreter, -{ - type State = I::State; - type Genesis = I::Genesis; - type Output = I::Output; - - async fn init( - &self, - state: Self::State, - genesis: Self::Genesis, - ) -> anyhow::Result<(Self::State, Self::Output)> { - self.inner.init(state, genesis).await - } -} - /// Convert a signed relayed bottom-up checkpoint to a syntetic message we can send to the FVM. /// /// By mapping to an FVM message we invoke the right contract to validate the checkpoint, diff --git a/fendermint/vm/interpreter/src/fvm/genesis.rs b/fendermint/vm/interpreter/src/fvm/genesis.rs deleted file mode 100644 index 23de68361..000000000 --- a/fendermint/vm/interpreter/src/fvm/genesis.rs +++ /dev/null @@ -1,682 +0,0 @@ -// Copyright 2022-2024 Protocol Labs -// SPDX-License-Identifier: Apache-2.0, MIT - -use std::collections::{BTreeSet, HashMap}; -use std::marker::PhantomData; -use std::path::{Path, PathBuf}; - -use anyhow::{anyhow, Context}; -use async_trait::async_trait; -use ethers::abi::Tokenize; -use ethers::core::types as et; -use fendermint_actor_eam::PermissionModeParams; -use fendermint_eth_hardhat::{Hardhat, FQN}; -use fendermint_vm_actor_interface::diamond::{EthContract, EthContractMap}; -use fendermint_vm_actor_interface::eam::EthAddress; -use fendermint_vm_actor_interface::ipc::IPC_CONTRACTS; -use fendermint_vm_actor_interface::{ - account, burntfunds, chainmetadata, cron, eam, init, ipc, reward, system, EMPTY_ARR, -}; -use fendermint_vm_core::{chainid, Timestamp}; -use fendermint_vm_genesis::{ActorMeta, Genesis, Power, PowerScale, Validator}; -use fvm_ipld_blockstore::Blockstore; -use fvm_shared::chainid::ChainID; -use fvm_shared::econ::TokenAmount; -use fvm_shared::version::NetworkVersion; -use ipc_actors_abis::i_diamond::FacetCut; -use num_traits::Zero; - -use crate::GenesisInterpreter; - -use super::state::FvmGenesisState; -use super::FvmMessageInterpreter; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct FvmGenesisOutput { - pub chain_id: ChainID, - pub timestamp: Timestamp, - pub network_version: NetworkVersion, - pub base_fee: TokenAmount, - pub power_scale: PowerScale, - pub circ_supply: TokenAmount, - pub validators: Vec>, -} - -#[async_trait] -impl GenesisInterpreter for FvmMessageInterpreter -where - DB: Blockstore + 'static + Send + Sync + Clone, - TC: Send + Sync + 'static, -{ - type State = FvmGenesisState; - type Genesis = Genesis; - type Output = FvmGenesisOutput; - - /// Initialize actor states from the Genesis spec. - /// - /// This method doesn't create all builtin Filecoin actors, - /// it leaves out the ones specific to file storage. - /// - /// The ones included are: - /// * system - /// * init - /// * cron - /// * EAM - /// * burnt funds - /// * rewards (placeholder) - /// * accounts - /// * IPC - /// - /// TODO: - /// * faucet? - /// - /// See genesis initialization in: - /// * [Lotus](https://github.com/filecoin-project/lotus/blob/v1.20.4/chain/gen/genesis/genesis.go) - /// * [ref-fvm tester](https://github.com/filecoin-project/ref-fvm/blob/fvm%40v3.1.0/testing/integration/src/tester.rs#L99-L103) - /// * [fvm-workbench](https://github.com/anorth/fvm-workbench/blob/67219b3fd0b5654d54f722ab5acea6ec0abb2edc/builtin/src/genesis.rs) - async fn init( - &self, - mut state: Self::State, - genesis: Self::Genesis, - ) -> anyhow::Result<(Self::State, Self::Output)> { - // Log the genesis in JSON format, hopefully it's not enormous. - tracing::debug!(genesis = serde_json::to_string(&genesis)?, "init"); - - // NOTE: We could consider adding the chain ID to the interpreter - // and rejecting genesis if it doesn't match the expectation, - // but the Tendermint genesis file also has this field, and - // presumably Tendermint checks that its peers have the same. - let chain_id = chainid::from_str_hashed(&genesis.chain_name)?; - - // Convert validators to CometBFT power scale. - let validators = genesis - .validators - .iter() - .cloned() - .map(|vc| vc.map_power(|c| c.into_power(genesis.power_scale))) - .collect(); - - // Currently we just pass them back as they are, but later we should - // store them in the IPC actors; or in case of a snapshot restore them - // from the state. - let out = FvmGenesisOutput { - chain_id, - timestamp: genesis.timestamp, - network_version: genesis.network_version, - circ_supply: circ_supply(&genesis), - base_fee: genesis.base_fee, - power_scale: genesis.power_scale, - validators, - }; - - // STAGE 0: Declare the built-in EVM contracts we'll have to deploy. - - // Pre-defined IDs for top-level Ethereum contracts. - let mut eth_builtin_ids = BTreeSet::new(); - let mut eth_root_contracts = Vec::new(); - let mut eth_contracts = EthContractMap::default(); - - // Only allocate IDs if the contracts are deployed. - if genesis.ipc.is_some() { - eth_contracts.extend(IPC_CONTRACTS.clone()); - } - - eth_builtin_ids.extend(eth_contracts.values().map(|c| c.actor_id)); - eth_root_contracts.extend(eth_contracts.keys()); - eth_root_contracts.extend( - eth_contracts - .values() - .flat_map(|c| c.facets.iter().map(|f| f.name)), - ); - // Collect dependencies of the main IPC actors. - let mut eth_libs = self - .contracts - .dependencies( - ð_root_contracts - .iter() - .map(|n| (contract_src(n), *n)) - .collect::>(), - ) - .context("failed to collect EVM contract dependencies")?; - - // Only keep library dependencies, not contracts with constructors. - eth_libs.retain(|(_, d)| !eth_contracts.contains_key(d.as_str())); - - // STAGE 1: First we initialize native built-in actors. - - // System actor - state - .create_builtin_actor( - system::SYSTEM_ACTOR_CODE_ID, - system::SYSTEM_ACTOR_ID, - &system::State { - builtin_actors: state.manifest_data_cid, - }, - TokenAmount::zero(), - None, - ) - .context("failed to create system actor")?; - - // Init actor - let (init_state, addr_to_id) = init::State::new( - state.store(), - genesis.chain_name.clone(), - &genesis.accounts, - ð_builtin_ids, - eth_libs.len() as u64, - ) - .context("failed to create init state")?; - - state - .create_builtin_actor( - init::INIT_ACTOR_CODE_ID, - init::INIT_ACTOR_ID, - &init_state, - TokenAmount::zero(), - None, - ) - .context("failed to create init actor")?; - - // Cron actor - state - .create_builtin_actor( - cron::CRON_ACTOR_CODE_ID, - cron::CRON_ACTOR_ID, - &cron::State { - entries: vec![], // TODO: Maybe with the IPC. - }, - TokenAmount::zero(), - None, - ) - .context("failed to create cron actor")?; - - // Ethereum Account Manager (EAM) actor - state - .create_builtin_actor( - eam::EAM_ACTOR_CODE_ID, - eam::EAM_ACTOR_ID, - &EMPTY_ARR, - TokenAmount::zero(), - None, - ) - .context("failed to create EAM actor")?; - - // Burnt funds actor (it's just an account). - state - .create_builtin_actor( - account::ACCOUNT_ACTOR_CODE_ID, - burntfunds::BURNT_FUNDS_ACTOR_ID, - &account::State { - address: burntfunds::BURNT_FUNDS_ACTOR_ADDR, - }, - TokenAmount::zero(), - None, - ) - .context("failed to create burnt funds actor")?; - - // A placeholder for the reward actor, beause I don't think - // using the one in the builtin actors library would be appropriate. - // This effectively burns the miner rewards. Better than panicking. - state - .create_builtin_actor( - account::ACCOUNT_ACTOR_CODE_ID, - reward::REWARD_ACTOR_ID, - &account::State { - address: reward::REWARD_ACTOR_ADDR, - }, - TokenAmount::zero(), - None, - ) - .context("failed to create reward actor")?; - - // STAGE 1b: Then we initialize the in-repo custom actors. - - // Initialize the chain metadata actor which handles saving metadata about the chain - // (e.g. block hashes) which we can query. - let chainmetadata_state = fendermint_actor_chainmetadata::State::new( - &state.store(), - fendermint_actor_chainmetadata::DEFAULT_LOOKBACK_LEN, - )?; - state - .create_custom_actor( - fendermint_actor_chainmetadata::CHAINMETADATA_ACTOR_NAME, - chainmetadata::CHAINMETADATA_ACTOR_ID, - &chainmetadata_state, - TokenAmount::zero(), - None, - ) - .context("failed to create chainmetadata actor")?; - - let eam_state = fendermint_actor_eam::State::new( - state.store(), - PermissionModeParams::from(genesis.eam_permission_mode), - )?; - state - .replace_builtin_actor( - eam::EAM_ACTOR_NAME, - eam::EAM_ACTOR_ID, - fendermint_actor_eam::IPC_EAM_ACTOR_NAME, - &eam_state, - TokenAmount::zero(), - None, - ) - .context("failed to replace built in eam actor")?; - - // STAGE 2: Create non-builtin accounts which do not have a fixed ID. - - // The next ID is going to be _after_ the accounts, which have already been assigned an ID by the `Init` actor. - // The reason we aren't using the `init_state.next_id` is because that already accounted for the multisig accounts. - let mut next_id = init::FIRST_NON_SINGLETON_ADDR + addr_to_id.len() as u64; - - for a in genesis.accounts { - let balance = a.balance; - match a.meta { - ActorMeta::Account(acct) => { - state - .create_account_actor(acct, balance, &addr_to_id) - .context("failed to create account actor")?; - } - ActorMeta::Multisig(ms) => { - state - .create_multisig_actor(ms, balance, &addr_to_id, next_id) - .context("failed to create multisig actor")?; - next_id += 1; - } - } - } - - // STAGE 3: Initialize the FVM and create built-in FEVM actors. - - state - .init_exec_state( - out.timestamp, - out.network_version, - out.base_fee.clone(), - out.circ_supply.clone(), - out.chain_id.into(), - out.power_scale, - ) - .context("failed to init exec state")?; - - let mut deployer = ContractDeployer::::new(&self.contracts, ð_contracts); - - // Deploy Ethereum libraries. - for (lib_src, lib_name) in eth_libs { - deployer.deploy_library(&mut state, &mut next_id, lib_src, &lib_name)?; - } - - if let Some(ipc_params) = genesis.ipc { - // IPC Gateway actor. - let gateway_addr = { - use ipc::gateway::ConstructorParameters; - - let params = ConstructorParameters::new(ipc_params.gateway, genesis.validators) - .context("failed to create gateway constructor")?; - - let facets = deployer - .facets(ipc::gateway::CONTRACT_NAME) - .context("failed to collect gateway facets")?; - - deployer.deploy_contract( - &mut state, - ipc::gateway::CONTRACT_NAME, - (facets, params), - )? - }; - - // IPC SubnetRegistry actor. - { - use ipc::registry::ConstructorParameters; - - let mut facets = deployer - .facets(ipc::registry::CONTRACT_NAME) - .context("failed to collect registry facets")?; - - let getter_facet = facets.remove(0); - let manager_facet = facets.remove(0); - let rewarder_facet = facets.remove(0); - let checkpointer_facet = facets.remove(0); - let pauser_facet = facets.remove(0); - let diamond_loupe_facet = facets.remove(0); - let diamond_cut_facet = facets.remove(0); - let ownership_facet = facets.remove(0); - - debug_assert_eq!(facets.len(), 2, "SubnetRegistry has 2 facets of its own"); - - let params = ConstructorParameters { - gateway: gateway_addr, - getter_facet: getter_facet.facet_address, - manager_facet: manager_facet.facet_address, - rewarder_facet: rewarder_facet.facet_address, - pauser_facet: pauser_facet.facet_address, - checkpointer_facet: checkpointer_facet.facet_address, - diamond_cut_facet: diamond_cut_facet.facet_address, - diamond_loupe_facet: diamond_loupe_facet.facet_address, - ownership_facet: ownership_facet.facet_address, - subnet_getter_selectors: getter_facet.function_selectors, - subnet_manager_selectors: manager_facet.function_selectors, - subnet_rewarder_selectors: rewarder_facet.function_selectors, - subnet_checkpointer_selectors: checkpointer_facet.function_selectors, - subnet_pauser_selectors: pauser_facet.function_selectors, - subnet_actor_diamond_cut_selectors: diamond_cut_facet.function_selectors, - subnet_actor_diamond_loupe_selectors: diamond_loupe_facet.function_selectors, - subnet_actor_ownership_selectors: ownership_facet.function_selectors, - creation_privileges: 0, - }; - - deployer.deploy_contract( - &mut state, - ipc::registry::CONTRACT_NAME, - (facets, params), - )?; - }; - } - - Ok((state, out)) - } -} - -fn contract_src(name: &str) -> PathBuf { - PathBuf::from(format!("{name}.sol")) -} - -struct ContractDeployer<'a, DB> { - hardhat: &'a Hardhat, - top_contracts: &'a EthContractMap, - // Assign dynamic ID addresses to libraries, but use fixed addresses for the top level contracts. - lib_addrs: HashMap, - phantom_db: PhantomData, -} - -impl<'a, DB> ContractDeployer<'a, DB> -where - DB: Blockstore + 'static + Send + Sync + Clone, -{ - pub fn new(hardhat: &'a Hardhat, top_contracts: &'a EthContractMap) -> Self { - Self { - hardhat, - top_contracts, - lib_addrs: Default::default(), - phantom_db: PhantomData, - } - } - - /// Deploy a library contract with a dynamic ID and no constructor. - pub fn deploy_library( - &mut self, - state: &mut FvmGenesisState, - next_id: &mut u64, - lib_src: impl AsRef, - lib_name: &str, - ) -> anyhow::Result<()> { - let fqn = self.hardhat.fqn(lib_src.as_ref(), lib_name); - - let bytecode = self - .hardhat - .bytecode(&lib_src, lib_name, &self.lib_addrs) - .with_context(|| format!("failed to load library bytecode {fqn}"))?; - - let eth_addr = state - .create_evm_actor(*next_id, bytecode) - .with_context(|| format!("failed to create library actor {fqn}"))?; - - let id_addr = et::Address::from(EthAddress::from_id(*next_id).0); - let eth_addr = et::Address::from(eth_addr.0); - - tracing::info!( - actor_id = next_id, - ?eth_addr, - ?id_addr, - fqn, - "deployed Ethereum library" - ); - - // We can use the masked ID here or the delegated address. - // Maybe the masked ID is quicker because it doesn't need to be resolved. - self.lib_addrs.insert(fqn, id_addr); - - *next_id += 1; - - Ok(()) - } - - /// Construct the bytecode of a top-level contract and deploy it with some constructor parameters. - pub fn deploy_contract( - &self, - state: &mut FvmGenesisState, - contract_name: &str, - constructor_params: T, - ) -> anyhow::Result - where - T: Tokenize, - { - let contract = self.top_contract(contract_name)?; - let contract_id = contract.actor_id; - let contract_src = contract_src(contract_name); - - let bytecode = self - .hardhat - .bytecode(contract_src, contract_name, &self.lib_addrs) - .with_context(|| format!("failed to load {contract_name} bytecode"))?; - - let eth_addr = state - .create_evm_actor_with_cons(contract_id, &contract.abi, bytecode, constructor_params) - .with_context(|| format!("failed to create {contract_name} actor"))?; - - let id_addr = et::Address::from(EthAddress::from_id(contract_id).0); - let eth_addr = et::Address::from(eth_addr.0); - - tracing::info!( - actor_id = contract_id, - ?eth_addr, - ?id_addr, - contract_name, - "deployed Ethereum contract" - ); - - // The Ethereum address is more usable inside the EVM than the ID address. - Ok(eth_addr) - } - - /// Collect Facet Cuts for the diamond pattern, where the facet address comes from already deployed library facets. - pub fn facets(&self, contract_name: &str) -> anyhow::Result> { - let contract = self.top_contract(contract_name)?; - let mut facet_cuts = Vec::new(); - - for facet in contract.facets.iter() { - let facet_name = facet.name; - let facet_src = contract_src(facet_name); - let facet_fqn = self.hardhat.fqn(&facet_src, facet_name); - - let facet_addr = self - .lib_addrs - .get(&facet_fqn) - .ok_or_else(|| anyhow!("facet {facet_name} has not been deployed"))?; - - let method_sigs = facet - .abi - .functions() - .filter(|f| f.signature() != "init(bytes)") - .map(|f| f.short_signature()) - .collect(); - - let facet_cut = FacetCut { - facet_address: *facet_addr, - action: 0, // Add - function_selectors: method_sigs, - }; - - facet_cuts.push(facet_cut); - } - - Ok(facet_cuts) - } - - fn top_contract(&self, contract_name: &str) -> anyhow::Result<&EthContract> { - self.top_contracts - .get(contract_name) - .ok_or_else(|| anyhow!("unknown top contract name: {contract_name}")) - } -} - -/// Sum of balances in the genesis accounts. -fn circ_supply(g: &Genesis) -> TokenAmount { - g.accounts - .iter() - .fold(TokenAmount::zero(), |s, a| s + a.balance.clone()) -} - -#[cfg(test)] -mod tests { - use std::{str::FromStr, sync::Arc}; - - use cid::Cid; - use fendermint_vm_genesis::{ipc::IpcParams, Genesis}; - use fvm::engine::MultiEngine; - use quickcheck::Arbitrary; - use tendermint_rpc::{MockClient, MockRequestMethodMatcher}; - - use crate::{ - fvm::{ - bundle::{bundle_path, contracts_path, custom_actors_bundle_path}, - state::ipc::GatewayCaller, - store::memory::MemoryBlockstore, - upgrades::UpgradeScheduler, - FvmMessageInterpreter, - }, - GenesisInterpreter, - }; - - use super::FvmGenesisState; - - #[tokio::test] - async fn load_genesis() { - let genesis = make_genesis(); - let bundle = read_bundle(); - let custom_actors_bundle = read_custom_actors_bundle(); - let interpreter = make_interpreter(); - - let multi_engine = Arc::new(MultiEngine::default()); - let store = MemoryBlockstore::new(); - - let state = FvmGenesisState::new(store, multi_engine, &bundle, &custom_actors_bundle) - .await - .expect("failed to create state"); - - let (mut state, out) = interpreter - .init(state, genesis.clone()) - .await - .expect("failed to create actors"); - - assert_eq!(out.validators.len(), genesis.validators.len()); - - // Try calling a method on the IPC Gateway. - let exec_state = state.exec_state().expect("should be in exec stage"); - let caller = GatewayCaller::default(); - - let period = caller - .bottom_up_check_period(exec_state) - .expect("error calling the gateway"); - - assert_eq!(period, genesis.ipc.unwrap().gateway.bottom_up_check_period); - - let _state_root = state.commit().expect("failed to commit"); - } - - #[tokio::test] - async fn load_genesis_deterministic() { - let genesis = make_genesis(); - let bundle = read_bundle(); - let custom_actors_bundle = read_custom_actors_bundle(); - let interpreter = make_interpreter(); - let multi_engine = Arc::new(MultiEngine::default()); - - // Create a couple of states and load the same thing. - let mut outputs = Vec::new(); - for _ in 0..3 { - let store = MemoryBlockstore::new(); - let state = - FvmGenesisState::new(store, multi_engine.clone(), &bundle, &custom_actors_bundle) - .await - .expect("failed to create state"); - - let (state, out) = interpreter - .init(state, genesis.clone()) - .await - .expect("failed to create actors"); - - let state_root_hash = state.commit().expect("failed to commit"); - outputs.push((state_root_hash, out)); - } - - for out in &outputs[1..] { - assert_eq!(out.0, outputs[0].0, "state root hash is different"); - } - } - - // This is a sort of canary test, if it fails means something changed in the way we do genesis, - // which is probably fine, but it's better to know about it, and if anybody doesn't get the same - // then we might have some non-determinism. - #[ignore] // I see a different value on CI than locally. - #[tokio::test] - async fn load_genesis_known() { - let genesis_json = "{\"chain_name\":\"/r314159/f410fnfmitm2ww7oehhtbokf6wulhrr62sgq3sgqmenq\",\"timestamp\":1073250,\"network_version\":18,\"base_fee\":\"1000\",\"power_scale\":3,\"validators\":[{\"public_key\":\"BLX9ojqB+8Z26aMmKoCRb3Te6AnSU6zY8hPcf1X5Q69XCNaHVcRxzYO2xx7o/2vgdS7nkDTMRRbkDGzy+FYdAFc=\",\"power\":\"1000000000000000000\"},{\"public_key\":\"BFcOveVieknZiscWsfXa06aGbBkKeucBycd/w0N1QHlaZfa/5dJcH7D0hvcdfv3B2Rv1OPuxo1PkgsEbWegWKcA=\",\"power\":\"1000000000000000000\"},{\"public_key\":\"BEP30ykovfrQp3zo+JVRvDVL2emC+Ju1Kpox3zMVYZyFKvYt64qyN/HOVjridDrkEsnQU8BVen4Aegja4fBZ+LU=\",\"power\":\"1000000000000000000\"}],\"accounts\":[{\"meta\":{\"Account\":{\"owner\":\"f410fggjevhgketpz6gw6ordusynlgcd5piyug4aomuq\"}},\"balance\":\"1000000000000000000\"},{\"meta\":{\"Account\":{\"owner\":\"f410frbdnwklaitcjsqe7swjwp5naple6vthq4woyfry\"}},\"balance\":\"2000000000000000000\"},{\"meta\":{\"Account\":{\"owner\":\"f410fxo4lih4n2acr3oadalidwqjgoqkzhp5dw3zwkvy\"}},\"balance\":\"1000000000000000000\"}],\"ipc\":{\"gateway\":{\"subnet_id\":\"/r314159/f410fnfmitm2ww7oehhtbokf6wulhrr62sgq3sgqmenq\",\"bottom_up_check_period\":30,\"msg_fee\":\"1000000000000\",\"majority_percentage\":60,\"active_validators_limit\":100}}}"; - - let genesis: Genesis = serde_json::from_str(genesis_json).expect("failed to parse genesis"); - - let bundle = read_bundle(); - let custom_actors_bundle = read_custom_actors_bundle(); - let interpreter = make_interpreter(); - let multi_engine = Arc::new(MultiEngine::default()); - - let store = MemoryBlockstore::new(); - let state = - FvmGenesisState::new(store, multi_engine.clone(), &bundle, &custom_actors_bundle) - .await - .expect("failed to create state"); - - let (state, _) = interpreter - .init(state, genesis.clone()) - .await - .expect("failed to create actors"); - - let state_root_hash = state.commit().expect("failed to commit"); - - let expected_root_hash = - Cid::from_str("bafy2bzacedebgy4j7qnh2v2x4kkr2jqfkryql5ookbjrwge6dbrr24ytlqnj4") - .unwrap(); - - assert_eq!(state_root_hash, expected_root_hash); - } - - fn make_genesis() -> Genesis { - let mut g = quickcheck::Gen::new(5); - let mut genesis = Genesis::arbitrary(&mut g); - - // Make sure we have IPC enabled. - genesis.ipc = Some(IpcParams::arbitrary(&mut g)); - genesis - } - - fn make_interpreter( - ) -> FvmMessageInterpreter> { - let (client, _) = MockClient::new(MockRequestMethodMatcher::default()); - FvmMessageInterpreter::new( - client, - None, - contracts_path(), - 1.05, - 1.05, - false, - UpgradeScheduler::new(), - ) - } - - fn read_bundle() -> Vec { - std::fs::read(bundle_path()).expect("failed to read bundle") - } - - fn read_custom_actors_bundle() -> Vec { - std::fs::read(custom_actors_bundle_path()).expect("failed to read custom actor bundle") - } -} diff --git a/fendermint/vm/interpreter/src/fvm/mod.rs b/fendermint/vm/interpreter/src/fvm/mod.rs index 0aefb4b2e..3e098765b 100644 --- a/fendermint/vm/interpreter/src/fvm/mod.rs +++ b/fendermint/vm/interpreter/src/fvm/mod.rs @@ -1,13 +1,11 @@ // Copyright 2022-2024 Protocol Labs // SPDX-License-Identifier: Apache-2.0, MIT -use std::path::PathBuf; mod broadcast; mod check; mod checkpoint; mod exec; mod externs; -mod genesis; pub mod observe; mod query; pub mod state; @@ -22,10 +20,8 @@ pub use check::FvmCheckRet; pub use checkpoint::PowerUpdates; pub use exec::FvmApplyRet; use fendermint_crypto::{PublicKey, SecretKey}; -use fendermint_eth_hardhat::Hardhat; pub use fendermint_vm_message::query::FvmQuery; use fvm_ipld_blockstore::Blockstore; -pub use genesis::FvmGenesisOutput; pub use query::FvmQueryRet; use tendermint_rpc::Client; @@ -63,7 +59,6 @@ pub struct FvmMessageInterpreter where DB: Blockstore + 'static + Clone, { - contracts: Hardhat, /// Tendermint client for querying the RPC. client: C, /// If this is a validator node, this should be the key we can use to sign transactions. @@ -91,7 +86,6 @@ where pub fn new( client: C, validator_ctx: Option>, - contracts_dir: PathBuf, gas_overestimation_rate: f64, gas_search_step: f64, exec_in_check: bool, @@ -100,7 +94,6 @@ where Self { client, validator_ctx, - contracts: Hardhat::new(contracts_dir), gas_overestimation_rate, gas_search_step, exec_in_check, diff --git a/fendermint/vm/interpreter/src/fvm/state/genesis.rs b/fendermint/vm/interpreter/src/fvm/state/genesis.rs index f43b6c4fd..bdbed9947 100644 --- a/fendermint/vm/interpreter/src/fvm/state/genesis.rs +++ b/fendermint/vm/interpreter/src/fvm/state/genesis.rs @@ -166,13 +166,13 @@ where Ok(()) } - /// Flush the data to the block store. - pub fn commit(self) -> anyhow::Result { + /// Flush the data to the block store. Returns the state root cid and the underlying state store. + pub fn finalize(self) -> anyhow::Result<(Cid, DB)> { match self.stage { - Stage::Tree(mut state_tree) => Ok(state_tree.flush()?), + Stage::Tree(_) => Err(anyhow!("invalid finalize state")), Stage::Exec(exec_state) => match exec_state.commit()? { (_, _, true) => bail!("FVM parameters are not expected to be updated in genesis"), - (cid, _, _) => Ok(cid), + (cid, _, _) => Ok((cid, self.store)), }, } } diff --git a/fendermint/vm/interpreter/src/fvm/state/snapshot.rs b/fendermint/vm/interpreter/src/fvm/state/snapshot.rs index 0801ade9e..7f70c5cbc 100644 --- a/fendermint/vm/interpreter/src/fvm/state/snapshot.rs +++ b/fendermint/vm/interpreter/src/fvm/state/snapshot.rs @@ -213,7 +213,7 @@ where } #[pin_project::pin_project] -struct StateTreeStreamer { +pub(crate) struct StateTreeStreamer { /// The list of cids to pull from the blockstore #[pin] dfs: VecDeque, @@ -286,7 +286,7 @@ fn walk_ipld_cids(ipld: Ipld, dfs: &mut VecDeque) { } } -fn derive_cid(t: &T) -> anyhow::Result<(Cid, Vec)> { +pub(crate) fn derive_cid(t: &T) -> anyhow::Result<(Cid, Vec)> { let bytes = fvm_ipld_encoding::to_vec(&t)?; let cid = Cid::new_v1(DAG_CBOR, Code::Blake2b256.digest(&bytes)); Ok((cid, bytes)) diff --git a/fendermint/vm/interpreter/src/fvm/store/memory.rs b/fendermint/vm/interpreter/src/fvm/store/memory.rs index d49b9eb3b..9ad8a4d86 100644 --- a/fendermint/vm/interpreter/src/fvm/store/memory.rs +++ b/fendermint/vm/interpreter/src/fvm/store/memory.rs @@ -24,11 +24,6 @@ impl MemoryBlockstore { } impl Blockstore for MemoryBlockstore { - fn has(&self, k: &Cid) -> Result { - let guard = self.blocks.read().unwrap(); - Ok(guard.contains_key(k)) - } - fn get(&self, k: &Cid) -> Result>> { let guard = self.blocks.read().unwrap(); Ok(guard.get(k).cloned()) @@ -39,4 +34,9 @@ impl Blockstore for MemoryBlockstore { guard.insert(*k, block.into()); Ok(()) } + + fn has(&self, k: &Cid) -> Result { + let guard = self.blocks.read().unwrap(); + Ok(guard.contains_key(k)) + } } diff --git a/fendermint/vm/interpreter/src/genesis.rs b/fendermint/vm/interpreter/src/genesis.rs new file mode 100644 index 000000000..4ccb8fc4b --- /dev/null +++ b/fendermint/vm/interpreter/src/genesis.rs @@ -0,0 +1,777 @@ +// Copyright 2022-2024 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT + +use std::collections::{BTreeSet, HashMap}; +use std::io::{Read, Write}; +use std::marker::PhantomData; +use std::path::{Path, PathBuf}; +use std::pin::Pin; +use std::sync::Arc; + +use anyhow::{anyhow, Context}; +use base64::Engine; +use cid::Cid; +use ethers::abi::Tokenize; +use ethers::core::types as et; +use fendermint_actor_eam::PermissionModeParams; +use fendermint_eth_hardhat::{ContractSourceAndName, Hardhat, FQN}; +use fendermint_vm_actor_interface::diamond::{EthContract, EthContractMap}; +use fendermint_vm_actor_interface::eam::EthAddress; +use fendermint_vm_actor_interface::ipc::IPC_CONTRACTS; +use fendermint_vm_actor_interface::{ + account, burntfunds, chainmetadata, cron, eam, init, ipc, reward, system, EMPTY_ARR, +}; +use fendermint_vm_core::{chainid, Timestamp}; +use fendermint_vm_genesis::{ActorMeta, Collateral, Genesis, Power, PowerScale, Validator}; +use futures_util::io::Cursor; +use fvm::engine::MultiEngine; +use fvm_ipld_blockstore::Blockstore; +use fvm_ipld_car::{load_car, CarHeader}; +use fvm_ipld_encoding::CborStore; +use fvm_shared::chainid::ChainID; +use fvm_shared::econ::TokenAmount; +use fvm_shared::version::NetworkVersion; +use ipc_actors_abis::i_diamond::FacetCut; +use num_traits::Zero; + +use crate::fvm::state::snapshot::{derive_cid, StateTreeStreamer}; +use crate::fvm::state::{FvmGenesisState, FvmStateParams}; +use crate::fvm::store::memory::MemoryBlockstore; +use fendermint_vm_genesis::ipc::IpcParams; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use tokio_stream::StreamExt; +use tokio_util::compat::TokioAsyncWriteCompatExt; + +/// The sealed genesis state metadata +#[serde_as] +#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] +struct GenesisMetadata { + pub state_params: FvmStateParams, + pub validators: Vec>, +} + +impl GenesisMetadata { + fn new(state_root: Cid, out: GenesisOutput) -> GenesisMetadata { + let state_params = FvmStateParams { + state_root, + timestamp: out.timestamp, + network_version: out.network_version, + base_fee: out.base_fee, + circ_supply: out.circ_supply, + chain_id: out.chain_id.into(), + power_scale: out.power_scale, + app_version: 0, + }; + + GenesisMetadata { + state_params, + validators: out.validators, + } + } +} + +/// Genesis app state wrapper for cometbft +#[repr(u8)] +pub enum GenesisAppState { + V1(Vec) = 1, +} + +impl GenesisAppState { + pub fn v1(bytes: Vec) -> Self { + Self::V1(bytes) + } + + pub fn compress_and_encode(&self) -> anyhow::Result { + let bytes = match self { + GenesisAppState::V1(ref bytes) => { + let mut buf = { + let len = snap::raw::max_compress_len(bytes.len()) + 1; // +1 for the version discriminator + Vec::with_capacity(len) + }; + + // Write version discriminator uncompressed. + buf.push(1); + + // Snappy compress the data. + let mut wtr = snap::write::FrameEncoder::new(buf); + wtr.write_all(bytes)?; + wtr.into_inner()? + } + }; + + Ok(base64::engine::general_purpose::STANDARD.encode(bytes)) + } + + pub fn decode_and_decompress(raw: &str) -> anyhow::Result> { + let bytes = base64::engine::general_purpose::STANDARD.decode(raw)?; + if bytes.is_empty() { + return Err(anyhow!("empty bytes for genesis app state")); + } + + // Strip the version discriminator. + let version = bytes[0]; + + match version { + 1 => { + let data = &bytes.as_slice()[1..]; + let len = snap::raw::decompress_len(data) + .context("failed to calculate length of decompressed app state")?; + let mut buf = Vec::with_capacity(len); + snap::read::FrameDecoder::new(data).read_to_end(&mut buf)?; + Ok(buf) + } + _ => Err(anyhow!("unsupported schema version")), + } + } +} + +pub async fn read_genesis_car( + bytes: Vec, + store: &DB, +) -> anyhow::Result<(Vec>, FvmStateParams)> { + let roots = load_car(store, Cursor::new(&bytes)).await?; + + let metadata_cid = roots + .first() + .ok_or_else(|| anyhow!("invalid genesis car, should have at least 1 root cid"))?; + + let metadata = store + .get_cbor::(metadata_cid)? + .ok_or_else(|| anyhow!("invalid genesis car, metadata not found"))?; + + Ok((metadata.validators, metadata.state_params)) +} + +/// The output of genesis creation +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GenesisOutput { + pub chain_id: ChainID, + pub timestamp: Timestamp, + pub network_version: NetworkVersion, + pub base_fee: TokenAmount, + pub power_scale: PowerScale, + pub circ_supply: TokenAmount, + pub validators: Vec>, +} + +pub struct GenesisBuilder { + /// Hardhat like util to deploy ipc contracts + hardhat: Option, + /// The built in actors bundle path + builtin_actors_path: PathBuf, + /// The custom actors bundle path + custom_actors_path: PathBuf, + + /// Genesis params + genesis_params: Genesis, +} + +impl GenesisBuilder { + pub fn new( + builtin_actors_path: PathBuf, + custom_actors_path: PathBuf, + genesis_params: Genesis, + ) -> Self { + Self { + hardhat: None, + builtin_actors_path, + custom_actors_path, + genesis_params, + } + } + + pub fn with_ipc_system_contracts(mut self, path: PathBuf) -> Self { + self.hardhat = Some(Hardhat::new(path)); + self + } + + /// Initialize actor states from the Genesis parameters and write the sealed genesis state to + /// a CAR file specified by `out_path` + pub async fn write_to(&self, out_path: PathBuf) -> anyhow::Result<()> { + let mut state = self.init_state().await?; + let genesis_state = self.populate_state(&mut state, self.genesis_params.clone())?; + let (state_root, store) = state.finalize()?; + self.write_car(state_root, genesis_state, out_path, store) + .await + } + + async fn write_car( + &self, + state_root: Cid, + genesis_state: GenesisOutput, + out_path: PathBuf, + store: MemoryBlockstore, + ) -> anyhow::Result<()> { + let file = tokio::fs::File::create(&out_path).await?; + + tracing::info!(state_root = state_root.to_string(), "state root"); + + let metadata = GenesisMetadata::new(state_root, genesis_state); + + let streamer = StateTreeStreamer::new(state_root, store); + let (metadata_cid, metadata_bytes) = derive_cid(&metadata)?; + tracing::info!("generated genesis metadata header cid: {}", metadata_cid); + + // create the target car header with the metadata cid as the only root + let car = CarHeader::new(vec![metadata_cid], 1); + + // create the stream to stream all the data into the car file + let mut streamer = tokio_stream::iter(vec![(metadata_cid, metadata_bytes)]).merge(streamer); + + let mut write = file.compat_write(); + car.write_stream_async(&mut Pin::new(&mut write), &mut streamer) + .await?; + + tracing::info!("written sealed genesis state to file"); + + Ok(()) + } + + async fn init_state(&self) -> anyhow::Result> { + let bundle = std::fs::read(&self.builtin_actors_path).with_context(|| { + format!( + "failed to read builtin actors bundle: {}", + self.builtin_actors_path.to_string_lossy() + ) + })?; + + let custom_actors_bundle = std::fs::read(&self.custom_actors_path).with_context(|| { + format!( + "failed to read custom actors bundle: {}", + self.custom_actors_path.to_string_lossy() + ) + })?; + + let store = MemoryBlockstore::new(); + + FvmGenesisState::new( + store, + Arc::new(MultiEngine::new(1)), + &bundle, + &custom_actors_bundle, + ) + .await + .context("failed to create genesis state") + } + + fn handle_ipc<'a, T, F: Fn(&'a Hardhat, &'a IpcParams) -> T>( + &'a self, + maybe_ipc: Option<&'a IpcParams>, + f: F, + ) -> anyhow::Result> { + // Only allocate IDs if the contracts are deployed. + match (maybe_ipc, &self.hardhat) { + (Some(ipc_params), Some(ref hardhat)) => Ok(Some(f(hardhat, ipc_params))), + (Some(_), None) => Err(anyhow!("ipc enabled but artifacts path not provided")), + _ => Ok(None), + } + } + + fn populate_state( + &self, + state: &mut FvmGenesisState, + genesis: Genesis, + ) -> anyhow::Result { + // NOTE: We could consider adding the chain ID to the interpreter + // and rejecting genesis if it doesn't match the expectation, + // but the Tendermint genesis file also has this field, and + // presumably Tendermint checks that its peers have the same. + let chain_id = chainid::from_str_hashed(&genesis.chain_name)?; + + // Convert validators to CometBFT power scale. + let validators = genesis + .validators + .iter() + .cloned() + .map(|vc| vc.map_power(|c| c.into_power(genesis.power_scale))) + .collect(); + + // Currently we just pass them back as they are, but later we should + // store them in the IPC actors; or in case of a snapshot restore them + // from the state. + let out = GenesisOutput { + chain_id, + timestamp: genesis.timestamp, + network_version: genesis.network_version, + circ_supply: circ_supply(&genesis), + base_fee: genesis.base_fee, + power_scale: genesis.power_scale, + validators, + }; + + // STAGE 0: Declare the built-in EVM contracts we'll have to deploy. + // ipc_entrypoints contains the external user facing contracts + // all_ipc_contracts contains ipc_entrypoints + util contracts + let (all_ipc_contracts, ipc_entrypoints) = self + .handle_ipc(genesis.ipc.as_ref(), |h, _| collect_contracts(h))? + .transpose()? + .unwrap_or((Vec::new(), EthContractMap::new())); + + // STAGE 1: First we initialize native built-in actors. + // System actor + state + .create_builtin_actor( + system::SYSTEM_ACTOR_CODE_ID, + system::SYSTEM_ACTOR_ID, + &system::State { + builtin_actors: state.manifest_data_cid, + }, + TokenAmount::zero(), + None, + ) + .context("failed to create system actor")?; + + // Init actor + let (init_state, addr_to_id) = init::State::new( + state.store(), + genesis.chain_name.clone(), + &genesis.accounts, + &ipc_entrypoints + .values() + .map(|c| c.actor_id) + .collect::>(), + all_ipc_contracts.len() as u64, + ) + .context("failed to create init state")?; + + state + .create_builtin_actor( + init::INIT_ACTOR_CODE_ID, + init::INIT_ACTOR_ID, + &init_state, + TokenAmount::zero(), + None, + ) + .context("failed to create init actor")?; + + // Cron actor + state + .create_builtin_actor( + cron::CRON_ACTOR_CODE_ID, + cron::CRON_ACTOR_ID, + &cron::State { + entries: vec![], // TODO: Maybe with the IPC. + }, + TokenAmount::zero(), + None, + ) + .context("failed to create cron actor")?; + + // Ethereum Account Manager (EAM) actor + state + .create_builtin_actor( + eam::EAM_ACTOR_CODE_ID, + eam::EAM_ACTOR_ID, + &EMPTY_ARR, + TokenAmount::zero(), + None, + ) + .context("failed to create EAM actor")?; + + // Burnt funds actor (it's just an account). + state + .create_builtin_actor( + account::ACCOUNT_ACTOR_CODE_ID, + burntfunds::BURNT_FUNDS_ACTOR_ID, + &account::State { + address: burntfunds::BURNT_FUNDS_ACTOR_ADDR, + }, + TokenAmount::zero(), + None, + ) + .context("failed to create burnt funds actor")?; + + // A placeholder for the reward actor, beause I don't think + // using the one in the builtin actors library would be appropriate. + // This effectively burns the miner rewards. Better than panicking. + state + .create_builtin_actor( + account::ACCOUNT_ACTOR_CODE_ID, + reward::REWARD_ACTOR_ID, + &account::State { + address: reward::REWARD_ACTOR_ADDR, + }, + TokenAmount::zero(), + None, + ) + .context("failed to create reward actor")?; + + // STAGE 1b: Then we initialize the in-repo custom actors. + + // Initialize the chain metadata actor which handles saving metadata about the chain + // (e.g. block hashes) which we can query. + let chainmetadata_state = fendermint_actor_chainmetadata::State::new( + &state.store(), + fendermint_actor_chainmetadata::DEFAULT_LOOKBACK_LEN, + )?; + state + .create_custom_actor( + fendermint_actor_chainmetadata::CHAINMETADATA_ACTOR_NAME, + chainmetadata::CHAINMETADATA_ACTOR_ID, + &chainmetadata_state, + TokenAmount::zero(), + None, + ) + .context("failed to create chainmetadata actor")?; + + let eam_state = fendermint_actor_eam::State::new( + state.store(), + PermissionModeParams::from(genesis.eam_permission_mode), + )?; + state + .replace_builtin_actor( + eam::EAM_ACTOR_NAME, + eam::EAM_ACTOR_ID, + fendermint_actor_eam::IPC_EAM_ACTOR_NAME, + &eam_state, + TokenAmount::zero(), + None, + ) + .context("failed to replace built in eam actor")?; + + // STAGE 2: Create non-builtin accounts which do not have a fixed ID. + + // The next ID is going to be _after_ the accounts, which have already been assigned an ID by the `Init` actor. + // The reason we aren't using the `init_state.next_id` is because that already accounted for the multisig accounts. + let mut next_id = init::FIRST_NON_SINGLETON_ADDR + addr_to_id.len() as u64; + + for a in genesis.accounts { + let balance = a.balance; + match a.meta { + ActorMeta::Account(acct) => { + state + .create_account_actor(acct, balance, &addr_to_id) + .context("failed to create account actor")?; + } + ActorMeta::Multisig(ms) => { + state + .create_multisig_actor(ms, balance, &addr_to_id, next_id) + .context("failed to create multisig actor")?; + next_id += 1; + } + } + } + + // STAGE 3: Initialize the FVM and create built-in FEVM actors. + + state + .init_exec_state( + out.timestamp, + out.network_version, + out.base_fee.clone(), + out.circ_supply.clone(), + out.chain_id.into(), + out.power_scale, + ) + .context("failed to init exec state")?; + + let maybe_ipc = self.handle_ipc(genesis.ipc.as_ref(), |hardhat, ipc_params| { + (hardhat, ipc_params) + })?; + if let Some((hardhat, ipc_params)) = maybe_ipc { + deploy_contracts( + all_ipc_contracts, + &ipc_entrypoints, + genesis.validators, + next_id, + state, + ipc_params, + hardhat, + )?; + } + + Ok(out) + } +} + +fn collect_contracts( + hardhat: &Hardhat, +) -> anyhow::Result<(Vec, EthContractMap)> { + let mut all_contracts = Vec::new(); + let mut top_level_contracts = EthContractMap::default(); + + top_level_contracts.extend(IPC_CONTRACTS.clone()); + + all_contracts.extend(top_level_contracts.keys()); + all_contracts.extend( + top_level_contracts + .values() + .flat_map(|c| c.facets.iter().map(|f| f.name)), + ); + // Collect dependencies of the main IPC actors. + let mut eth_libs = hardhat + .dependencies( + &all_contracts + .iter() + .map(|n| (contract_src(n), *n)) + .collect::>(), + ) + .context("failed to collect EVM contract dependencies")?; + + // Only keep library dependencies, not contracts with constructors. + eth_libs.retain(|(_, d)| !top_level_contracts.contains_key(d.as_str())); + Ok((eth_libs, top_level_contracts)) +} + +fn deploy_contracts( + ipc_contracts: Vec, + top_level_contracts: &EthContractMap, + validators: Vec>, + mut next_id: u64, + state: &mut FvmGenesisState, + ipc_params: &IpcParams, + hardhat: &Hardhat, +) -> anyhow::Result<()> { + let mut deployer = ContractDeployer::::new(hardhat, top_level_contracts); + + // Deploy Ethereum libraries. + for (lib_src, lib_name) in ipc_contracts { + deployer.deploy_library(state, &mut next_id, lib_src, &lib_name)?; + } + + // IPC Gateway actor. + let gateway_addr = { + use ipc::gateway::ConstructorParameters; + + let params = ConstructorParameters::new(ipc_params.gateway.clone(), validators) + .context("failed to create gateway constructor")?; + + let facets = deployer + .facets(ipc::gateway::CONTRACT_NAME) + .context("failed to collect gateway facets")?; + + deployer.deploy_contract(state, ipc::gateway::CONTRACT_NAME, (facets, params))? + }; + + // IPC SubnetRegistry actor. + { + use ipc::registry::ConstructorParameters; + + let mut facets = deployer + .facets(ipc::registry::CONTRACT_NAME) + .context("failed to collect registry facets")?; + + let getter_facet = facets.remove(0); + let manager_facet = facets.remove(0); + let rewarder_facet = facets.remove(0); + let checkpointer_facet = facets.remove(0); + let pauser_facet = facets.remove(0); + let diamond_loupe_facet = facets.remove(0); + let diamond_cut_facet = facets.remove(0); + let ownership_facet = facets.remove(0); + + debug_assert_eq!(facets.len(), 2, "SubnetRegistry has 2 facets of its own"); + + let params = ConstructorParameters { + gateway: gateway_addr, + getter_facet: getter_facet.facet_address, + manager_facet: manager_facet.facet_address, + rewarder_facet: rewarder_facet.facet_address, + pauser_facet: pauser_facet.facet_address, + checkpointer_facet: checkpointer_facet.facet_address, + diamond_cut_facet: diamond_cut_facet.facet_address, + diamond_loupe_facet: diamond_loupe_facet.facet_address, + ownership_facet: ownership_facet.facet_address, + subnet_getter_selectors: getter_facet.function_selectors, + subnet_manager_selectors: manager_facet.function_selectors, + subnet_rewarder_selectors: rewarder_facet.function_selectors, + subnet_checkpointer_selectors: checkpointer_facet.function_selectors, + subnet_pauser_selectors: pauser_facet.function_selectors, + subnet_actor_diamond_cut_selectors: diamond_cut_facet.function_selectors, + subnet_actor_diamond_loupe_selectors: diamond_loupe_facet.function_selectors, + subnet_actor_ownership_selectors: ownership_facet.function_selectors, + creation_privileges: 0, + }; + + deployer.deploy_contract(state, ipc::registry::CONTRACT_NAME, (facets, params))?; + } + + Ok(()) +} + +fn contract_src(name: &str) -> PathBuf { + PathBuf::from(format!("{name}.sol")) +} + +struct ContractDeployer<'a, DB> { + hardhat: &'a Hardhat, + top_contracts: &'a EthContractMap, + // Assign dynamic ID addresses to libraries, but use fixed addresses for the top level contracts. + lib_addrs: HashMap, + phantom_db: PhantomData, +} + +impl<'a, DB> ContractDeployer<'a, DB> +where + DB: Blockstore + 'static + Clone, +{ + pub fn new(hardhat: &'a Hardhat, top_contracts: &'a EthContractMap) -> Self { + Self { + hardhat, + top_contracts, + lib_addrs: Default::default(), + phantom_db: PhantomData, + } + } + + /// Deploy a library contract with a dynamic ID and no constructor. + fn deploy_library( + &mut self, + state: &mut FvmGenesisState, + next_id: &mut u64, + lib_src: impl AsRef, + lib_name: &str, + ) -> anyhow::Result<()> { + let fqn = self.hardhat.fqn(lib_src.as_ref(), lib_name); + + let bytecode = self + .hardhat + .bytecode(&lib_src, lib_name, &self.lib_addrs) + .with_context(|| format!("failed to load library bytecode {fqn}"))?; + + let eth_addr = state + .create_evm_actor(*next_id, bytecode) + .with_context(|| format!("failed to create library actor {fqn}"))?; + + let id_addr = et::Address::from(EthAddress::from_id(*next_id).0); + let eth_addr = et::Address::from(eth_addr.0); + + tracing::info!( + actor_id = next_id, + ?eth_addr, + ?id_addr, + fqn, + "deployed Ethereum library" + ); + + // We can use the masked ID here or the delegated address. + // Maybe the masked ID is quicker because it doesn't need to be resolved. + self.lib_addrs.insert(fqn, id_addr); + + *next_id += 1; + + Ok(()) + } + + /// Construct the bytecode of a top-level contract and deploy it with some constructor parameters. + fn deploy_contract( + &self, + state: &mut FvmGenesisState, + contract_name: &str, + constructor_params: T, + ) -> anyhow::Result + where + T: Tokenize, + { + let contract = self.top_contract(contract_name)?; + let contract_id = contract.actor_id; + let contract_src = contract_src(contract_name); + + let bytecode = self + .hardhat + .bytecode(contract_src, contract_name, &self.lib_addrs) + .with_context(|| format!("failed to load {contract_name} bytecode"))?; + + let eth_addr = state + .create_evm_actor_with_cons(contract_id, &contract.abi, bytecode, constructor_params) + .with_context(|| format!("failed to create {contract_name} actor"))?; + + let id_addr = et::Address::from(EthAddress::from_id(contract_id).0); + let eth_addr = et::Address::from(eth_addr.0); + + tracing::info!( + actor_id = contract_id, + ?eth_addr, + ?id_addr, + contract_name, + "deployed Ethereum contract" + ); + + // The Ethereum address is more usable inside the EVM than the ID address. + Ok(eth_addr) + } + + /// Collect Facet Cuts for the diamond pattern, where the facet address comes from already deployed library facets. + fn facets(&self, contract_name: &str) -> anyhow::Result> { + let contract = self.top_contract(contract_name)?; + let mut facet_cuts = Vec::new(); + + for facet in contract.facets.iter() { + let facet_name = facet.name; + let facet_src = contract_src(facet_name); + let facet_fqn = self.hardhat.fqn(&facet_src, facet_name); + + let facet_addr = self + .lib_addrs + .get(&facet_fqn) + .ok_or_else(|| anyhow!("facet {facet_name} has not been deployed"))?; + + let method_sigs = facet + .abi + .functions() + .filter(|f| f.signature() != "init(bytes)") + .map(|f| f.short_signature()) + .collect(); + + let facet_cut = FacetCut { + facet_address: *facet_addr, + action: 0, // Add + function_selectors: method_sigs, + }; + + facet_cuts.push(facet_cut); + } + + Ok(facet_cuts) + } + + fn top_contract(&self, contract_name: &str) -> anyhow::Result<&EthContract> { + self.top_contracts + .get(contract_name) + .ok_or_else(|| anyhow!("unknown top contract name: {contract_name}")) + } +} + +/// Sum of balances in the genesis accounts. +fn circ_supply(g: &Genesis) -> TokenAmount { + g.accounts + .iter() + .fold(TokenAmount::zero(), |s, a| s + a.balance.clone()) +} + +#[cfg(any(feature = "test-util", test))] +pub async fn create_test_genesis_state( + bundle_path: PathBuf, + custom_actors_bundle_path: PathBuf, + genesis_params: Genesis, + maybe_ipc_path: Option, +) -> anyhow::Result<(FvmGenesisState, GenesisOutput)> { + let mut builder = GenesisBuilder::new(bundle_path, custom_actors_bundle_path, genesis_params); + if let Some(p) = maybe_ipc_path { + builder = builder.with_ipc_system_contracts(p); + } + + let mut state = builder.init_state().await?; + let out = builder.populate_state(&mut state, builder.genesis_params.clone())?; + Ok((state, out)) +} + +#[cfg(test)] +mod tests { + use crate::genesis::GenesisAppState; + + #[test] + fn test_compression() { + let bytes = (0..10000) + .map(|_| rand::random::()) + .collect::>(); + + let s = GenesisAppState::v1(bytes.clone()) + .compress_and_encode() + .unwrap(); + let recovered = GenesisAppState::decode_and_decompress(&s).unwrap(); + + assert_eq!(recovered, bytes); + } +} diff --git a/fendermint/vm/interpreter/src/lib.rs b/fendermint/vm/interpreter/src/lib.rs index 42dab76b8..40868b6cd 100644 --- a/fendermint/vm/interpreter/src/lib.rs +++ b/fendermint/vm/interpreter/src/lib.rs @@ -5,28 +5,12 @@ use async_trait::async_trait; pub mod bytes; pub mod chain; pub mod fvm; +pub mod genesis; pub mod signed; #[cfg(feature = "arb")] mod arb; -/// Initialize the chain state. -/// -/// This could be from the original genesis file, or perhaps a checkpointed snapshot. -#[async_trait] -pub trait GenesisInterpreter: Sync + Send { - type State: Send; - type Genesis: Send; - type Output; - - /// Initialize the chain. - async fn init( - &self, - state: Self::State, - genesis: Self::Genesis, - ) -> anyhow::Result<(Self::State, Self::Output)>; -} - /// Prepare and process transaction proposals. #[async_trait] pub trait ProposalInterpreter: Sync + Send { diff --git a/fendermint/vm/interpreter/src/signed.rs b/fendermint/vm/interpreter/src/signed.rs index 847d790a5..0998fb27e 100644 --- a/fendermint/vm/interpreter/src/signed.rs +++ b/fendermint/vm/interpreter/src/signed.rs @@ -14,7 +14,7 @@ use serde::Serialize; use crate::{ fvm::{FvmApplyRet, FvmCheckRet, FvmMessage}, - CheckInterpreter, ExecInterpreter, GenesisInterpreter, QueryInterpreter, + CheckInterpreter, ExecInterpreter, QueryInterpreter, }; /// Message validation failed due to an invalid signature. @@ -230,21 +230,3 @@ where self.inner.query(state, qry).await } } - -#[async_trait] -impl GenesisInterpreter for SignedMessageInterpreter -where - I: GenesisInterpreter, -{ - type State = I::State; - type Genesis = I::Genesis; - type Output = I::Output; - - async fn init( - &self, - state: Self::State, - genesis: Self::Genesis, - ) -> anyhow::Result<(Self::State, Self::Output)> { - self.inner.init(state, genesis).await - } -} diff --git a/fendermint/vm/snapshot/Cargo.toml b/fendermint/vm/snapshot/Cargo.toml index 4183249fd..25b5c1e1b 100644 --- a/fendermint/vm/snapshot/Cargo.toml +++ b/fendermint/vm/snapshot/Cargo.toml @@ -43,7 +43,7 @@ fendermint_testing = { path = "../../testing", features = ["arb"], optional = tr [dev-dependencies] fvm = { workspace = true } fendermint_testing = { path = "../../testing", features = ["golden"] } -fendermint_vm_interpreter = { path = "../interpreter", features = ["bundle"] } +fendermint_vm_interpreter = { path = "../interpreter", features = ["bundle", "test-util"] } fendermint_vm_genesis = { path = "../genesis", features = ["arb"] } fendermint_vm_snapshot = { path = ".", features = ["arb"] } diff --git a/fendermint/vm/snapshot/src/manager.rs b/fendermint/vm/snapshot/src/manager.rs index 495219067..4a6f490f8 100644 --- a/fendermint/vm/snapshot/src/manager.rs +++ b/fendermint/vm/snapshot/src/manager.rs @@ -309,21 +309,16 @@ fn move_or_copy(from: &Path, to: &Path) -> anyhow::Result<()> { #[cfg(test)] mod tests { - use std::{sync::Arc, time::Duration}; + use std::time::Duration; use async_stm::{atomically, retry}; use fendermint_vm_genesis::Genesis; - use fendermint_vm_interpreter::{ - fvm::{ - bundle::{bundle_path, contracts_path, custom_actors_bundle_path}, - state::{snapshot::Snapshot, FvmGenesisState, FvmStateParams}, - store::memory::MemoryBlockstore, - upgrades::UpgradeScheduler, - FvmMessageInterpreter, - }, - GenesisInterpreter, + use fendermint_vm_interpreter::fvm::{ + bundle::{bundle_path, contracts_path, custom_actors_bundle_path}, + state::{snapshot::Snapshot, FvmStateParams}, + store::memory::MemoryBlockstore, }; - use fvm::engine::MultiEngine; + use fendermint_vm_interpreter::genesis::create_test_genesis_state; use quickcheck::Arbitrary; use crate::{manager::SnapshotParams, manifest, PARTS_DIR_NAME}; @@ -446,33 +441,22 @@ mod tests { let mut g = quickcheck::Gen::new(5); let genesis = Genesis::arbitrary(&mut g); - let bundle = std::fs::read(bundle_path()).expect("failed to read bundle"); - let custom_actors_bundle = std::fs::read(custom_actors_bundle_path()) - .expect("failed to read custom actors bundle"); - let multi_engine = Arc::new(MultiEngine::default()); - - let store = MemoryBlockstore::new(); - let state = - FvmGenesisState::new(store.clone(), multi_engine, &bundle, &custom_actors_bundle) - .await - .expect("failed to create state"); - - let interpreter = FvmMessageInterpreter::new( - mock_client(), - None, - contracts_path(), - 1.05, - 1.05, - false, - UpgradeScheduler::new(), - ); - - let (state, out) = interpreter - .init(state, genesis) - .await - .expect("failed to init genesis"); - - let state_root = state.commit().expect("failed to commit"); + let maybe_contract_path = genesis.ipc.as_ref().map(|_| contracts_path()); + let (state, out) = create_test_genesis_state( + bundle_path(), + custom_actors_bundle_path(), + genesis, + maybe_contract_path, + ) + .await + .expect("cannot create genesis state"); + let store = state.store().clone(); + // unwrap_or_else + panic is used because the return type is not Result, also the exec state + // does not implement debug, which expect cannot be used + let state = state + .into_exec_state() + .unwrap_or_else(|_| panic!("cannot create exec state")); + let (state_root, _, _) = state.commit().expect("failed to commit"); let state_params = FvmStateParams { state_root, diff --git a/infra/fendermint/Makefile.toml b/infra/fendermint/Makefile.toml index 8e7549a86..42962658e 100644 --- a/infra/fendermint/Makefile.toml +++ b/infra/fendermint/Makefile.toml @@ -73,6 +73,7 @@ CMT_DIR = "${BASE_DIR}/${NODE_NAME}/cometbft" ENV_FILE = "${BASE_DIR}/.env" GENESIS_FILE = "${BASE_DIR}/genesis.json" +SEALED_GENESIS = "/data/sealed_genesis.car" KEYS_SUBDIR = "keys" VALIDATOR_KEY_NAME = "validator_key" @@ -122,6 +123,7 @@ echo - CometBFT directory: ${CMT_DIR} echo - Fendermint directory: ${FM_DIR} echo - Keys directory: ${KEYS_DIR} echo - Genesis file: ${GENESIS_FILE} +echo - Sealed Genesis: ${SEALED_GENESIS} echo - Validator Private key: ${VALIDATOR_PRIV_KEY_PATH} echo - Network: ${NETWORK_NAME} echo - CometBFT container: ${CMT_CONTAINER_NAME} @@ -131,6 +133,7 @@ echo echo 4 nodes testnet layout: echo - IPC directory: ${BASE_DIR} echo - Genesis file: ${GENESIS_FILE} +echo - Sealed Genesis: ${SEALED_GENESIS} echo - Network: ${NETWORK_NAME} echo """ diff --git a/infra/fendermint/scripts/genesis.toml b/infra/fendermint/scripts/genesis.toml index 28a424a2d..a182836be 100644 --- a/infra/fendermint/scripts/genesis.toml +++ b/infra/fendermint/scripts/genesis.toml @@ -35,7 +35,11 @@ env = { "CMD" = """genesis --genesis-file /data/genesis.json ipc gateway --subne --majority-percentage 67 \ """ } +[tasks.genesis-seal] +extend = "fendermint-tool" +env = { "CMD" = "genesis --genesis-file /data/genesis.json ipc seal-genesis --builtin-actors-path /fendermint/bundle.car --custom-actors-path /fendermint/custom_actors_bundle.car --artifacts-path /fendermint/contracts --output-path ${SEALED_GENESIS}" } + [tasks.genesis-write] extend = "fendermint-tool" -env = { "CMD" = "genesis --genesis-file /data/genesis.json into-tendermint --out /data/genesis.committed.json" } +env = { "CMD" = "genesis --genesis-file /data/genesis.json into-tendermint --app-state ${SEALED_GENESIS} --out /data/genesis.committed.json" } script.post = "cp ${BASE_DIR}/genesis.committed.json ${CMT_DIR}/config/genesis.json" diff --git a/infra/fendermint/scripts/subnet.toml b/infra/fendermint/scripts/subnet.toml index 9a30db494..583718ab0 100644 --- a/infra/fendermint/scripts/subnet.toml +++ b/infra/fendermint/scripts/subnet.toml @@ -103,6 +103,7 @@ dependencies = [ dependencies = [ "subnet-fetch-genesis", "subnet-genesis-set-eam-permissions", + "genesis-seal", "genesis-write", "fendermint-new-network-key", ] diff --git a/infra/fendermint/scripts/testnode.toml b/infra/fendermint/scripts/testnode.toml index 3477c8039..503f116c6 100644 --- a/infra/fendermint/scripts/testnode.toml +++ b/infra/fendermint/scripts/testnode.toml @@ -6,7 +6,6 @@ workspace = false dependencies = [ "create-log-volume", "testnode-down", - "fendermint-pull", "testnode-init", "docker-network-create", "cometbft-init", @@ -71,6 +70,7 @@ dependencies = [ "genesis-new-accounts", "genesis-add-validator", "genesis-new-gateway", + "genesis-seal", "genesis-write", "testnode-export-keys", ]