reth_evm/
execute.rs

1//! Traits for execution.
2
3use crate::{ConfigureEvm, Database, OnStateHook};
4use alloc::{boxed::Box, vec::Vec};
5use alloy_consensus::{BlockHeader, Header};
6use alloy_eips::eip2718::WithEncoded;
7pub use alloy_evm::block::{BlockExecutor, BlockExecutorFactory};
8use alloy_evm::{
9    block::{CommitChanges, ExecutableTx},
10    Evm, EvmEnv, EvmFactory,
11};
12use alloy_primitives::B256;
13use core::fmt::Debug;
14pub use reth_execution_errors::{
15    BlockExecutionError, BlockValidationError, InternalBlockExecutionError,
16};
17use reth_execution_types::BlockExecutionResult;
18pub use reth_execution_types::{BlockExecutionOutput, ExecutionOutcome};
19use reth_primitives_traits::{
20    Block, HeaderTy, NodePrimitives, ReceiptTy, Recovered, RecoveredBlock, SealedHeader, TxTy,
21};
22use reth_storage_api::StateProvider;
23pub use reth_storage_errors::provider::ProviderError;
24use reth_trie_common::{updates::TrieUpdates, HashedPostState};
25use revm::{
26    context::result::ExecutionResult,
27    database::{states::bundle_state::BundleRetention, BundleState, State},
28};
29
30/// A type that knows how to execute a block. It is assumed to operate on a
31/// [`crate::Evm`] internally and use [`State`] as database.
32pub trait Executor<DB: Database>: Sized {
33    /// The primitive types used by the executor.
34    type Primitives: NodePrimitives;
35    /// The error type returned by the executor.
36    type Error;
37
38    /// Executes a single block and returns [`BlockExecutionResult`], without the state changes.
39    fn execute_one(
40        &mut self,
41        block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
42    ) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>;
43
44    /// Executes the EVM with the given input and accepts a state hook closure that is invoked with
45    /// the EVM state after execution.
46    fn execute_one_with_state_hook<F>(
47        &mut self,
48        block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
49        state_hook: F,
50    ) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
51    where
52        F: OnStateHook + 'static;
53
54    /// Consumes the type and executes the block.
55    ///
56    /// # Note
57    /// Execution happens without any validation of the output.
58    ///
59    /// # Returns
60    /// The output of the block execution.
61    fn execute(
62        mut self,
63        block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
64    ) -> Result<BlockExecutionOutput<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
65    {
66        let result = self.execute_one(block)?;
67        let mut state = self.into_state();
68        Ok(BlockExecutionOutput { state: state.take_bundle(), result })
69    }
70
71    /// Executes multiple inputs in the batch, and returns an aggregated [`ExecutionOutcome`].
72    fn execute_batch<'a, I>(
73        mut self,
74        blocks: I,
75    ) -> Result<ExecutionOutcome<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
76    where
77        I: IntoIterator<Item = &'a RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>>,
78    {
79        let mut results = Vec::new();
80        let mut first_block = None;
81        for block in blocks {
82            if first_block.is_none() {
83                first_block = Some(block.header().number());
84            }
85            results.push(self.execute_one(block)?);
86        }
87
88        Ok(ExecutionOutcome::from_blocks(
89            first_block.unwrap_or_default(),
90            self.into_state().take_bundle(),
91            results,
92        ))
93    }
94
95    /// Executes the EVM with the given input and accepts a state closure that is invoked with
96    /// the EVM state after execution.
97    fn execute_with_state_closure<F>(
98        mut self,
99        block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
100        mut f: F,
101    ) -> Result<BlockExecutionOutput<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
102    where
103        F: FnMut(&State<DB>),
104    {
105        let result = self.execute_one(block)?;
106        let mut state = self.into_state();
107        f(&state);
108        Ok(BlockExecutionOutput { state: state.take_bundle(), result })
109    }
110
111    /// Executes the EVM with the given input and accepts a state hook closure that is invoked with
112    /// the EVM state after execution.
113    fn execute_with_state_hook<F>(
114        mut self,
115        block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
116        state_hook: F,
117    ) -> Result<BlockExecutionOutput<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
118    where
119        F: OnStateHook + 'static,
120    {
121        let result = self.execute_one_with_state_hook(block, state_hook)?;
122        let mut state = self.into_state();
123        Ok(BlockExecutionOutput { state: state.take_bundle(), result })
124    }
125
126    /// Consumes the executor and returns the [`State`] containing all state changes.
127    fn into_state(self) -> State<DB>;
128
129    /// The size hint of the batch's tracked state size.
130    ///
131    /// This is used to optimize DB commits depending on the size of the state.
132    fn size_hint(&self) -> usize;
133}
134
135/// Helper type for the output of executing a block.
136#[derive(Debug, Clone)]
137pub struct ExecuteOutput<R> {
138    /// Receipts obtained after executing a block.
139    pub receipts: Vec<R>,
140    /// Cumulative gas used in the block execution.
141    pub gas_used: u64,
142}
143
144/// Input for block building. Consumed by [`BlockAssembler`].
145#[derive(derive_more::Debug)]
146#[non_exhaustive]
147pub struct BlockAssemblerInput<'a, 'b, F: BlockExecutorFactory, H = Header> {
148    /// Configuration of EVM used when executing the block.
149    ///
150    /// Contains context relevant to EVM such as [`revm::context::BlockEnv`].
151    pub evm_env: EvmEnv<<F::EvmFactory as EvmFactory>::Spec>,
152    /// [`BlockExecutorFactory::ExecutionCtx`] used to execute the block.
153    pub execution_ctx: F::ExecutionCtx<'a>,
154    /// Parent block header.
155    pub parent: &'a SealedHeader<H>,
156    /// Transactions that were executed in this block.
157    pub transactions: Vec<F::Transaction>,
158    /// Output of block execution.
159    pub output: &'b BlockExecutionResult<F::Receipt>,
160    /// [`BundleState`] after the block execution.
161    pub bundle_state: &'a BundleState,
162    /// Provider with access to state.
163    #[debug(skip)]
164    pub state_provider: &'b dyn StateProvider,
165    /// State root for this block.
166    pub state_root: B256,
167}
168
169/// A type that knows how to assemble a block.
170#[auto_impl::auto_impl(&, Arc)]
171pub trait BlockAssembler<F: BlockExecutorFactory> {
172    /// The block type produced by the assembler.
173    type Block: Block;
174
175    /// Builds a block. see [`BlockAssemblerInput`] documentation for more details.
176    fn assemble_block(
177        &self,
178        input: BlockAssemblerInput<'_, '_, F, <Self::Block as Block>::Header>,
179    ) -> Result<Self::Block, BlockExecutionError>;
180}
181
182/// Output of block building.
183#[derive(Debug, Clone)]
184pub struct BlockBuilderOutcome<N: NodePrimitives> {
185    /// Result of block execution.
186    pub execution_result: BlockExecutionResult<N::Receipt>,
187    /// Hashed state after execution.
188    pub hashed_state: HashedPostState,
189    /// Trie updates collected during state root calculation.
190    pub trie_updates: TrieUpdates,
191    /// The built block.
192    pub block: RecoveredBlock<N::Block>,
193}
194
195/// A type that knows how to execute and build a block.
196///
197/// It wraps an inner [`BlockExecutor`] and provides a way to execute transactions and
198/// construct a block.
199///
200/// This is a helper to erase `BasicBlockBuilder` type.
201pub trait BlockBuilder {
202    /// The primitive types used by the inner [`BlockExecutor`].
203    type Primitives: NodePrimitives;
204    /// Inner [`BlockExecutor`].
205    type Executor: BlockExecutor<
206        Transaction = TxTy<Self::Primitives>,
207        Receipt = ReceiptTy<Self::Primitives>,
208    >;
209
210    /// Invokes [`BlockExecutor::apply_pre_execution_changes`].
211    fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError>;
212
213    /// Invokes [`BlockExecutor::execute_transaction_with_commit_condition`] and saves the
214    /// transaction in internal state only if the transaction was committed.
215    fn execute_transaction_with_commit_condition(
216        &mut self,
217        tx: impl ExecutorTx<Self::Executor>,
218        f: impl FnOnce(
219            &ExecutionResult<<<Self::Executor as BlockExecutor>::Evm as Evm>::HaltReason>,
220        ) -> CommitChanges,
221    ) -> Result<Option<u64>, BlockExecutionError>;
222
223    /// Invokes [`BlockExecutor::execute_transaction_with_result_closure`] and saves the
224    /// transaction in internal state.
225    fn execute_transaction_with_result_closure(
226        &mut self,
227        tx: impl ExecutorTx<Self::Executor>,
228        f: impl FnOnce(&ExecutionResult<<<Self::Executor as BlockExecutor>::Evm as Evm>::HaltReason>),
229    ) -> Result<u64, BlockExecutionError> {
230        self.execute_transaction_with_commit_condition(tx, |res| {
231            f(res);
232            CommitChanges::Yes
233        })
234        .map(Option::unwrap_or_default)
235    }
236
237    /// Invokes [`BlockExecutor::execute_transaction`] and saves the transaction in
238    /// internal state.
239    fn execute_transaction(
240        &mut self,
241        tx: impl ExecutorTx<Self::Executor>,
242    ) -> Result<u64, BlockExecutionError> {
243        self.execute_transaction_with_result_closure(tx, |_| ())
244    }
245
246    /// Add transaction
247    ///
248    /// Seismic team added this function to the trait for our stuff,
249    /// default unimplemented for backward compatibility
250    fn add_transaction(
251        &mut self,
252        _tx: Recovered<TxTy<Self::Primitives>>,
253    ) -> Result<u64, BlockExecutionError> {
254        unimplemented!("BlockBuilder trait's add_transaction function is not implemented")
255    }
256
257    /// Completes the block building process and returns the [`BlockBuilderOutcome`].
258    fn finish(
259        self,
260        state_provider: impl StateProvider,
261    ) -> Result<BlockBuilderOutcome<Self::Primitives>, BlockExecutionError>;
262
263    /// Provides mutable access to the inner [`BlockExecutor`].
264    fn executor_mut(&mut self) -> &mut Self::Executor;
265
266    /// Provides access to the inner [`BlockExecutor`].
267    fn executor(&self) -> &Self::Executor;
268
269    /// Helper to access inner [`BlockExecutor::Evm`] mutably.
270    fn evm_mut(&mut self) -> &mut <Self::Executor as BlockExecutor>::Evm {
271        self.executor_mut().evm_mut()
272    }
273
274    /// Helper to access inner [`BlockExecutor::Evm`].
275    fn evm(&self) -> &<Self::Executor as BlockExecutor>::Evm {
276        self.executor().evm()
277    }
278
279    /// Consumes the type and returns the underlying [`BlockExecutor`].
280    fn into_executor(self) -> Self::Executor;
281}
282
283pub(crate) struct BasicBlockBuilder<'a, F, Executor, Builder, N: NodePrimitives>
284where
285    F: BlockExecutorFactory,
286{
287    pub(crate) executor: Executor,
288    pub(crate) transactions: Vec<Recovered<TxTy<N>>>,
289    pub(crate) ctx: F::ExecutionCtx<'a>,
290    pub(crate) parent: &'a SealedHeader<HeaderTy<N>>,
291    pub(crate) assembler: Builder,
292}
293
294/// Conversions for executable transactions.
295pub trait ExecutorTx<Executor: BlockExecutor> {
296    /// Converts the transaction into [`ExecutableTx`].
297    fn as_executable(&self) -> impl ExecutableTx<Executor>;
298
299    /// Converts the transaction into [`Recovered`].
300    fn into_recovered(self) -> Recovered<Executor::Transaction>;
301}
302
303impl<Executor: BlockExecutor> ExecutorTx<Executor>
304    for WithEncoded<Recovered<Executor::Transaction>>
305{
306    fn as_executable(&self) -> impl ExecutableTx<Executor> {
307        self
308    }
309
310    fn into_recovered(self) -> Recovered<Executor::Transaction> {
311        self.1
312    }
313}
314
315impl<Executor: BlockExecutor> ExecutorTx<Executor> for Recovered<Executor::Transaction> {
316    fn as_executable(&self) -> impl ExecutableTx<Executor> {
317        self
318    }
319
320    fn into_recovered(self) -> Self {
321        self
322    }
323}
324
325impl<'a, F, DB, Executor, Builder, N> BlockBuilder
326    for BasicBlockBuilder<'a, F, Executor, Builder, N>
327where
328    F: BlockExecutorFactory<Transaction = N::SignedTx, Receipt = N::Receipt>,
329    Executor: BlockExecutor<
330        Evm: Evm<
331            Spec = <F::EvmFactory as EvmFactory>::Spec,
332            HaltReason = <F::EvmFactory as EvmFactory>::HaltReason,
333            DB = &'a mut State<DB>,
334        >,
335        Transaction = N::SignedTx,
336        Receipt = N::Receipt,
337    >,
338    DB: Database + 'a,
339    Builder: BlockAssembler<F, Block = N::Block>,
340    N: NodePrimitives,
341{
342    type Primitives = N;
343    type Executor = Executor;
344
345    fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
346        self.executor.apply_pre_execution_changes()
347    }
348
349    fn execute_transaction_with_commit_condition(
350        &mut self,
351        tx: impl ExecutorTx<Self::Executor>,
352        f: impl FnOnce(
353            &ExecutionResult<<<Self::Executor as BlockExecutor>::Evm as Evm>::HaltReason>,
354        ) -> CommitChanges,
355    ) -> Result<Option<u64>, BlockExecutionError> {
356        if let Some(gas_used) =
357            self.executor.execute_transaction_with_commit_condition(tx.as_executable(), f)?
358        {
359            self.transactions.push(tx.into_recovered());
360            Ok(Some(gas_used))
361        } else {
362            Ok(None)
363        }
364    }
365
366    fn finish(
367        self,
368        state: impl StateProvider,
369    ) -> Result<BlockBuilderOutcome<N>, BlockExecutionError> {
370        let (evm, result) = self.executor.finish()?;
371        let (db, evm_env) = evm.finish();
372
373        // merge all transitions into bundle state
374        db.merge_transitions(BundleRetention::Reverts);
375
376        // calculate the state root
377        let hashed_state = state.hashed_post_state(&db.bundle_state);
378        let (state_root, trie_updates) = state
379            .state_root_with_updates(hashed_state.clone())
380            .map_err(BlockExecutionError::other)?;
381
382        let (transactions, senders) =
383            self.transactions.into_iter().map(|tx| tx.into_parts()).unzip();
384
385        let block = self.assembler.assemble_block(BlockAssemblerInput {
386            evm_env,
387            execution_ctx: self.ctx,
388            parent: self.parent,
389            transactions,
390            output: &result,
391            bundle_state: &db.bundle_state,
392            state_provider: &state,
393            state_root,
394        })?;
395
396        let block = RecoveredBlock::new_unhashed(block, senders);
397
398        Ok(BlockBuilderOutcome { execution_result: result, hashed_state, trie_updates, block })
399    }
400
401    fn add_transaction(
402        &mut self,
403        tx: Recovered<TxTy<Self::Primitives>>,
404    ) -> Result<u64, BlockExecutionError> {
405        self.transactions.push(tx);
406        Ok(self.transactions.len() as u64)
407    }
408
409    fn executor_mut(&mut self) -> &mut Self::Executor {
410        &mut self.executor
411    }
412
413    fn executor(&self) -> &Self::Executor {
414        &self.executor
415    }
416
417    fn into_executor(self) -> Self::Executor {
418        self.executor
419    }
420}
421
422/// A generic block executor that uses a [`BlockExecutor`] to
423/// execute blocks.
424#[expect(missing_debug_implementations)]
425pub struct BasicBlockExecutor<F, DB> {
426    /// Block execution strategy.
427    pub(crate) strategy_factory: F,
428    /// Database.
429    pub(crate) db: State<DB>,
430}
431
432impl<F, DB: Database> BasicBlockExecutor<F, DB> {
433    /// Creates a new `BasicBlockExecutor` with the given strategy.
434    pub fn new(strategy_factory: F, db: DB) -> Self {
435        let db =
436            State::builder().with_database(db).with_bundle_update().without_state_clear().build();
437        Self { strategy_factory, db }
438    }
439}
440
441impl<F, DB> Executor<DB> for BasicBlockExecutor<F, DB>
442where
443    F: ConfigureEvm,
444    DB: Database,
445{
446    type Primitives = F::Primitives;
447    type Error = BlockExecutionError;
448
449    fn execute_one(
450        &mut self,
451        block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
452    ) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
453    {
454        let result = self
455            .strategy_factory
456            .executor_for_block(&mut self.db, block)
457            .execute_block(block.transactions_recovered())?;
458
459        self.db.merge_transitions(BundleRetention::Reverts);
460
461        Ok(result)
462    }
463
464    fn execute_one_with_state_hook<H>(
465        &mut self,
466        block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
467        state_hook: H,
468    ) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
469    where
470        H: OnStateHook + 'static,
471    {
472        let result = self
473            .strategy_factory
474            .executor_for_block(&mut self.db, block)
475            .with_state_hook(Some(Box::new(state_hook)))
476            .execute_block(block.transactions_recovered())?;
477
478        self.db.merge_transitions(BundleRetention::Reverts);
479
480        Ok(result)
481    }
482
483    fn into_state(self) -> State<DB> {
484        self.db
485    }
486
487    fn size_hint(&self) -> usize {
488        self.db.bundle_state.size_hint()
489    }
490}
491
492#[cfg(test)]
493mod tests {
494    use super::*;
495    use crate::Address;
496    use alloy_consensus::constants::KECCAK_EMPTY;
497    use alloy_evm::block::state_changes::balance_increment_state;
498    use alloy_primitives::{address, map::HashMap, U256};
499    use core::marker::PhantomData;
500    use reth_ethereum_primitives::EthPrimitives;
501    use revm::{
502        database::{CacheDB, EmptyDB},
503        state::AccountInfo,
504    };
505
506    #[derive(Clone, Debug, Default)]
507    struct TestExecutorProvider;
508
509    impl TestExecutorProvider {
510        fn executor<DB>(&self, _db: DB) -> TestExecutor<DB>
511        where
512            DB: Database,
513        {
514            TestExecutor(PhantomData)
515        }
516    }
517
518    struct TestExecutor<DB>(PhantomData<DB>);
519
520    impl<DB: Database> Executor<DB> for TestExecutor<DB> {
521        type Primitives = EthPrimitives;
522        type Error = BlockExecutionError;
523
524        fn execute_one(
525            &mut self,
526            _block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
527        ) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
528        {
529            Err(BlockExecutionError::msg("execution unavailable for tests"))
530        }
531
532        fn execute_one_with_state_hook<F>(
533            &mut self,
534            _block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
535            _state_hook: F,
536        ) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
537        where
538            F: OnStateHook + 'static,
539        {
540            Err(BlockExecutionError::msg("execution unavailable for tests"))
541        }
542
543        fn into_state(self) -> State<DB> {
544            unreachable!()
545        }
546
547        fn size_hint(&self) -> usize {
548            0
549        }
550    }
551
552    #[test]
553    fn test_provider() {
554        let provider = TestExecutorProvider;
555        let db = CacheDB::<EmptyDB>::default();
556        let executor = provider.executor(db);
557        let _ = executor.execute(&Default::default());
558    }
559
560    fn setup_state_with_account(
561        addr: Address,
562        balance: u128,
563        nonce: u64,
564    ) -> State<CacheDB<EmptyDB>> {
565        let db = CacheDB::<EmptyDB>::default();
566        let mut state = State::builder().with_database(db).with_bundle_update().build();
567
568        let account_info = AccountInfo {
569            balance: U256::from(balance),
570            nonce,
571            code_hash: KECCAK_EMPTY,
572            code: None,
573        };
574        state.insert_account(addr, account_info);
575        state
576    }
577
578    #[test]
579    fn test_balance_increment_state_zero() {
580        let addr = address!("0x1000000000000000000000000000000000000000");
581        let mut state = setup_state_with_account(addr, 100, 1);
582
583        let mut increments = HashMap::default();
584        increments.insert(addr, 0);
585
586        let result = balance_increment_state(&increments, &mut state).unwrap();
587        assert!(result.is_empty(), "Zero increments should be ignored");
588    }
589
590    #[test]
591    fn test_balance_increment_state_empty_increments_map() {
592        let mut state = State::builder()
593            .with_database(CacheDB::<EmptyDB>::default())
594            .with_bundle_update()
595            .build();
596
597        let increments = HashMap::default();
598        let result = balance_increment_state(&increments, &mut state).unwrap();
599        assert!(result.is_empty(), "Empty increments map should return empty state");
600    }
601
602    #[test]
603    fn test_balance_increment_state_multiple_valid_increments() {
604        let addr1 = address!("0x1000000000000000000000000000000000000000");
605        let addr2 = address!("0x2000000000000000000000000000000000000000");
606
607        let mut state = setup_state_with_account(addr1, 100, 1);
608
609        let account2 =
610            AccountInfo { balance: U256::from(200), nonce: 1, code_hash: KECCAK_EMPTY, code: None };
611        state.insert_account(addr2, account2);
612
613        let mut increments = HashMap::default();
614        increments.insert(addr1, 50);
615        increments.insert(addr2, 100);
616
617        let result = balance_increment_state(&increments, &mut state).unwrap();
618
619        assert_eq!(result.len(), 2);
620        assert_eq!(result.get(&addr1).unwrap().info.balance, U256::from(100));
621        assert_eq!(result.get(&addr2).unwrap().info.balance, U256::from(200));
622    }
623
624    #[test]
625    fn test_balance_increment_state_mixed_zero_and_nonzero_increments() {
626        let addr1 = address!("0x1000000000000000000000000000000000000000");
627        let addr2 = address!("0x2000000000000000000000000000000000000000");
628
629        let mut state = setup_state_with_account(addr1, 100, 1);
630
631        let account2 =
632            AccountInfo { balance: U256::from(200), nonce: 1, code_hash: KECCAK_EMPTY, code: None };
633        state.insert_account(addr2, account2);
634
635        let mut increments = HashMap::default();
636        increments.insert(addr1, 0);
637        increments.insert(addr2, 100);
638
639        let result = balance_increment_state(&increments, &mut state).unwrap();
640
641        assert_eq!(result.len(), 1, "Only non-zero increments should be included");
642        assert!(!result.contains_key(&addr1), "Zero increment account should not be included");
643        assert_eq!(result.get(&addr2).unwrap().info.balance, U256::from(200));
644    }
645}