Skip to main content

reth_bb/
evm.rs

1//! Big-block executor.
2//!
3//! Provides [`BbBlockExecutor`] and [`BbBlockExecutorFactory`] which handle
4//! segment boundaries within big-block payloads.
5//!
6//! [`BbBlockExecutor`] wraps [`EthBlockExecutor`] and intercepts
7//! `execute_transaction` to apply segment-boundary changes.
8
9use crate::evm_config::BigBlockSegment;
10use alloy_consensus::TransactionEnvelope;
11use alloy_eips::eip7685::Requests;
12use alloy_evm::{
13    block::{
14        BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockExecutorFactory,
15        ExecutableTx, GasOutput, StateDB,
16    },
17    eth::{EthBlockExecutionCtx, EthBlockExecutor, EthEvmContext, EthTxResult},
18    precompiles::PrecompilesMap,
19    Database, EthEvm, EthEvmFactory, Evm, EvmFactory, FromRecoveredTx, FromTxWithEncoded,
20};
21use alloy_primitives::B256;
22use reth_ethereum_primitives::{Receipt, TransactionSigned};
23use reth_evm_ethereum::RethReceiptBuilder;
24use revm::{
25    context::{BlockEnv, TxEnv},
26    context_interface::result::{EVMError, HaltReason},
27    handler::PrecompileProvider,
28    interpreter::InterpreterResult,
29    primitives::hardfork::SpecId,
30    Inspector,
31};
32use tracing::{debug, trace};
33
34// ---------------------------------------------------------------------------
35// BbEvmPlan — runtime segment tracking state
36// ---------------------------------------------------------------------------
37
38/// Runtime state for segment boundary tracking.
39#[derive(Debug, Clone)]
40pub struct BbEvmPlan<'a> {
41    /// The segment boundaries and environments.
42    pub(crate) segments: Vec<BigBlockSegment<'a>>,
43    /// Index of the next segment to switch to (starts at 1).
44    pub(crate) next_segment: usize,
45    /// Number of user transactions executed so far.
46    pub(crate) tx_counter: usize,
47    /// Block hashes to seed for inter-segment BLOCKHASH resolution.
48    /// Includes both prior block hashes and inter-segment hashes.
49    pub(crate) block_hashes_to_seed: Vec<(u64, B256)>,
50}
51
52impl<'a> BbEvmPlan<'a> {
53    /// Creates a new `BbEvmPlan` from segments and hardfork flags.
54    pub(crate) fn new(segments: Vec<BigBlockSegment<'a>>) -> Self {
55        // Pre-compute all inter-segment block hashes.
56        let mut block_hashes_to_seed = Vec::new();
57        for seg in segments.iter().skip(1) {
58            let finished_block_number = seg.evm_env.block_env.number.saturating_to::<u64>() - 1;
59            let finished_block_hash = seg.ctx.parent_hash;
60            block_hashes_to_seed.push((finished_block_number, finished_block_hash));
61        }
62
63        Self { segments, next_segment: 1, tx_counter: 0, block_hashes_to_seed }
64    }
65
66    /// Returns the 256 block hashes relevant to a segment with the given block
67    /// number. BLOCKHASH can look back 256 blocks, so we select entries in
68    /// `[block_number - 256, block_number)`.
69    pub(crate) fn hashes_for_block(&self, block_number: u64) -> Vec<(u64, B256)> {
70        let min = block_number.saturating_sub(256);
71        self.block_hashes_to_seed
72            .iter()
73            .copied()
74            .filter(|(n, _)| *n >= min && *n < block_number)
75            .collect()
76    }
77
78    /// Returns the segment that contains the transaction at `tx_index`.
79    pub(crate) fn segment_index_for_tx(&self, tx_index: usize) -> usize {
80        self.segments.partition_point(|segment| segment.start_tx <= tx_index).saturating_sub(1)
81    }
82}
83
84// ---------------------------------------------------------------------------
85// BbBlockExecutor — handles segment boundaries
86// ---------------------------------------------------------------------------
87
88/// Function pointer that seeds block hashes into the DB's block hash cache.
89///
90/// Injected from `ConfigureEvm::create_executor` where the concrete `State<DB>`
91/// type is known, allowing `BbBlockExecutor` to reseed the ring buffer at
92/// segment boundaries without requiring additional trait bounds on `DB`.
93pub(crate) type BlockHashSeeder<DB> = fn(&mut DB, &[(u64, B256)]);
94
95/// Function pointer that reads the BAL index from the DB.
96///
97/// Injected from `ConfigureEvm::create_executor` where the concrete
98/// `State<DB>` type is known. `BbBlockExecutor` calls this lazily on first
99/// use to decide which segment to start at: `0` means canonical execution
100/// from segment 0, `n > 0` means a BAL worker that runs only the n-th
101/// transaction (in whichever segment contains it).
102pub(crate) type BalIndexReader<DB> = fn(&DB) -> u64;
103
104/// Function pointer that bumps the BAL index in the DB.
105///
106/// Injected from `ConfigureEvm::create_executor` like the other DB callbacks
107/// so `BbBlockExecutor` can advance `bal_index` between sub-events of a
108/// segment boundary (post-N's `finish()` and pre-N+1's
109/// `apply_pre_execution_changes()`) without requiring additional trait bounds
110/// on `DB`. The renumbering scheme places these on consecutive `bal_indexes` so
111/// workers reading the BAL overlay see post-N's writes via the strict
112/// less-than `BalWrites::get` semantic.
113pub(crate) type BalIndexBumper<DB> = fn(&mut DB);
114
115/// Function pointer that overwrites the BAL index in the DB.
116///
117/// Used in `BbBlockExecutor::initialize` to map a worker's incoming
118/// `bal_index = i + 1` (the standard "tx i + 1" convention from
119/// `execute_block_in_pool`) onto the renumbered space `i + 1 + 2k`, where
120/// `k` is the segment index containing tx `i`. Renumbering reserves two
121/// extra `bal_indexes` per segment boundary (one for each segment's
122/// post-execution and one for the next segment's pre-execution), so workers'
123/// strict less-than reads can see those boundary writes.
124pub(crate) type BalIndexSetter<DB> = fn(&mut DB, u64);
125
126/// Block executor that wraps [`EthBlockExecutor`] and handles segment-boundary
127/// changes for big-block execution.
128///
129/// At segment boundaries, the inner executor is finished (applying its
130/// end-of-block logic: post-execution system calls, withdrawal balance
131/// increments) and a new one is constructed for the next segment (applying
132/// its start-of-block logic: EIP-2935/EIP-4788 system calls).
133///
134/// Gas counters reset at each boundary so that each segment's real gas limit
135/// is used (preserving correct GASLIMIT opcode behavior). Accumulated offsets
136/// are applied to receipts and totals in `finish()`.
137#[expect(missing_debug_implementations)]
138pub struct BbBlockExecutor<'a, DB, I, P, Spec>
139where
140    DB: Database,
141{
142    /// The inner executor. `None` transiently during `apply_segment_boundary`.
143    inner: Option<EthBlockExecutor<'a, EthEvm<DB, I, P>, Spec, RethReceiptBuilder>>,
144    plan: BbEvmPlan<'a>,
145    /// Requests accumulated from segments that have been finished at
146    /// boundaries. Merged into the final result in `finish()`.
147    accumulated_requests: Requests,
148    /// Cumulative gas used by all segments that have been finished at
149    /// boundaries. Added to receipts and the final gas total in `finish()`.
150    gas_used_offset: u64,
151    /// Cumulative blob gas used by all segments that have been finished at
152    /// boundaries.
153    blob_gas_used_offset: u64,
154    /// Callback to reseed block hashes into the DB's cache at segment
155    /// boundaries. See [`BlockHashSeeder`].
156    block_hash_seeder: Option<BlockHashSeeder<DB>>,
157    /// Callback to read `bal_index` from the DB. See [`BalIndexReader`].
158    bal_index_reader: Option<BalIndexReader<DB>>,
159    /// Callback to bump `bal_index` on the DB. See [`BalIndexBumper`]. Used at
160    /// segment boundaries to put post-N's writes and pre-N+1's writes on
161    /// consecutive `bal_indexes`.
162    bal_index_bumper: Option<BalIndexBumper<DB>>,
163    /// Callback to set `bal_index` on the DB. See [`BalIndexSetter`]. Used in
164    /// [`Self::initialize`] to renumber a worker's incoming `bal_index` into
165    /// the boundary-padded space.
166    bal_index_setter: Option<BalIndexSetter<DB>>,
167    /// Whether the executor has selected its starting segment.
168    initialized: bool,
169}
170
171impl<'a, DB, I, P, Spec> BbBlockExecutor<'a, DB, I, P, Spec>
172where
173    DB: StateDB,
174    I: Inspector<EthEvmContext<DB>>,
175    P: PrecompileProvider<EthEvmContext<DB>, Output = InterpreterResult>,
176    Spec: alloy_evm::eth::spec::EthExecutorSpec + Clone,
177    EthEvm<DB, I, P>: Evm<
178        DB = DB,
179        Tx = TxEnv,
180        HaltReason = HaltReason,
181        Error = EVMError<DB::Error>,
182        Spec = SpecId,
183        BlockEnv = BlockEnv,
184    >,
185    TxEnv: FromRecoveredTx<TransactionSigned> + FromTxWithEncoded<TransactionSigned>,
186{
187    #[expect(clippy::too_many_arguments)]
188    pub(crate) fn new(
189        evm: EthEvm<DB, I, P>,
190        plan: BbEvmPlan<'a>,
191        spec: Spec,
192        receipt_builder: RethReceiptBuilder,
193        block_hash_seeder: Option<BlockHashSeeder<DB>>,
194        bal_index_reader: Option<BalIndexReader<DB>>,
195        bal_index_bumper: Option<BalIndexBumper<DB>>,
196        bal_index_setter: Option<BalIndexSetter<DB>>,
197    ) -> Self {
198        let inner = EthBlockExecutor::new(evm, plan.segments[0].ctx.clone(), spec, receipt_builder);
199        Self {
200            inner: Some(inner),
201            plan,
202            accumulated_requests: Requests::default(),
203            gas_used_offset: 0,
204            blob_gas_used_offset: 0,
205            block_hash_seeder,
206            bal_index_reader,
207            bal_index_bumper,
208            bal_index_setter,
209            initialized: false,
210        }
211    }
212
213    /// Idempotent first-use init. Reads `bal_index` from the DB to pick the
214    /// starting segment, swaps the inner executor's env/ctx to that segment,
215    /// and reseeds the block hash cache for its window.
216    ///
217    /// `bal_index == 0` selects segment 0 — canonical execution that walks
218    /// all segments via `maybe_apply_boundary`. `bal_index > 0` selects the
219    /// segment containing the `(bal_index - 1)`-th transaction and drops the
220    /// plan so segment boundaries don't fire — a BAL worker only runs one
221    /// transaction in its segment.
222    fn initialize(&mut self) -> Result<(), BlockExecutionError> {
223        if self.initialized {
224            return Ok(());
225        }
226
227        let segment_idx = if let Some(bal_index) = self
228            .bal_index_reader
229            .map(|reader| reader(self.inner().evm().db()))
230            .filter(|bal_index| *bal_index > 0)
231        {
232            let segment_idx = self.plan.segment_index_for_tx((bal_index - 1) as usize);
233
234            // Renumber the worker's bal_index from the raw "tx i + 1"
235            // convention to "tx i + 1 + 2k" where k is the segment index.
236            // This reserves two `bal_indexes` per crossed segment boundary
237            // (one for post-N's `finish()`, one for pre-N+1's
238            // `apply_pre_execution_changes`) so worker reads via
239            // `BalWrites::get` see those writes via the strict less-than
240            // semantic.
241            if let Some(setter) = self.bal_index_setter {
242                let renumbered = bal_index + 2 * segment_idx as u64;
243                setter(self.inner_mut().evm_mut().db_mut(), renumbered);
244            }
245
246            segment_idx
247        } else {
248            if self.initialized {
249                return Ok(());
250            }
251
252            self.initialized = true;
253            0
254        };
255        let segment = &self.plan.segments[segment_idx];
256        let block_env = segment.evm_env.block_env.clone();
257        let block_number = block_env.number.saturating_to::<u64>();
258        let mut cfg_env = segment.evm_env.cfg_env.clone();
259        cfg_env.disable_base_fee = true;
260
261        let inner = self.inner.as_mut().expect("inner executor must exist");
262        let evm_ctx = inner.evm.ctx_mut();
263        evm_ctx.block = block_env;
264        evm_ctx.cfg = cfg_env;
265        inner.ctx = segment.ctx.clone();
266
267        self.reseed_block_hashes_for(block_number);
268
269        Ok(())
270    }
271
272    const fn inner(&self) -> &EthBlockExecutor<'a, EthEvm<DB, I, P>, Spec, RethReceiptBuilder> {
273        self.inner.as_ref().expect("inner executor must exist")
274    }
275
276    const fn inner_mut(
277        &mut self,
278    ) -> &mut EthBlockExecutor<'a, EthEvm<DB, I, P>, Spec, RethReceiptBuilder> {
279        self.inner.as_mut().expect("inner executor must exist")
280    }
281
282    fn reseed_block_hashes_for(&mut self, block_number: u64) {
283        let Some(seeder) = self.block_hash_seeder else { return };
284        let hashes = self.plan.hashes_for_block(block_number);
285        seeder(self.inner_mut().evm_mut().db_mut(), &hashes);
286    }
287
288    fn apply_segment_boundary(&mut self) -> Result<(), BlockExecutionError> {
289        let plan = &mut self.plan;
290        let seg_idx = plan.next_segment;
291        let prev_seg_idx = seg_idx - 1;
292
293        debug!(
294            target: "engine::bb::evm",
295            seg_idx,
296            tx_counter = plan.tx_counter,
297            "Applying segment boundary"
298        );
299
300        // Swap the inner executor's ctx to the finished segment's values so
301        // that finish() applies the correct withdrawals and post-execution
302        // system calls for that segment.
303        let prev_segment = &plan.segments[prev_seg_idx];
304
305        // Clone the next segment's data before we consume inner.
306        let new_segment = &plan.segments[seg_idx];
307        let new_block_env = new_segment.evm_env.block_env.clone();
308        let mut new_cfg_env = new_segment.evm_env.cfg_env.clone();
309        new_cfg_env.disable_base_fee = true;
310
311        plan.next_segment += 1;
312
313        // Finish the inner executor for the completed segment. This applies
314        // post-execution system calls (EIP-7002/7251) and withdrawal balance
315        // increments via EthBlockExecutor::finish() at the current bal_index
316        // (= K, the boundary's "post-N slot").
317        let mut inner = self.inner.take().expect("inner executor must exist");
318        inner.ctx = prev_segment.ctx.clone();
319        let spec = inner.spec.clone();
320        let receipt_builder = inner.receipt_builder;
321
322        if let Some(bumper) = self.bal_index_bumper {
323            bumper(inner.evm_mut().db_mut());
324        }
325
326        let (mut evm, result) = inner.finish()?;
327
328        // Renumbering: bump bal_index so the new segment's
329        // `apply_pre_execution_changes` writes land at K+1 instead of colliding
330        // with post-N at K. Without this, BAL workers querying at K can't see
331        // either boundary write via `BalWrites::get`'s strict less-than.
332        if let Some(bumper) = self.bal_index_bumper {
333            bumper(evm.db_mut());
334        }
335
336        // Receipts already have globally-correct cumulative_gas_used (fixed
337        // up in commit_transaction). Update the offset with this segment's
338        // gas so that subsequent segments' receipts are adjusted correctly.
339        self.gas_used_offset += result.gas_used;
340        self.blob_gas_used_offset += result.blob_gas_used;
341        self.accumulated_requests.extend(result.requests);
342
343        let last_receipt_cumulative =
344            result.receipts.last().map(|r| r.cumulative_gas_used).unwrap_or(0);
345        let seg_block_number = prev_segment.evm_env.block_env.number.saturating_to::<u64>();
346        debug!(
347            target: "engine::bb::evm",
348            prev_seg_idx,
349            seg_block_number,
350            segment_gas_used = result.gas_used,
351            gas_used_offset = self.gas_used_offset,
352            last_receipt_cumulative,
353            receipt_count = result.receipts.len(),
354            "Finished segment"
355        );
356
357        // Swap EVM env to the next segment's values (using real gas_limit).
358        let ctx = evm.ctx_mut();
359        ctx.block = new_block_env;
360        ctx.cfg = new_cfg_env;
361
362        // Build a new inner executor for the next segment. gas_used starts
363        // at 0 so the per-transaction gas check uses this segment's real
364        // gas_limit correctly.
365        let mut new_inner =
366            EthBlockExecutor::new(evm, new_segment.ctx.clone(), spec, receipt_builder);
367
368        // Carry forward receipts from prior segments.
369        new_inner.receipts = result.receipts;
370
371        self.inner = Some(new_inner);
372
373        // Reseed the block hash cache for the new segment's 256-block window
374        // before applying pre-execution changes (which may use BLOCKHASH).
375        let new_block_number =
376            self.plan.segments[seg_idx].evm_env.block_env.number.saturating_to::<u64>();
377        self.reseed_block_hashes_for(new_block_number);
378
379        // Apply pre-execution changes for the new segment (EIP-2935, EIP-4788)
380        // at bal_index K+1.
381        self.inner_mut().apply_pre_execution_changes()?;
382
383        trace!(target: "engine::bb::evm", "Started segment {seg_idx}");
384
385        Ok(())
386    }
387}
388
389impl<'a, DB, I, P, Spec> BlockExecutor for BbBlockExecutor<'a, DB, I, P, Spec>
390where
391    DB: StateDB,
392    I: Inspector<EthEvmContext<DB>>,
393    P: PrecompileProvider<EthEvmContext<DB>, Output = InterpreterResult>,
394    Spec: alloy_evm::eth::spec::EthExecutorSpec + Clone,
395    EthEvm<DB, I, P>: Evm<
396        DB = DB,
397        Tx = TxEnv,
398        HaltReason = HaltReason,
399        Error = EVMError<DB::Error>,
400        Spec = SpecId,
401        BlockEnv = BlockEnv,
402    >,
403    TxEnv: FromRecoveredTx<TransactionSigned> + FromTxWithEncoded<TransactionSigned>,
404{
405    type Transaction = TransactionSigned;
406    type Receipt = Receipt;
407    type Evm = EthEvm<DB, I, P>;
408    type Result = EthTxResult<HaltReason, alloy_consensus::TxType>;
409
410    fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
411        // Init swaps the EVM's block_env and executor ctx to the starting
412        // segment's values so the EIP-2935/EIP-4788 system calls use the
413        // correct block number and parent hash. Without this the outer big
414        // block header's (synthetic) block_number would be used, writing to
415        // wrong EIP-2935 slots and corrupting state.
416        self.initialize()?;
417        self.inner_mut().apply_pre_execution_changes()
418    }
419
420    fn execute_transaction_without_commit(
421        &mut self,
422        tx: impl ExecutableTx<Self>,
423    ) -> Result<Self::Result, BlockExecutionError> {
424        self.initialize()?;
425        self.inner_mut().execute_transaction_without_commit(tx)
426    }
427
428    fn commit_transaction(&mut self, output: Self::Result) -> GasOutput {
429        let gas_used = self.inner_mut().commit_transaction(output);
430
431        // Fix up cumulative_gas_used on the just-committed receipt so that
432        // the receipt root task (which reads receipts incrementally) sees
433        // globally-correct values across all segments.
434        let offset = self.gas_used_offset;
435        if offset > 0 &&
436            let Some(receipt) = self.inner_mut().receipts.last_mut()
437        {
438            receipt.cumulative_gas_used += offset;
439        }
440
441        self.plan.tx_counter += 1;
442
443        while self.plan.next_segment < self.plan.segments.len() &&
444            self.plan.tx_counter == self.plan.segments[self.plan.next_segment].start_tx
445        {
446            self.apply_segment_boundary().expect("must succeed");
447        }
448
449        gas_used
450    }
451
452    fn finish(
453        mut self,
454    ) -> Result<(Self::Evm, BlockExecutionResult<Self::Receipt>), BlockExecutionError> {
455        // Swap the inner executor's ctx to the last segment's ctx so that
456        // EthBlockExecutor::finish() applies the correct withdrawal balance
457        // increments and post-execution system calls.
458        let last_seg = self.plan.segments.last().unwrap();
459        let last_ctx = EthBlockExecutionCtx {
460            parent_hash: last_seg.ctx.parent_hash,
461            parent_beacon_block_root: last_seg.ctx.parent_beacon_block_root,
462            ommers: last_seg.ctx.ommers,
463            withdrawals: last_seg.ctx.withdrawals.clone(),
464            extra_data: last_seg.ctx.extra_data.clone(),
465            tx_count_hint: last_seg.ctx.tx_count_hint,
466            slot_number: last_seg.ctx.slot_number,
467        };
468        self.inner_mut().ctx = last_ctx;
469        let inner = self.inner.take().expect("inner executor must exist");
470        let (evm, mut result) = inner.finish()?;
471
472        // Receipts already have globally-correct cumulative_gas_used (fixed
473        // up in commit_transaction). Add the offset to the totals so they
474        // reflect gas across all segments.
475        let last_segment_gas = result.gas_used;
476        result.gas_used += self.gas_used_offset;
477        result.blob_gas_used += self.blob_gas_used_offset;
478
479        let last_receipt_cumulative =
480            result.receipts.last().map(|r| r.cumulative_gas_used).unwrap_or(0);
481        debug!(
482            target: "engine::bb::evm",
483            last_segment_gas,
484            gas_used_offset = self.gas_used_offset,
485            total_gas_used = result.gas_used,
486            last_receipt_cumulative,
487            receipt_count = result.receipts.len(),
488            "Finished final segment"
489        );
490
491        // Merge requests accumulated from earlier segment boundaries into
492        // the final result.
493        if !self.accumulated_requests.is_empty() {
494            let mut merged = self.accumulated_requests;
495            merged.extend(result.requests);
496            result.requests = merged;
497        }
498
499        Ok((evm, result))
500    }
501
502    fn evm_mut(&mut self) -> &mut Self::Evm {
503        self.inner_mut().evm_mut()
504    }
505
506    fn evm(&self) -> &Self::Evm {
507        self.inner().evm()
508    }
509
510    fn receipts(&self) -> &[Self::Receipt] {
511        self.inner().receipts()
512    }
513}
514
515// ---------------------------------------------------------------------------
516// BbBlockExecutorFactory
517// ---------------------------------------------------------------------------
518
519/// Block executor factory that produces [`BbBlockExecutor`] for
520/// boundary-aware big-block execution.
521#[derive(Debug, Clone)]
522pub struct BbBlockExecutorFactory<Spec> {
523    receipt_builder: RethReceiptBuilder,
524    spec: Spec,
525    evm_factory: EthEvmFactory,
526}
527
528impl<Spec> BbBlockExecutorFactory<Spec> {
529    pub const fn new(
530        receipt_builder: RethReceiptBuilder,
531        spec: Spec,
532        evm_factory: EthEvmFactory,
533    ) -> Self {
534        Self { receipt_builder, spec, evm_factory }
535    }
536
537    pub const fn evm_factory(&self) -> &EthEvmFactory {
538        &self.evm_factory
539    }
540
541    pub const fn spec(&self) -> &Spec {
542        &self.spec
543    }
544
545    pub const fn receipt_builder(&self) -> &RethReceiptBuilder {
546        &self.receipt_builder
547    }
548
549    pub(crate) fn create_executor_with_seeder<'a, DB, I>(
550        &'a self,
551        evm: EthEvm<DB, I, PrecompilesMap>,
552        plan: BbEvmPlan<'a>,
553        block_hash_seeder: Option<BlockHashSeeder<DB>>,
554        bal_index_reader: Option<BalIndexReader<DB>>,
555        bal_index_bumper: Option<BalIndexBumper<DB>>,
556        bal_index_setter: Option<BalIndexSetter<DB>>,
557    ) -> BbBlockExecutor<'a, DB, I, PrecompilesMap, &'a Spec>
558    where
559        Spec: alloy_evm::eth::spec::EthExecutorSpec,
560        DB: StateDB,
561        I: Inspector<EthEvmContext<DB>>,
562    {
563        BbBlockExecutor::new(
564            evm,
565            plan,
566            &self.spec,
567            self.receipt_builder,
568            block_hash_seeder,
569            bal_index_reader,
570            bal_index_bumper,
571            bal_index_setter,
572        )
573    }
574}
575
576impl<Spec> BlockExecutorFactory for BbBlockExecutorFactory<Spec>
577where
578    Spec: alloy_evm::eth::spec::EthExecutorSpec + 'static,
579    TxEnv: FromRecoveredTx<TransactionSigned> + FromTxWithEncoded<TransactionSigned>,
580{
581    type EvmFactory = EthEvmFactory;
582    type ExecutionCtx<'a> = BbEvmPlan<'a>;
583    type Transaction = TransactionSigned;
584    type Receipt = Receipt;
585    type TxExecutionResult = EthTxResult<
586        <EthEvmFactory as EvmFactory>::HaltReason,
587        <TransactionSigned as TransactionEnvelope>::TxType,
588    >;
589    type Executor<'a, DB: StateDB, I: Inspector<EthEvmContext<DB>>> =
590        BbBlockExecutor<'a, DB, I, PrecompilesMap, &'a Spec>;
591
592    fn evm_factory(&self) -> &Self::EvmFactory {
593        &self.evm_factory
594    }
595
596    fn create_executor<'a, DB, I>(
597        &'a self,
598        evm: EthEvm<DB, I, PrecompilesMap>,
599        ctx: BbEvmPlan<'a>,
600    ) -> Self::Executor<'a, DB, I>
601    where
602        DB: StateDB,
603        I: Inspector<EthEvmContext<DB>>,
604    {
605        BbBlockExecutor::new(evm, ctx, &self.spec, self.receipt_builder, None, None, None, None)
606    }
607}