diff --git a/Cargo.lock b/Cargo.lock index 3bf6b8d8d2..f584133e45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2893,6 +2893,7 @@ dependencies = [ "cid", "fendermint_abci", "fendermint_actor_gas_market_eip1559", + "fendermint_actors_api", "fendermint_app_options", "fendermint_app_settings", "fendermint_crypto", diff --git a/fendermint/app/Cargo.toml b/fendermint/app/Cargo.toml index 27bf1db702..9cb9ed118b 100644 --- a/fendermint/app/Cargo.toml +++ b/fendermint/app/Cargo.toml @@ -42,6 +42,7 @@ tracing-subscriber = { workspace = true } literally = { workspace = true } fendermint_abci = { path = "../abci" } +fendermint_actors_api = { path = "../actors/api" } fendermint_app_options = { path = "./options" } fendermint_app_settings = { path = "./settings" } fendermint_crypto = { path = "../crypto" } diff --git a/fendermint/app/options/src/genesis.rs b/fendermint/app/options/src/genesis.rs index 04cf9b0700..f2133a049a 100644 --- a/fendermint/app/options/src/genesis.rs +++ b/fendermint/app/options/src/genesis.rs @@ -176,7 +176,7 @@ pub struct SealGenesisArgs { /// 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, + pub artifacts_path: PathBuf, /// The sealed genesis state output path, i.e. finalized genesis state CAR file dump path #[arg(long, short)] diff --git a/fendermint/app/src/app.rs b/fendermint/app/src/app.rs index 2b6178fcb3..e4f996e610 100644 --- a/fendermint/app/src/app.rs +++ b/fendermint/app/src/app.rs @@ -3,12 +3,22 @@ use std::future::Future; use std::sync::Arc; +use crate::observe::{ + BlockCommitted, BlockProposalEvaluated, BlockProposalReceived, BlockProposalSent, Message, + MpoolReceived, +}; +use crate::validators::ValidatorCache; +use crate::AppExitCode; +use crate::BlockHeight; +use crate::{tmconv::*, VERSION}; use anyhow::{anyhow, Context, Result}; use async_stm::{atomically, atomically_or_err}; use async_trait::async_trait; use cid::Cid; use fendermint_abci::util::take_until_max_size; use fendermint_abci::{AbciResult, Application}; +use fendermint_actors_api::gas_market::Reading; +use fendermint_crypto::PublicKey; use fendermint_storage::{ Codec, Encode, KVCollection, KVRead, KVReadable, KVStore, KVWritable, KVWrite, }; @@ -41,18 +51,9 @@ use num_traits::Zero; use serde::{Deserialize, Serialize}; use tendermint::abci::request::CheckTxKind; use tendermint::abci::{request, response}; -use tendermint_rpc::Client; +use tendermint::consensus::params::Params as TendermintConsensusParams; use tracing::instrument; -use crate::observe::{ - BlockCommitted, BlockProposalEvaluated, BlockProposalReceived, BlockProposalSent, Message, - MpoolReceived, -}; -use crate::validators::ValidatorTracker; -use crate::AppExitCode; -use crate::BlockHeight; -use crate::{tmconv::*, VERSION}; - #[derive(Serialize)] #[repr(u8)] pub enum AppStoreKey { @@ -117,11 +118,10 @@ pub struct AppConfig { /// Handle ABCI requests. #[derive(Clone)] -pub struct App +pub struct App where SS: Blockstore + Clone + 'static, S: KVStore, - C: Client, { /// Database backing all key-value operations. db: Arc, @@ -162,13 +162,11 @@ where /// /// Zero means unlimited. state_hist_size: u64, - /// Tracks the validator - validators: ValidatorTracker, - /// The cometbft client - client: C, + /// Caches the validators. + validators_cache: Arc>>, } -impl App +impl App where S: KVStore + Codec @@ -177,7 +175,6 @@ where + Codec, DB: KVWritable + KVReadable + Clone + 'static, SS: Blockstore + Clone + 'static, - C: Client + Clone, { pub fn new( config: AppConfig, @@ -186,7 +183,6 @@ where interpreter: I, chain_env: ChainEnv, snapshots: Option, - client: C, ) -> Result { let app = Self { db: Arc::new(db), @@ -201,15 +197,14 @@ where snapshots, exec_state: Arc::new(tokio::sync::Mutex::new(None)), check_state: Arc::new(tokio::sync::Mutex::new(None)), - validators: ValidatorTracker::new(client.clone()), - client, + validators_cache: Arc::new(tokio::sync::Mutex::new(None)), }; app.init_committed_state()?; Ok(app) } } -impl App +impl App where S: KVStore + Codec @@ -218,7 +213,6 @@ where + Codec, DB: KVWritable + KVReadable + 'static + Clone, SS: Blockstore + 'static + Clone, - C: Client, { /// Get an owned clone of the state store. fn state_store_clone(&self) -> SS { @@ -244,6 +238,7 @@ where chain_id: 0, power_scale: 0, app_version: 0, + consensus_params: None, }, }; self.set_committed_state(state)?; @@ -294,6 +289,31 @@ where .context("commit failed") } + /// Diff our current consensus params with new values, and return Some with the final params + /// if they differ (and therefore a consensus layer update is necessary). + fn maybe_update_app_state( + &self, + gas_market: &Reading, + ) -> Result> { + let mut state = self.committed_state()?; + let current = state + .state_params + .consensus_params + .ok_or_else(|| anyhow!("no current consensus params in state"))?; + + if current.block.max_gas == gas_market.block_gas_limit as i64 { + return Ok(None); // No update necessary. + } + + // Proceeding with update. + let mut updated = current; + updated.block.max_gas = gas_market.block_gas_limit as i64; + state.state_params.consensus_params = Some(updated.clone()); + self.set_committed_state(state)?; + + Ok(Some(updated)) + } + /// Put the execution state during block execution. Has to be empty. async fn put_exec_state(&self, state: FvmExecState) { let mut guard = self.exec_state.lock().await; @@ -397,6 +417,37 @@ where _ => Err(anyhow!("invalid app state json")), } } + + /// Replaces the current validators cache with a new one. + async fn refresh_validators_cache(&self) -> Result<()> { + let mut state = self + .read_only_view(None)? + .ok_or_else(|| anyhow!("exec state should be present"))?; + + let mut cache = self.validators_cache.lock().await; + *cache = Some(ValidatorCache::new_from_state(&mut state)?); + Ok(()) + } + + /// Retrieves a validator from the cache, initializing it if necessary. + async fn get_validator_from_cache(&self, id: &tendermint::account::Id) -> Result { + let mut cache = self.validators_cache.lock().await; + + // If cache is not initialized, update it from the state + if cache.is_none() { + let mut state = self + .read_only_view(None)? + .ok_or_else(|| anyhow!("exec state should be present"))?; + + *cache = Some(ValidatorCache::new_from_state(&mut state)?); + } + + // Retrieve the validator from the cache + cache + .as_ref() + .context("Validator cache is not available")? + .get_validator(id) + } } // NOTE: The `Application` interface doesn't allow failures at the moment. The protobuf @@ -405,7 +456,7 @@ where // the `tower-abci` library would throw an exception when it tried to convert a // `Response::Exception` into a `ConsensusResponse` for example. #[async_trait] -impl Application for App +impl Application for App where S: KVStore + Codec @@ -436,7 +487,6 @@ where Query = BytesMessageQuery, Output = BytesMessageQueryRes, >, - C: Client + Sync + Clone, { /// Provide information about the ABCI application. async fn info(&self, _request: request::Info) -> AbciResult { @@ -463,7 +513,11 @@ where // Make it easy to spot any discrepancies between nodes. tracing::info!(genesis_hash = genesis_hash.to_string(), "genesis"); - let (validators, state_params) = read_genesis_car(genesis_bytes, &self.state_store).await?; + let (validators, mut state_params) = + read_genesis_car(genesis_bytes, &self.state_store).await?; + + state_params.consensus_params = Some(request.consensus_params); + let validators = to_validator_updates(validators).context("failed to convert validators")?; @@ -488,7 +542,7 @@ where }; let response = response::InitChain { - consensus_params: None, + consensus_params: None, // not updating the proposed consensus params validators, app_hash: app_state.app_hash(), }; @@ -732,8 +786,7 @@ where state_params.timestamp = to_timestamp(request.header.time); let validator = self - .validators - .get_validator(&request.header.proposer_address, block_height) + .get_validator_from_cache(&request.header.proposer_address) .await?; let state = FvmExecState::new(db, self.multi_engine.as_ref(), block_height, state_params) @@ -796,7 +849,7 @@ where // End the interpreter for this block. let EndBlockOutput { power_updates, - block_gas_limit: new_block_gas_limit, + gas_market, events, } = self .modify_exec_state(|s| self.interpreter.end(s)) @@ -807,22 +860,15 @@ where let validator_updates = to_validator_updates(power_updates.0).context("failed to convert validator updates")?; - // If the block gas limit has changed, we need to update the consensus layer so it can - // pack subsequent blocks against the new limit. - let consensus_param_updates = { - let mut consensus_params = self - .client - .consensus_params(tendermint::block::Height::try_from(request.height)?) - .await? - .consensus_params; - - if consensus_params.block.max_gas != new_block_gas_limit as i64 { - consensus_params.block.max_gas = new_block_gas_limit as i64; - Some(consensus_params) - } else { - None - } - }; + // Replace the validator cache if the validator set has changed. + if !validator_updates.is_empty() { + self.refresh_validators_cache().await?; + } + + // Maybe update the app state with the new block gas limit. + let consensus_param_updates = self + .maybe_update_app_state(&gas_market) + .context("failed to update block gas limit")?; let ret = response::EndBlock { validator_updates, diff --git a/fendermint/app/src/cmd/genesis.rs b/fendermint/app/src/cmd/genesis.rs index 65de4d2d5f..4e9a5e8a94 100644 --- a/fendermint/app/src/cmd/genesis.rs +++ b/fendermint/app/src/cmd/genesis.rs @@ -305,16 +305,13 @@ 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( + let builder = GenesisBuilder::new( args.builtin_actors_path.clone(), args.custom_actors_path.clone(), + args.artifacts_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 } diff --git a/fendermint/app/src/cmd/run.rs b/fendermint/app/src/cmd/run.rs index 9ddb9cf166..a412b39a44 100644 --- a/fendermint/app/src/cmd/run.rs +++ b/fendermint/app/src/cmd/run.rs @@ -298,7 +298,7 @@ async fn run(settings: Settings) -> anyhow::Result<()> { None }; - let app: App<_, _, AppStore, _, _> = App::new( + let app: App<_, _, AppStore, _> = App::new( AppConfig { app_namespace: ns.app, state_hist_namespace: ns.state_hist, @@ -314,7 +314,6 @@ async fn run(settings: Settings) -> anyhow::Result<()> { parent_finality_votes: parent_finality_votes.clone(), }, snapshots, - tendermint_client.clone(), )?; if let Some((agent_proxy, config)) = ipc_tuple { diff --git a/fendermint/app/src/ipc.rs b/fendermint/app/src/ipc.rs index 8f187a7873..eb5c221307 100644 --- a/fendermint/app/src/ipc.rs +++ b/fendermint/app/src/ipc.rs @@ -15,7 +15,6 @@ use fvm_ipld_blockstore::Blockstore; use std::sync::Arc; use serde::{Deserialize, Serialize}; -use tendermint_rpc::Client; /// All the things that can be voted on in a subnet. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -25,18 +24,17 @@ pub enum AppVote { } /// Queries the LATEST COMMITTED parent finality from the storage -pub struct AppParentFinalityQuery +pub struct AppParentFinalityQuery where SS: Blockstore + Clone + 'static, S: KVStore, - C: Client, { /// The app to get state - app: App, + app: App, gateway_caller: GatewayCaller>>, } -impl AppParentFinalityQuery +impl AppParentFinalityQuery where S: KVStore + Codec @@ -45,9 +43,8 @@ where + Codec, DB: KVWritable + KVReadable + 'static + Clone, SS: Blockstore + 'static + Clone, - C: Client, { - pub fn new(app: App) -> Self { + pub fn new(app: App) -> Self { Self { app, gateway_caller: GatewayCaller::default(), @@ -65,7 +62,7 @@ where } } -impl ParentFinalityStateQuery for AppParentFinalityQuery +impl ParentFinalityStateQuery for AppParentFinalityQuery where S: KVStore + Codec @@ -74,7 +71,6 @@ where + Codec, DB: KVWritable + KVReadable + 'static + Clone, SS: Blockstore + 'static + Clone, - C: Client, { fn get_latest_committed_finality(&self) -> anyhow::Result> { self.with_exec_state(|mut exec_state| { diff --git a/fendermint/app/src/validators.rs b/fendermint/app/src/validators.rs index 5870b71606..3987d44373 100644 --- a/fendermint/app/src/validators.rs +++ b/fendermint/app/src/validators.rs @@ -1,69 +1,49 @@ // Copyright 2022-2024 Protocol Labs // SPDX-License-Identifier: Apache-2.0, MIT -//! Tracks the validator id from tendermint to their corresponding public key. - -use anyhow::anyhow; +use anyhow::{anyhow, Ok, Result}; use fendermint_crypto::PublicKey; -use fvm_shared::clock::ChainEpoch; +use fendermint_vm_interpreter::fvm::state::ipc::GatewayCaller; +use fendermint_vm_interpreter::fvm::state::FvmExecState; use std::collections::HashMap; -use std::sync::{Arc, RwLock}; -use tendermint::block::Height; -use tendermint_rpc::{Client, Paging}; - -#[derive(Clone)] -pub(crate) struct ValidatorTracker { - client: C, - public_keys: Arc>>, -} - -impl ValidatorTracker { - pub fn new(client: C) -> Self { - Self { - client, - public_keys: Arc::new(RwLock::new(HashMap::new())), - } - } -} - -impl ValidatorTracker { - /// Get the public key of the validator by id. Note that the id is expected to be a validator. - pub async fn get_validator( - &self, - id: &tendermint::account::Id, - height: ChainEpoch, - ) -> anyhow::Result { - if let Some(key) = self.get_from_cache(id) { - return Ok(key); - } - - // this means validators have changed, re-pull all validators - let height = Height::try_from(height)?; - let response = self.client.validators(height, Paging::All).await?; - let mut new_validators = HashMap::new(); - let mut pubkey = None; - for validator in response.validators { - let p = validator.pub_key.secp256k1().unwrap(); - let compressed = p.to_encoded_point(true); - let b = compressed.as_bytes(); - let key = PublicKey::parse_slice(b, None)?; +use tendermint::account::Id as TendermintId; +use tendermint::PublicKey as TendermintPubKey; - if *id == validator.address { - pubkey = Some(key); - } +use fvm_ipld_blockstore::Blockstore; - new_validators.insert(validator.address, key); - } - - *self.public_keys.write().unwrap() = new_validators; +#[derive(Clone)] +// Tracks the validator ID from Tendermint to their corresponding public key. +pub(crate) struct ValidatorCache { + map: HashMap, +} - // cannot find the validator, this should not have happened usually - pubkey.ok_or_else(|| anyhow!("{} not validator", id)) +impl ValidatorCache { + pub fn new_from_state(state: &mut FvmExecState) -> Result + where + SS: Blockstore + Clone + 'static, + { + let gateway = GatewayCaller::default(); + let (_, validators) = gateway.current_power_table(state)?; + + let map = validators + .iter() + .map(|v| { + let tendermint_pub_key: TendermintPubKey = + TendermintPubKey::try_from(v.public_key.clone())?; + let id = TendermintId::from(tendermint_pub_key); + let key = *v.public_key.public_key(); + Ok((id, key)) + }) + .collect::, _>>()?; + + Ok(Self { map }) } - fn get_from_cache(&self, id: &tendermint::account::Id) -> Option { - let keys = self.public_keys.read().unwrap(); - keys.get(id).copied() + pub fn get_validator(&self, id: &tendermint::account::Id) -> Result { + self.map + .get(id) + .cloned() + .ok_or_else(|| anyhow!("validator not found")) } } diff --git a/fendermint/testing/contract-test/src/lib.rs b/fendermint/testing/contract-test/src/lib.rs index 8d6e3f9cf6..071ae6677a 100644 --- a/fendermint/testing/contract-test/src/lib.rs +++ b/fendermint/testing/contract-test/src/lib.rs @@ -33,13 +33,13 @@ pub async fn create_test_exec_state( )> { let bundle_path = bundle_path(); let custom_actors_bundle_path = custom_actors_bundle_path(); - let maybe_contract_path = genesis.ipc.as_ref().map(|_| contracts_path()); + let artifacts_path = contracts_path(); let (state, out) = create_test_genesis_state( bundle_path, custom_actors_bundle_path, + artifacts_path, genesis, - maybe_contract_path, ) .await?; let store = state.store().clone(); @@ -85,6 +85,7 @@ where chain_id: out.chain_id.into(), power_scale: out.power_scale, app_version: 0, + consensus_params: None, }; Ok(Self { diff --git a/fendermint/vm/genesis/src/lib.rs b/fendermint/vm/genesis/src/lib.rs index 6e04c0a592..b01e19e62d 100644 --- a/fendermint/vm/genesis/src/lib.rs +++ b/fendermint/vm/genesis/src/lib.rs @@ -256,6 +256,27 @@ pub mod ipc { pub majority_percentage: u8, pub active_validators_limit: u16, } + + impl Default for GatewayParams { + fn default() -> Self { + // Default values are taken from here contracts/tasks/deploy-gateway.ts. + Self { + subnet_id: SubnetID::default(), + bottom_up_check_period: 10, + majority_percentage: 66, + active_validators_limit: 100, + } + } + } + + impl GatewayParams { + pub fn new(subnet_id: SubnetID) -> Self { + Self { + subnet_id, + ..Default::default() + } + } + } } #[cfg(test)] diff --git a/fendermint/vm/interpreter/src/arb.rs b/fendermint/vm/interpreter/src/arb.rs index 991e403cb4..4ae411946b 100644 --- a/fendermint/vm/interpreter/src/arb.rs +++ b/fendermint/vm/interpreter/src/arb.rs @@ -21,6 +21,7 @@ impl Arbitrary for FvmStateParams { .into(), power_scale: *g.choose(&[-1, 0, 3]).unwrap(), app_version: *g.choose(&[0, 1, 2]).unwrap(), + consensus_params: None, } } } diff --git a/fendermint/vm/interpreter/src/fvm/checkpoint.rs b/fendermint/vm/interpreter/src/fvm/checkpoint.rs index 6f077de653..cbe1cf5a99 100644 --- a/fendermint/vm/interpreter/src/fvm/checkpoint.rs +++ b/fendermint/vm/interpreter/src/fvm/checkpoint.rs @@ -331,7 +331,7 @@ pub fn emit_trace_if_check_checkpoint_finalized( where DB: Blockstore + Clone, { - if !gateway.enabled(state)? { + if !gateway.is_anchored(state)? { return Ok(()); } @@ -372,10 +372,6 @@ fn should_create_checkpoint( where DB: Blockstore + Clone, { - if !gateway.enabled(state)? { - return Ok(None); - } - let id = gateway.subnet_id(state)?; let is_root = id.route.is_empty(); diff --git a/fendermint/vm/interpreter/src/fvm/exec.rs b/fendermint/vm/interpreter/src/fvm/exec.rs index 803d54bc4a..e123e106a1 100644 --- a/fendermint/vm/interpreter/src/fvm/exec.rs +++ b/fendermint/vm/interpreter/src/fvm/exec.rs @@ -5,12 +5,13 @@ use super::{ checkpoint::{self, PowerUpdates}, observe::{CheckpointFinalized, MsgExec, MsgExecPurpose}, state::FvmExecState, - BlockGasLimit, FvmMessage, FvmMessageInterpreter, + FvmMessage, FvmMessageInterpreter, }; use crate::fvm::activity::ValidatorActivityTracker; use crate::ExecInterpreter; use anyhow::Context; use async_trait::async_trait; +use fendermint_actors_api::gas_market::Reading; use fendermint_vm_actor_interface::{chainmetadata, cron, system}; use fvm::executor::ApplyRet; use fvm_ipld_blockstore::Blockstore; @@ -39,7 +40,7 @@ pub struct FvmApplyRet { pub struct EndBlockOutput { pub power_updates: PowerUpdates, - pub block_gas_limit: BlockGasLimit, + pub gas_market: Reading, /// The end block events to be recorded pub events: BlockEndEvents, } @@ -269,7 +270,7 @@ where let ret = EndBlockOutput { power_updates: updates, - block_gas_limit: next_gas_market.block_gas_limit, + gas_market: next_gas_market, events: block_end_events, }; Ok((state, ret)) diff --git a/fendermint/vm/interpreter/src/fvm/state/exec.rs b/fendermint/vm/interpreter/src/fvm/state/exec.rs index d849328d7f..49666634c0 100644 --- a/fendermint/vm/interpreter/src/fvm/state/exec.rs +++ b/fendermint/vm/interpreter/src/fvm/state/exec.rs @@ -30,6 +30,8 @@ use fvm_shared::{ }; use serde::{Deserialize, Serialize}; use serde_with::serde_as; +use std::fmt; +use tendermint::consensus::params::Params as TendermintConsensusParams; pub type BlockHash = [u8; 32]; @@ -40,7 +42,7 @@ pub type ExecResult = anyhow::Result<(ApplyRet, ActorAddressMap)>; /// Parts of the state which evolve during the lifetime of the chain. #[serde_as] -#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Eq, PartialEq)] pub struct FvmStateParams { /// Root CID of the actor state map. #[serde_as(as = "IsHumanReadable")] @@ -66,6 +68,38 @@ pub struct FvmStateParams { /// The application protocol version. #[serde(default)] pub app_version: u64, + /// Tendermint consensus params. + pub consensus_params: Option, +} + +/// Custom implementation of Debug to exclude `consensus_params` from the debug output +/// if it is `None`. This ensures consistency between the debug output and JSON/CBOR +/// serialization, which omits `None` values for `consensus_params`. See: fendermint/vm/interpreter/tests/golden.rs. +/// +/// This implementation is temporary and should be removed once `consensus_params` is +/// no longer part of `FvmStateParams`. +/// +/// @TODO: Remove this implementation when `consensus_params` is deprecated. +impl fmt::Debug for FvmStateParams { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut ds = f.debug_struct("FvmStateParams"); + + ds.field("state_root", &self.state_root) + .field("timestamp", &self.timestamp) + .field("network_version", &self.network_version) + .field("base_fee", &self.base_fee) + .field("circ_supply", &self.circ_supply) + .field("chain_id", &self.chain_id) + .field("power_scale", &self.power_scale) + .field("app_version", &self.app_version); + + // Only include `consensus_params` in the debug output if it is `Some`. + if let Some(ref params) = self.consensus_params { + ds.field("consensus_params", params); + } + + ds.finish() + } } /// Parts of the state which can be updated by message execution, apart from the actor state. diff --git a/fendermint/vm/interpreter/src/fvm/state/genesis.rs b/fendermint/vm/interpreter/src/fvm/state/genesis.rs index 7959bf47e7..24ff25e093 100644 --- a/fendermint/vm/interpreter/src/fvm/state/genesis.rs +++ b/fendermint/vm/interpreter/src/fvm/state/genesis.rs @@ -154,6 +154,7 @@ where chain_id, power_scale, app_version: 0, + consensus_params: None, }; let exec_state = diff --git a/fendermint/vm/interpreter/src/fvm/state/ipc.rs b/fendermint/vm/interpreter/src/fvm/state/ipc.rs index 8fd1538321..9a91429b30 100644 --- a/fendermint/vm/interpreter/src/fvm/state/ipc.rs +++ b/fendermint/vm/interpreter/src/fvm/state/ipc.rs @@ -79,16 +79,8 @@ impl GatewayCaller { } impl GatewayCaller { - /// Check that IPC is configured in this deployment. - pub fn enabled(&self, state: &mut FvmExecState) -> anyhow::Result { - match state.state_tree_mut().get_actor(GATEWAY_ACTOR_ID)? { - None => Ok(false), - Some(a) => Ok(!state.builtin_actors().is_placeholder_actor(&a.code)), - } - } - /// Return true if the current subnet is the root subnet. - pub fn is_root(&self, state: &mut FvmExecState) -> anyhow::Result { + pub fn is_anchored(&self, state: &mut FvmExecState) -> anyhow::Result { self.subnet_id(state).map(|id| id.route.is_empty()) } diff --git a/fendermint/vm/interpreter/src/fvm/state/snapshot.rs b/fendermint/vm/interpreter/src/fvm/state/snapshot.rs index 7f70c5cbc4..a375a7c960 100644 --- a/fendermint/vm/interpreter/src/fvm/state/snapshot.rs +++ b/fendermint/vm/interpreter/src/fvm/state/snapshot.rs @@ -375,6 +375,7 @@ mod tests { chain_id: 1024, power_scale: 0, app_version: 0, + consensus_params: None, }; let block_height = 2048; diff --git a/fendermint/vm/interpreter/src/genesis.rs b/fendermint/vm/interpreter/src/genesis.rs index 156f45d037..c156d199a9 100644 --- a/fendermint/vm/interpreter/src/genesis.rs +++ b/fendermint/vm/interpreter/src/genesis.rs @@ -38,7 +38,7 @@ 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 fendermint_vm_genesis::ipc::{GatewayParams, IpcParams}; use serde::{Deserialize, Serialize}; use serde_with::serde_as; use tokio_stream::StreamExt; @@ -63,6 +63,7 @@ impl GenesisMetadata { chain_id: out.chain_id.into(), power_scale: out.power_scale, app_version: 0, + consensus_params: None, }; GenesisMetadata { @@ -158,7 +159,7 @@ pub struct GenesisOutput { pub struct GenesisBuilder { /// Hardhat like util to deploy ipc contracts - hardhat: Option, + hardhat: Hardhat, /// The built in actors bundle path builtin_actors_path: PathBuf, /// The custom actors bundle path @@ -172,21 +173,17 @@ impl GenesisBuilder { pub fn new( builtin_actors_path: PathBuf, custom_actors_path: PathBuf, + artifacts_path: PathBuf, genesis_params: Genesis, ) -> Self { Self { - hardhat: None, + hardhat: Hardhat::new(artifacts_path), 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<()> { @@ -256,19 +253,6 @@ impl GenesisBuilder { .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, @@ -304,10 +288,7 @@ impl GenesisBuilder { // 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())); + let (all_ipc_contracts, ipc_entrypoints) = self.collect_contracts()?; // STAGE 1: First we initialize native built-in actors. // System actor @@ -500,52 +481,61 @@ impl GenesisBuilder { ) .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, - )?; - } + // STAGE 4: Deploy the IPC system contracts. + + let config = DeployConfig { + ipc_params: genesis.ipc.as_ref(), + chain_id: out.chain_id, + hardhat: &self.hardhat, + }; + + deploy_contracts( + all_ipc_contracts, + &ipc_entrypoints, + genesis.validators, + next_id, + state, + config, + )?; Ok(out) } -} -fn collect_contracts( - hardhat: &Hardhat, -) -> anyhow::Result<(Vec, EthContractMap)> { - let mut all_contracts = Vec::new(); - let mut top_level_contracts = EthContractMap::default(); + fn collect_contracts(&self) -> anyhow::Result<(Vec, EthContractMap)> { + let mut all_contracts = Vec::new(); + let mut top_level_contracts = EthContractMap::default(); - top_level_contracts.extend(IPC_CONTRACTS.clone()); + 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")?; + 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 = self + .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)) + // 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)) + } +} + +// Configuration for deploying IPC contracts. +// This is to circumvent the arguments limit of the deploy_contracts function. +struct DeployConfig<'a> { + ipc_params: Option<&'a IpcParams>, + chain_id: ChainID, + hardhat: &'a Hardhat, } fn deploy_contracts( @@ -554,10 +544,10 @@ fn deploy_contracts( validators: Vec>, mut next_id: u64, state: &mut FvmGenesisState, - ipc_params: &IpcParams, - hardhat: &Hardhat, + config: DeployConfig, ) -> anyhow::Result<()> { - let mut deployer = ContractDeployer::::new(hardhat, top_level_contracts); + let mut deployer = + ContractDeployer::::new(config.hardhat, top_level_contracts); // Deploy Ethereum libraries. for (lib_src, lib_name) in ipc_contracts { @@ -567,8 +557,15 @@ fn deploy_contracts( // IPC Gateway actor. let gateway_addr = { use ipc::gateway::ConstructorParameters; + use ipc_api::subnet_id::SubnetID; + + let ipc_params = if let Some(p) = config.ipc_params { + p.gateway.clone() + } else { + GatewayParams::new(SubnetID::new(config.chain_id.into(), vec![])) + }; - let params = ConstructorParameters::new(ipc_params.gateway.clone(), validators) + let params = ConstructorParameters::new(ipc_params, validators) .context("failed to create gateway constructor")?; let facets = deployer @@ -781,13 +778,15 @@ fn circ_supply(g: &Genesis) -> TokenAmount { pub async fn create_test_genesis_state( bundle_path: PathBuf, custom_actors_bundle_path: PathBuf, + ipc_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 builder = GenesisBuilder::new( + bundle_path, + custom_actors_bundle_path, + ipc_path, + genesis_params, + ); let mut state = builder.init_state().await?; let out = builder.populate_state(&mut state, builder.genesis_params.clone())?; diff --git a/fendermint/vm/snapshot/src/manager.rs b/fendermint/vm/snapshot/src/manager.rs index 4a6f490f88..2a749ed905 100644 --- a/fendermint/vm/snapshot/src/manager.rs +++ b/fendermint/vm/snapshot/src/manager.rs @@ -441,12 +441,11 @@ mod tests { let mut g = quickcheck::Gen::new(5); let genesis = Genesis::arbitrary(&mut g); - let maybe_contract_path = genesis.ipc.as_ref().map(|_| contracts_path()); let (state, out) = create_test_genesis_state( bundle_path(), custom_actors_bundle_path(), + contracts_path(), genesis, - maybe_contract_path, ) .await .expect("cannot create genesis state"); @@ -467,6 +466,7 @@ mod tests { chain_id: out.chain_id.into(), power_scale: out.power_scale, app_version: 0, + consensus_params: None, }; (state_params, store) diff --git a/fendermint/vm/snapshot/src/manifest.rs b/fendermint/vm/snapshot/src/manifest.rs index aea7717189..42ffbbc513 100644 --- a/fendermint/vm/snapshot/src/manifest.rs +++ b/fendermint/vm/snapshot/src/manifest.rs @@ -190,6 +190,7 @@ mod arb { .into(), power_scale: *g.choose(&[-1, 0, 3]).unwrap(), app_version: 0, + consensus_params: None, }, version: Arbitrary::arbitrary(g), }