1use 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
30pub trait Executor<DB: Database>: Sized {
33 type Primitives: NodePrimitives;
35 type Error;
37
38 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 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 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 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 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 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 fn into_state(self) -> State<DB>;
128
129 fn size_hint(&self) -> usize;
133}
134
135#[derive(Debug, Clone)]
137pub struct ExecuteOutput<R> {
138 pub receipts: Vec<R>,
140 pub gas_used: u64,
142}
143
144#[derive(derive_more::Debug)]
146#[non_exhaustive]
147pub struct BlockAssemblerInput<'a, 'b, F: BlockExecutorFactory, H = Header> {
148 pub evm_env: EvmEnv<<F::EvmFactory as EvmFactory>::Spec>,
152 pub execution_ctx: F::ExecutionCtx<'a>,
154 pub parent: &'a SealedHeader<H>,
156 pub transactions: Vec<F::Transaction>,
158 pub output: &'b BlockExecutionResult<F::Receipt>,
160 pub bundle_state: &'a BundleState,
162 #[debug(skip)]
164 pub state_provider: &'b dyn StateProvider,
165 pub state_root: B256,
167}
168
169#[auto_impl::auto_impl(&, Arc)]
171pub trait BlockAssembler<F: BlockExecutorFactory> {
172 type Block: Block;
174
175 fn assemble_block(
177 &self,
178 input: BlockAssemblerInput<'_, '_, F, <Self::Block as Block>::Header>,
179 ) -> Result<Self::Block, BlockExecutionError>;
180}
181
182#[derive(Debug, Clone)]
184pub struct BlockBuilderOutcome<N: NodePrimitives> {
185 pub execution_result: BlockExecutionResult<N::Receipt>,
187 pub hashed_state: HashedPostState,
189 pub trie_updates: TrieUpdates,
191 pub block: RecoveredBlock<N::Block>,
193}
194
195pub trait BlockBuilder {
202 type Primitives: NodePrimitives;
204 type Executor: BlockExecutor<
206 Transaction = TxTy<Self::Primitives>,
207 Receipt = ReceiptTy<Self::Primitives>,
208 >;
209
210 fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError>;
212
213 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 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 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 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 fn finish(
259 self,
260 state_provider: impl StateProvider,
261 ) -> Result<BlockBuilderOutcome<Self::Primitives>, BlockExecutionError>;
262
263 fn executor_mut(&mut self) -> &mut Self::Executor;
265
266 fn executor(&self) -> &Self::Executor;
268
269 fn evm_mut(&mut self) -> &mut <Self::Executor as BlockExecutor>::Evm {
271 self.executor_mut().evm_mut()
272 }
273
274 fn evm(&self) -> &<Self::Executor as BlockExecutor>::Evm {
276 self.executor().evm()
277 }
278
279 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
294pub trait ExecutorTx<Executor: BlockExecutor> {
296 fn as_executable(&self) -> impl ExecutableTx<Executor>;
298
299 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 db.merge_transitions(BundleRetention::Reverts);
375
376 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#[expect(missing_debug_implementations)]
425pub struct BasicBlockExecutor<F, DB> {
426 pub(crate) strategy_factory: F,
428 pub(crate) db: State<DB>,
430}
431
432impl<F, DB: Database> BasicBlockExecutor<F, DB> {
433 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}