diff --git a/Cargo.toml b/Cargo.toml index 29b9bd5..b74a8a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "trevm" -version = "0.19.8" +version = "0.19.9" rust-version = "1.83.0" edition = "2021" authors = ["init4"] @@ -35,6 +35,7 @@ zenith-types = { version = "0.15" } dashmap = { version = "6.1.0", optional = true } tracing = { version = "0.1.41", optional = true} +thiserror = "2.0.11" [dev-dependencies] revm = { version = "19.5.0", features = [ diff --git a/src/db/mod.rs b/src/db/mod.rs index e0bbd3e..612c33b 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,78 +1,7 @@ -mod builder; +/// Concurrent version of [`revm::db::State`] +#[cfg(feature = "concurrent-db")] +pub mod sync; -use alloy::rpc::types::BlockOverrides; -pub use builder::ConcurrentStateBuilder; - -mod cache_state; -pub use cache_state::ConcurrentCacheState; - -mod sync_state; -pub use sync_state::{ConcurrentState, ConcurrentStateInfo}; - -use crate::{Block, EvmNeedsBlock, EvmNeedsTx, Trevm}; -use revm::{ - db::{states::bundle_state::BundleRetention, BundleState}, - DatabaseRef, -}; - -impl Trevm<'_, Ext, ConcurrentState, TrevmState> { - /// Set the [EIP-161] state clear flag, activated in the Spurious Dragon - /// hardfork. - /// - /// This function changes the behavior of the inner [`ConcurrentState`]. - pub fn set_state_clear_flag(&mut self, flag: bool) { - self.inner.db_mut().set_state_clear_flag(flag) - } -} - -impl EvmNeedsBlock<'_, Ext, ConcurrentState> { - /// Finish execution and return the outputs. - /// - /// If the State has not been built with - /// [revm::StateBuilder::with_bundle_update] then the returned - /// [`BundleState`] will be meaningless. - /// - /// See [`ConcurrentState::merge_transitions`] and - /// [`ConcurrentState::take_bundle`]. - pub fn finish(self) -> BundleState { - let Self { inner: mut evm, .. } = self; - evm.db_mut().merge_transitions(BundleRetention::Reverts); - let bundle = evm.db_mut().take_bundle(); - - bundle - } -} - -impl EvmNeedsTx<'_, Ext, ConcurrentState> { - /// Apply block overrides to the current block. - /// - /// Note that this is NOT reversible. The overrides are applied directly to - /// the underlying state and these changes cannot be removed. If it is - /// important that you have access to the pre-change state, you should wrap - /// the existing DB in a new [`ConcurrentState`] and apply the overrides to - /// that. - pub fn apply_block_overrides(mut self, overrides: &BlockOverrides) -> Self { - overrides.fill_block(&mut self.inner); - - if let Some(hashes) = &overrides.block_hash { - self.inner.db_mut().info.block_hashes.write().unwrap().extend(hashes) - } - - self - } - - /// Apply block overrides to the current block, if they are provided. - /// - /// Note that this is NOT reversible. The overrides are applied directly to - /// the underlying state and these changes cannot be removed. If it is - /// important that you have access to the pre-change state, you should wrap - /// the existing DB in a new [`ConcurrentState`] and apply the overrides to - /// that. - pub fn maybe_apply_block_overrides(self, overrides: Option<&BlockOverrides>) -> Self { - if let Some(overrides) = overrides { - self.apply_block_overrides(overrides) - } else { - self - } - } -} +/// Database abstraction traits. +mod traits; +pub use traits::{ArcUpgradeError, StateAcc, TryDatabaseCommit, TryStateAcc}; diff --git a/src/db/builder.rs b/src/db/sync/builder.rs similarity index 99% rename from src/db/builder.rs rename to src/db/sync/builder.rs index 89db4a7..2f2fa97 100644 --- a/src/db/builder.rs +++ b/src/db/sync/builder.rs @@ -1,4 +1,4 @@ -use crate::db::ConcurrentState; +use crate::db::sync::ConcurrentState; use revm::{ db::{ states::{BundleState, TransitionState}, diff --git a/src/db/cache_state.rs b/src/db/sync/cache.rs similarity index 100% rename from src/db/cache_state.rs rename to src/db/sync/cache.rs diff --git a/src/db/sync/mod.rs b/src/db/sync/mod.rs new file mode 100644 index 0000000..5a06f34 --- /dev/null +++ b/src/db/sync/mod.rs @@ -0,0 +1,35 @@ +mod builder; + +pub use builder::ConcurrentStateBuilder; + +mod cache; +pub use cache::ConcurrentCacheState; + +mod state; +pub use state::{ConcurrentState, ConcurrentStateInfo}; + +use crate::db::StateAcc; +use revm::{ + db::{states::bundle_state::BundleRetention, BundleState}, + primitives::B256, + DatabaseRef, +}; +use std::collections::BTreeMap; + +impl StateAcc for ConcurrentState { + fn set_state_clear_flag(&mut self, flag: bool) { + Self::set_state_clear_flag(self, flag) + } + + fn merge_transitions(&mut self, retention: BundleRetention) { + Self::merge_transitions(self, retention) + } + + fn take_bundle(&mut self) -> BundleState { + Self::take_bundle(self) + } + + fn set_block_hashes(&mut self, block_hashes: &BTreeMap) { + self.info.block_hashes.write().unwrap().extend(block_hashes) + } +} diff --git a/src/db/sync_state.rs b/src/db/sync/state.rs similarity index 99% rename from src/db/sync_state.rs rename to src/db/sync/state.rs index f6665e9..25c3709 100644 --- a/src/db/sync_state.rs +++ b/src/db/sync/state.rs @@ -1,4 +1,4 @@ -use crate::db::ConcurrentCacheState; +use crate::db::sync::ConcurrentCacheState; use alloy::primitives::{Address, B256, U256}; use dashmap::mapref::one::RefMut; use revm::{ diff --git a/src/db/traits.rs b/src/db/traits.rs new file mode 100644 index 0000000..81abfa4 --- /dev/null +++ b/src/db/traits.rs @@ -0,0 +1,172 @@ +use revm::{ + db::{states::bundle_state::BundleRetention, BundleState, State}, + primitives::{Account, Address, B256}, + Database, DatabaseCommit, +}; +use std::{collections::BTreeMap, convert::Infallible, sync::Arc}; + +/// Abstraction trait covering types that accumulate state changes into a +/// [`BundleState`]. The prime example of this is [`State`]. These types are +/// use to accumulate state changes during the execution of a sequence of +/// transactions, and then provide access to the net changes in the form of a +/// [`BundleState`]. +pub trait StateAcc { + /// Set the state clear flag. See [`State::set_state_clear_flag`]. + fn set_state_clear_flag(&mut self, flag: bool); + + /// Merge transitions into the bundle. See [`State::merge_transitions`]. + fn merge_transitions(&mut self, retention: BundleRetention); + + /// Take the bundle. See [`State::take_bundle`]. + fn take_bundle(&mut self) -> BundleState; + + /// Set the block hashes, overriding any existing values, and inserting any + /// absent values. + fn set_block_hashes(&mut self, block_hashes: &BTreeMap); +} + +impl StateAcc for State { + fn set_state_clear_flag(&mut self, flag: bool) { + Self::set_state_clear_flag(self, flag) + } + + fn merge_transitions(&mut self, retention: BundleRetention) { + Self::merge_transitions(self, retention) + } + + fn take_bundle(&mut self) -> BundleState { + Self::take_bundle(self) + } + + fn set_block_hashes(&mut self, block_hashes: &BTreeMap) { + self.block_hashes.extend(block_hashes) + } +} + +/// Fallible version of [`StateAcc`]. +/// +/// Abstraction trait covering types that accumulate state changes into a +/// [`BundleState`]. The prime example of this is [`State`]. These types are +/// use to accumulate state changes during the execution of a sequence of +/// transactions, and then provide access to the net changes in the form of a +/// [`BundleState`]. +/// +/// The primary motivator for this trait is to allow for the implementation of +/// [`StateAcc`] for [`Arc`]-wrapped DBs, which may fail to mutate if the +/// reference is not unique. +pub trait TryStateAcc: Sync { + /// Error type to be thrown when state accumulation fails. + type Error: core::error::Error; + + /// Attempt to set the state clear flag. See [`State::set_state_clear_flag`]. + fn try_set_state_clear_flag(&mut self, flag: bool) -> Result<(), Self::Error>; + + /// Attempt to merge transitions into the bundle. See + /// [`State::merge_transitions`]. + fn try_merge_transitions(&mut self, retention: BundleRetention) -> Result<(), Self::Error>; + + /// Attempt to take the bundle. See [`State::take_bundle`]. + fn try_take_bundle(&mut self) -> Result; + + /// Attempt to set the block hashes, overriding any existing values, and + /// inserting any absent values. + fn try_set_block_hashes( + &mut self, + block_hashes: &BTreeMap, + ) -> Result<(), Self::Error>; +} + +impl TryStateAcc for Db +where + Db: StateAcc + Sync, +{ + type Error = Infallible; + + fn try_set_state_clear_flag(&mut self, flag: bool) -> Result<(), Infallible> { + self.set_state_clear_flag(flag); + Ok(()) + } + + fn try_merge_transitions(&mut self, retention: BundleRetention) -> Result<(), Infallible> { + self.merge_transitions(retention); + Ok(()) + } + + fn try_take_bundle(&mut self) -> Result { + Ok(self.take_bundle()) + } + + fn try_set_block_hashes( + &mut self, + block_hashes: &BTreeMap, + ) -> Result<(), Infallible> { + self.set_block_hashes(block_hashes); + Ok(()) + } +} + +/// Error type for implementation of [`TryStateAcc`] for [`Arc`]-wrapped +/// DBs. +#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] +pub enum ArcUpgradeError { + /// Arc reference is not unique. Ensure that all other references are + /// dropped before attempting to mutate the state. + #[error("Arc reference is not unique, cannot mutate")] + NotUnique, +} + +impl TryStateAcc for Arc +where + Db: StateAcc + Sync + Send, +{ + type Error = ArcUpgradeError; + + fn try_set_state_clear_flag(&mut self, flag: bool) -> Result<(), ArcUpgradeError> { + Self::get_mut(self).ok_or(ArcUpgradeError::NotUnique)?.set_state_clear_flag(flag); + Ok(()) + } + + fn try_merge_transitions(&mut self, retention: BundleRetention) -> Result<(), ArcUpgradeError> { + Self::get_mut(self).ok_or(ArcUpgradeError::NotUnique)?.merge_transitions(retention); + Ok(()) + } + + fn try_take_bundle(&mut self) -> Result { + Ok(Self::get_mut(self).ok_or(ArcUpgradeError::NotUnique)?.take_bundle()) + } + + fn try_set_block_hashes( + &mut self, + block_hashes: &BTreeMap, + ) -> Result<(), ArcUpgradeError> { + Self::get_mut(self).ok_or(ArcUpgradeError::NotUnique)?.set_block_hashes(block_hashes); + Ok(()) + } +} + +/// A fallible version of [`DatabaseCommit`]. +pub trait TryDatabaseCommit { + /// Error type to be thrown when committing changes fails. + type Error: core::error::Error; + + /// Attempt to commit changes to the database. + fn try_commit( + &mut self, + changes: revm::primitives::HashMap, + ) -> Result<(), Self::Error>; +} + +impl TryDatabaseCommit for Arc +where + Db: DatabaseCommit, +{ + type Error = ArcUpgradeError; + + fn try_commit( + &mut self, + changes: revm::primitives::HashMap, + ) -> Result<(), Self::Error> { + Self::get_mut(self).ok_or(ArcUpgradeError::NotUnique)?.commit(changes); + Ok(()) + } +} diff --git a/src/evm.rs b/src/evm.rs index 3fce61a..d4c7dba 100644 --- a/src/evm.rs +++ b/src/evm.rs @@ -1,16 +1,17 @@ use crate::{ - driver::DriveBlockResult, Block, BlockDriver, BundleDriver, Cfg, ChainDriver, - DriveBundleResult, DriveChainResult, ErroredState, EvmErrored, EvmExtUnchecked, EvmNeedsBlock, - EvmNeedsCfg, EvmNeedsTx, EvmReady, EvmTransacted, HasBlock, HasCfg, HasTx, NeedsCfg, NeedsTx, - TransactedState, Tx, + db::{StateAcc, TryDatabaseCommit, TryStateAcc}, + driver::DriveBlockResult, + Block, BlockDriver, BundleDriver, Cfg, ChainDriver, DriveBundleResult, DriveChainResult, + ErroredState, EvmErrored, EvmExtUnchecked, EvmNeedsBlock, EvmNeedsCfg, EvmNeedsTx, EvmReady, + EvmTransacted, HasBlock, HasCfg, HasTx, NeedsCfg, NeedsTx, TransactedState, Tx, }; use alloy::{ primitives::{Address, Bytes, U256}, rpc::types::{state::StateOverride, BlockOverrides}, }; -use core::convert::Infallible; +use core::{convert::Infallible, fmt}; use revm::{ - db::{states::bundle_state::BundleRetention, BundleState, State}, + db::{states::bundle_state::BundleRetention, BundleState}, interpreter::gas::calculate_initial_tx_gas, primitives::{ AccountInfo, AuthorizationList, BlockEnv, Bytecode, EVMError, Env, EvmState, @@ -18,7 +19,6 @@ use revm::{ }, Database, DatabaseCommit, DatabaseRef, Evm, }; -use std::fmt; /// Trevm provides a type-safe interface to the EVM, using the typestate pattern. /// @@ -372,7 +372,8 @@ impl + DatabaseRef, Tr impl Trevm<'_, Ext, Db, TrevmState> { /// Commit a set of state changes to the database. This is a low-level API, - /// and is not intended for general use. Prefer executing a transaction. + /// and is not intended for general use. Regular users should prefer + /// executing a transaction. pub fn commit_unchecked(&mut self, state: EvmState) where Db: DatabaseCommit, @@ -601,7 +602,7 @@ impl, TrevmState> Trevm<'_, Ext, Db, Trevm // --- ALL STATES, WITH State -impl Trevm<'_, Ext, State, TrevmState> { +impl Trevm<'_, Ext, Db, TrevmState> { /// Set the [EIP-161] state clear flag, activated in the Spurious Dragon /// hardfork. pub fn set_state_clear_flag(&mut self, flag: bool) { @@ -609,6 +610,20 @@ impl Trevm<'_, Ext, State, TrevmState> { } } +impl Trevm<'_, Ext, Db, TrevmState> { + /// Fallibly set the [EIP-161] state clear flag, activated in the Spurious + /// Dragon hardfork. This function is intended to be used by shared states, + /// where mutable access may fail, e.g. an `Arc`. + /// + /// Prefer [`Self::set_state_clear_flag`] when available. + pub fn try_set_state_clear_flag( + &mut self, + flag: bool, + ) -> Result<(), ::Error> { + self.inner.db_mut().try_set_state_clear_flag(flag) + } +} + // --- NEEDS CFG impl<'a, Ext, Db: Database> EvmNeedsCfg<'a, Ext, Db> { @@ -1066,7 +1081,7 @@ impl<'a, Ext, Db: Database, TrevmState: HasBlock> Trevm<'a, Ext, Db, TrevmState> // --- Needs Block with State -impl EvmNeedsBlock<'_, Ext, State> { +impl EvmNeedsBlock<'_, Ext, Db> { /// Finish execution and return the outputs. /// /// If the State has not been built with @@ -1074,6 +1089,9 @@ impl EvmNeedsBlock<'_, Ext, State> { /// [`BundleState`] will be meaningless. /// /// See [`State::merge_transitions`] and [`State::take_bundle`]. + /// + /// [`State::merge_transitions`]: revm::db::State::merge_transitions + /// [`State::take_bundle`]: revm::db::State::take_bundle pub fn finish(self) -> BundleState { let Self { inner: mut evm, .. } = self; evm.db_mut().merge_transitions(BundleRetention::Reverts); @@ -1083,6 +1101,32 @@ impl EvmNeedsBlock<'_, Ext, State> { } } +impl<'a, Ext, Db: Database + TryStateAcc> EvmNeedsBlock<'a, Ext, Db> { + /// Fallibly finish execution and return the outputs. This function is + /// intended to be used by shared states, where mutable access may fail, e. + /// g. an `Arc`. Prefer [`Self::finish`] when available. + /// + /// If the State has not been built with + /// [revm::StateBuilder::with_bundle_update] then the returned + /// [`BundleState`] will be meaningless. + /// + /// See [`State::merge_transitions`] and [`State::take_bundle`]. + /// + /// [`State::merge_transitions`]: revm::db::State::merge_transitions + /// [`State::take_bundle`]: revm::db::State::take_bundle + pub fn try_finish( + mut self, + ) -> Result::Error>> { + let db = self.inner.db_mut(); + + unwrap_or_trevm_err!(db.try_merge_transitions(BundleRetention::Reverts), self); + + let bundle = unwrap_or_trevm_err!(db.try_take_bundle(), self); + + Ok(bundle) + } +} + // --- NEEDS TX impl<'a, Ext, Db: Database> EvmNeedsTx<'a, Ext, Db> { @@ -1372,34 +1416,86 @@ impl<'a, Ext, Db: Database, TrevmState: HasTx> Trevm<'a, Ext, Db, TrevmState> { // -- NEEDS TX with State -impl EvmNeedsTx<'_, Ext, State> { - /// Apply block overrides to the current block. +impl EvmNeedsTx<'_, Ext, Db> { + /// Apply block overrides to the current block. This function is + /// intended to be used by shared states, where mutable access may fail, e. + /// g. an `Arc`. Prefer [`Self::try_apply_block_overrides`] when + /// available. /// /// Note that this is NOT reversible. The overrides are applied directly to /// the underlying state and these changes cannot be removed. If it is /// important that you have access to the pre-change state, you should wrap /// the existing DB in a new [`State`] and apply the overrides to that. - pub fn apply_block_overrides(mut self, overrides: &BlockOverrides) -> Self { + /// + /// [`State`]: revm::db::State + pub fn try_apply_block_overrides(mut self, overrides: &BlockOverrides) -> Self { overrides.fill_block(&mut self.inner); - if let Some(hashes) = &overrides.block_hash { - self.inner.db_mut().block_hashes.extend(hashes) + if let Some(hashes) = overrides.block_hash.as_ref() { + self.inner.db_mut().set_block_hashes(hashes) } self } + /// Apply block overrides to the current block, if they are provided. This + /// function is intended to be used by shared states, where mutable access + /// may fail, e.g. an `Arc`.Prefer + /// [`Self::try_maybe_apply_block_overrides`] when available. + /// + /// Note that this is NOT reversible. The overrides are applied directly to + /// the underlying state and these changes cannot be removed. If it is + /// important that you have access to the pre-change state, you should wrap + /// the existing DB in a new [`State`] and apply the overrides to that. + /// + /// [`State`]: revm::db::State + pub fn try_maybe_apply_block_overrides(self, overrides: Option<&BlockOverrides>) -> Self { + if let Some(overrides) = overrides { + self.try_apply_block_overrides(overrides) + } else { + self + } + } +} + +impl<'a, Ext, Db: Database + TryStateAcc> EvmNeedsTx<'a, Ext, Db> { + /// Apply block overrides to the current block. + /// + /// Note that this is NOT reversible. The overrides are applied directly to + /// the underlying state and these changes cannot be removed. If it is + /// important that you have access to the pre-change state, you should wrap + /// the existing DB in a new [`State`] and apply the overrides to that. + /// + /// [`State`]: revm::db::State + pub fn apply_block_overrides( + mut self, + overrides: &BlockOverrides, + ) -> Result::Error>> { + overrides.fill_block(&mut self.inner); + + if let Some(hashes) = overrides.block_hash.as_ref() { + unwrap_or_trevm_err!(self.inner.db_mut().try_set_block_hashes(hashes), self); + } + + Ok(self) + } + /// Apply block overrides to the current block, if they are provided. /// /// Note that this is NOT reversible. The overrides are applied directly to /// the underlying state and these changes cannot be removed. If it is /// important that you have access to the pre-change state, you should wrap /// the existing DB in a new [`State`] and apply the overrides to that. - pub fn maybe_apply_block_overrides(self, overrides: Option<&BlockOverrides>) -> Self { + /// + /// [`State`]: revm::db::State + pub fn maybe_apply_block_overrides( + self, + overrides: Option<&BlockOverrides>, + ) -> Result::Error>> { if let Some(overrides) = overrides { self.apply_block_overrides(overrides) } else { - self + Ok(self) } } } @@ -1872,6 +1968,31 @@ impl<'a, Ext, Db: Database> EvmTransacted<'a, Ext, Db> { (result.result, Trevm { inner, state: NeedsTx::new() }) } + /// Try to accept the state changes, commiting them to the database, and + /// return the EVM with the [`ExecutionResult`]. If the commit fails, return + /// the EVM with the error, discarding the state changes. This is a fallible + /// version of [`Self::accept`], intended for use with databases that can + /// fail to commit. Prefer [`Self::accept`] when possible. + // Type alias would make it less clear I think + #[allow(clippy::type_complexity)] + pub fn try_accept( + self, + ) -> Result< + (ExecutionResult, EvmNeedsTx<'a, Ext, Db>), + EvmErrored<'a, Ext, Db, ::Error>, + > + where + Db: TryDatabaseCommit, + { + let Trevm { mut inner, state: TransactedState { result } } = self; + + unwrap_or_trevm_err!( + inner.db_mut().try_commit(result.state), + Trevm { inner, state: NeedsTx::new() } + ); + Ok((result.result, Trevm { inner, state: NeedsTx::new() })) + } + /// Accept the state changes, commiting them to the database. Do not return /// the [`ExecutionResult.`] pub fn accept_state(self) -> EvmNeedsTx<'a, Ext, Db> @@ -1881,6 +2002,20 @@ impl<'a, Ext, Db: Database> EvmTransacted<'a, Ext, Db> { self.accept().1 } + /// Try to accept the state changes, commiting them to the database. If the + /// commit fails, return the EVM with the error, discarding the state + /// changes. This is a fallible version of [`Self::accept_state`], intended + /// for use with databases that can fail to commit. Prefer + /// [`Self::accept_state`] when possible. + pub fn try_accept_state( + self, + ) -> Result, EvmErrored<'a, Ext, Db, ::Error>> + where + Db: TryDatabaseCommit, + { + self.try_accept().map(|(_, evm)| evm) + } + /// Create an [`EstimationResult`] from the transaction [`ExecutionResult`]. /// /// [`EstimationResult`]: crate::EstimationResult diff --git a/src/lib.rs b/src/lib.rs index 5621525..1b6fe9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -367,8 +367,7 @@ mod macros; mod connect; pub use connect::{DbConnect, EvmFactory}; -/// Contains database implementations for concurrent EVM operation. -#[cfg(feature = "concurrent-db")] +/// Contains database implementations and related pub mod db; mod driver;