From 9f13ec84a1f7e6fdf6342a5482176f37f24c9515 Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Thu, 2 Feb 2023 14:59:11 +0000 Subject: [PATCH 01/16] FM-13: Add interpreter interface --- Cargo.lock | 17 +++- Cargo.toml | 3 +- fendermint/app/Cargo.toml | 9 +- fendermint/vm/interpreter/Cargo.toml | 21 +++++ fendermint/vm/interpreter/src/externs.rs | 46 ++++++++++ fendermint/vm/interpreter/src/lib.rs | 39 +++++++++ fendermint/vm/interpreter/src/vm.rs | 94 +++++++++++++++++++++ fendermint/vm/message/Cargo.toml | 2 +- fendermint/vm/message/src/lib.rs | 7 +- fendermint/vm/message/src/signed_message.rs | 24 +++++- 10 files changed, 250 insertions(+), 12 deletions(-) create mode 100644 fendermint/vm/interpreter/Cargo.toml create mode 100644 fendermint/vm/interpreter/src/externs.rs create mode 100644 fendermint/vm/interpreter/src/lib.rs create mode 100644 fendermint/vm/interpreter/src/vm.rs diff --git a/Cargo.lock b/Cargo.lock index 2a2b3114..057a229c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1363,6 +1363,7 @@ name = "fendermint_app" version = "0.1.0" dependencies = [ "fendermint_abci", + "fendermint_vm_interpreter", "fil_builtin_actors_bundle", "forest_db", "fvm", @@ -1372,15 +1373,29 @@ dependencies = [ ] [[package]] -name = "fendermint_vm_message" +name = "fendermint_vm_interpreter" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", + "cid", + "fendermint_vm_message", + "fvm", + "fvm_ipld_blockstore", + "fvm_ipld_encoding 0.3.3", + "fvm_shared 3.0.0-alpha.17", +] + +[[package]] +name = "fendermint_vm_message" +version = "0.1.0" +dependencies = [ "cid", "fvm_ipld_encoding 0.3.3", "fvm_shared 3.0.0-alpha.17", "serde", "serde_tuple", + "thiserror", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1e0a576c..be5d0591 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,11 +11,12 @@ license-file = "LICENSE-APACHE" [workspace.dependencies] +anyhow = "1" async-trait = "0.1" futures = "0.3" tokio = { version = "1", features = ["rt-multi-thread"] } tempfile = "3.3" -anyhow = "1" +thiserror = "1" serde = { version = "1", features = ["derive"] } serde_tuple = "0.5" cid = { version = "0.8", default-features = false, features = ["serde-codec", "std", "arb"] } diff --git a/fendermint/app/Cargo.toml b/fendermint/app/Cargo.toml index 490893b5..ebce14dc 100644 --- a/fendermint/app/Cargo.toml +++ b/fendermint/app/Cargo.toml @@ -8,6 +8,8 @@ license.workspace = true [dependencies] fendermint_abci = { path = "../abci" } +fendermint_vm_interpreter = { path = "../vm/interpreter" } + fvm = { workspace = true } fvm_ipld_car = { workspace = true } tokio = { workspace = true } @@ -16,11 +18,12 @@ tokio = { workspace = true } # Using the `tag` of the `forest` repo rather than the `version` of just `forest_db` so we don't get a random commit in `main`. forest_db = { git = "https://github.com/ChainSafe/forest.git", tag = "v0.6.0", features = ["rocksdb"] } +[dev-dependencies] +tempfile = { workspace = true } + # Load the same built-in actor bundle as the ref-fvm integration tests. We'll probably need built-in actors, # for example to deploy Solidity code. We can compile Wasm actors and deploy them too, but certain functions # in `ref-fvm` like looking up actor addresses depend on built-in actors like the `InitActor` maintaining state. +# TODO: This is dev-dependency for testing - in prod it whould be taken from a file, not packaged with the app, so the release cycle is independent. # TODO: Figure out how to load only what we need, and nothing else. actors-v10 = { package = "fil_builtin_actors_bundle", git = "https://github.com/filecoin-project/builtin-actors", branch = "next", features = ["m2-native"] } - -[dev-dependencies] -tempfile = { workspace = true } diff --git a/fendermint/vm/interpreter/Cargo.toml b/fendermint/vm/interpreter/Cargo.toml new file mode 100644 index 00000000..d932a7eb --- /dev/null +++ b/fendermint/vm/interpreter/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "fendermint_vm_interpreter" +description = "Execute messages using the FVM" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +fendermint_vm_message = { path = "../message" } + +async-trait = { workspace = true } +anyhow = { workspace = true } + +cid = { workspace = true } +fvm = { workspace = true } +fvm_shared = { workspace = true } +fvm_ipld_blockstore = { workspace = true } +fvm_ipld_encoding = { workspace = true } diff --git a/fendermint/vm/interpreter/src/externs.rs b/fendermint/vm/interpreter/src/externs.rs new file mode 100644 index 00000000..8b685f58 --- /dev/null +++ b/fendermint/vm/interpreter/src/externs.rs @@ -0,0 +1,46 @@ +use cid::Cid; +use fvm::externs::{Chain, Consensus, Externs, Rand}; +use fvm_shared::clock::ChainEpoch; + +/// Dummy externs - these are related to Expected Consensus, +/// which I believe we have nothing to do with. +pub struct FendermintExterns; + +impl Rand for FendermintExterns { + fn get_chain_randomness( + &self, + _pers: i64, + _round: ChainEpoch, + _entropy: &[u8], + ) -> anyhow::Result<[u8; 32]> { + todo!("might need randomness") + } + + fn get_beacon_randomness( + &self, + _pers: i64, + _round: ChainEpoch, + _entropy: &[u8], + ) -> anyhow::Result<[u8; 32]> { + unimplemented!("not expecting to use the beacon") + } +} + +impl Consensus for FendermintExterns { + fn verify_consensus_fault( + &self, + _h1: &[u8], + _h2: &[u8], + _extra: &[u8], + ) -> anyhow::Result<(Option, i64)> { + unimplemented!("not expecting to use consensus faults") + } +} + +impl Chain for FendermintExterns { + fn get_tipset_cid(&self, _epoch: ChainEpoch) -> anyhow::Result { + unimplemented!("not expecting to use tipsets") + } +} + +impl Externs for FendermintExterns {} diff --git a/fendermint/vm/interpreter/src/lib.rs b/fendermint/vm/interpreter/src/lib.rs new file mode 100644 index 00000000..2a645a57 --- /dev/null +++ b/fendermint/vm/interpreter/src/lib.rs @@ -0,0 +1,39 @@ +use async_trait::async_trait; + +mod externs; +mod vm; + +/// The interpreter applies messages on some state. +/// +/// It is asynchronous so that message execution can have side effects, +/// such as scheduling the resolution of embedded CIDs. These kind of +/// side effects could be signalled through the `Output` as well, but +/// the intention is to be able to stack interpreters, and at least +/// some of them might want execute something asynchronous. +/// +/// There is no separate type for `Error`, only `Output`. The reason +/// is that we'll be calling high level executors internally that +/// already have their internal error handling, returning all domain +/// errors such as `OutOfGas` in their output, and only using the +/// error case for things that are independent of the message itself. +#[async_trait] +trait Interpreter: Sync + Send { + type State: Send; + type Message: Send; + type Output; + + /// Apply a message onto the state. + /// + /// The state is taken by value, so there's no issue with sharing + /// mutable references in futures. The modified value should be + /// returned along with the return value. + /// + /// Only return an error case if something truly unexpected happens + /// that should stop message processing altogether; otherwise use + /// the output for signalling all execution results. + async fn exec_msg( + &self, + state: Self::State, + msg: Self::Message, + ) -> anyhow::Result<(Self::State, Self::Output)>; +} diff --git a/fendermint/vm/interpreter/src/vm.rs b/fendermint/vm/interpreter/src/vm.rs new file mode 100644 index 00000000..31fe2e9f --- /dev/null +++ b/fendermint/vm/interpreter/src/vm.rs @@ -0,0 +1,94 @@ +use std::marker::PhantomData; + +use anyhow::anyhow; +use async_trait::async_trait; + +use fendermint_vm_message::{SignedMessage, SignedMessageError}; +use fvm::{ + call_manager::DefaultCallManager, + executor::{ApplyRet, DefaultExecutor, Executor}, + machine::DefaultMachine, + DefaultKernel, +}; +use fvm_ipld_blockstore::Blockstore; +use fvm_shared::message::Message as FvmMessage; + +use crate::{externs::FendermintExterns, Interpreter}; + +pub struct FvmState +where + DB: Blockstore + 'static, +{ + executor: + DefaultExecutor>>>, +} + +/// Interpreter working on already verified unsigned messages. +pub struct MessageInterpreter { + _phantom_db: PhantomData, +} + +#[async_trait] +impl Interpreter for MessageInterpreter +where + DB: Blockstore + 'static + Send + Sync, +{ + type Message = FvmMessage; + type State = FvmState; + type Output = ApplyRet; + + async fn exec_msg( + &self, + mut state: Self::State, + msg: Self::Message, + ) -> anyhow::Result<(Self::State, Self::Output)> { + let raw_length = fvm_ipld_encoding::to_vec(&msg).map(|bz| bz.len())?; + let ret = + state + .executor + .execute_message(msg, fvm::executor::ApplyKind::Explicit, raw_length)?; + Ok((state, ret)) + } +} + +/// Interpreter working on signed messages, validating their signature before sending +/// the unsigned parts on for execution. +pub struct SignedMessageInterpreter { + message_interpreter: MI, +} + +pub enum SignedMesssageApplyRet { + InvalidSignature(String), + Applied(ApplyRet), +} + +#[async_trait] +impl Interpreter for SignedMessageInterpreter +where + MI: Interpreter, +{ + type Message = SignedMessage; + type Output = SignedMesssageApplyRet; + type State = MI::State; + + async fn exec_msg( + &self, + state: Self::State, + msg: Self::Message, + ) -> anyhow::Result<(Self::State, Self::Output)> { + match msg.verify() { + Err(SignedMessageError::Ipld(e)) => Err(anyhow!(e)), + Err(SignedMessageError::InvalidSignature(s)) => { + Ok((state, SignedMesssageApplyRet::InvalidSignature(s))) + } + Ok(()) => { + let (state, ret) = self + .message_interpreter + .exec_msg(state, msg.message) + .await?; + + Ok((state, SignedMesssageApplyRet::Applied(ret))) + } + } + } +} diff --git a/fendermint/vm/message/Cargo.toml b/fendermint/vm/message/Cargo.toml index 0615c79c..4062ea0f 100644 --- a/fendermint/vm/message/Cargo.toml +++ b/fendermint/vm/message/Cargo.toml @@ -7,7 +7,7 @@ edition.workspace = true license.workspace = true [dependencies] -anyhow = { workspace = true } +thiserror = { workspace = true } cid = { workspace = true } serde = { workspace = true } serde_tuple = { workspace = true } diff --git a/fendermint/vm/message/src/lib.rs b/fendermint/vm/message/src/lib.rs index 36f237b6..d15047c9 100644 --- a/fendermint/vm/message/src/lib.rs +++ b/fendermint/vm/message/src/lib.rs @@ -4,8 +4,11 @@ use cid::{multihash, multihash::MultihashDigest, Cid}; use fvm_ipld_encoding::{to_vec, Error as IpldError, DAG_CBOR}; use serde::Serialize; -pub mod chain_message; -pub mod signed_message; +mod chain_message; +mod signed_message; + +pub use chain_message::ChainMessage; +pub use signed_message::{SignedMessage, SignedMessageError}; /// Calculate the CID using Blake2b256 digest and DAG_CBOR. /// diff --git a/fendermint/vm/message/src/signed_message.rs b/fendermint/vm/message/src/signed_message.rs index b26ee4c9..6e1b73f7 100644 --- a/fendermint/vm/message/src/signed_message.rs +++ b/fendermint/vm/message/src/signed_message.rs @@ -6,6 +6,16 @@ use fvm_ipld_encoding::tuple::{Deserialize_tuple, Serialize_tuple}; use fvm_shared::crypto::signature::{Signature, SignatureType}; use fvm_shared::message::Message; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum SignedMessageError { + #[error("message cannot be serialized")] + Ipld(#[from] fvm_ipld_encoding::Error), + #[error("invalid signature: {0}")] + InvalidSignature(String), +} + /// Represents a wrapped message with signature bytes. /// /// This is the message @@ -26,21 +36,27 @@ impl SignedMessage { /// Generate a new signed message from fields. /// /// The signature will be verified. - pub fn new_checked(message: Message, signature: Signature) -> anyhow::Result { + pub fn new_checked( + message: Message, + signature: Signature, + ) -> Result { Self::verify_signature(&message, &signature)?; Ok(SignedMessage { message, signature }) } /// Verify that the message CID was signed by the `from` address. - pub fn verify_signature(message: &Message, signature: &Signature) -> anyhow::Result<()> { + pub fn verify_signature( + message: &Message, + signature: &Signature, + ) -> Result<(), SignedMessageError> { let cid = crate::cid(&message)?.to_bytes(); signature .verify(&cid, &message.from) - .map_err(anyhow::Error::msg) + .map_err(SignedMessageError::InvalidSignature) } /// Verifies that the from address of the message generated the signature. - pub fn verify(&self) -> anyhow::Result<()> { + pub fn verify(&self) -> Result<(), SignedMessageError> { Self::verify_signature(&self.message, &self.signature) } From 07b9ba7026cc45d10f8d0b185cbe6111b216cc12 Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Thu, 2 Feb 2023 15:38:55 +0000 Subject: [PATCH 02/16] FM-13: FvmState::new --- fendermint/vm/interpreter/src/lib.rs | 3 ++ fendermint/vm/interpreter/src/vm.rs | 42 ++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/fendermint/vm/interpreter/src/lib.rs b/fendermint/vm/interpreter/src/lib.rs index 2a645a57..0261a21e 100644 --- a/fendermint/vm/interpreter/src/lib.rs +++ b/fendermint/vm/interpreter/src/lib.rs @@ -3,6 +3,9 @@ use async_trait::async_trait; mod externs; mod vm; +/// Unix timestamp (in seconds) of the current block. +pub struct Timestamp(u64); + /// The interpreter applies messages on some state. /// /// It is asynchronous so that message execution can have side effects, diff --git a/fendermint/vm/interpreter/src/vm.rs b/fendermint/vm/interpreter/src/vm.rs index 31fe2e9f..4df82b24 100644 --- a/fendermint/vm/interpreter/src/vm.rs +++ b/fendermint/vm/interpreter/src/vm.rs @@ -3,18 +3,23 @@ use std::marker::PhantomData; use anyhow::anyhow; use async_trait::async_trait; +use cid::Cid; use fendermint_vm_message::{SignedMessage, SignedMessageError}; use fvm::{ call_manager::DefaultCallManager, + engine::{EngineConfig, EnginePool}, executor::{ApplyRet, DefaultExecutor, Executor}, - machine::DefaultMachine, + machine::{DefaultMachine, NetworkConfig}, DefaultKernel, }; use fvm_ipld_blockstore::Blockstore; -use fvm_shared::message::Message as FvmMessage; +use fvm_shared::{ + clock::ChainEpoch, econ::TokenAmount, message::Message as FvmMessage, version::NetworkVersion, +}; -use crate::{externs::FendermintExterns, Interpreter}; +use crate::{externs::FendermintExterns, Interpreter, Timestamp}; +/// A state we create for the execution of all the messages in a block. pub struct FvmState where DB: Blockstore + 'static, @@ -23,6 +28,37 @@ where DefaultExecutor>>>, } +impl FvmState +where + DB: Blockstore + 'static, +{ + pub fn new( + blockstore: DB, + block_height: ChainEpoch, + block_timestamp: Timestamp, + network_version: NetworkVersion, + initial_state: Cid, + base_fee: TokenAmount, + circ_supply: TokenAmount, + ) -> anyhow::Result { + let nc = NetworkConfig::new(network_version); + + // TODO: Configure: + // * circ_supply; by default it's for Filecoin + // * base_fee; by default it's zero + let mut mc = nc.for_epoch(block_height, block_timestamp.0, initial_state); + mc.set_base_fee(base_fee); + mc.set_circulating_supply(circ_supply); + + let ec = EngineConfig::from(&nc); + let engine = EnginePool::new_default(ec)?; + let machine = DefaultMachine::new(&mc, blockstore, FendermintExterns)?; + let executor = DefaultExecutor::new(engine, machine)?; + + Ok(Self { executor }) + } +} + /// Interpreter working on already verified unsigned messages. pub struct MessageInterpreter { _phantom_db: PhantomData, From 118552a882f052a15dd7c1fef28c0a9b2c726837 Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Fri, 3 Feb 2023 09:10:52 +0000 Subject: [PATCH 03/16] FM-13: Try to implement begin_block --- Cargo.lock | 6 ++ Cargo.toml | 5 ++ fendermint/abci/Cargo.toml | 6 +- fendermint/app/Cargo.toml | 9 +- fendermint/app/src/app.rs | 130 +++++++++++++++++++++++++++ fendermint/app/src/lib.rs | 1 + fendermint/vm/interpreter/src/lib.rs | 36 ++++++-- fendermint/vm/message/Cargo.toml | 3 +- 8 files changed, 182 insertions(+), 14 deletions(-) create mode 100644 fendermint/app/src/app.rs create mode 100644 fendermint/app/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 057a229c..1f2a6e43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1362,13 +1362,19 @@ dependencies = [ name = "fendermint_app" version = "0.1.0" dependencies = [ + "async-trait", + "cid", "fendermint_abci", "fendermint_vm_interpreter", + "fendermint_vm_message", "fil_builtin_actors_bundle", "forest_db", "fvm", + "fvm_ipld_blockstore", "fvm_ipld_car", + "fvm_shared 3.0.0-alpha.17", "tempfile", + "tendermint", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index be5d0591..ebbbbcb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ license-file = "LICENSE-APACHE" [workspace.dependencies] anyhow = "1" async-trait = "0.1" +async-stm = "0.1.2" futures = "0.3" tokio = { version = "1", features = ["rt-multi-thread"] } tempfile = "3.3" @@ -29,3 +30,7 @@ fvm_ipld_car = "0.6" # The following are on crates.io but as pre-releases. fvm = { version = "3.0.0-alpha.21", default-features = false } # no opencl or it fails on CI fvm_shared = "3.0.0-alpha.17" + +# Tendermint dependencies are forked because we are building against 0.37 release candidates. +tower-abci = { git = "https://github.com/consensus-shipyard/tower-abci.git", branch = "tendermint-v0.37" } +tendermint = { git = "https://github.com/aakoshh/tendermint-rs.git", branch = "mikhail/multi-tc-version-support" } diff --git a/fendermint/abci/Cargo.toml b/fendermint/abci/Cargo.toml index 76444171..dea64150 100644 --- a/fendermint/abci/Cargo.toml +++ b/fendermint/abci/Cargo.toml @@ -11,12 +11,12 @@ async-trait = { workspace = true } futures = { workspace = true } tower = "0.4" -tower-abci = { git = "https://github.com/consensus-shipyard/tower-abci.git", branch = "tendermint-v0.37" } -tendermint = { git = "https://github.com/aakoshh/tendermint-rs.git", branch = "mikhail/multi-tc-version-support" } +tower-abci = { workspace = true } +tendermint = { workspace = true } [dev-dependencies] -async-stm = "0.1.2" +async-stm = { workspace = true } im = "15.1.0" structopt = "0.3" tokio = { version = "1" } diff --git a/fendermint/app/Cargo.toml b/fendermint/app/Cargo.toml index ebce14dc..c46dd180 100644 --- a/fendermint/app/Cargo.toml +++ b/fendermint/app/Cargo.toml @@ -7,12 +7,19 @@ edition.workspace = true license.workspace = true [dependencies] +async-trait = { workspace = true } +tokio = { workspace = true } +tendermint = { workspace = true } + fendermint_abci = { path = "../abci" } fendermint_vm_interpreter = { path = "../vm/interpreter" } +fendermint_vm_message = { path = "../vm/message" } +cid = { workspace = true } fvm = { workspace = true } fvm_ipld_car = { workspace = true } -tokio = { workspace = true } +fvm_ipld_blockstore = { workspace = true } +fvm_shared = { workspace = true } # The current 0.2.0 version of forest_db is not published to crates.io. # Using the `tag` of the `forest` repo rather than the `version` of just `forest_db` so we don't get a random commit in `main`. diff --git a/fendermint/app/src/app.rs b/fendermint/app/src/app.rs new file mode 100644 index 00000000..a55cf8e6 --- /dev/null +++ b/fendermint/app/src/app.rs @@ -0,0 +1,130 @@ +use std::sync::{Arc, Mutex}; + +use async_trait::async_trait; +use cid::Cid; +use fendermint_abci::Application; +use fendermint_vm_interpreter::vm::{FvmState, SignedMesssageApplyRet}; +use fendermint_vm_interpreter::{Interpreter, Timestamp}; +use fendermint_vm_message::SignedMessage; +use fvm_ipld_blockstore::Blockstore; +use fvm_shared::econ::TokenAmount; +use fvm_shared::version::NetworkVersion; +use tendermint::abci::{request, response}; + +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +struct State { + block_height: u64, + state_root: Cid, + network_version: NetworkVersion, + base_fee: TokenAmount, + circ_supply: TokenAmount, +} + +/// Handle ABCI requests. +pub struct FendermintApp +where + DB: Blockstore + 'static, +{ + db: Arc, + exec_interpreter: Arc, + /// State accumulating changes during block execution. + exec_state: Arc>>>, +} + +impl FendermintApp +where + DB: Blockstore + 'static, +{ + /// Get the last committed state. + fn committed_state(&self) -> State { + todo!("retrieve state from the DB") + } +} + +#[async_trait] +impl Application for FendermintApp +where + DB: Blockstore + Clone + Send + Sync + 'static, + EI: Interpreter, Message = SignedMessage, Output = SignedMesssageApplyRet>, +{ + /// Provide information about the ABCI application. + async fn info(&self, _request: request::Info) -> response::Info { + let state = self.committed_state(); + let height = + tendermint::block::Height::try_from(state.block_height).expect("height too big"); + let app_hash = tendermint::hash::AppHash::try_from(state.state_root.to_bytes()) + .expect("hash can be wrapped"); + response::Info { + data: "fendermint".to_string(), + version: VERSION.to_owned(), + app_version: 1, + last_block_height: height, + last_block_app_hash: app_hash, + } + } + + /// Called once upon genesis. + async fn init_chain(&self, _request: request::InitChain) -> response::InitChain { + Default::default() + } + + /// Query the application for data at the current or past height. + async fn query(&self, _request: request::Query) -> response::Query { + todo!("make a query interpreter") + } + + /// Check the given transaction before putting it into the local mempool. + async fn check_tx(&self, _request: request::CheckTx) -> response::CheckTx { + todo!("make an interpreter for checks, on a projected state") + } + + /// Signals the beginning of a new block, prior to any `DeliverTx` calls. + async fn begin_block(&self, request: request::BeginBlock) -> response::BeginBlock { + let state = self.committed_state(); + let height = request.header.height.into(); + let timestamp = Timestamp( + request + .header + .time + .unix_timestamp() + .try_into() + .expect("negative timestamp"), + ); + let db = self.db.as_ref().to_owned(); + + let state = FvmState::new( + db, + height, + timestamp, + state.network_version, + state.state_root, + state.base_fee, + state.circ_supply, + ) + .expect("error creating new state"); + + // TODO: Call the begin to run cron stuff. + + let mut guard = self.exec_state.lock().expect("mutex poisoned"); + assert!(guard.is_none(), "state not empty at begin"); + *guard = Some(state); + + response::BeginBlock { events: Vec::new() } + } + + /// Apply a transaction to the application's state. + async fn deliver_tx(&self, _request: request::DeliverTx) -> response::DeliverTx { + todo!() + } + + /// Signals the end of a block. + async fn end_block(&self, _request: request::EndBlock) -> response::EndBlock { + todo!() + } + + /// Commit the current state at the current height. + async fn commit(&self) -> response::Commit { + todo!() + } +} diff --git a/fendermint/app/src/lib.rs b/fendermint/app/src/lib.rs new file mode 100644 index 00000000..c0de3909 --- /dev/null +++ b/fendermint/app/src/lib.rs @@ -0,0 +1 @@ +mod app; diff --git a/fendermint/vm/interpreter/src/lib.rs b/fendermint/vm/interpreter/src/lib.rs index 0261a21e..40d60839 100644 --- a/fendermint/vm/interpreter/src/lib.rs +++ b/fendermint/vm/interpreter/src/lib.rs @@ -1,26 +1,44 @@ use async_trait::async_trait; mod externs; -mod vm; +pub mod vm; /// Unix timestamp (in seconds) of the current block. -pub struct Timestamp(u64); +pub struct Timestamp(pub u64); /// The interpreter applies messages on some state. /// -/// It is asynchronous so that message execution can have side effects, -/// such as scheduling the resolution of embedded CIDs. These kind of -/// side effects could be signalled through the `Output` as well, but -/// the intention is to be able to stack interpreters, and at least -/// some of them might want execute something asynchronous. +/// By making them generic, the intention is that interpreters can +/// be stacked, changing the type of message along the way. For +/// example on the outermost layer the input message can be a mix +/// of self-contained messages and CIDs proposed for resolution +/// or execution, while in the innermost layer it's all self-contained. +/// Some interpreters would act like middlewares to resolve CIDs into +/// a concrete message. +/// +/// The execution is asynchronous, so that the middleware is allowed +/// to potentially interact with the outside world. If this was restricted +/// to things like scheduling a CID resolution, we could use effects +/// returned from message processing. However, when a node is catching +/// up with the chain others have already committed, they have to do the +/// message resolution synchronously, so it has to be done during +/// message processing. Alternatively we'd have to split the processing +/// into async steps to pre-process the message, then synchronous steps +/// to update the state. But this approach is more flexible, because +/// the middlewares can decide on a message-by-message basis whether +/// to forward the message to the inner layer. Unfortunately block-level +/// pre-processing is not possible, because we are fed the messages +/// one by one through the ABCI. /// /// There is no separate type for `Error`, only `Output`. The reason /// is that we'll be calling high level executors internally that /// already have their internal error handling, returning all domain /// errors such as `OutOfGas` in their output, and only using the -/// error case for things that are independent of the message itself. +/// error case for things that are independent of the message itself, +/// signalling unexpected problems there's no recovering from and +/// that should stop the block processing altogether. #[async_trait] -trait Interpreter: Sync + Send { +pub trait Interpreter: Sync + Send { type State: Send; type Message: Send; type Output; diff --git a/fendermint/vm/message/Cargo.toml b/fendermint/vm/message/Cargo.toml index 4062ea0f..4379c625 100644 --- a/fendermint/vm/message/Cargo.toml +++ b/fendermint/vm/message/Cargo.toml @@ -8,8 +8,9 @@ license.workspace = true [dependencies] thiserror = { workspace = true } -cid = { workspace = true } serde = { workspace = true } serde_tuple = { workspace = true } + +cid = { workspace = true } fvm_shared = { workspace = true } fvm_ipld_encoding = { workspace = true } From cdbaf3a5af2c74b991875d5c7db3659f8d59871b Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Fri, 3 Feb 2023 09:32:50 +0000 Subject: [PATCH 04/16] FM-13: Split into signed/chain --- fendermint/app/src/app.rs | 7 ++- fendermint/vm/interpreter/src/chain.rs | 38 +++++++++++++ .../vm/interpreter/src/{vm.rs => fvm.rs} | 56 ++----------------- fendermint/vm/interpreter/src/lib.rs | 6 +- fendermint/vm/interpreter/src/signed.rs | 47 ++++++++++++++++ .../src/{chain_message.rs => chain.rs} | 4 +- fendermint/vm/message/src/lib.rs | 7 +-- .../src/{signed_message.rs => signed.rs} | 0 8 files changed, 103 insertions(+), 62 deletions(-) create mode 100644 fendermint/vm/interpreter/src/chain.rs rename fendermint/vm/interpreter/src/{vm.rs => fvm.rs} (59%) create mode 100644 fendermint/vm/interpreter/src/signed.rs rename fendermint/vm/message/src/{chain_message.rs => chain.rs} (96%) rename fendermint/vm/message/src/{signed_message.rs => signed.rs} (100%) diff --git a/fendermint/app/src/app.rs b/fendermint/app/src/app.rs index a55cf8e6..9139aee0 100644 --- a/fendermint/app/src/app.rs +++ b/fendermint/app/src/app.rs @@ -3,9 +3,10 @@ use std::sync::{Arc, Mutex}; use async_trait::async_trait; use cid::Cid; use fendermint_abci::Application; -use fendermint_vm_interpreter::vm::{FvmState, SignedMesssageApplyRet}; +use fendermint_vm_interpreter::chain::ChainMessageApplyRet; +use fendermint_vm_interpreter::fvm::FvmState; use fendermint_vm_interpreter::{Interpreter, Timestamp}; -use fendermint_vm_message::SignedMessage; +use fendermint_vm_message::chain::ChainMessage; use fvm_ipld_blockstore::Blockstore; use fvm_shared::econ::TokenAmount; use fvm_shared::version::NetworkVersion; @@ -46,7 +47,7 @@ where impl Application for FendermintApp where DB: Blockstore + Clone + Send + Sync + 'static, - EI: Interpreter, Message = SignedMessage, Output = SignedMesssageApplyRet>, + EI: Interpreter, Message = ChainMessage, Output = ChainMessageApplyRet>, { /// Provide information about the ABCI application. async fn info(&self, _request: request::Info) -> response::Info { diff --git a/fendermint/vm/interpreter/src/chain.rs b/fendermint/vm/interpreter/src/chain.rs new file mode 100644 index 00000000..666b6aee --- /dev/null +++ b/fendermint/vm/interpreter/src/chain.rs @@ -0,0 +1,38 @@ +use async_trait::async_trait; + +use fendermint_vm_message::{chain::ChainMessage, signed::SignedMessage}; + +use crate::{signed::SignedMesssageApplyRet, Interpreter}; + +/// Interpreter working on chain messages; in the future it will schedule +/// CID lookups to turn references into self-contained user or cross messages. +pub struct ChainMessageInterpreter { + inner: I, +} + +pub enum ChainMessageApplyRet { + Signed(SignedMesssageApplyRet), +} + +#[async_trait] +impl Interpreter for ChainMessageInterpreter +where + I: Interpreter, +{ + type Message = ChainMessage; + type Output = ChainMessageApplyRet; + type State = I::State; + + async fn exec( + &self, + state: Self::State, + msg: Self::Message, + ) -> anyhow::Result<(Self::State, Self::Output)> { + match msg { + ChainMessage::Signed(msg) => { + let (state, ret) = self.inner.exec(state, msg).await?; + Ok((state, ChainMessageApplyRet::Signed(ret))) + } + } + } +} diff --git a/fendermint/vm/interpreter/src/vm.rs b/fendermint/vm/interpreter/src/fvm.rs similarity index 59% rename from fendermint/vm/interpreter/src/vm.rs rename to fendermint/vm/interpreter/src/fvm.rs index 4df82b24..9bb66de2 100644 --- a/fendermint/vm/interpreter/src/vm.rs +++ b/fendermint/vm/interpreter/src/fvm.rs @@ -1,10 +1,8 @@ use std::marker::PhantomData; -use anyhow::anyhow; use async_trait::async_trait; use cid::Cid; -use fendermint_vm_message::{SignedMessage, SignedMessageError}; use fvm::{ call_manager::DefaultCallManager, engine::{EngineConfig, EnginePool}, @@ -13,12 +11,12 @@ use fvm::{ DefaultKernel, }; use fvm_ipld_blockstore::Blockstore; -use fvm_shared::{ - clock::ChainEpoch, econ::TokenAmount, message::Message as FvmMessage, version::NetworkVersion, -}; +use fvm_shared::{clock::ChainEpoch, econ::TokenAmount, version::NetworkVersion}; use crate::{externs::FendermintExterns, Interpreter, Timestamp}; +pub type FvmMessage = fvm_shared::message::Message; + /// A state we create for the execution of all the messages in a block. pub struct FvmState where @@ -60,12 +58,12 @@ where } /// Interpreter working on already verified unsigned messages. -pub struct MessageInterpreter { +pub struct FvmMessageInterpreter { _phantom_db: PhantomData, } #[async_trait] -impl Interpreter for MessageInterpreter +impl Interpreter for FvmMessageInterpreter where DB: Blockstore + 'static + Send + Sync, { @@ -73,7 +71,7 @@ where type State = FvmState; type Output = ApplyRet; - async fn exec_msg( + async fn exec( &self, mut state: Self::State, msg: Self::Message, @@ -86,45 +84,3 @@ where Ok((state, ret)) } } - -/// Interpreter working on signed messages, validating their signature before sending -/// the unsigned parts on for execution. -pub struct SignedMessageInterpreter { - message_interpreter: MI, -} - -pub enum SignedMesssageApplyRet { - InvalidSignature(String), - Applied(ApplyRet), -} - -#[async_trait] -impl Interpreter for SignedMessageInterpreter -where - MI: Interpreter, -{ - type Message = SignedMessage; - type Output = SignedMesssageApplyRet; - type State = MI::State; - - async fn exec_msg( - &self, - state: Self::State, - msg: Self::Message, - ) -> anyhow::Result<(Self::State, Self::Output)> { - match msg.verify() { - Err(SignedMessageError::Ipld(e)) => Err(anyhow!(e)), - Err(SignedMessageError::InvalidSignature(s)) => { - Ok((state, SignedMesssageApplyRet::InvalidSignature(s))) - } - Ok(()) => { - let (state, ret) = self - .message_interpreter - .exec_msg(state, msg.message) - .await?; - - Ok((state, SignedMesssageApplyRet::Applied(ret))) - } - } - } -} diff --git a/fendermint/vm/interpreter/src/lib.rs b/fendermint/vm/interpreter/src/lib.rs index 40d60839..2f5582fa 100644 --- a/fendermint/vm/interpreter/src/lib.rs +++ b/fendermint/vm/interpreter/src/lib.rs @@ -1,7 +1,9 @@ use async_trait::async_trait; +pub mod chain; mod externs; -pub mod vm; +pub mod fvm; +pub mod signed; /// Unix timestamp (in seconds) of the current block. pub struct Timestamp(pub u64); @@ -52,7 +54,7 @@ pub trait Interpreter: Sync + Send { /// Only return an error case if something truly unexpected happens /// that should stop message processing altogether; otherwise use /// the output for signalling all execution results. - async fn exec_msg( + async fn exec( &self, state: Self::State, msg: Self::Message, diff --git a/fendermint/vm/interpreter/src/signed.rs b/fendermint/vm/interpreter/src/signed.rs new file mode 100644 index 00000000..b89fe7cd --- /dev/null +++ b/fendermint/vm/interpreter/src/signed.rs @@ -0,0 +1,47 @@ +use anyhow::anyhow; +use async_trait::async_trait; + +use fendermint_vm_message::signed::{SignedMessage, SignedMessageError}; +use fvm::executor::ApplyRet; + +use crate::{fvm::FvmMessage, Interpreter}; + +/// Interpreter working on signed messages, validating their signature before sending +/// the unsigned parts on for execution. +pub struct SignedMessageInterpreter { + inner: I, +} + +pub enum SignedMesssageApplyRet { + InvalidSignature(String), + Applied(ApplyRet), +} + +#[async_trait] +impl Interpreter for SignedMessageInterpreter +where + I: Interpreter, +{ + type Message = SignedMessage; + type Output = SignedMesssageApplyRet; + type State = I::State; + + async fn exec( + &self, + state: Self::State, + msg: Self::Message, + ) -> anyhow::Result<(Self::State, Self::Output)> { + match msg.verify() { + Err(SignedMessageError::Ipld(e)) => Err(anyhow!(e)), + Err(SignedMessageError::InvalidSignature(s)) => { + // TODO: We can penalize the validator for including an invalid signature. + Ok((state, SignedMesssageApplyRet::InvalidSignature(s))) + } + Ok(()) => { + let (state, ret) = self.inner.exec(state, msg.message).await?; + + Ok((state, SignedMesssageApplyRet::Applied(ret))) + } + } + } +} diff --git a/fendermint/vm/message/src/chain_message.rs b/fendermint/vm/message/src/chain.rs similarity index 96% rename from fendermint/vm/message/src/chain_message.rs rename to fendermint/vm/message/src/chain.rs index 70b624e1..cb997b85 100644 --- a/fendermint/vm/message/src/chain_message.rs +++ b/fendermint/vm/message/src/chain.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0, MIT use serde::{Deserialize, Serialize}; -use crate::signed_message::SignedMessage; +use crate::signed::SignedMessage; /// The different kinds of messages that can appear in blocks, ie. the transactions /// we can receive from Tendermint through the ABCI. @@ -15,7 +15,7 @@ use crate::signed_message::SignedMessage; #[serde(untagged)] pub enum ChainMessage { /// A message that can be passed on to the FVM as-is. - SelfContained(SignedMessage), + Signed(SignedMessage), // TODO: ForResolution - A message CID proposed for async resolution. // This will not need a signature, it is proposed by the validator who made the block. // We might want to add a `from` and a signature anyway if we want to reward relayers. diff --git a/fendermint/vm/message/src/lib.rs b/fendermint/vm/message/src/lib.rs index d15047c9..46d56061 100644 --- a/fendermint/vm/message/src/lib.rs +++ b/fendermint/vm/message/src/lib.rs @@ -4,11 +4,8 @@ use cid::{multihash, multihash::MultihashDigest, Cid}; use fvm_ipld_encoding::{to_vec, Error as IpldError, DAG_CBOR}; use serde::Serialize; -mod chain_message; -mod signed_message; - -pub use chain_message::ChainMessage; -pub use signed_message::{SignedMessage, SignedMessageError}; +pub mod chain; +pub mod signed; /// Calculate the CID using Blake2b256 digest and DAG_CBOR. /// diff --git a/fendermint/vm/message/src/signed_message.rs b/fendermint/vm/message/src/signed.rs similarity index 100% rename from fendermint/vm/message/src/signed_message.rs rename to fendermint/vm/message/src/signed.rs From 925bf359525f215d834252309118c4248157dbff Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Fri, 3 Feb 2023 09:44:00 +0000 Subject: [PATCH 05/16] FM-13: Rename to Deliverer --- fendermint/app/src/app.rs | 12 ++++++------ fendermint/vm/interpreter/src/chain.rs | 12 ++++++------ fendermint/vm/interpreter/src/fvm.rs | 8 ++++---- fendermint/vm/interpreter/src/lib.rs | 4 ++-- fendermint/vm/interpreter/src/signed.rs | 12 ++++++------ 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/fendermint/app/src/app.rs b/fendermint/app/src/app.rs index 9139aee0..b51e1c25 100644 --- a/fendermint/app/src/app.rs +++ b/fendermint/app/src/app.rs @@ -5,7 +5,7 @@ use cid::Cid; use fendermint_abci::Application; use fendermint_vm_interpreter::chain::ChainMessageApplyRet; use fendermint_vm_interpreter::fvm::FvmState; -use fendermint_vm_interpreter::{Interpreter, Timestamp}; +use fendermint_vm_interpreter::{Deliverer, Timestamp}; use fendermint_vm_message::chain::ChainMessage; use fvm_ipld_blockstore::Blockstore; use fvm_shared::econ::TokenAmount; @@ -23,17 +23,17 @@ struct State { } /// Handle ABCI requests. -pub struct FendermintApp +pub struct FendermintApp where DB: Blockstore + 'static, { db: Arc, - exec_interpreter: Arc, + interpreter: Arc, /// State accumulating changes during block execution. exec_state: Arc>>>, } -impl FendermintApp +impl FendermintApp where DB: Blockstore + 'static, { @@ -44,10 +44,10 @@ where } #[async_trait] -impl Application for FendermintApp +impl Application for FendermintApp where DB: Blockstore + Clone + Send + Sync + 'static, - EI: Interpreter, Message = ChainMessage, Output = ChainMessageApplyRet>, + I: Deliverer, Message = ChainMessage, Output = ChainMessageApplyRet>, { /// Provide information about the ABCI application. async fn info(&self, _request: request::Info) -> response::Info { diff --git a/fendermint/vm/interpreter/src/chain.rs b/fendermint/vm/interpreter/src/chain.rs index 666b6aee..12404559 100644 --- a/fendermint/vm/interpreter/src/chain.rs +++ b/fendermint/vm/interpreter/src/chain.rs @@ -2,11 +2,11 @@ use async_trait::async_trait; use fendermint_vm_message::{chain::ChainMessage, signed::SignedMessage}; -use crate::{signed::SignedMesssageApplyRet, Interpreter}; +use crate::{signed::SignedMesssageApplyRet, Deliverer}; /// Interpreter working on chain messages; in the future it will schedule /// CID lookups to turn references into self-contained user or cross messages. -pub struct ChainMessageInterpreter { +pub struct ChainMessageDeliverer { inner: I, } @@ -15,22 +15,22 @@ pub enum ChainMessageApplyRet { } #[async_trait] -impl Interpreter for ChainMessageInterpreter +impl Deliverer for ChainMessageDeliverer where - I: Interpreter, + I: Deliverer, { type Message = ChainMessage; type Output = ChainMessageApplyRet; type State = I::State; - async fn exec( + async fn deliver( &self, state: Self::State, msg: Self::Message, ) -> anyhow::Result<(Self::State, Self::Output)> { match msg { ChainMessage::Signed(msg) => { - let (state, ret) = self.inner.exec(state, msg).await?; + let (state, ret) = self.inner.deliver(state, msg).await?; Ok((state, ChainMessageApplyRet::Signed(ret))) } } diff --git a/fendermint/vm/interpreter/src/fvm.rs b/fendermint/vm/interpreter/src/fvm.rs index 9bb66de2..88ea7d13 100644 --- a/fendermint/vm/interpreter/src/fvm.rs +++ b/fendermint/vm/interpreter/src/fvm.rs @@ -13,7 +13,7 @@ use fvm::{ use fvm_ipld_blockstore::Blockstore; use fvm_shared::{clock::ChainEpoch, econ::TokenAmount, version::NetworkVersion}; -use crate::{externs::FendermintExterns, Interpreter, Timestamp}; +use crate::{externs::FendermintExterns, Deliverer, Timestamp}; pub type FvmMessage = fvm_shared::message::Message; @@ -58,12 +58,12 @@ where } /// Interpreter working on already verified unsigned messages. -pub struct FvmMessageInterpreter { +pub struct FvmMessageDeliverer { _phantom_db: PhantomData, } #[async_trait] -impl Interpreter for FvmMessageInterpreter +impl Deliverer for FvmMessageDeliverer where DB: Blockstore + 'static + Send + Sync, { @@ -71,7 +71,7 @@ where type State = FvmState; type Output = ApplyRet; - async fn exec( + async fn deliver( &self, mut state: Self::State, msg: Self::Message, diff --git a/fendermint/vm/interpreter/src/lib.rs b/fendermint/vm/interpreter/src/lib.rs index 2f5582fa..db5745ee 100644 --- a/fendermint/vm/interpreter/src/lib.rs +++ b/fendermint/vm/interpreter/src/lib.rs @@ -40,7 +40,7 @@ pub struct Timestamp(pub u64); /// signalling unexpected problems there's no recovering from and /// that should stop the block processing altogether. #[async_trait] -pub trait Interpreter: Sync + Send { +pub trait Deliverer: Sync + Send { type State: Send; type Message: Send; type Output; @@ -54,7 +54,7 @@ pub trait Interpreter: Sync + Send { /// Only return an error case if something truly unexpected happens /// that should stop message processing altogether; otherwise use /// the output for signalling all execution results. - async fn exec( + async fn deliver( &self, state: Self::State, msg: Self::Message, diff --git a/fendermint/vm/interpreter/src/signed.rs b/fendermint/vm/interpreter/src/signed.rs index b89fe7cd..dbdefa97 100644 --- a/fendermint/vm/interpreter/src/signed.rs +++ b/fendermint/vm/interpreter/src/signed.rs @@ -4,11 +4,11 @@ use async_trait::async_trait; use fendermint_vm_message::signed::{SignedMessage, SignedMessageError}; use fvm::executor::ApplyRet; -use crate::{fvm::FvmMessage, Interpreter}; +use crate::{fvm::FvmMessage, Deliverer}; /// Interpreter working on signed messages, validating their signature before sending /// the unsigned parts on for execution. -pub struct SignedMessageInterpreter { +pub struct SignedMessageDeliverer { inner: I, } @@ -18,15 +18,15 @@ pub enum SignedMesssageApplyRet { } #[async_trait] -impl Interpreter for SignedMessageInterpreter +impl Deliverer for SignedMessageDeliverer where - I: Interpreter, + I: Deliverer, { type Message = SignedMessage; type Output = SignedMesssageApplyRet; type State = I::State; - async fn exec( + async fn deliver( &self, state: Self::State, msg: Self::Message, @@ -38,7 +38,7 @@ where Ok((state, SignedMesssageApplyRet::InvalidSignature(s))) } Ok(()) => { - let (state, ret) = self.inner.exec(state, msg.message).await?; + let (state, ret) = self.inner.deliver(state, msg.message).await?; Ok((state, SignedMesssageApplyRet::Applied(ret))) } From 66cce46f4461980dd8dc37cb17e17f5dedcac60b Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Fri, 3 Feb 2023 11:17:37 +0000 Subject: [PATCH 06/16] FM-13: Rename back to Interpreter and add begin/end methods --- fendermint/app/src/app.rs | 49 +++++++++++++++++++++---- fendermint/app/src/lib.rs | 2 +- fendermint/app/src/main.rs | 18 ++++++++- fendermint/vm/interpreter/src/chain.rs | 30 +++++++++++---- fendermint/vm/interpreter/src/fvm.rs | 33 ++++++++++++++--- fendermint/vm/interpreter/src/lib.rs | 24 +++++++++--- fendermint/vm/interpreter/src/signed.rs | 36 +++++++++++++----- 7 files changed, 156 insertions(+), 36 deletions(-) diff --git a/fendermint/app/src/app.rs b/fendermint/app/src/app.rs index b51e1c25..de367c57 100644 --- a/fendermint/app/src/app.rs +++ b/fendermint/app/src/app.rs @@ -5,7 +5,7 @@ use cid::Cid; use fendermint_abci::Application; use fendermint_vm_interpreter::chain::ChainMessageApplyRet; use fendermint_vm_interpreter::fvm::FvmState; -use fendermint_vm_interpreter::{Deliverer, Timestamp}; +use fendermint_vm_interpreter::{Interpreter, Timestamp}; use fendermint_vm_message::chain::ChainMessage; use fvm_ipld_blockstore::Blockstore; use fvm_shared::econ::TokenAmount; @@ -33,6 +33,19 @@ where exec_state: Arc>>>, } +impl FendermintApp +where + DB: Blockstore + 'static, +{ + pub fn new(db: Arc, interpreter: I) -> Self { + Self { + db, + interpreter: Arc::new(interpreter), + exec_state: Arc::new(Mutex::new(None)), + } + } +} + impl FendermintApp where DB: Blockstore + 'static, @@ -41,13 +54,37 @@ where fn committed_state(&self) -> State { todo!("retrieve state from the DB") } + + /// Put the execution state during block execution. Has to be empty. + fn put_exec_state(&self, state: FvmState) { + let mut guard = self.exec_state.lock().expect("mutex poisoned"); + assert!(guard.is_some(), "exec state not empty"); + *guard = Some(state); + } + + /// Take the execution state during block execution. Has to be non-empty. + fn take_exec_state(&self) -> FvmState { + let mut guard = self.exec_state.lock().expect("mutex poisoned"); + guard.take().expect("exec state empty") + } } +// NOTE: The `Application` interface doesn't allow failures at the moment. The protobuf +// of `Response` actually has an `Exception` type, so in theory we could use that, and +// Tendermint would break up the connection. However, before the response could reach it, +// the `tower-abci` library would throw an exception because when it tried to convert +// a `Response::Exception` into a `ConensusResponse` for example. #[async_trait] impl Application for FendermintApp where DB: Blockstore + Clone + Send + Sync + 'static, - I: Deliverer, Message = ChainMessage, Output = ChainMessageApplyRet>, + I: Interpreter< + State = FvmState, + Message = ChainMessage, + BeginOutput = (), + DeliverOutput = ChainMessageApplyRet, + EndOutput = (), + >, { /// Provide information about the ABCI application. async fn info(&self, _request: request::Info) -> response::Info { @@ -105,17 +142,15 @@ where ) .expect("error creating new state"); - // TODO: Call the begin to run cron stuff. + let (state, ()) = self.interpreter.begin(state).await.expect("begin failed"); - let mut guard = self.exec_state.lock().expect("mutex poisoned"); - assert!(guard.is_none(), "state not empty at begin"); - *guard = Some(state); + self.put_exec_state(state); response::BeginBlock { events: Vec::new() } } /// Apply a transaction to the application's state. - async fn deliver_tx(&self, _request: request::DeliverTx) -> response::DeliverTx { + async fn deliver_tx(&self, request: request::DeliverTx) -> response::DeliverTx { todo!() } diff --git a/fendermint/app/src/lib.rs b/fendermint/app/src/lib.rs index c0de3909..309be628 100644 --- a/fendermint/app/src/lib.rs +++ b/fendermint/app/src/lib.rs @@ -1 +1 @@ -mod app; +pub mod app; diff --git a/fendermint/app/src/main.rs b/fendermint/app/src/main.rs index 7dcc1936..fffec14a 100644 --- a/fendermint/app/src/main.rs +++ b/fendermint/app/src/main.rs @@ -1,9 +1,25 @@ // Copyright 2022-2023 Protocol Labs // SPDX-License-Identifier: Apache-2.0, MIT +use std::sync::Arc; + +use fendermint_app::app; +use fendermint_vm_interpreter::{ + chain::ChainMessageInterpreter, fvm::FvmMessageInterpreter, signed::SignedMessageInterpreter, +}; +use forest_db::rocks::RocksDb; + #[tokio::main] async fn main() { - println!("Soon.") + let interpreter = FvmMessageInterpreter::::new(); + let interpreter = SignedMessageInterpreter::new(interpreter); + let interpreter = ChainMessageInterpreter::new(interpreter); + let db = open_db(); + let _app: app::FendermintApp = app::FendermintApp::new(db, interpreter); +} + +fn open_db() -> Arc { + todo!() } #[cfg(test)] diff --git a/fendermint/vm/interpreter/src/chain.rs b/fendermint/vm/interpreter/src/chain.rs index 12404559..cdca7abc 100644 --- a/fendermint/vm/interpreter/src/chain.rs +++ b/fendermint/vm/interpreter/src/chain.rs @@ -2,32 +2,40 @@ use async_trait::async_trait; use fendermint_vm_message::{chain::ChainMessage, signed::SignedMessage}; -use crate::{signed::SignedMesssageApplyRet, Deliverer}; +use crate::{signed::SignedMesssageApplyRet, Interpreter}; /// Interpreter working on chain messages; in the future it will schedule /// CID lookups to turn references into self-contained user or cross messages. -pub struct ChainMessageDeliverer { +pub struct ChainMessageInterpreter { inner: I, } +impl ChainMessageInterpreter { + pub fn new(inner: I) -> Self { + Self { inner } + } +} + pub enum ChainMessageApplyRet { Signed(SignedMesssageApplyRet), } #[async_trait] -impl Deliverer for ChainMessageDeliverer +impl Interpreter for ChainMessageInterpreter where - I: Deliverer, + I: Interpreter, { - type Message = ChainMessage; - type Output = ChainMessageApplyRet; type State = I::State; + type Message = ChainMessage; + type BeginOutput = I::BeginOutput; + type DeliverOutput = ChainMessageApplyRet; + type EndOutput = I::EndOutput; async fn deliver( &self, state: Self::State, msg: Self::Message, - ) -> anyhow::Result<(Self::State, Self::Output)> { + ) -> anyhow::Result<(Self::State, Self::DeliverOutput)> { match msg { ChainMessage::Signed(msg) => { let (state, ret) = self.inner.deliver(state, msg).await?; @@ -35,4 +43,12 @@ where } } } + + async fn begin(&self, state: Self::State) -> anyhow::Result<(Self::State, Self::BeginOutput)> { + self.inner.begin(state).await + } + + async fn end(&self, state: Self::State) -> anyhow::Result<(Self::State, Self::EndOutput)> { + self.inner.end(state).await + } } diff --git a/fendermint/vm/interpreter/src/fvm.rs b/fendermint/vm/interpreter/src/fvm.rs index 88ea7d13..0dcd022b 100644 --- a/fendermint/vm/interpreter/src/fvm.rs +++ b/fendermint/vm/interpreter/src/fvm.rs @@ -13,9 +13,10 @@ use fvm::{ use fvm_ipld_blockstore::Blockstore; use fvm_shared::{clock::ChainEpoch, econ::TokenAmount, version::NetworkVersion}; -use crate::{externs::FendermintExterns, Deliverer, Timestamp}; +use crate::{externs::FendermintExterns, Interpreter, Timestamp}; pub type FvmMessage = fvm_shared::message::Message; +pub type FvmApplyRet = ApplyRet; /// A state we create for the execution of all the messages in a block. pub struct FvmState @@ -58,24 +59,39 @@ where } /// Interpreter working on already verified unsigned messages. -pub struct FvmMessageDeliverer { +pub struct FvmMessageInterpreter { _phantom_db: PhantomData, } +impl FvmMessageInterpreter { + pub fn new() -> Self { + Self { + _phantom_db: PhantomData, + } + } +} + #[async_trait] -impl Deliverer for FvmMessageDeliverer +impl Interpreter for FvmMessageInterpreter where DB: Blockstore + 'static + Send + Sync, { - type Message = FvmMessage; type State = FvmState; - type Output = ApplyRet; + type Message = FvmMessage; + type BeginOutput = (); + type DeliverOutput = FvmApplyRet; + type EndOutput = (); + + async fn begin(&self, state: Self::State) -> anyhow::Result<(Self::State, Self::BeginOutput)> { + // TODO: Cron. + Ok((state, ())) + } async fn deliver( &self, mut state: Self::State, msg: Self::Message, - ) -> anyhow::Result<(Self::State, Self::Output)> { + ) -> anyhow::Result<(Self::State, Self::DeliverOutput)> { let raw_length = fvm_ipld_encoding::to_vec(&msg).map(|bz| bz.len())?; let ret = state @@ -83,4 +99,9 @@ where .execute_message(msg, fvm::executor::ApplyKind::Explicit, raw_length)?; Ok((state, ret)) } + + async fn end(&self, state: Self::State) -> anyhow::Result<(Self::State, Self::EndOutput)> { + // TODO: Epoch transitions for checkpointing. + Ok((state, ())) + } } diff --git a/fendermint/vm/interpreter/src/lib.rs b/fendermint/vm/interpreter/src/lib.rs index db5745ee..5868e730 100644 --- a/fendermint/vm/interpreter/src/lib.rs +++ b/fendermint/vm/interpreter/src/lib.rs @@ -8,9 +8,10 @@ pub mod signed; /// Unix timestamp (in seconds) of the current block. pub struct Timestamp(pub u64); -/// The interpreter applies messages on some state. +/// The `Interpreter`applies messages on some state, which is +/// tied to the lifecycle of a block in the ABCI. /// -/// By making them generic, the intention is that interpreters can +/// By making it generic, the intention is that interpreters can /// be stacked, changing the type of message along the way. For /// example on the outermost layer the input message can be a mix /// of self-contained messages and CIDs proposed for resolution @@ -40,10 +41,17 @@ pub struct Timestamp(pub u64); /// signalling unexpected problems there's no recovering from and /// that should stop the block processing altogether. #[async_trait] -pub trait Deliverer: Sync + Send { +pub trait Interpreter: Sync + Send { type State: Send; type Message: Send; - type Output; + type BeginOutput; + type DeliverOutput; + type EndOutput; + + /// Called once at the beginning of a block. + /// + /// This is our chance to to run `cron` jobs for example. + async fn begin(&self, state: Self::State) -> anyhow::Result<(Self::State, Self::BeginOutput)>; /// Apply a message onto the state. /// @@ -58,5 +66,11 @@ pub trait Deliverer: Sync + Send { &self, state: Self::State, msg: Self::Message, - ) -> anyhow::Result<(Self::State, Self::Output)>; + ) -> anyhow::Result<(Self::State, Self::DeliverOutput)>; + + /// Called once at the end of a block. + /// + /// This is where we can apply end-of-epoch processing, for example to process staking + /// requests once every 1000 blocks. + async fn end(&self, state: Self::State) -> anyhow::Result<(Self::State, Self::EndOutput)>; } diff --git a/fendermint/vm/interpreter/src/signed.rs b/fendermint/vm/interpreter/src/signed.rs index dbdefa97..28978257 100644 --- a/fendermint/vm/interpreter/src/signed.rs +++ b/fendermint/vm/interpreter/src/signed.rs @@ -2,35 +2,45 @@ use anyhow::anyhow; use async_trait::async_trait; use fendermint_vm_message::signed::{SignedMessage, SignedMessageError}; -use fvm::executor::ApplyRet; -use crate::{fvm::FvmMessage, Deliverer}; +use crate::{ + fvm::{FvmApplyRet, FvmMessage}, + Interpreter, +}; /// Interpreter working on signed messages, validating their signature before sending /// the unsigned parts on for execution. -pub struct SignedMessageDeliverer { +pub struct SignedMessageInterpreter { inner: I, } +impl SignedMessageInterpreter { + pub fn new(inner: I) -> Self { + Self { inner } + } +} + pub enum SignedMesssageApplyRet { InvalidSignature(String), - Applied(ApplyRet), + Applied(FvmApplyRet), } #[async_trait] -impl Deliverer for SignedMessageDeliverer +impl Interpreter for SignedMessageInterpreter where - I: Deliverer, + I: Interpreter, { - type Message = SignedMessage; - type Output = SignedMesssageApplyRet; type State = I::State; + type Message = SignedMessage; + type BeginOutput = I::BeginOutput; + type DeliverOutput = SignedMesssageApplyRet; + type EndOutput = I::EndOutput; async fn deliver( &self, state: Self::State, msg: Self::Message, - ) -> anyhow::Result<(Self::State, Self::Output)> { + ) -> anyhow::Result<(Self::State, Self::DeliverOutput)> { match msg.verify() { Err(SignedMessageError::Ipld(e)) => Err(anyhow!(e)), Err(SignedMessageError::InvalidSignature(s)) => { @@ -44,4 +54,12 @@ where } } } + + async fn begin(&self, state: Self::State) -> anyhow::Result<(Self::State, Self::BeginOutput)> { + self.inner.begin(state).await + } + + async fn end(&self, state: Self::State) -> anyhow::Result<(Self::State, Self::EndOutput)> { + self.inner.end(state).await + } } From 2e0c1dd729b830080ee0e4ef63f034b150cf159d Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Fri, 3 Feb 2023 12:20:42 +0000 Subject: [PATCH 07/16] FM-13: Start implementing deliver_tx --- Cargo.lock | 2 + fendermint/app/Cargo.toml | 4 +- fendermint/app/src/app.rs | 112 +++++++++++++++++++++++++++++++++---- fendermint/app/src/main.rs | 2 +- 4 files changed, 107 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1f2a6e43..791a61ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1362,6 +1362,7 @@ dependencies = [ name = "fendermint_app" version = "0.1.0" dependencies = [ + "anyhow", "async-trait", "cid", "fendermint_abci", @@ -1372,6 +1373,7 @@ dependencies = [ "fvm", "fvm_ipld_blockstore", "fvm_ipld_car", + "fvm_ipld_encoding 0.3.3", "fvm_shared 3.0.0-alpha.17", "tempfile", "tendermint", diff --git a/fendermint/app/Cargo.toml b/fendermint/app/Cargo.toml index c46dd180..b5733989 100644 --- a/fendermint/app/Cargo.toml +++ b/fendermint/app/Cargo.toml @@ -7,6 +7,7 @@ edition.workspace = true license.workspace = true [dependencies] +anyhow = { workspace = true } async-trait = { workspace = true } tokio = { workspace = true } tendermint = { workspace = true } @@ -17,8 +18,9 @@ fendermint_vm_message = { path = "../vm/message" } cid = { workspace = true } fvm = { workspace = true } -fvm_ipld_car = { workspace = true } fvm_ipld_blockstore = { workspace = true } +fvm_ipld_car = { workspace = true } +fvm_ipld_encoding = { workspace = true } fvm_shared = { workspace = true } # The current 0.2.0 version of forest_db is not published to crates.io. diff --git a/fendermint/app/src/app.rs b/fendermint/app/src/app.rs index de367c57..21ab7b26 100644 --- a/fendermint/app/src/app.rs +++ b/fendermint/app/src/app.rs @@ -1,20 +1,33 @@ +use std::future::Future; +use std::num::NonZeroU32; use std::sync::{Arc, Mutex}; use async_trait::async_trait; use cid::Cid; use fendermint_abci::Application; use fendermint_vm_interpreter::chain::ChainMessageApplyRet; -use fendermint_vm_interpreter::fvm::FvmState; +use fendermint_vm_interpreter::fvm::{FvmApplyRet, FvmState}; +use fendermint_vm_interpreter::signed::SignedMesssageApplyRet; use fendermint_vm_interpreter::{Interpreter, Timestamp}; use fendermint_vm_message::chain::ChainMessage; use fvm_ipld_blockstore::Blockstore; use fvm_shared::econ::TokenAmount; use fvm_shared::version::NetworkVersion; -use tendermint::abci::{request, response}; +use tendermint::abci::{request, response, Code}; const VERSION: &str = env!("CARGO_PKG_VERSION"); -struct State { +// TODO: What range should we use for our own error codes? Shold we shift FVM errors? + +#[repr(u32)] +enum AppError { + /// Failed to deserialize the transaction. + InvalidEncoding = 51, + /// Failed to validate the user signature. + InvalidSignature = 52, +} + +struct AppState { block_height: u64, state_root: Cid, network_version: NetworkVersion, @@ -23,7 +36,7 @@ struct State { } /// Handle ABCI requests. -pub struct FendermintApp +pub struct App where DB: Blockstore + 'static, { @@ -33,7 +46,7 @@ where exec_state: Arc>>>, } -impl FendermintApp +impl App where DB: Blockstore + 'static, { @@ -46,12 +59,12 @@ where } } -impl FendermintApp +impl App where DB: Blockstore + 'static, { /// Get the last committed state. - fn committed_state(&self) -> State { + fn committed_state(&self) -> AppState { todo!("retrieve state from the DB") } @@ -67,6 +80,18 @@ where let mut guard = self.exec_state.lock().expect("mutex poisoned"); guard.take().expect("exec state empty") } + + /// Take the execution state, update it, put it back, return the output. + async fn modify_exec_state(&self, f: F) -> anyhow::Result + where + F: FnOnce(FvmState) -> R, + R: Future, T)>>, + { + let state = self.take_exec_state(); + let (state, ret) = f(state).await?; + self.put_exec_state(state); + Ok(ret) + } } // NOTE: The `Application` interface doesn't allow failures at the moment. The protobuf @@ -75,7 +100,7 @@ where // the `tower-abci` library would throw an exception because when it tried to convert // a `Response::Exception` into a `ConensusResponse` for example. #[async_trait] -impl Application for FendermintApp +impl Application for App where DB: Blockstore + Clone + Send + Sync + 'static, I: Interpreter< @@ -142,16 +167,36 @@ where ) .expect("error creating new state"); - let (state, ()) = self.interpreter.begin(state).await.expect("begin failed"); - self.put_exec_state(state); + let () = self + .modify_exec_state(|s| self.interpreter.begin(s)) + .await + .expect("begin failed"); + response::BeginBlock { events: Vec::new() } } /// Apply a transaction to the application's state. async fn deliver_tx(&self, request: request::DeliverTx) -> response::DeliverTx { - todo!() + match fvm_ipld_encoding::from_slice::(&request.tx) { + Err(e) => invalid_deliver_tx(AppError::InvalidEncoding, e.description), + Ok(msg) => { + let output = self + .modify_exec_state(|s| self.interpreter.deliver(s, msg)) + .await + .expect("deliver failed"); + + match output { + ChainMessageApplyRet::Signed(SignedMesssageApplyRet::InvalidSignature(d)) => { + invalid_deliver_tx(AppError::InvalidSignature, d) + } + ChainMessageApplyRet::Signed(SignedMesssageApplyRet::Applied(ret)) => { + valid_deliver_tx(ret) + } + } + } + } } /// Signals the end of a block. @@ -164,3 +209,48 @@ where todo!() } } + +/// Response to delivery where the input was blatantly invalid. +/// This indicates that the validator who made the block was Byzantine. +fn invalid_deliver_tx(err: AppError, description: String) -> response::DeliverTx { + response::DeliverTx { + code: Code::Err(NonZeroU32::try_from(err as u32).expect("error codes are non-zero")), + info: description, + ..Default::default() + } +} + +fn valid_deliver_tx(ret: FvmApplyRet) -> response::DeliverTx { + let code = if ret.msg_receipt.exit_code.is_success() { + Code::Ok + } else { + Code::Err( + NonZeroU32::try_from(ret.msg_receipt.exit_code.value()) + .expect("error codes are non-zero"), + ) + }; + + // Based on the sanity check in the `DefaultExecutor`. + // gas_cost = gas_fee_cap * gas_limit + // &base_fee_burn + &over_estimation_burn + &refund + &miner_tip == gas_cost + // But that's in tokens, and there doesn't seem to be a way to see what the price was. + // TODO: Add the gas limit to the result. + let gas_wanted = 0; + let gas_used = ret.msg_receipt.gas_used; + + let data = ret.msg_receipt.return_data.to_vec().into(); + + // TODO: Convert events. + let events = Vec::new(); + + response::DeliverTx { + code, + data, + log: Default::default(), + info: Default::default(), + gas_wanted, + gas_used, + events, + codespace: Default::default(), + } +} diff --git a/fendermint/app/src/main.rs b/fendermint/app/src/main.rs index fffec14a..880afc41 100644 --- a/fendermint/app/src/main.rs +++ b/fendermint/app/src/main.rs @@ -15,7 +15,7 @@ async fn main() { let interpreter = SignedMessageInterpreter::new(interpreter); let interpreter = ChainMessageInterpreter::new(interpreter); let db = open_db(); - let _app: app::FendermintApp = app::FendermintApp::new(db, interpreter); + let _app: app::App = app::App::new(db, interpreter); } fn open_db() -> Arc { From e161ea2535a2d46facd83986a8df468e62c92101 Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Fri, 3 Feb 2023 13:52:30 +0000 Subject: [PATCH 08/16] FM-13: Commit --- fendermint/app/src/app.rs | 37 +++++++++++++++++++++++++--- fendermint/vm/interpreter/src/fvm.rs | 11 +++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/fendermint/app/src/app.rs b/fendermint/app/src/app.rs index 21ab7b26..52c73fb6 100644 --- a/fendermint/app/src/app.rs +++ b/fendermint/app/src/app.rs @@ -68,6 +68,11 @@ where todo!("retrieve state from the DB") } + /// Set the last committed state. + fn set_committed_state(&self, _state: AppState) { + todo!("write state to the DB") + } + /// Put the execution state during block execution. Has to be empty. fn put_exec_state(&self, state: FvmState) { let mut guard = self.exec_state.lock().expect("mutex poisoned"); @@ -169,6 +174,7 @@ where self.put_exec_state(state); + // TODO: Map events from cron. let () = self .modify_exec_state(|s| self.interpreter.begin(s)) .await @@ -201,12 +207,34 @@ where /// Signals the end of a block. async fn end_block(&self, _request: request::EndBlock) -> response::EndBlock { - todo!() + // TODO: Return events from epoch transitions. + let () = self + .modify_exec_state(|s| self.interpreter.end(s)) + .await + .expect("end failed"); + + // TODO: Map the end. + response::EndBlock { + validator_updates: Vec::new(), + consensus_param_updates: None, + events: Vec::new(), + } } /// Commit the current state at the current height. async fn commit(&self) -> response::Commit { - todo!() + let exec_state = self.take_exec_state(); + let state_root = exec_state.commit().expect("failed to commit FVM"); + + let mut state = self.committed_state(); + state.state_root = state_root; + self.set_committed_state(state); + + response::Commit { + data: state_root.to_bytes().into(), + // We have to retain blocks until we can support Snapshots. + retain_height: Default::default(), + } } } @@ -240,7 +268,10 @@ fn valid_deliver_tx(ret: FvmApplyRet) -> response::DeliverTx { let data = ret.msg_receipt.return_data.to_vec().into(); - // TODO: Convert events. + // TODO: Convert events. This is currently not possible because the event fields are private. + // I changed that in https://github.com/filecoin-project/ref-fvm/pull/1507 but it's still in review. + // A possible workaround would be to retrieve the events by their CID, and use a custom type to parse. + // It will be part of https://github.com/filecoin-project/ref-fvm/pull/1635 :) let events = Vec::new(); response::DeliverTx { diff --git a/fendermint/vm/interpreter/src/fvm.rs b/fendermint/vm/interpreter/src/fvm.rs index 0dcd022b..14ac3ef1 100644 --- a/fendermint/vm/interpreter/src/fvm.rs +++ b/fendermint/vm/interpreter/src/fvm.rs @@ -56,6 +56,17 @@ where Ok(Self { executor }) } + + /// Commit the state. It must not fail, but we're returning a result so that error + /// handling can be done in the application root. + /// + /// For now this is not part of the `Interpreter` because it's not clear what atomic + /// semantics we can hope to provide if the middlewares call each other: did it go + /// all the way down, or did it stop somewhere? Easier to have one commit of the state + /// as a whole. + pub fn commit(mut self) -> anyhow::Result { + self.executor.flush() + } } /// Interpreter working on already verified unsigned messages. From 0ed4d54b2755171e92ec4f1370e95a94a8878d76 Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Fri, 3 Feb 2023 13:57:22 +0000 Subject: [PATCH 09/16] FM-13: Placeholder mappings. --- fendermint/app/src/app.rs | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/fendermint/app/src/app.rs b/fendermint/app/src/app.rs index 52c73fb6..1c08c39c 100644 --- a/fendermint/app/src/app.rs +++ b/fendermint/app/src/app.rs @@ -175,12 +175,12 @@ where self.put_exec_state(state); // TODO: Map events from cron. - let () = self + let ret = self .modify_exec_state(|s| self.interpreter.begin(s)) .await .expect("begin failed"); - response::BeginBlock { events: Vec::new() } + to_begin_block(ret) } /// Apply a transaction to the application's state. @@ -198,7 +198,7 @@ where invalid_deliver_tx(AppError::InvalidSignature, d) } ChainMessageApplyRet::Signed(SignedMesssageApplyRet::Applied(ret)) => { - valid_deliver_tx(ret) + to_deliver_tx(ret) } } } @@ -208,17 +208,12 @@ where /// Signals the end of a block. async fn end_block(&self, _request: request::EndBlock) -> response::EndBlock { // TODO: Return events from epoch transitions. - let () = self + let ret = self .modify_exec_state(|s| self.interpreter.end(s)) .await .expect("end failed"); - // TODO: Map the end. - response::EndBlock { - validator_updates: Vec::new(), - consensus_param_updates: None, - events: Vec::new(), - } + to_end_block(ret) } /// Commit the current state at the current height. @@ -248,7 +243,7 @@ fn invalid_deliver_tx(err: AppError, description: String) -> response::DeliverTx } } -fn valid_deliver_tx(ret: FvmApplyRet) -> response::DeliverTx { +fn to_deliver_tx(ret: FvmApplyRet) -> response::DeliverTx { let code = if ret.msg_receipt.exit_code.is_success() { Code::Ok } else { @@ -285,3 +280,21 @@ fn valid_deliver_tx(ret: FvmApplyRet) -> response::DeliverTx { codespace: Default::default(), } } + +/// Map the return values from epoch boundary operations to validator updates. +/// +/// (Currently just a placeholder). +fn to_end_block(_ret: ()) -> response::EndBlock { + response::EndBlock { + validator_updates: Vec::new(), + consensus_param_updates: None, + events: Vec::new(), + } +} + +/// Map the return values from cron operations. +/// +/// (Currently just a placeholder). +fn to_begin_block(_ret: ()) -> response::BeginBlock { + response::BeginBlock { events: Vec::new() } +} From d5a96c408ab82ad0a60361f70af9c4db6c8b68ef Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Fri, 3 Feb 2023 14:03:29 +0000 Subject: [PATCH 10/16] FM-13: Add gas limit to return value. --- fendermint/app/src/app.rs | 18 ++++++++---------- fendermint/vm/interpreter/src/fvm.rs | 20 ++++++++++++++++++-- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/fendermint/app/src/app.rs b/fendermint/app/src/app.rs index 1c08c39c..f79a24a5 100644 --- a/fendermint/app/src/app.rs +++ b/fendermint/app/src/app.rs @@ -174,7 +174,6 @@ where self.put_exec_state(state); - // TODO: Map events from cron. let ret = self .modify_exec_state(|s| self.interpreter.begin(s)) .await @@ -244,24 +243,23 @@ fn invalid_deliver_tx(err: AppError, description: String) -> response::DeliverTx } fn to_deliver_tx(ret: FvmApplyRet) -> response::DeliverTx { - let code = if ret.msg_receipt.exit_code.is_success() { + let receipt = ret.apply_ret.msg_receipt; + let code = if receipt.exit_code.is_success() { Code::Ok } else { Code::Err( - NonZeroU32::try_from(ret.msg_receipt.exit_code.value()) - .expect("error codes are non-zero"), + NonZeroU32::try_from(receipt.exit_code.value()).expect("error codes are non-zero"), ) }; // Based on the sanity check in the `DefaultExecutor`. - // gas_cost = gas_fee_cap * gas_limit + // gas_cost = gas_fee_cap * gas_limit; this is how much the account is charged up front. // &base_fee_burn + &over_estimation_burn + &refund + &miner_tip == gas_cost - // But that's in tokens, and there doesn't seem to be a way to see what the price was. - // TODO: Add the gas limit to the result. - let gas_wanted = 0; - let gas_used = ret.msg_receipt.gas_used; + // But that's in tokens. I guess the closes to what we want is the limit. + let gas_wanted = ret.gas_limit; + let gas_used = receipt.gas_used; - let data = ret.msg_receipt.return_data.to_vec().into(); + let data = receipt.return_data.to_vec().into(); // TODO: Convert events. This is currently not possible because the event fields are private. // I changed that in https://github.com/filecoin-project/ref-fvm/pull/1507 but it's still in review. diff --git a/fendermint/vm/interpreter/src/fvm.rs b/fendermint/vm/interpreter/src/fvm.rs index 14ac3ef1..bc3c8b62 100644 --- a/fendermint/vm/interpreter/src/fvm.rs +++ b/fendermint/vm/interpreter/src/fvm.rs @@ -16,7 +16,15 @@ use fvm_shared::{clock::ChainEpoch, econ::TokenAmount, version::NetworkVersion}; use crate::{externs::FendermintExterns, Interpreter, Timestamp}; pub type FvmMessage = fvm_shared::message::Message; -pub type FvmApplyRet = ApplyRet; + +/// The return value extended with some things from the message that +/// might not be available to the caller, because of the message lookups +/// and transformations that happen along the way, e.g. where we need +/// a field, we might just have a CID. +pub struct FvmApplyRet { + pub apply_ret: ApplyRet, + pub gas_limit: i64, +} /// A state we create for the execution of all the messages in a block. pub struct FvmState @@ -104,10 +112,18 @@ where msg: Self::Message, ) -> anyhow::Result<(Self::State, Self::DeliverOutput)> { let raw_length = fvm_ipld_encoding::to_vec(&msg).map(|bz| bz.len())?; - let ret = + let gas_limit = msg.gas_limit; + + let apply_ret = state .executor .execute_message(msg, fvm::executor::ApplyKind::Explicit, raw_length)?; + + let ret = FvmApplyRet { + apply_ret, + gas_limit, + }; + Ok((state, ret)) } From 3b46e5571d11ffc6da22fcd70deb8cb87319b70b Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Fri, 3 Feb 2023 14:19:49 +0000 Subject: [PATCH 11/16] FM-13: Add license headers. --- fendermint/app/src/app.rs | 2 ++ fendermint/app/src/lib.rs | 2 ++ fendermint/vm/interpreter/src/chain.rs | 2 ++ fendermint/vm/interpreter/src/externs.rs | 2 ++ fendermint/vm/interpreter/src/fvm.rs | 2 ++ fendermint/vm/interpreter/src/lib.rs | 2 ++ fendermint/vm/interpreter/src/signed.rs | 2 ++ 7 files changed, 14 insertions(+) diff --git a/fendermint/app/src/app.rs b/fendermint/app/src/app.rs index f79a24a5..7b128bcc 100644 --- a/fendermint/app/src/app.rs +++ b/fendermint/app/src/app.rs @@ -1,3 +1,5 @@ +// Copyright 2022-2023 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT use std::future::Future; use std::num::NonZeroU32; use std::sync::{Arc, Mutex}; diff --git a/fendermint/app/src/lib.rs b/fendermint/app/src/lib.rs index 309be628..126157f3 100644 --- a/fendermint/app/src/lib.rs +++ b/fendermint/app/src/lib.rs @@ -1 +1,3 @@ +// Copyright 2022-2023 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT pub mod app; diff --git a/fendermint/vm/interpreter/src/chain.rs b/fendermint/vm/interpreter/src/chain.rs index cdca7abc..20e3836c 100644 --- a/fendermint/vm/interpreter/src/chain.rs +++ b/fendermint/vm/interpreter/src/chain.rs @@ -1,3 +1,5 @@ +// Copyright 2022-2023 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT use async_trait::async_trait; use fendermint_vm_message::{chain::ChainMessage, signed::SignedMessage}; diff --git a/fendermint/vm/interpreter/src/externs.rs b/fendermint/vm/interpreter/src/externs.rs index 8b685f58..3c90c0d0 100644 --- a/fendermint/vm/interpreter/src/externs.rs +++ b/fendermint/vm/interpreter/src/externs.rs @@ -1,3 +1,5 @@ +// Copyright 2022-2023 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT use cid::Cid; use fvm::externs::{Chain, Consensus, Externs, Rand}; use fvm_shared::clock::ChainEpoch; diff --git a/fendermint/vm/interpreter/src/fvm.rs b/fendermint/vm/interpreter/src/fvm.rs index bc3c8b62..9e61781d 100644 --- a/fendermint/vm/interpreter/src/fvm.rs +++ b/fendermint/vm/interpreter/src/fvm.rs @@ -1,3 +1,5 @@ +// Copyright 2022-2023 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT use std::marker::PhantomData; use async_trait::async_trait; diff --git a/fendermint/vm/interpreter/src/lib.rs b/fendermint/vm/interpreter/src/lib.rs index 5868e730..0524dc9c 100644 --- a/fendermint/vm/interpreter/src/lib.rs +++ b/fendermint/vm/interpreter/src/lib.rs @@ -1,3 +1,5 @@ +// Copyright 2022-2023 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT use async_trait::async_trait; pub mod chain; diff --git a/fendermint/vm/interpreter/src/signed.rs b/fendermint/vm/interpreter/src/signed.rs index 28978257..b9ff8be4 100644 --- a/fendermint/vm/interpreter/src/signed.rs +++ b/fendermint/vm/interpreter/src/signed.rs @@ -1,3 +1,5 @@ +// Copyright 2022-2023 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT use anyhow::anyhow; use async_trait::async_trait; From 955881869e5e2743d965a1fca792f759061bf308 Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Fri, 3 Feb 2023 14:46:56 +0000 Subject: [PATCH 12/16] FM-13: Add default. --- fendermint/vm/interpreter/src/fvm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fendermint/vm/interpreter/src/fvm.rs b/fendermint/vm/interpreter/src/fvm.rs index 9e61781d..50be32cb 100644 --- a/fendermint/vm/interpreter/src/fvm.rs +++ b/fendermint/vm/interpreter/src/fvm.rs @@ -92,6 +92,12 @@ impl FvmMessageInterpreter { } } +impl Default for FvmMessageInterpreter { + fn default() -> Self { + Self::new() + } +} + #[async_trait] impl Interpreter for FvmMessageInterpreter where From 3d4cebc99400144caafcefb123af7e6a20da94b1 Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Fri, 3 Feb 2023 15:45:53 +0000 Subject: [PATCH 13/16] FM-13: Add a BytesMessageInterpreter. --- fendermint/app/src/app.rs | 41 +++++++++--------- fendermint/app/src/main.rs | 6 ++- fendermint/vm/interpreter/src/bytes.rs | 60 ++++++++++++++++++++++++++ fendermint/vm/interpreter/src/lib.rs | 1 + 4 files changed, 85 insertions(+), 23 deletions(-) create mode 100644 fendermint/vm/interpreter/src/bytes.rs diff --git a/fendermint/app/src/app.rs b/fendermint/app/src/app.rs index 7b128bcc..28417296 100644 --- a/fendermint/app/src/app.rs +++ b/fendermint/app/src/app.rs @@ -7,11 +7,11 @@ use std::sync::{Arc, Mutex}; use async_trait::async_trait; use cid::Cid; use fendermint_abci::Application; +use fendermint_vm_interpreter::bytes::BytesMessageApplyRet; use fendermint_vm_interpreter::chain::ChainMessageApplyRet; use fendermint_vm_interpreter::fvm::{FvmApplyRet, FvmState}; use fendermint_vm_interpreter::signed::SignedMesssageApplyRet; use fendermint_vm_interpreter::{Interpreter, Timestamp}; -use fendermint_vm_message::chain::ChainMessage; use fvm_ipld_blockstore::Blockstore; use fvm_shared::econ::TokenAmount; use fvm_shared::version::NetworkVersion; @@ -19,7 +19,7 @@ use tendermint::abci::{request, response, Code}; const VERSION: &str = env!("CARGO_PKG_VERSION"); -// TODO: What range should we use for our own error codes? Shold we shift FVM errors? +// TODO: What range should we use for our own error codes? Should we shift FVM errors? #[repr(u32)] enum AppError { @@ -104,17 +104,17 @@ where // NOTE: The `Application` interface doesn't allow failures at the moment. The protobuf // of `Response` actually has an `Exception` type, so in theory we could use that, and // Tendermint would break up the connection. However, before the response could reach it, -// the `tower-abci` library would throw an exception because when it tried to convert -// a `Response::Exception` into a `ConensusResponse` for example. +// the `tower-abci` library would throw an exception when it tried to convert a +// `Response::Exception` into a `ConensusResponse` for example. #[async_trait] impl Application for App where DB: Blockstore + Clone + Send + Sync + 'static, I: Interpreter< State = FvmState, - Message = ChainMessage, + Message = Vec, BeginOutput = (), - DeliverOutput = ChainMessageApplyRet, + DeliverOutput = BytesMessageApplyRet, EndOutput = (), >, { @@ -186,23 +186,22 @@ where /// Apply a transaction to the application's state. async fn deliver_tx(&self, request: request::DeliverTx) -> response::DeliverTx { - match fvm_ipld_encoding::from_slice::(&request.tx) { + let msg = request.tx.to_vec(); + let result = self + .modify_exec_state(|s| self.interpreter.deliver(s, msg)) + .await + .expect("deliver failed"); + + match result { Err(e) => invalid_deliver_tx(AppError::InvalidEncoding, e.description), - Ok(msg) => { - let output = self - .modify_exec_state(|s| self.interpreter.deliver(s, msg)) - .await - .expect("deliver failed"); - - match output { - ChainMessageApplyRet::Signed(SignedMesssageApplyRet::InvalidSignature(d)) => { - invalid_deliver_tx(AppError::InvalidSignature, d) - } - ChainMessageApplyRet::Signed(SignedMesssageApplyRet::Applied(ret)) => { - to_deliver_tx(ret) - } + Ok(ret) => match ret { + ChainMessageApplyRet::Signed(SignedMesssageApplyRet::InvalidSignature(d)) => { + invalid_deliver_tx(AppError::InvalidSignature, d) + } + ChainMessageApplyRet::Signed(SignedMesssageApplyRet::Applied(ret)) => { + to_deliver_tx(ret) } - } + }, } } diff --git a/fendermint/app/src/main.rs b/fendermint/app/src/main.rs index 880afc41..c2f0abd8 100644 --- a/fendermint/app/src/main.rs +++ b/fendermint/app/src/main.rs @@ -5,7 +5,8 @@ use std::sync::Arc; use fendermint_app::app; use fendermint_vm_interpreter::{ - chain::ChainMessageInterpreter, fvm::FvmMessageInterpreter, signed::SignedMessageInterpreter, + bytes::BytesMessageInterpreter, chain::ChainMessageInterpreter, fvm::FvmMessageInterpreter, + signed::SignedMessageInterpreter, }; use forest_db::rocks::RocksDb; @@ -14,8 +15,9 @@ async fn main() { let interpreter = FvmMessageInterpreter::::new(); let interpreter = SignedMessageInterpreter::new(interpreter); let interpreter = ChainMessageInterpreter::new(interpreter); + let interpreter = BytesMessageInterpreter::new(interpreter); let db = open_db(); - let _app: app::App = app::App::new(db, interpreter); + let _app = app::App::new(db, interpreter); } fn open_db() -> Arc { diff --git a/fendermint/vm/interpreter/src/bytes.rs b/fendermint/vm/interpreter/src/bytes.rs new file mode 100644 index 00000000..85e1f350 --- /dev/null +++ b/fendermint/vm/interpreter/src/bytes.rs @@ -0,0 +1,60 @@ +// Copyright 2022-2023 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT +use async_trait::async_trait; + +use fendermint_vm_message::chain::ChainMessage; + +use crate::{chain::ChainMessageApplyRet, Interpreter}; + +pub type BytesMessageApplyRet = Result; + +/// Interpreter working on raw bytes. +pub struct BytesMessageInterpreter { + inner: I, +} + +impl BytesMessageInterpreter { + pub fn new(inner: I) -> Self { + Self { inner } + } +} + +#[async_trait] +impl Interpreter for BytesMessageInterpreter +where + I: Interpreter, +{ + type State = I::State; + type Message = Vec; + type BeginOutput = I::BeginOutput; + type DeliverOutput = BytesMessageApplyRet; + type EndOutput = I::EndOutput; + + async fn deliver( + &self, + state: Self::State, + msg: Self::Message, + ) -> anyhow::Result<(Self::State, Self::DeliverOutput)> { + match fvm_ipld_encoding::from_slice::(&msg) { + Err(e) => + // TODO: Punish the validator for including rubbish. + // There is always the possibility that our codebase is incompatible, + // but then we'll have a consensu failure. + { + Ok((state, Err(e))) + } + Ok(msg) => { + let (state, ret) = self.inner.deliver(state, msg).await?; + Ok((state, Ok(ret))) + } + } + } + + async fn begin(&self, state: Self::State) -> anyhow::Result<(Self::State, Self::BeginOutput)> { + self.inner.begin(state).await + } + + async fn end(&self, state: Self::State) -> anyhow::Result<(Self::State, Self::EndOutput)> { + self.inner.end(state).await + } +} diff --git a/fendermint/vm/interpreter/src/lib.rs b/fendermint/vm/interpreter/src/lib.rs index 0524dc9c..4b0e495c 100644 --- a/fendermint/vm/interpreter/src/lib.rs +++ b/fendermint/vm/interpreter/src/lib.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0, MIT use async_trait::async_trait; +pub mod bytes; pub mod chain; mod externs; pub mod fvm; From ac32be9dab0324ac82883267736c1cca6ad73e8f Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Fri, 3 Feb 2023 15:53:41 +0000 Subject: [PATCH 14/16] FM-13: Add bounds to ApplicationService so it shows an error without attempting to use it as a Service. --- fendermint/abci/src/application.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fendermint/abci/src/application.rs b/fendermint/abci/src/application.rs index 787e4041..a28c9fcd 100644 --- a/fendermint/abci/src/application.rs +++ b/fendermint/abci/src/application.rs @@ -126,7 +126,7 @@ pub trait Application { } /// Wrapper to adapt an `Application` to a `tower::Service`. -pub struct ApplicationService(pub A); +pub struct ApplicationService(pub A); impl Service for ApplicationService where From 00b2bdd515eac2b6d200982827e4ba4eae33e1d5 Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Fri, 3 Feb 2023 15:54:05 +0000 Subject: [PATCH 15/16] FM-13: Make sure App compiles as an Application by wrapping. --- fendermint/app/src/app.rs | 1 + fendermint/app/src/main.rs | 5 ++++- fendermint/vm/interpreter/src/bytes.rs | 1 + fendermint/vm/interpreter/src/chain.rs | 1 + fendermint/vm/interpreter/src/fvm.rs | 1 + fendermint/vm/interpreter/src/signed.rs | 1 + 6 files changed, 9 insertions(+), 1 deletion(-) diff --git a/fendermint/app/src/app.rs b/fendermint/app/src/app.rs index 28417296..15cd60fc 100644 --- a/fendermint/app/src/app.rs +++ b/fendermint/app/src/app.rs @@ -38,6 +38,7 @@ struct AppState { } /// Handle ABCI requests. +#[derive(Clone)] pub struct App where DB: Blockstore + 'static, diff --git a/fendermint/app/src/main.rs b/fendermint/app/src/main.rs index c2f0abd8..874a82d5 100644 --- a/fendermint/app/src/main.rs +++ b/fendermint/app/src/main.rs @@ -3,6 +3,7 @@ use std::sync::Arc; +use fendermint_abci::ApplicationService; use fendermint_app::app; use fendermint_vm_interpreter::{ bytes::BytesMessageInterpreter, chain::ChainMessageInterpreter, fvm::FvmMessageInterpreter, @@ -16,8 +17,10 @@ async fn main() { let interpreter = SignedMessageInterpreter::new(interpreter); let interpreter = ChainMessageInterpreter::new(interpreter); let interpreter = BytesMessageInterpreter::new(interpreter); + let db = open_db(); - let _app = app::App::new(db, interpreter); + let app = app::App::new(db, interpreter); + let _service = ApplicationService(app); } fn open_db() -> Arc { diff --git a/fendermint/vm/interpreter/src/bytes.rs b/fendermint/vm/interpreter/src/bytes.rs index 85e1f350..b369543f 100644 --- a/fendermint/vm/interpreter/src/bytes.rs +++ b/fendermint/vm/interpreter/src/bytes.rs @@ -9,6 +9,7 @@ use crate::{chain::ChainMessageApplyRet, Interpreter}; pub type BytesMessageApplyRet = Result; /// Interpreter working on raw bytes. +#[derive(Clone)] pub struct BytesMessageInterpreter { inner: I, } diff --git a/fendermint/vm/interpreter/src/chain.rs b/fendermint/vm/interpreter/src/chain.rs index 20e3836c..0f40db8a 100644 --- a/fendermint/vm/interpreter/src/chain.rs +++ b/fendermint/vm/interpreter/src/chain.rs @@ -8,6 +8,7 @@ use crate::{signed::SignedMesssageApplyRet, Interpreter}; /// Interpreter working on chain messages; in the future it will schedule /// CID lookups to turn references into self-contained user or cross messages. +#[derive(Clone)] pub struct ChainMessageInterpreter { inner: I, } diff --git a/fendermint/vm/interpreter/src/fvm.rs b/fendermint/vm/interpreter/src/fvm.rs index 50be32cb..ccb2e963 100644 --- a/fendermint/vm/interpreter/src/fvm.rs +++ b/fendermint/vm/interpreter/src/fvm.rs @@ -80,6 +80,7 @@ where } /// Interpreter working on already verified unsigned messages. +#[derive(Clone)] pub struct FvmMessageInterpreter { _phantom_db: PhantomData, } diff --git a/fendermint/vm/interpreter/src/signed.rs b/fendermint/vm/interpreter/src/signed.rs index b9ff8be4..383af44c 100644 --- a/fendermint/vm/interpreter/src/signed.rs +++ b/fendermint/vm/interpreter/src/signed.rs @@ -12,6 +12,7 @@ use crate::{ /// Interpreter working on signed messages, validating their signature before sending /// the unsigned parts on for execution. +#[derive(Clone)] pub struct SignedMessageInterpreter { inner: I, } From 88b035745e4b54c41b940d8af608045ed614afa2 Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Fri, 3 Feb 2023 16:01:57 +0000 Subject: [PATCH 16/16] FM-13: Include github token for setup-protoc --- .github/workflows/ci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index af0d19f6..e5803ef5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -70,6 +70,8 @@ jobs: # Protobuf compiler required by libp2p-core - name: Install Protoc uses: arduino/setup-protoc@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Setup sccache uses: hanabi1224/sccache-action@v1.2.0 # https://github.com/hanabi1224/sccache-action used by Forest.