diff --git a/crates/evm/src/execute.rs b/crates/evm/src/execute.rs index 476c695e7a..eb77d054bd 100644 --- a/crates/evm/src/execute.rs +++ b/crates/evm/src/execute.rs @@ -7,11 +7,12 @@ pub use reth_execution_errors::{ pub use reth_execution_types::{BlockExecutionInput, BlockExecutionOutput, ExecutionOutcome}; pub use reth_storage_errors::provider::ProviderError; +use alloc::{boxed::Box, vec::Vec}; use alloy_primitives::BlockNumber; -use core::fmt::Display; -use reth_primitives::{BlockWithSenders, Receipt}; +use core::{fmt::Display, marker::PhantomData}; +use reth_primitives::{BlockWithSenders, Receipt, Request}; use reth_prune_types::PruneModes; -use revm::State; +use revm::{db::BundleState, State}; use revm_primitives::db::Database; use crate::system_calls::OnStateHook; @@ -163,12 +164,233 @@ pub trait BlockExecutorProvider: Send + Sync + Clone + Unpin + 'static { DB: Database + Display>; } +/// Defines the strategy for executing a single block. +pub trait BlockExecutionStrategy { + /// The error type returned by this strategy's methods. + type Error: From + core::error::Error; + + /// Applies any necessary changes before executing the block's transactions. + fn apply_pre_execution_changes(&mut self) -> Result<(), Self::Error>; + + /// Executes all transactions in the block. + fn execute_transactions( + &mut self, + block: &BlockWithSenders, + ) -> Result<(Vec, u64), Self::Error>; + + /// Applies any necessary changes after executing the block's transactions. + fn apply_post_execution_changes(&mut self) -> Result, Self::Error>; + + /// Returns a reference to the current state. + fn state_ref(&self) -> &State; + + /// Sets a hook to be called after each state change during execution. + fn with_state_hook(&mut self, hook: Option>); + + /// Returns the final bundle state. + fn finish(&self) -> BundleState; +} + +/// A strategy factory that can create block execution strategies. +pub trait BlockExecutionStrategyFactory: Send + Sync + Clone + Unpin + 'static { + /// Associated strategy type. + type Strategy + Display>>: BlockExecutionStrategy< + DB, + Error = BlockExecutionError, + >; + + /// Creates a strategy using the give database. + fn create_strategy(&self, db: DB) -> Self::Strategy + where + DB: Database + Display>; +} + +impl Clone for GenericBlockExecutorProvider +where + F: Clone, +{ + fn clone(&self) -> Self { + Self { strategy_factory: self.strategy_factory.clone() } + } +} + +/// A generic block executor provider that can create executors using a strategy factory. +#[allow(missing_debug_implementations)] +pub struct GenericBlockExecutorProvider { + strategy_factory: F, +} + +impl GenericBlockExecutorProvider { + /// Creates a new `GenericBlockExecutorProvider` with the given strategy factory. + pub const fn new(strategy_factory: F) -> Self { + Self { strategy_factory } + } +} + +impl BlockExecutorProvider for GenericBlockExecutorProvider +where + F: BlockExecutionStrategyFactory, +{ + type Executor + Display>> = + GenericBlockExecutor, DB>; + + type BatchExecutor + Display>> = + GenericBatchExecutor, DB>; + + fn executor(&self, db: DB) -> Self::Executor + where + DB: Database + Display>, + { + let strategy = self.strategy_factory.create_strategy(db); + GenericBlockExecutor::new(strategy) + } + + fn batch_executor(&self, db: DB) -> Self::BatchExecutor + where + DB: Database + Display>, + { + let strategy = self.strategy_factory.create_strategy(db); + GenericBatchExecutor::new(strategy) + } +} + +/// A generic block executor that uses a [`BlockExecutionStrategy`] to +/// execute blocks. +#[allow(missing_debug_implementations, dead_code)] +pub struct GenericBlockExecutor +where + S: BlockExecutionStrategy, +{ + strategy: S, + _phantom: PhantomData, +} + +impl GenericBlockExecutor +where + S: BlockExecutionStrategy, +{ + /// Creates a new `GenericBlockExecutor` with the given strategy. + pub const fn new(strategy: S) -> Self { + Self { strategy, _phantom: PhantomData } + } +} + +impl Executor for GenericBlockExecutor +where + S: BlockExecutionStrategy, + DB: Database + Display>, +{ + type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>; + type Output = BlockExecutionOutput; + type Error = S::Error; + + fn execute(mut self, input: Self::Input<'_>) -> Result { + let BlockExecutionInput { block, total_difficulty: _ } = input; + + self.strategy.apply_pre_execution_changes()?; + let (receipts, gas_used) = self.strategy.execute_transactions(block)?; + let requests = self.strategy.apply_post_execution_changes()?; + let state = self.strategy.finish(); + + Ok(BlockExecutionOutput { state, receipts, requests, gas_used }) + } + + fn execute_with_state_closure( + mut self, + input: Self::Input<'_>, + mut state: F, + ) -> Result + where + F: FnMut(&State), + { + let BlockExecutionInput { block, total_difficulty: _ } = input; + + self.strategy.apply_pre_execution_changes()?; + let (receipts, gas_used) = self.strategy.execute_transactions(block)?; + let requests = self.strategy.apply_post_execution_changes()?; + + state(self.strategy.state_ref()); + + let state = self.strategy.finish(); + + Ok(BlockExecutionOutput { state, receipts, requests, gas_used }) + } + + fn execute_with_state_hook( + mut self, + input: Self::Input<'_>, + state_hook: H, + ) -> Result + where + H: OnStateHook + 'static, + { + let BlockExecutionInput { block, total_difficulty: _ } = input; + + self.strategy.with_state_hook(Some(Box::new(state_hook))); + + self.strategy.apply_pre_execution_changes()?; + let (receipts, gas_used) = self.strategy.execute_transactions(block)?; + let requests = self.strategy.apply_post_execution_changes()?; + + let state = self.strategy.finish(); + + Ok(BlockExecutionOutput { state, receipts, requests, gas_used }) + } +} + +/// A generic batch executor that uses a [`BlockExecutionStrategy`] to +/// execute batches. +#[allow(missing_debug_implementations)] +pub struct GenericBatchExecutor { + _strategy: S, + _phantom: PhantomData, +} + +impl GenericBatchExecutor { + /// Creates a new `GenericBatchExecutor` with the given strategy. + pub const fn new(_strategy: S) -> Self { + Self { _strategy, _phantom: PhantomData } + } +} + +impl BatchExecutor for GenericBatchExecutor +where + S: BlockExecutionStrategy, + DB: Database + Display>, +{ + type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>; + type Output = ExecutionOutcome; + type Error = BlockExecutionError; + + fn execute_and_verify_one(&mut self, _input: Self::Input<'_>) -> Result<(), Self::Error> { + todo!() + } + + fn finalize(self) -> Self::Output { + todo!() + } + + fn set_tip(&mut self, _tip: BlockNumber) { + todo!() + } + + fn set_prune_modes(&mut self, _prune_modes: PruneModes) { + todo!() + } + + fn size_hint(&self) -> Option { + None + } +} + #[cfg(test)] mod tests { use super::*; + use alloy_eips::eip6110::DepositRequest; use alloy_primitives::U256; + use reth_chainspec::{ChainSpec, MAINNET}; use revm::db::{CacheDB, EmptyDBTyped}; - use std::marker::PhantomData; + use std::sync::Arc; #[derive(Clone, Default)] struct TestExecutorProvider; @@ -252,6 +474,83 @@ mod tests { } } + struct TestExecutorStrategy { + // chain spec and evm config here only to illustrate how the strategy + // factory can use them in a real use case. + _chain_spec: Arc, + _evm_config: EvmConfig, + state: State, + execute_transactions_result: (Vec, u64), + apply_post_execution_changes_result: Vec, + finish_result: BundleState, + } + + #[derive(Clone)] + struct TestExecutorStrategyFactory { + execute_transactions_result: (Vec, u64), + apply_post_execution_changes_result: Vec, + finish_result: BundleState, + } + + impl BlockExecutionStrategyFactory for TestExecutorStrategyFactory { + type Strategy + Display>> = + TestExecutorStrategy; + + fn create_strategy(&self, db: DB) -> Self::Strategy + where + DB: Database + Display>, + { + let state = State::builder() + .with_database(db) + .with_bundle_update() + .without_state_clear() + .build(); + + TestExecutorStrategy { + _chain_spec: MAINNET.clone(), + _evm_config: TestEvmConfig {}, + execute_transactions_result: self.execute_transactions_result.clone(), + apply_post_execution_changes_result: self + .apply_post_execution_changes_result + .clone(), + finish_result: self.finish_result.clone(), + state, + } + } + } + + impl BlockExecutionStrategy for TestExecutorStrategy { + type Error = BlockExecutionError; + + fn apply_pre_execution_changes(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + fn execute_transactions( + &mut self, + _block: &BlockWithSenders, + ) -> Result<(Vec, u64), Self::Error> { + Ok(self.execute_transactions_result.clone()) + } + + fn apply_post_execution_changes(&mut self) -> Result, Self::Error> { + Ok(self.apply_post_execution_changes_result.clone()) + } + + fn state_ref(&self) -> &State { + &self.state + } + + fn with_state_hook(&mut self, _hook: Option>) {} + + fn finish(&self) -> BundleState { + self.finish_result.clone() + } + } + + #[derive(Clone)] + struct TestEvmConfig {} + #[test] fn test_provider() { let provider = TestExecutorProvider; @@ -259,4 +558,32 @@ mod tests { let executor = provider.executor(db); let _ = executor.execute(BlockExecutionInput::new(&Default::default(), U256::ZERO)); } + + #[test] + fn test_strategy() { + let expected_gas_used = 10; + let expected_receipts = vec![Receipt::default()]; + let expected_execute_transactions_result = (expected_receipts.clone(), expected_gas_used); + let expected_apply_post_execution_changes_result = + vec![Request::DepositRequest(DepositRequest::default())]; + let expected_finish_result = BundleState::default(); + + let strategy_factory = TestExecutorStrategyFactory { + execute_transactions_result: expected_execute_transactions_result, + apply_post_execution_changes_result: expected_apply_post_execution_changes_result + .clone(), + finish_result: expected_finish_result.clone(), + }; + let provider = GenericBlockExecutorProvider::new(strategy_factory); + let db = CacheDB::>::default(); + let executor = provider.executor(db); + let result = executor.execute(BlockExecutionInput::new(&Default::default(), U256::ZERO)); + + assert!(result.is_ok()); + let block_execution_output = result.unwrap(); + assert_eq!(block_execution_output.gas_used, expected_gas_used); + assert_eq!(block_execution_output.receipts, expected_receipts); + assert_eq!(block_execution_output.requests, expected_apply_post_execution_changes_result); + assert_eq!(block_execution_output.state, expected_finish_result); + } }