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