diff --git a/Cargo.lock b/Cargo.lock index 06ef89a7a..8e1c02fa4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2698,6 +2698,7 @@ version = "0.1.0" dependencies = [ "cid", "fil_actors_runtime", + "frc42_dispatch", "fvm_ipld_encoding", "fvm_shared", "num-derive 0.3.3", @@ -3098,6 +3099,7 @@ dependencies = [ "fendermint_vm_message", "fendermint_vm_resolver", "fendermint_vm_topdown", + "fil_actors_runtime", "futures-core", "futures-util", "fvm", @@ -3502,6 +3504,44 @@ dependencies = [ "thiserror", ] +[[package]] +name = "frc42_dispatch" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe63cf3ff3e332ef15fd19d95cffcb3fd2af14ccb3cb04abc730271c1362c4f" +dependencies = [ + "frc42_hasher", + "frc42_macros", + "fvm_ipld_encoding", + "fvm_sdk", + "fvm_shared", + "thiserror", +] + +[[package]] +name = "frc42_hasher" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a35e7214108f81cefc17b0466be01279f384faf913918a12dbc8528bb758a4" +dependencies = [ + "fvm_sdk", + "fvm_shared", + "thiserror", +] + +[[package]] +name = "frc42_macros" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f50cd62b077775194bde67eef8076b31f915b9c099f3a7fd1a760363d65f145" +dependencies = [ + "blake2b_simd", + "frc42_hasher", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "fs2" version = "0.4.3" diff --git a/Cargo.toml b/Cargo.toml index 52e599f47..fae8e9a54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,7 +101,9 @@ log = "0.4" lru_time_cache = "0.11" merkle-tree-rs = "0.1.0" multiaddr = "0.18" -multihash = { version = "0.18.1", default-features = false, features = ["sha2"] } +multihash = { version = "0.18.1", default-features = false, features = [ + "sha2", +] } num-bigint = "0.4" num-derive = "0.3" num-traits = "0.2" @@ -156,7 +158,7 @@ openssl = { version = "0.10", features = ["vendored"] } # Using the 3.3 version of the FVM because the newer ones update the IPLD dependencies # to version which are different than the ones in the builtin-actors project, and since # they are 0.x cargo cannot upgrade them automatically, which leads to version conflicts. -fvm = { version = "4.0.0", default-features = false } # no opencl feature or it fails on CI +fvm = { version = "4.0.0", default-features = false } # no opencl feature or it fails on CI fvm_shared = { version = "4.0.0" } fvm_sdk = { version = "4.0.0" } @@ -189,9 +191,7 @@ cid = { version = "0.10.1", default-features = false, features = [ "std", ] } -# Depending on the release cycle, this dependency might want an earlier version of the FVM. -# We can work around it by hardcoding the method hashes; currently there is only one. -# frc42_dispatch = "3.2" +frc42_dispatch = "5.0.0" # Using the same tendermint-rs dependency as tower-abci. From both we are interested in v037 modules. tower-abci = { version = "0.7" } diff --git a/docs/fendermint/demos/milestone-1/fendermint-demo.sh b/docs/fendermint/demos/milestone-1/fendermint-demo.sh index ee3d82ee1..26024b7db 100644 --- a/docs/fendermint/demos/milestone-1/fendermint-demo.sh +++ b/docs/fendermint/demos/milestone-1/fendermint-demo.sh @@ -73,6 +73,7 @@ rm -rf ~/.fendermint/data mkdir -p ~/.fendermint/data cp -r ./fendermint/app/config ~/.fendermint/config cp ./builtin-actors/output/bundle.car ~/.fendermint/bundle.car +cp ./actors/output/actors_bundle.car ~/.fendermint/actors_bundle.car #17 fendermint run diff --git a/docs/fendermint/running.md b/docs/fendermint/running.md index f2a0a9e8b..65f676fc6 100644 --- a/docs/fendermint/running.md +++ b/docs/fendermint/running.md @@ -375,6 +375,7 @@ configuration will look for it at `~/.fendermint/bundle.car`, so we might as wel ```shell make actor-bundle cp ./builtin-actors/output/bundle.car ~/.fendermint/bundle.car +cp ./actors/output/actors_bundle.car ~/.fendermint/actors_bundle.car ``` Now, start the application. diff --git a/fendermint/Makefile b/fendermint/Makefile index 8d089b12f..903f83fb9 100644 --- a/fendermint/Makefile +++ b/fendermint/Makefile @@ -107,6 +107,10 @@ $(BUILTIN_ACTORS_BUNDLE): mkdir -p $(dir $@) curl -L -o $@ https://github.com/filecoin-project/builtin-actors/releases/download/$(BUILTIN_ACTORS_TAG)/builtin-actors-mainnet.car +# Build a bundle CAR for the actors in this repo. +($(ACTORS_BUNDLE)): + cargo build --release -p fendermint_actors + # Regenerate the ABI artifacts if we don't have them already, or they changed. $(IPC_ACTORS_GEN): $(IPC_ACTORS_CODE) cd $(IPC_ACTORS_DIR) && make compile-abi diff --git a/fendermint/actors/chainmetadata/Cargo.toml b/fendermint/actors/chainmetadata/Cargo.toml index 91d947b83..a0b2f5d6d 100644 --- a/fendermint/actors/chainmetadata/Cargo.toml +++ b/fendermint/actors/chainmetadata/Cargo.toml @@ -18,6 +18,7 @@ num-derive = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_tuple = { workspace = true } num-traits = { workspace = true } +frc42_dispatch = { workspace = true } [features] fil-actor = [] diff --git a/fendermint/actors/chainmetadata/src/actor.rs b/fendermint/actors/chainmetadata/src/actor.rs index 879c7d529..baf46f23c 100644 --- a/fendermint/actors/chainmetadata/src/actor.rs +++ b/fendermint/actors/chainmetadata/src/actor.rs @@ -1,13 +1,19 @@ // Copyright 2021-2023 Protocol Labs // SPDX-License-Identifier: Apache-2.0, MIT +use std::str::FromStr; + use cid::Cid; use fil_actors_runtime::actor_dispatch; use fil_actors_runtime::actor_error; +use fil_actors_runtime::builtin::singletons::SYSTEM_ACTOR_ADDR; use fil_actors_runtime::runtime::{ActorCode, Runtime}; +use fil_actors_runtime::ActorDowncast; use fil_actors_runtime::ActorError; -use std::collections::VecDeque; +use fil_actors_runtime::Array; +use fvm_shared::error::ExitCode; +use crate::shared::BLOCKHASHES_AMT_BITWIDTH; use crate::{ConstructorParams, Method, State}; #[cfg(feature = "fil-actor")] @@ -17,21 +23,59 @@ pub struct Actor; impl Actor { fn constructor(rt: &impl Runtime, params: ConstructorParams) -> Result<(), ActorError> { + rt.validate_immediate_caller_is(std::iter::once(&SYSTEM_ACTOR_ADDR))?; + + let empty_arr_cid = + Array::<(), _>::new_with_bit_width(rt.store(), BLOCKHASHES_AMT_BITWIDTH) + .flush() + .map_err(|e| { + e.downcast_default(ExitCode::USR_ILLEGAL_STATE, "failed to create empty AMT") + })?; + let state = State { - blockhashes: VecDeque::new(), - params, + blockhashes: empty_arr_cid, + lookback_len: params.lookback_len, }; + rt.create(&state)?; + Ok(()) } fn push_block(rt: &impl Runtime, block: Cid) -> Result<(), ActorError> { - rt.transaction(|st: &mut State, _rt| { - st.blockhashes.push_back(block); - if st.blockhashes.len() > st.params.lookback_len as usize { - st.blockhashes.pop_front(); + rt.validate_immediate_caller_is(std::iter::once(&SYSTEM_ACTOR_ADDR))?; + + rt.transaction(|st: &mut State, rt| { + // load the blockhashes AMT + let mut blockhashes = Array::load(&st.blockhashes, rt.store()).map_err(|e| { + e.downcast_default( + ExitCode::USR_ILLEGAL_STATE, + "failed to load blockhashes states", + ) + })?; + + // push the block to the AMT + blockhashes + .set(rt.curr_epoch().try_into().unwrap(), block.to_string()) + .unwrap(); + + // remove the oldest block if the AMT is full + if blockhashes.count() > st.lookback_len { + let mut first_idx = 0; + blockhashes + .for_each_while(|i, _: &String| { + first_idx = i; + Ok(false) + }) + .unwrap(); + blockhashes.delete(first_idx).unwrap(); } + // save the new blockhashes AMT cid root + st.blockhashes = blockhashes.flush().map_err(|e| { + e.downcast_default(ExitCode::USR_ILLEGAL_STATE, "failed to save blockhashes") + })?; + Ok(()) })?; @@ -40,17 +84,34 @@ impl Actor { fn lookback_len(rt: &impl Runtime) -> Result { let state: State = rt.state()?; - Ok(state.params.lookback_len) + Ok(state.lookback_len) } fn block_cid(rt: &impl Runtime, rewind: u64) -> Result { - let state: State = rt.state()?; - let block = state - .blockhashes - .get(state.blockhashes.len() - rewind as usize - 1) - .ok_or_else(|| actor_error!(illegal_argument; "lookback too large"))?; + let st: State = rt.state()?; + + // load the blockhashes AMT + let blockhashes = Array::load(&st.blockhashes, rt.store()).map_err(|e| { + e.downcast_default( + ExitCode::USR_ILLEGAL_STATE, + "failed to load blockhashes states", + ) + })?; + + let blockhash: &String = blockhashes + .get(blockhashes.count() - rewind - 1) + .unwrap() + .unwrap(); - Ok(*block) + Cid::from_str(blockhash.as_str()).map_err(|_| { + ActorError::unchecked( + ExitCode::USR_ILLEGAL_STATE, + format!( + "failed to parse cid, hash: {}, rewind: {}", + blockhash, rewind + ), + ) + }) } } diff --git a/fendermint/actors/chainmetadata/src/lib.rs b/fendermint/actors/chainmetadata/src/lib.rs index e381de842..b39787342 100644 --- a/fendermint/actors/chainmetadata/src/lib.rs +++ b/fendermint/actors/chainmetadata/src/lib.rs @@ -3,4 +3,4 @@ mod actor; mod shared; -pub use shared::{ConstructorParams, Method, State}; +pub use shared::*; diff --git a/fendermint/actors/chainmetadata/src/shared.rs b/fendermint/actors/chainmetadata/src/shared.rs index c0d04506d..a930a7c5a 100644 --- a/fendermint/actors/chainmetadata/src/shared.rs +++ b/fendermint/actors/chainmetadata/src/shared.rs @@ -5,14 +5,24 @@ use cid::Cid; use fvm_ipld_encoding::tuple::{Deserialize_tuple, Serialize_tuple}; use fvm_shared::METHOD_CONSTRUCTOR; use num_derive::FromPrimitive; -use std::collections::VecDeque; +// The state is a stores `blockhashes` in an AMT containing the blockhashes of the +// last `lookback_len` epochs #[derive(Serialize_tuple, Deserialize_tuple)] pub struct State { - pub blockhashes: VecDeque, - pub params: ConstructorParams, + // the AMT root cid of blockhashes + pub blockhashes: Cid, + + // the maximum size of blockhashes before removing the oldest epoch + pub lookback_len: u64, } +// the default lookback length is 256 epochs +pub const DEFAULT_LOOKBACK_LEN: u64 = 256; + +// the default bitwidth of the blockhashes AMT +pub const BLOCKHASHES_AMT_BITWIDTH: u32 = 3; + #[derive(Default, Debug, Serialize_tuple, Deserialize_tuple)] pub struct ConstructorParams { pub lookback_len: u64, @@ -23,6 +33,6 @@ pub struct ConstructorParams { pub enum Method { Constructor = METHOD_CONSTRUCTOR, PushBlock = 2, - LookbackLen = 3, - BlockCID = 4, + LookbackLen = frc42_dispatch::method_hash!("LookbackLen"), + BlockCID = frc42_dispatch::method_hash!("BlockCID"), } diff --git a/fendermint/actors/src/lib.rs b/fendermint/actors/src/lib.rs index adbd47bf8..c8bec5c6b 100644 --- a/fendermint/actors/src/lib.rs +++ b/fendermint/actors/src/lib.rs @@ -2,4 +2,4 @@ // SPDX-License-Identifier: Apache-2.0, MIT mod manifest; -pub use manifest::{Manifest, CHAINMETADATA_ACTOR_CODE_ID}; +pub use manifest::{Manifest, CHAINMETADATA_ACTOR_CODE_ID, CHAINMETADATA_ACTOR_ID}; diff --git a/fendermint/actors/src/manifest.rs b/fendermint/actors/src/manifest.rs index f7980e9dc..d53ab3139 100644 --- a/fendermint/actors/src/manifest.rs +++ b/fendermint/actors/src/manifest.rs @@ -16,6 +16,7 @@ pub struct Manifest { } pub const CHAINMETADATA_ACTOR_CODE_ID: u32 = 1; +pub const CHAINMETADATA_ACTOR_ID: u64 = 48; impl Manifest { /// Load a manifest from the blockstore. diff --git a/fendermint/docker/runner.Dockerfile b/fendermint/docker/runner.Dockerfile index 44a096903..7ec297260 100644 --- a/fendermint/docker/runner.Dockerfile +++ b/fendermint/docker/runner.Dockerfile @@ -25,6 +25,7 @@ ENV FM_ABCI__LISTEN__HOST=0.0.0.0 ENV FM_ETH__LISTEN__HOST=0.0.0.0 COPY fendermint/docker/.artifacts/bundle.car $FM_HOME_DIR/bundle.car +COPY fendermint/docker/.artifacts/actors_bundle.car $FM_HOME_DIR/actors_bundle.car COPY fendermint/docker/.artifacts/contracts $FM_HOME_DIR/contracts COPY --from=builder /app/fendermint/app/config $FM_HOME_DIR/config COPY --from=builder /app/output/bin/fendermint /usr/local/bin/fendermint diff --git a/fendermint/vm/interpreter/Cargo.toml b/fendermint/vm/interpreter/Cargo.toml index ddcc102fe..57f31bc8f 100644 --- a/fendermint/vm/interpreter/Cargo.toml +++ b/fendermint/vm/interpreter/Cargo.toml @@ -45,6 +45,7 @@ fvm_shared = { workspace = true } fvm_ipld_blockstore = { workspace = true } fvm_ipld_encoding = { workspace = true } fvm_ipld_car = { workspace = true } +fil_actors_runtime = { workspace = true } futures-core = { workspace = true } futures-util = { workspace = true } diff --git a/fendermint/vm/interpreter/src/fvm/exec.rs b/fendermint/vm/interpreter/src/fvm/exec.rs index 925489ffc..4b799341b 100644 --- a/fendermint/vm/interpreter/src/fvm/exec.rs +++ b/fendermint/vm/interpreter/src/fvm/exec.rs @@ -83,6 +83,27 @@ where anyhow::bail!("failed to apply block cron message: {}", err); } + // Push the current block hash to the chainmetadata actor + // + let block_cid = fendermint_vm_message::cid(&state.block_hash().unwrap()).unwrap(); + let params = fvm_ipld_encoding::RawBytes::serialize(block_cid)?; + let msg = FvmMessage { + from: system::SYSTEM_ACTOR_ADDR, + to: fvm_shared::address::Address::new_id(fendermint_actors::CHAINMETADATA_ACTOR_ID), + sequence: height as u64, + gas_limit, + method_num: fendermint_actor_chainmetadata::Method::PushBlock as u64, + params, + value: Default::default(), + version: Default::default(), + gas_fee_cap: Default::default(), + gas_premium: Default::default(), + }; + let (apply_ret, _) = state.execute_implicit(msg)?; + if let Some(err) = apply_ret.failure_info { + anyhow::bail!("failed to apply chainmetadata message: {}", err); + } + let ret = FvmApplyRet { apply_ret, from, diff --git a/fendermint/vm/interpreter/src/fvm/genesis.rs b/fendermint/vm/interpreter/src/fvm/genesis.rs index 1aa8a90f4..ec936674b 100644 --- a/fendermint/vm/interpreter/src/fvm/genesis.rs +++ b/fendermint/vm/interpreter/src/fvm/genesis.rs @@ -9,6 +9,7 @@ use anyhow::{anyhow, Context}; use async_trait::async_trait; use ethers::abi::Tokenize; use ethers::core::types as et; +use fendermint_actor_chainmetadata::BLOCKHASHES_AMT_BITWIDTH; use fendermint_eth_hardhat::{Hardhat, FQN}; use fendermint_vm_actor_interface::diamond::{EthContract, EthContractMap}; use fendermint_vm_actor_interface::eam::EthAddress; @@ -18,6 +19,7 @@ use fendermint_vm_actor_interface::{ }; use fendermint_vm_core::{chainid, Timestamp}; use fendermint_vm_genesis::{ActorMeta, Genesis, Power, PowerScale, Validator}; +use fil_actors_runtime::Array; use fvm_ipld_blockstore::Blockstore; use fvm_shared::chainid::ChainID; use fvm_shared::econ::TokenAmount; @@ -228,6 +230,26 @@ where ) .context("failed to create reward actor")?; + // STAGE 1b: Then we initialize the in-repo actors. + // + + // Initialize the chain metadata actor which handles saving metadata about the chain + // (e.g. block hashes) which we can query. + let empty_blockhashes_cid = + Array::<(), _>::new_with_bit_width(state.store(), BLOCKHASHES_AMT_BITWIDTH).flush()?; + state + .create_actor( + fendermint_actors::CHAINMETADATA_ACTOR_CODE_ID, + fendermint_actors::CHAINMETADATA_ACTOR_ID, + &fendermint_actor_chainmetadata::State { + blockhashes: empty_blockhashes_cid, + lookback_len: fendermint_actor_chainmetadata::DEFAULT_LOOKBACK_LEN, + }, + TokenAmount::zero(), + None, + ) + .context("failed to create chainmetadata 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. @@ -251,22 +273,7 @@ where } } - // STAGE 3: Create the non-builtin actors - - state - .create_actor( - fendermint_actors::CHAINMETADATA_ACTOR_CODE_ID, - next_id, - &fendermint_actor_chainmetadata::State { - blockhashes: vec![].into(), - params: fendermint_actor_chainmetadata::ConstructorParams { lookback_len: 256 }, - }, - TokenAmount::zero(), - None, - ) - .context("failed to create chainmetadata actor")?; - - // STAGE 4: Initialize the FVM and create built-in FEVM actors. + // STAGE 3: Initialize the FVM and create built-in FEVM actors. state .init_exec_state(