Skip to main content

reth_evm_ethereum/
executor.rs

1//! EVM-backed Ethereum executor.
2use reth_execution_types::{BlockState, StateUpdate};
3
4use crate::{
5    execution::{
6        base_block_reward, block_requests_from_receipts, commit_detached_transaction,
7        commit_pending_state, execute_transaction_without_commit, map_transaction_error,
8        post_block_balance_state_changes, post_execution_system_call_state_changes,
9        pre_execution_system_call_state_changes, BlockExecutionContext, BlockSystemCalls,
10    },
11    factory::{EthBlockExecutorFactory, EvmFactory},
12    EthBlockExecutionCtx, EthEvmEnv, RethReceiptBuilder,
13};
14use alloc::{boxed::Box, sync::Arc, vec::Vec};
15use alloy_consensus::{
16    transaction::{TransactionEnvelope, TxHashRef},
17    Header, Transaction, TxType as EthTxType,
18};
19use alloy_eip7928::{BlockAccessIndex, BlockAccessList};
20use alloy_eips::{eip2718::Typed2718, eip4895::Withdrawal, eip7685::Requests};
21use alloy_primitives::{Address, B256};
22use evm2::{
23    ethereum::TxEnvelope, evm::Bal, interpreter::Host, BaseEvmTypes, Evm, EvmTypes,
24    TxResultWithState,
25};
26use reth_chainspec::EthChainSpec;
27use reth_ethereum_forks::{EthereumHardfork, EthereumHardforks};
28use reth_ethereum_primitives::{Receipt, TransactionSigned};
29use reth_evm::{
30    BlockExecutionError, BlockExecutionOutput, BlockExecutor, BlockTransactionResult,
31    BlockValidationError, ExecutorTx, GasOutput, ReceiptBuilder, ReceiptBuilderCtx, RecoveredTx,
32};
33use reth_execution_types::BlockExecutionResult;
34
35/// Configured Ethereum block executor backed by evm2.
36#[expect(missing_debug_implementations)]
37pub struct EthBlockExecutor<'a, T = BaseEvmTypes, R = RethReceiptBuilder>
38where
39    T: EvmTypes<Tx: Typed2718, TxResultExt: Send + Sync>,
40    R: ReceiptBuilder,
41{
42    /// EVM containing the execution overlay and optional block access list builder.
43    pub evm: Evm<'a, T>,
44    ctx: EthBlockExecutionCtx<'a>,
45    receipt_builder: R,
46    spec_id: evm2::SpecId,
47    base_block_reward: Option<u128>,
48    dao_fork_transition: bool,
49    deposit_contract_address: Option<Address>,
50    /// Accumulated state changes, including state committed through the executor's hooks.
51    pub block_state: BlockState,
52    state_update_hook: StateUpdateHook,
53    /// Transaction receipts in execution order.
54    pub receipts: Vec<R::Receipt>,
55    /// Total receipt gas used.
56    pub cumulative_gas_used: u64,
57    block_regular_gas_used: u64,
58    block_state_gas_used: u64,
59    separate_block_gas: bool,
60    /// Total blob gas used.
61    pub blob_gas_used: u64,
62    bal_index_offset: u64,
63}
64
65/// Detached Ethereum transaction result with the metadata needed for canonical receipt commit.
66#[derive(Debug)]
67pub struct EthTransactionResultWithState<T = BaseEvmTypes, TxType = EthTxType>
68where
69    T: EvmTypes,
70{
71    result: TxResultWithState<T>,
72    tx_type: TxType,
73    blob_gas_used: u64,
74}
75
76impl<T: EvmTypes, TxType> BlockTransactionResult<T> for EthTransactionResultWithState<T, TxType> {
77    fn result(&self) -> &TxResultWithState<T> {
78        &self.result
79    }
80}
81
82impl<T: EvmTypes, TxType> EthTransactionResultWithState<T, TxType> {
83    /// Creates a detached transaction result with its receipt metadata.
84    pub const fn new(result: TxResultWithState<T>, tx_type: TxType, blob_gas_used: u64) -> Self {
85        Self { result, tx_type, blob_gas_used }
86    }
87
88    /// Returns the detached EVM transaction result.
89    pub const fn result(&self) -> &TxResultWithState<T> {
90        &self.result
91    }
92}
93
94type StateUpdateHook = Option<Box<dyn FnMut(StateUpdate) + Send>>;
95
96impl<'a, T, R> EthBlockExecutor<'a, T, R>
97where
98    T: EvmTypes<Tx: Typed2718, TxResultExt: Send + Sync>,
99    R: ReceiptBuilder,
100{
101    /// Creates a configured Ethereum block executor.
102    pub fn new<C>(
103        mut evm: Evm<'a, T>,
104        ctx: EthBlockExecutionCtx<'a>,
105        chain_spec: &C,
106        receipt_builder: R,
107    ) -> Self
108    where
109        C: EthChainSpec + EthereumHardforks + ?Sized,
110    {
111        // The executor stores evm2's base spec ID, while custom type families may expose a
112        // richer host spec ID that converts into it.
113        #[allow(clippy::useless_conversion)]
114        let spec_id = evm.spec_id().into();
115        let block_number = evm.block_env().number.to::<u64>();
116        let separate_block_gas = evm.version().feature(evm2::EvmFeatures::EIP8037);
117        let dao_fork_transition = chain_spec
118            .ethereum_fork_activation(EthereumHardfork::Dao)
119            .transitions_at_block(block_number);
120        let receipts = Vec::with_capacity(ctx.tx_count_hint.unwrap_or_default());
121
122        Self {
123            evm,
124            ctx,
125            receipt_builder,
126            spec_id,
127            base_block_reward: base_block_reward(chain_spec, block_number),
128            dao_fork_transition,
129            deposit_contract_address: chain_spec
130                .deposit_contract()
131                .map(|contract| contract.address),
132            block_state: BlockState::new(),
133            state_update_hook: None,
134            receipts,
135            cumulative_gas_used: 0,
136            block_regular_gas_used: 0,
137            block_state_gas_used: 0,
138            separate_block_gas,
139            blob_gas_used: 0,
140            bal_index_offset: 0,
141        }
142    }
143
144    const fn block_context<'ctx>(
145        deposit_contract_address: Option<Address>,
146        parent_hash: B256,
147        parent_beacon_block_root: Option<B256>,
148        ommers: &'ctx [Header],
149        withdrawals: Option<&'ctx [Withdrawal]>,
150    ) -> BlockExecutionContext<'ctx> {
151        BlockExecutionContext {
152            system_calls: Some(BlockSystemCalls { parent_hash, parent_beacon_block_root }),
153            ommers: Some(ommers),
154            withdrawals,
155            deposit_contract_address,
156        }
157    }
158
159    const fn block_access_list_builder_enabled(&self) -> bool {
160        self.evm.state().bal_builder().is_some()
161    }
162
163    /// Returns the block execution context.
164    pub const fn context(&self) -> &EthBlockExecutionCtx<'a> {
165        &self.ctx
166    }
167
168    /// Returns the accumulated block state.
169    pub const fn block_state(&self) -> &BlockState {
170        &self.block_state
171    }
172
173    /// Commits detached state without recording a transaction receipt or gas usage.
174    pub fn commit_pending_state(&mut self, state: &evm2::evm::PendingState) {
175        let stream_state = self.state_update_hook.is_some();
176        commit_pending_state(
177            &mut self.evm,
178            &mut self.block_state,
179            stream_state,
180            &mut |state| emit_state(&mut self.state_update_hook, state),
181            state,
182        );
183    }
184
185    /// Sets the Ethereum fork used for block system calls and deposit request processing.
186    pub const fn set_spec_id(&mut self, spec_id: evm2::SpecId) {
187        self.spec_id = spec_id;
188    }
189
190    #[inline]
191    const fn set_transaction_block_access_index(&mut self) {
192        if self.block_access_list_builder_enabled() {
193            let index = self.receipts.len() as u64 + 1 + self.bal_index_offset;
194            self.evm.state_mut().set_bal_index(BlockAccessIndex::new(index));
195        }
196    }
197}
198
199impl<'a, T, R> BlockExecutor for EthBlockExecutor<'a, T, R>
200where
201    T: EvmTypes<Tx: Typed2718, TxResultExt: Send + Sync>,
202    R: ReceiptBuilder,
203{
204    type Transaction = R::Transaction;
205    type Receipt = R::Receipt;
206    type Evm = Evm<'a, T>;
207    type TransactionResultWithState =
208        EthTransactionResultWithState<T, <R::Transaction as TransactionEnvelope>::TxType>;
209    type BlockAccessList = Bal;
210
211    fn evm(&self) -> &Self::Evm {
212        &self.evm
213    }
214
215    fn evm_mut(&mut self) -> &mut Self::Evm {
216        &mut self.evm
217    }
218
219    fn set_state_hook(&mut self, hook: impl FnMut(StateUpdate) + Send + 'static) -> bool {
220        self.state_update_hook = Some(Box::new(hook));
221        true
222    }
223
224    fn convert_block_access_list(
225        block_access_list: &BlockAccessList,
226    ) -> Result<Self::BlockAccessList, BlockExecutionError> {
227        Bal::try_from(block_access_list.as_slice()).map_err(BlockExecutionError::other)
228    }
229
230    fn set_block_access_list(
231        &mut self,
232        block_access_list: alloc::sync::Arc<Self::BlockAccessList>,
233    ) {
234        self.evm.state_mut().set_bal(block_access_list);
235    }
236
237    fn set_block_access_index(&mut self, index: BlockAccessIndex) {
238        self.evm.state_mut().set_bal_index(index);
239    }
240
241    fn enable_block_access_list_builder(&mut self) {
242        self.evm.state_mut().enable_bal_builder();
243        self.evm.state_mut().reset_bal_index();
244    }
245
246    fn take_block_access_list(&mut self) -> Option<BlockAccessList> {
247        self.evm.state_mut().take_bal_builder().map(Into::into)
248    }
249
250    fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
251        let context = Self::block_context(
252            self.deposit_contract_address,
253            self.ctx.parent_hash,
254            self.ctx.parent_beacon_block_root,
255            self.ctx.ommers,
256            None,
257        );
258        let block_number = self.evm.block_env().number.to::<u64>();
259        let stream_state = self.state_update_hook.is_some();
260        pre_execution_system_call_state_changes(
261            &mut self.evm,
262            &mut self.block_state,
263            stream_state,
264            &mut |state| emit_state(&mut self.state_update_hook, state),
265            self.spec_id,
266            block_number,
267            context,
268        )
269    }
270
271    fn into_state(self) -> reth_evm::BundleState {
272        self.block_state.into_bundle()
273    }
274
275    fn validate_transaction_gas_limit(
276        &mut self,
277        transaction_gas_limit: u64,
278    ) -> Result<(), BlockExecutionError> {
279        let block_gas_limit = self.evm.block_env().gas_limit.to::<u64>();
280        let unavailable = if self.separate_block_gas {
281            let regular_available = block_gas_limit.saturating_sub(self.block_regular_gas_used);
282            let state_available = block_gas_limit.saturating_sub(self.block_state_gas_used);
283            let regular_limit = transaction_gas_limit.min(self.evm.version().tx_gas_limit_cap);
284            if regular_limit > regular_available {
285                Some((transaction_gas_limit, regular_available))
286            } else if transaction_gas_limit > state_available {
287                Some((transaction_gas_limit, state_available))
288            } else {
289                None
290            }
291        } else {
292            let available = block_gas_limit.saturating_sub(self.cumulative_gas_used);
293            let regular_limit = transaction_gas_limit.min(self.evm.version().tx_gas_limit_cap);
294            (regular_limit > available).then_some((transaction_gas_limit, available))
295        };
296        if let Some((transaction_gas_limit, block_available_gas)) = unavailable {
297            return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
298                transaction_gas_limit,
299                block_available_gas,
300            }
301            .into())
302        }
303        Ok(())
304    }
305
306    fn execute_transaction_with_commit_condition(
307        &mut self,
308        transaction: impl ExecutorTx<Self>,
309        commit: impl FnOnce(&evm2::TxResult<T>, evm2::TxStateChanges<'_>) -> reth_evm::CommitChanges,
310    ) -> Result<Option<GasOutput>, BlockExecutionError> {
311        let (transaction, tx) = transaction.into_parts();
312        self.set_transaction_block_access_index();
313        self.validate_transaction_gas_limit(tx.tx().gas_limit())?;
314        let blob_gas_used = tx.tx().blob_gas_used().unwrap_or_default();
315        let tx_type = tx.tx().tx_type();
316        let Some(outcome) = crate::execution::execute_transaction_with_condition(
317            &mut self.evm,
318            &mut self.block_state,
319            self.state_update_hook.is_some(),
320            &mut |state| emit_state(&mut self.state_update_hook, state),
321            &transaction,
322            commit,
323        )
324        .map_err(|error| map_transaction_error(error, *tx.tx().tx_hash()))?
325        else {
326            return Ok(None);
327        };
328        let tx_gas_used = outcome.tx_gas_used();
329        let regular_gas_used = outcome.execution_gas_spent();
330        let state_gas_used = outcome.state_gas_spent();
331        self.block_regular_gas_used = self.block_regular_gas_used.saturating_add(regular_gas_used);
332        self.block_state_gas_used = self.block_state_gas_used.saturating_add(state_gas_used);
333        self.cumulative_gas_used += tx_gas_used;
334        self.blob_gas_used += blob_gas_used;
335        self.receipts.push(self.receipt_builder.build_receipt::<T>(ReceiptBuilderCtx {
336            tx_type,
337            result: outcome,
338            cumulative_gas_used: self.cumulative_gas_used,
339        }));
340        Ok(Some(GasOutput::new_with_regular(tx_gas_used, regular_gas_used, state_gas_used)))
341    }
342
343    fn execute_transaction_without_commit(
344        &mut self,
345        transaction: impl ExecutorTx<Self>,
346    ) -> Result<Self::TransactionResultWithState, BlockExecutionError> {
347        let (transaction, tx) = transaction.into_parts();
348        self.set_transaction_block_access_index();
349        let transaction_gas_limit = tx.tx().gas_limit();
350        self.validate_transaction_gas_limit(transaction_gas_limit)?;
351        let blob_gas_used = tx.tx().blob_gas_used().unwrap_or_default();
352        let tx_type = tx.tx().tx_type();
353        let result = execute_transaction_without_commit(&mut self.evm, &transaction)
354            .map_err(|err| map_transaction_error(err, *tx.tx().tx_hash()))?;
355        Ok(EthTransactionResultWithState::new(result, tx_type, blob_gas_used))
356    }
357
358    fn commit_transaction(
359        &mut self,
360        output: Self::TransactionResultWithState,
361    ) -> Result<GasOutput, BlockExecutionError> {
362        let EthTransactionResultWithState { result, tx_type, blob_gas_used } = output;
363        self.set_transaction_block_access_index();
364        let stream_state = self.state_update_hook.is_some();
365        let outcome = commit_detached_transaction(
366            &mut self.evm,
367            &mut self.block_state,
368            stream_state,
369            &mut |state| emit_state(&mut self.state_update_hook, state),
370            result,
371        );
372        let tx_gas_used = outcome.tx_gas_used();
373        let regular_gas_used = outcome.execution_gas_spent();
374        let state_gas_used = outcome.state_gas_spent();
375        self.block_regular_gas_used = self.block_regular_gas_used.saturating_add(regular_gas_used);
376        self.block_state_gas_used = self.block_state_gas_used.saturating_add(state_gas_used);
377        self.cumulative_gas_used += tx_gas_used;
378        self.blob_gas_used += blob_gas_used;
379        self.receipts.push(self.receipt_builder.build_receipt::<T>(ReceiptBuilderCtx {
380            tx_type,
381            result: outcome,
382            cumulative_gas_used: self.cumulative_gas_used,
383        }));
384        Ok(GasOutput::new_with_regular(tx_gas_used, regular_gas_used, state_gas_used))
385    }
386
387    fn receipts(&self) -> &[Self::Receipt] {
388        &self.receipts
389    }
390
391    fn finish_with_block_access_list(
392        self,
393    ) -> Result<(BlockExecutionOutput<Self::Receipt>, Option<BlockAccessList>), BlockExecutionError>
394    {
395        let (output, bal) = self.finish_with_prepared_block_access_list()?;
396        Ok((output, bal.map(Into::into)))
397    }
398
399    fn finish_with_prepared_block_access_list(
400        mut self,
401    ) -> Result<(BlockExecutionOutput<Self::Receipt>, Option<Bal>), BlockExecutionError> {
402        self.set_transaction_block_access_index();
403        let context = Self::block_context(
404            self.deposit_contract_address,
405            self.ctx.parent_hash,
406            self.ctx.parent_beacon_block_root,
407            self.ctx.ommers,
408            None,
409        );
410        let mut requests = block_requests_from_receipts(self.spec_id, context, &self.receipts)?;
411        let stream_state = self.state_update_hook.is_some();
412        post_execution_system_call_state_changes(
413            &mut self.evm,
414            &mut self.block_state,
415            stream_state,
416            &mut |state| emit_state(&mut self.state_update_hook, state),
417            self.spec_id,
418            context,
419            &mut requests,
420        )?;
421
422        let withdrawals =
423            self.ctx.withdrawals.clone().filter(|_| self.spec_id.enables(evm2::SpecId::SHANGHAI));
424        let context = Self::block_context(
425            self.deposit_contract_address,
426            self.ctx.parent_hash,
427            self.ctx.parent_beacon_block_root,
428            self.ctx.ommers,
429            withdrawals.as_deref(),
430        );
431        let block_number = self.evm.block_env().number.to::<u64>();
432        let block_beneficiary = self.evm.block_env().beneficiary;
433        post_block_balance_state_changes(
434            &mut self.evm,
435            &mut self.block_state,
436            stream_state,
437            &mut |state| emit_state(&mut self.state_update_hook, state),
438            self.base_block_reward,
439            self.dao_fork_transition,
440            block_number,
441            block_beneficiary,
442            context.ommers,
443            context.withdrawals,
444        )?;
445
446        let block_access_list = self.evm.state_mut().take_bal_builder();
447        let block_gas_used = final_block_gas_used(
448            self.separate_block_gas,
449            self.cumulative_gas_used,
450            self.block_regular_gas_used,
451            self.block_state_gas_used,
452        );
453        let output = BlockExecutionOutput::new(
454            BlockExecutionResult {
455                receipts: self.receipts,
456                requests,
457                gas_used: block_gas_used,
458                blob_gas_used: self.blob_gas_used,
459            },
460            self.block_state.into_bundle(),
461        );
462
463        Ok((output, block_access_list))
464    }
465}
466
467/// One block execution segment inside a merged big-block payload.
468pub struct EthBigBlockSegment<'a, T = BaseEvmTypes>
469where
470    T: EvmTypes<Tx = TxEnvelope>,
471{
472    /// Transaction index at which this segment starts.
473    pub start_tx: usize,
474    /// EVM environment for this segment.
475    pub evm_env: EthEvmEnv<T>,
476    /// Ethereum execution context for this segment.
477    pub ctx: EthBlockExecutionCtx<'a>,
478}
479
480impl<'a, T> Clone for EthBigBlockSegment<'a, T>
481where
482    T: EvmTypes<Tx = TxEnvelope>,
483{
484    fn clone(&self) -> Self {
485        Self { start_tx: self.start_tx, evm_env: self.evm_env.clone(), ctx: self.ctx.clone() }
486    }
487}
488
489impl<'a, T> core::fmt::Debug for EthBigBlockSegment<'a, T>
490where
491    T: EvmTypes<Tx = TxEnvelope>,
492    T::SpecId: core::fmt::Debug,
493{
494    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
495        f.debug_struct("EthBigBlockSegment")
496            .field("start_tx", &self.start_tx)
497            .field("evm_env", &self.evm_env)
498            .field("ctx", &self.ctx)
499            .finish()
500    }
501}
502
503/// Execution plan for a merged big-block payload.
504pub struct EthBigBlockPlan<'a, T = BaseEvmTypes>
505where
506    T: EvmTypes<Tx = TxEnvelope>,
507{
508    /// Ordered execution segments.
509    pub segments: Vec<EthBigBlockSegment<'a, T>>,
510    /// Total number of transactions across all segments.
511    pub transaction_count: usize,
512    /// Block hashes that must be available to `BLOCKHASH` during execution.
513    pub block_hashes: Vec<(u64, B256)>,
514}
515
516impl<'a, T> Clone for EthBigBlockPlan<'a, T>
517where
518    T: EvmTypes<Tx = TxEnvelope>,
519{
520    fn clone(&self) -> Self {
521        Self {
522            segments: self.segments.clone(),
523            transaction_count: self.transaction_count,
524            block_hashes: self.block_hashes.clone(),
525        }
526    }
527}
528
529impl<'a, T> core::fmt::Debug for EthBigBlockPlan<'a, T>
530where
531    T: EvmTypes<Tx = TxEnvelope>,
532    T::SpecId: core::fmt::Debug,
533{
534    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
535        f.debug_struct("EthBigBlockPlan")
536            .field("segments", &self.segments)
537            .field("transaction_count", &self.transaction_count)
538            .field("block_hashes", &self.block_hashes)
539            .finish()
540    }
541}
542
543impl<'a, T> EthBigBlockPlan<'a, T>
544where
545    T: EvmTypes<Tx = TxEnvelope>,
546{
547    /// Creates a plan and adds hashes for the boundaries between segments.
548    pub fn new(
549        segments: Vec<EthBigBlockSegment<'a, T>>,
550        prior_block_hashes: Vec<(u64, B256)>,
551        transaction_count: usize,
552    ) -> Self {
553        let mut block_hashes = segments
554            .iter()
555            .skip(1)
556            .map(|segment| {
557                (
558                    segment.evm_env.block.number.to::<u64>().saturating_sub(1),
559                    segment.ctx.parent_hash,
560                )
561            })
562            .collect::<Vec<_>>();
563        block_hashes.extend(prior_block_hashes);
564        block_hashes.sort_unstable_by_key(|(number, _)| *number);
565        Self { segments, transaction_count, block_hashes }
566    }
567
568    fn hashes_for_block(&self, block_number: u64) -> impl Iterator<Item = (u64, B256)> + '_ {
569        let min = block_number.saturating_sub(256);
570        self.block_hashes
571            .iter()
572            .copied()
573            .filter(move |(number, _)| *number >= min && *number < block_number)
574    }
575
576    fn segment_index_for_tx(&self, tx_index: usize) -> usize {
577        self.segments.partition_point(|segment| segment.start_tx <= tx_index).saturating_sub(1)
578    }
579}
580
581struct FinishedBigBlockSegment {
582    requests: Requests,
583    gas_used: u64,
584    blob_gas_used: u64,
585}
586
587/// Block executor for merged payloads that switches the EVM context at block boundaries.
588#[expect(missing_debug_implementations)]
589pub struct EthBigBlockExecutor<'a, C, F = crate::factory::RethEvmFactory>
590where
591    F: EvmFactory,
592    F::Types: EvmTypes<Tx = TxEnvelope>,
593    <F::Types as evm2::EvmTypesHost>::Tx: Typed2718,
594    <F::Types as evm2::EvmTypesHost>::TxResultExt: Send + Sync,
595{
596    inner: EthBlockExecutor<'a, F::Types, &'a RethReceiptBuilder>,
597    factory: &'a EthBlockExecutorFactory<RethReceiptBuilder, C, F>,
598    chain_spec: Arc<C>,
599    plan: EthBigBlockPlan<'a, F::Types>,
600    next_segment: usize,
601    tx_counter: usize,
602    segment_receipt_start: usize,
603    worker_segment: Option<usize>,
604    initialized: bool,
605    requests: Requests,
606    gas_used_offset: u64,
607    receipt_gas_used_offset: u64,
608    blob_gas_used_offset: u64,
609}
610
611impl<'a, C, F> EthBigBlockExecutor<'a, C, F>
612where
613    C: EthereumHardforks,
614    F: EvmFactory,
615    F::Types: EvmTypes<Tx = TxEnvelope>,
616    <F::Types as evm2::EvmTypesHost>::Tx: Typed2718,
617    <F::Types as evm2::EvmTypesHost>::TxResultExt: Send + Sync,
618{
619    pub(crate) fn new(
620        inner: EthBlockExecutor<'a, F::Types, &'a RethReceiptBuilder>,
621        factory: &'a EthBlockExecutorFactory<RethReceiptBuilder, C, F>,
622        chain_spec: Arc<C>,
623        plan: EthBigBlockPlan<'a, F::Types>,
624    ) -> Self {
625        assert!(!plan.segments.is_empty(), "big-block execution requires at least one segment");
626        Self {
627            inner,
628            factory,
629            chain_spec,
630            plan,
631            next_segment: 1,
632            tx_counter: 0,
633            segment_receipt_start: 0,
634            worker_segment: None,
635            initialized: false,
636            requests: Requests::default(),
637            gas_used_offset: 0,
638            receipt_gas_used_offset: 0,
639            blob_gas_used_offset: 0,
640        }
641    }
642
643    fn configure_segment(&mut self, segment_idx: usize) {
644        let segment = &self.plan.segments[segment_idx];
645        let env = &segment.evm_env;
646        // The version carries more than the spec and features (blob cap, gas caps, code limits), so
647        // install the whole segment config.
648        self.factory.reconfigure_evm(&mut self.inner.evm, env);
649
650        let block_number = env.block.number.to::<u64>();
651        self.inner.spec_id = env.spec.into();
652        self.inner.base_block_reward = base_block_reward(self.chain_spec.as_ref(), block_number);
653        self.inner.dao_fork_transition = self
654            .chain_spec
655            .ethereum_fork_activation(EthereumHardfork::Dao)
656            .transitions_at_block(block_number);
657        self.inner.ctx = segment.ctx.clone();
658        self.inner.separate_block_gas = env.version.feature(evm2::EvmFeatures::EIP8037);
659        self.inner.bal_index_offset = segment_idx as u64 * 2;
660        self.inner.cumulative_gas_used = 0;
661        self.inner.block_regular_gas_used = 0;
662        self.inner.block_state_gas_used = 0;
663        self.inner.blob_gas_used = 0;
664    }
665
666    fn reseed_block_hashes(&mut self, block_number: u64) {
667        for (number, hash) in self.plan.hashes_for_block(block_number) {
668            let number = evm2::interpreter::Word::from(number);
669            self.inner.evm.overlay_db_mut().insert_block_hash(&number, &hash);
670        }
671    }
672
673    fn initialize(&mut self) -> Result<(), BlockExecutionError> {
674        if self.initialized {
675            return Ok(())
676        }
677
678        let raw_index = self.inner.evm.state().bal_index().get();
679        let segment_idx = if raw_index == 0 {
680            0
681        } else {
682            let tx_index = (raw_index - 1) as usize;
683            if tx_index >= self.plan.transaction_count {
684                return Err(BlockExecutionError::msg("BAL index is outside big-block transactions"))
685            }
686            let segment_idx = self.plan.segment_index_for_tx(tx_index);
687            self.worker_segment = Some(segment_idx);
688            self.inner
689                .evm
690                .state_mut()
691                .set_bal_index(BlockAccessIndex::new(raw_index + segment_idx as u64 * 2));
692            self.next_segment = self.plan.segments.len();
693            segment_idx
694        };
695
696        self.configure_segment(segment_idx);
697        self.reseed_block_hashes(self.plan.segments[segment_idx].evm_env.block.number.to::<u64>());
698        self.initialized = true;
699        Ok(())
700    }
701
702    fn finish_segment(&mut self) -> Result<FinishedBigBlockSegment, BlockExecutionError> {
703        self.inner.set_transaction_block_access_index();
704        let context = EthBlockExecutor::<F::Types>::block_context(
705            self.inner.deposit_contract_address,
706            self.inner.ctx.parent_hash,
707            self.inner.ctx.parent_beacon_block_root,
708            self.inner.ctx.ommers,
709            None,
710        );
711        let receipts = &self.inner.receipts[self.segment_receipt_start..];
712        let mut requests = block_requests_from_receipts(self.inner.spec_id, context, receipts)?;
713        let stream_state = self.inner.state_update_hook.is_some();
714        post_execution_system_call_state_changes(
715            &mut self.inner.evm,
716            &mut self.inner.block_state,
717            stream_state,
718            &mut |state| emit_state(&mut self.inner.state_update_hook, state),
719            self.inner.spec_id,
720            context,
721            &mut requests,
722        )?;
723
724        let withdrawals = self
725            .inner
726            .ctx
727            .withdrawals
728            .clone()
729            .filter(|_| self.inner.spec_id.enables(evm2::SpecId::SHANGHAI));
730        let context = EthBlockExecutor::<F::Types>::block_context(
731            self.inner.deposit_contract_address,
732            self.inner.ctx.parent_hash,
733            self.inner.ctx.parent_beacon_block_root,
734            self.inner.ctx.ommers,
735            withdrawals.as_deref(),
736        );
737        let block_number = self.inner.evm.block_env().number.to::<u64>();
738        let block_beneficiary = self.inner.evm.block_env().beneficiary;
739        post_block_balance_state_changes(
740            &mut self.inner.evm,
741            &mut self.inner.block_state,
742            stream_state,
743            &mut |state| emit_state(&mut self.inner.state_update_hook, state),
744            self.inner.base_block_reward,
745            self.inner.dao_fork_transition,
746            block_number,
747            block_beneficiary,
748            context.ommers,
749            context.withdrawals,
750        )?;
751
752        Ok(FinishedBigBlockSegment {
753            requests,
754            gas_used: final_block_gas_used(
755                self.inner.separate_block_gas,
756                self.inner.cumulative_gas_used,
757                self.inner.block_regular_gas_used,
758                self.inner.block_state_gas_used,
759            ),
760            blob_gas_used: self.inner.blob_gas_used,
761        })
762    }
763
764    fn merge_segment(&mut self, segment: FinishedBigBlockSegment) {
765        self.requests.extend(segment.requests);
766        self.gas_used_offset += segment.gas_used;
767        self.receipt_gas_used_offset += self.inner.cumulative_gas_used;
768        self.blob_gas_used_offset += segment.blob_gas_used;
769        self.inner.cumulative_gas_used = 0;
770        self.inner.block_regular_gas_used = 0;
771        self.inner.block_state_gas_used = 0;
772        self.inner.blob_gas_used = 0;
773    }
774
775    fn after_committed_transaction(&mut self) -> Result<(), BlockExecutionError> {
776        if let Some(receipt) = self.inner.receipts.last_mut() {
777            receipt.cumulative_gas_used += self.receipt_gas_used_offset;
778        }
779        self.tx_counter += 1;
780        while self.worker_segment.is_none() &&
781            self.next_segment < self.plan.segments.len() &&
782            self.tx_counter == self.plan.segments[self.next_segment].start_tx
783        {
784            self.apply_segment_boundary()?;
785        }
786        Ok(())
787    }
788
789    fn apply_segment_boundary(&mut self) -> Result<(), BlockExecutionError> {
790        let segment = self.finish_segment()?;
791        self.merge_segment(segment);
792        self.inner.evm.state_mut().bump_bal_index();
793
794        let segment_idx = self.next_segment;
795        self.next_segment += 1;
796        self.segment_receipt_start = self.inner.receipts.len();
797        self.configure_segment(segment_idx);
798        self.reseed_block_hashes(self.plan.segments[segment_idx].evm_env.block.number.to::<u64>());
799        self.inner.apply_pre_execution_changes()
800    }
801}
802
803impl<'a, C, F> BlockExecutor for EthBigBlockExecutor<'a, C, F>
804where
805    C: EthereumHardforks,
806    F: EvmFactory,
807    F::Types: EvmTypes<Tx = TxEnvelope>,
808    <F::Types as evm2::EvmTypesHost>::Tx: Typed2718,
809    <F::Types as evm2::EvmTypesHost>::TxResultExt: Send + Sync,
810{
811    type Transaction = TransactionSigned;
812    type Receipt = Receipt;
813    type Evm = Evm<'a, F::Types>;
814    type TransactionResultWithState = EthTransactionResultWithState<F::Types>;
815    type BlockAccessList = Bal;
816
817    fn evm(&self) -> &Self::Evm {
818        self.inner.evm()
819    }
820
821    fn evm_mut(&mut self) -> &mut Self::Evm {
822        self.inner.evm_mut()
823    }
824
825    fn set_state_hook(&mut self, hook: impl FnMut(StateUpdate) + Send + 'static) -> bool {
826        self.inner.set_state_hook(hook)
827    }
828
829    fn convert_block_access_list(
830        block_access_list: &BlockAccessList,
831    ) -> Result<Self::BlockAccessList, BlockExecutionError> {
832        EthBlockExecutor::<F::Types>::convert_block_access_list(block_access_list)
833    }
834
835    fn set_block_access_list(&mut self, block_access_list: Arc<Self::BlockAccessList>) {
836        self.inner.set_block_access_list(block_access_list);
837    }
838
839    fn set_block_access_index(&mut self, index: BlockAccessIndex) {
840        let index = if let Some(current_segment) = self.worker_segment {
841            let raw_index = index.get();
842            if let Some(tx_index) = raw_index.checked_sub(1).map(|index| index as usize) {
843                if tx_index < self.plan.transaction_count {
844                    let segment = self.plan.segment_index_for_tx(tx_index);
845                    if segment != current_segment {
846                        self.configure_segment(segment);
847                        self.reseed_block_hashes(
848                            self.plan.segments[segment].evm_env.block.number.to::<u64>(),
849                        );
850                    }
851                    self.worker_segment = Some(segment);
852                    BlockAccessIndex::new(raw_index + segment as u64 * 2)
853                } else {
854                    index
855                }
856            } else {
857                index
858            }
859        } else {
860            index
861        };
862        self.inner.set_block_access_index(index);
863    }
864
865    fn enable_block_access_list_builder(&mut self) {
866        self.inner.enable_block_access_list_builder();
867    }
868
869    fn take_block_access_list(&mut self) -> Option<BlockAccessList> {
870        self.inner.take_block_access_list()
871    }
872
873    fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
874        self.initialize()?;
875        self.inner.apply_pre_execution_changes()?;
876        while self.worker_segment.is_none() &&
877            self.next_segment < self.plan.segments.len() &&
878            self.tx_counter == self.plan.segments[self.next_segment].start_tx
879        {
880            self.apply_segment_boundary()?;
881        }
882        Ok(())
883    }
884
885    fn into_state(self) -> reth_evm::BundleState {
886        self.inner.into_state()
887    }
888
889    fn validate_transaction_gas_limit(
890        &mut self,
891        gas_limit: u64,
892    ) -> Result<(), BlockExecutionError> {
893        self.inner.validate_transaction_gas_limit(gas_limit)
894    }
895
896    fn execute_transaction_with_commit_condition(
897        &mut self,
898        transaction: impl ExecutorTx<Self>,
899        commit: impl FnOnce(
900            &evm2::TxResult<F::Types>,
901            evm2::TxStateChanges<'_>,
902        ) -> reth_evm::CommitChanges,
903    ) -> Result<Option<GasOutput>, BlockExecutionError> {
904        self.initialize()?;
905        let output = self.inner.execute_transaction_with_commit_condition(transaction, commit)?;
906        if output.is_some() {
907            self.after_committed_transaction()?;
908        }
909        Ok(output)
910    }
911
912    fn execute_transaction_without_commit(
913        &mut self,
914        transaction: impl ExecutorTx<Self>,
915    ) -> Result<Self::TransactionResultWithState, BlockExecutionError> {
916        self.initialize()?;
917        self.inner.execute_transaction_without_commit(transaction)
918    }
919
920    fn commit_transaction(
921        &mut self,
922        output: Self::TransactionResultWithState,
923    ) -> Result<GasOutput, BlockExecutionError> {
924        self.initialize()?;
925        let output = self.inner.commit_transaction(output)?;
926        self.after_committed_transaction()?;
927        Ok(output)
928    }
929
930    fn receipts(&self) -> &[Receipt] {
931        self.inner.receipts()
932    }
933
934    fn finish_with_block_access_list(
935        self,
936    ) -> Result<(BlockExecutionOutput<Receipt>, Option<BlockAccessList>), BlockExecutionError> {
937        let (output, bal) = self.finish_with_prepared_block_access_list()?;
938        Ok((output, bal.map(Into::into)))
939    }
940
941    fn finish_with_prepared_block_access_list(
942        mut self,
943    ) -> Result<(BlockExecutionOutput<Receipt>, Option<Bal>), BlockExecutionError> {
944        self.initialize()?;
945        let segment = self.finish_segment()?;
946        self.merge_segment(segment);
947        let block_access_list = self.inner.evm.state_mut().take_bal_builder();
948        let result = reth_execution_types::BlockExecutionResult {
949            receipts: core::mem::take(&mut self.inner.receipts),
950            requests: self.requests,
951            gas_used: self.gas_used_offset,
952            blob_gas_used: self.blob_gas_used_offset,
953        };
954        Ok((
955            BlockExecutionOutput::new(result, self.inner.block_state.into_bundle()),
956            block_access_list,
957        ))
958    }
959}
960
961const fn final_block_gas_used(
962    separate_block_gas: bool,
963    cumulative_gas_used: u64,
964    block_regular_gas_used: u64,
965    block_state_gas_used: u64,
966) -> u64 {
967    if separate_block_gas {
968        if block_regular_gas_used > block_state_gas_used {
969            block_regular_gas_used
970        } else {
971            block_state_gas_used
972        }
973    } else {
974        cumulative_gas_used
975    }
976}
977
978fn emit_state(hook: &mut StateUpdateHook, state: StateUpdate) {
979    if let Some(hook) = hook.as_mut() {
980        hook(state);
981    }
982}
983
984#[cfg(test)]
985mod tests {
986    use super::*;
987    use crate::dao_fork;
988    use alloy_consensus::{SignableTransaction, TxEip4844, TxLegacy};
989    use alloy_eip7928::BlockAccessIndex;
990    use alloy_primitives::{address, Address, Bytes, Signature, TxKind, B256, U256};
991    use evm2::{
992        bytecode::Bytecode,
993        env::BlockEnv,
994        evm::{AccountInfo, Database, Db},
995        interpreter::Word,
996        BaseEvmTypes, SpecId,
997    };
998    use reth_chainspec::{Chain, ChainSpecBuilder, EthereumHardfork, ForkCondition};
999    use reth_ethereum_primitives::TransactionSigned;
1000    use reth_evm::{BlockExecutor, BlockExecutorFactory, CommitChanges};
1001    use reth_primitives_traits::Recovered;
1002    use std::{collections::BTreeMap, convert::Infallible, sync::Arc};
1003
1004    fn segment(
1005        start_tx: usize,
1006        block_number: u64,
1007        parent_hash: B256,
1008    ) -> EthBigBlockSegment<'static> {
1009        let block =
1010            BlockEnv::<BaseEvmTypes> { number: U256::from(block_number), ..Default::default() };
1011        EthBigBlockSegment {
1012            start_tx,
1013            evm_env: EthEvmEnv { block, ..EthEvmEnv::default() },
1014            ctx: EthBlockExecutionCtx {
1015                tx_count_hint: Some(1),
1016                parent_hash,
1017                parent_beacon_block_root: None,
1018                ommers: &[],
1019                withdrawals: None,
1020                extra_data: Bytes::new(),
1021                slot_number: None,
1022            },
1023        }
1024    }
1025
1026    #[test]
1027    fn big_block_plan_tracks_segment_boundaries_and_hashes() {
1028        let plan = EthBigBlockPlan::new(
1029            vec![segment(0, 10, B256::with_last_byte(1)), segment(2, 11, B256::with_last_byte(2))],
1030            vec![(8, B256::with_last_byte(8)), (9, B256::with_last_byte(9))],
1031            3,
1032        );
1033
1034        assert_eq!(plan.transaction_count, 3);
1035        assert_eq!(plan.segment_index_for_tx(0), 0);
1036        assert_eq!(plan.segment_index_for_tx(1), 0);
1037        assert_eq!(plan.segment_index_for_tx(2), 1);
1038        assert_eq!(
1039            plan.block_hashes,
1040            vec![
1041                (8, B256::with_last_byte(8)),
1042                (9, B256::with_last_byte(9)),
1043                (10, B256::with_last_byte(2)),
1044            ]
1045        );
1046    }
1047
1048    #[derive(Default)]
1049    struct TestDatabase {
1050        accounts: BTreeMap<Address, AccountInfo>,
1051    }
1052
1053    impl Database for TestDatabase {
1054        type Error = Infallible;
1055
1056        fn get_account(&mut self, address: &Address) -> Result<Option<AccountInfo>, Self::Error> {
1057            Ok(self.accounts.get(address).cloned())
1058        }
1059
1060        fn get_code_by_hash(&mut self, _code_hash: &B256) -> Result<Bytecode, Self::Error> {
1061            Ok(Bytecode::default())
1062        }
1063
1064        fn get_storage(&mut self, _address: &Address, _key: &Word) -> Result<Word, Self::Error> {
1065            Ok(Word::ZERO)
1066        }
1067
1068        fn get_block_hash(&mut self, _number: &Word) -> Result<B256, Self::Error> {
1069            Ok(B256::ZERO)
1070        }
1071    }
1072
1073    fn transfer(from: Address, to: Address, nonce: u64) -> Recovered<TransactionSigned> {
1074        let tx = TransactionSigned::Legacy(
1075            TxLegacy {
1076                chain_id: Some(1),
1077                nonce,
1078                gas_limit: 21_000,
1079                gas_price: 1,
1080                to: TxKind::Call(to),
1081                value: U256::from(1),
1082                ..Default::default()
1083            }
1084            .into_signed(Signature::test_signature()),
1085        );
1086        Recovered::new_unchecked(tx, from)
1087    }
1088
1089    #[test]
1090    fn big_block_executor_preserves_state_across_segment_switch() {
1091        let from = address!("0000000000000000000000000000000000000001");
1092        let first_target = address!("0000000000000000000000000000000000000010");
1093        let second_target = address!("0000000000000000000000000000000000000020");
1094        let mut database = TestDatabase::default();
1095        database
1096            .accounts
1097            .insert(from, AccountInfo::default().with_balance(U256::from(1_000_000u64)));
1098
1099        let mut first = segment(0, 1, B256::with_last_byte(1));
1100        first.evm_env = EthEvmEnv::new(SpecId::LONDON, first.evm_env.block, 1);
1101        first.evm_env.block.basefee = U256::from(1);
1102        let mut second = segment(1, 2, B256::with_last_byte(2));
1103        second.evm_env = EthEvmEnv::new(SpecId::SHANGHAI, second.evm_env.block, 1);
1104        second.evm_env.block.basefee = U256::from(1);
1105        let plan = EthBigBlockPlan::new(vec![first, second], Vec::new(), 2);
1106        let chain_spec =
1107            ChainSpecBuilder::mainnet().chain(Chain::mainnet()).paris_activated().build();
1108        let factory = super::super::factory::EthBigBlockExecutorFactory::new(
1109            super::super::factory::EthBlockExecutorFactory::new(Arc::new(chain_spec)),
1110        );
1111        let mut env = plan.segments[0].evm_env.clone();
1112        env.version.features.remove(evm2::EvmFeatures::BASE_FEE_CHECK);
1113        let evm = factory.evm_with_env(Db::new(database), env);
1114        let mut executor = factory.create_executor(evm, plan);
1115
1116        executor.apply_pre_execution_changes().expect("first segment pre-execution");
1117        assert!(executor
1118            .execute_transaction_with_commit_condition(
1119                transfer(from, first_target, 0),
1120                |_, changes| {
1121                    // The callback sees the uncommitted state changes before deciding.
1122                    assert_eq!(changes.account_info(&from).expect("sender changed").nonce, 1);
1123                    CommitChanges::No
1124                }
1125            )
1126            .expect("discarded transaction")
1127            .is_none());
1128        executor
1129            .execute_transaction_with_commit_condition(transfer(from, first_target, 0), |_, _| {
1130                CommitChanges::Yes
1131            })
1132            .expect("first segment transaction")
1133            .expect("transaction committed");
1134        executor
1135            .execute_transaction_with_commit_condition(transfer(from, second_target, 1), |_, _| {
1136                CommitChanges::Yes
1137            })
1138            .expect("second segment transaction")
1139            .expect("transaction committed");
1140        let (output, _) = executor.finish_with_block_access_list().expect("finish big block");
1141
1142        assert_eq!(output.result.receipts.len(), 2);
1143        assert_eq!(output.result.gas_used, 42_000);
1144        assert_eq!(output.account(&first_target).unwrap().unwrap().balance, U256::from(1));
1145        assert_eq!(output.account(&second_target).unwrap().unwrap().balance, U256::from(1));
1146        // The prewarming environment must not change the beneficiary reward during execution.
1147        assert_eq!(
1148            output
1149                .account(&Address::ZERO)
1150                .flatten()
1151                .map(|account| account.balance)
1152                .unwrap_or_default(),
1153            U256::ZERO
1154        );
1155    }
1156
1157    #[test]
1158    fn amsterdam_admission_enforces_uncapped_state_gas_budget() {
1159        let factory = super::super::factory::EthBlockExecutorFactory::new(Arc::new(
1160            ChainSpecBuilder::mainnet().amsterdam_activated().build(),
1161        ));
1162        let ctx = segment(0, 1, B256::ZERO).ctx;
1163        for cap in [u64::MAX, 400_000] {
1164            let mut env = EthEvmEnv::new(
1165                SpecId::AMSTERDAM,
1166                BlockEnv::<BaseEvmTypes> { gas_limit: U256::from(1_000_000), ..Default::default() },
1167                1,
1168            );
1169            env.version.tx_gas_limit_cap = cap;
1170            let evm = factory.evm_with_env(Db::new(TestDatabase::default()), env);
1171            let mut executor = factory.create_executor(evm, ctx.clone());
1172            executor.block_state_gas_used = 600_000;
1173            executor.block_regular_gas_used = 0;
1174            assert!(executor.validate_transaction_gas_limit(500_000).is_err());
1175            assert!(executor.validate_transaction_gas_limit(400_000).is_ok());
1176        }
1177    }
1178
1179    #[test]
1180    fn pre_london_creation_preserves_legacy_ef01_code() {
1181        let sender = Address::with_last_byte(1);
1182        let mut database = TestDatabase::default();
1183        database
1184            .accounts
1185            .insert(sender, AccountInfo::default().with_balance(U256::from(1_000_000)));
1186        let factory = super::super::factory::EthBlockExecutorFactory::new(Arc::new(
1187            ChainSpecBuilder::mainnet().berlin_activated().build(),
1188        ));
1189        let env = EthEvmEnv::new(
1190            SpecId::BERLIN,
1191            BlockEnv::<BaseEvmTypes> { gas_limit: U256::from(1_000_000), ..Default::default() },
1192            1,
1193        );
1194        let evm = factory.evm_with_env(Db::new(database), env);
1195        let mut executor = factory.create_executor(evm, segment(0, 1, B256::ZERO).ctx);
1196        let tx = TransactionSigned::Legacy(
1197            TxLegacy {
1198                chain_id: Some(1),
1199                gas_limit: 100_000,
1200                gas_price: 1,
1201                to: TxKind::Create,
1202                // Return the two-byte runtime code 0xEF01 from initcode.
1203                input: alloy_primitives::bytes!("61ef016000526002601ef3"),
1204                ..Default::default()
1205            }
1206            .into_signed(Signature::test_signature()),
1207        );
1208        executor.apply_pre_execution_changes().unwrap();
1209        executor.execute_transaction(Recovered::new_unchecked(tx, sender)).unwrap();
1210        let (output, _) = executor.finish_with_block_access_list().unwrap();
1211        let code = &output.state.contracts[&alloy_primitives::keccak256([0xef, 0x01])];
1212        assert!(code.is_legacy());
1213        assert_eq!(code.original_bytes().as_ref(), &[0xef, 0x01]);
1214    }
1215
1216    #[test]
1217    fn executor_applies_dao_fork_balance_transfer_before_rewards() {
1218        let dao_account = address!("d4fe7bc31cedb7bfb8a345f31e668033056b2728");
1219        let beneficiary = address!("bf4ed7b27f1d666546e30d74d50d173d20bca754");
1220        let mut database = TestDatabase::default();
1221        database.accounts.insert(dao_account, AccountInfo::default().with_balance(U256::from(7)));
1222
1223        let chain_spec = ChainSpecBuilder::mainnet()
1224            .with_fork(EthereumHardfork::Dao, ForkCondition::Block(1))
1225            .build();
1226        let factory = super::super::factory::EthBlockExecutorFactory::new(Arc::new(chain_spec));
1227        let env = EthEvmEnv::new(
1228            SpecId::FRONTIER,
1229            BlockEnv::<BaseEvmTypes> {
1230                number: U256::from(1),
1231                gas_limit: U256::from(30_000),
1232                beneficiary: dao_account,
1233                ..Default::default()
1234            },
1235            1,
1236        );
1237        let evm = factory.evm_with_env(Db::new(database), env);
1238        let mut executor = factory.create_executor(
1239            evm,
1240            EthBlockExecutionCtx {
1241                tx_count_hint: Some(0),
1242                parent_hash: B256::ZERO,
1243                parent_beacon_block_root: None,
1244                ommers: &[],
1245                withdrawals: None,
1246                extra_data: Bytes::new(),
1247                slot_number: None,
1248            },
1249        );
1250
1251        executor.apply_pre_execution_changes().expect("DAO block pre-execution succeeds");
1252        let (output, _) =
1253            executor.finish_with_block_access_list().expect("DAO block post-execution succeeds");
1254
1255        assert_eq!(
1256            output.account(&dao_account).unwrap().unwrap().balance,
1257            U256::from(5 * alloy_consensus::constants::ETH_TO_WEI)
1258        );
1259        assert_eq!(output.account(&beneficiary).unwrap().unwrap().balance, U256::from(7));
1260    }
1261
1262    #[test]
1263    fn dao_fork_drain_removes_empty_accounts() {
1264        let [absent, empty, empty_with_balance, nonce_only, ..] = dao_fork::DAO_HARDFORK_ACCOUNTS;
1265        let beneficiary = dao_fork::DAO_HARDFORK_BENEFICIARY;
1266
1267        let execute = |accounts: &[(Address, AccountInfo)]| {
1268            let mut database = TestDatabase::default();
1269            database.accounts.extend(accounts.iter().cloned());
1270            let chain_spec = ChainSpecBuilder::mainnet()
1271                .with_fork(EthereumHardfork::Dao, ForkCondition::Block(1))
1272                .build();
1273            let factory = super::super::factory::EthBlockExecutorFactory::new(Arc::new(chain_spec));
1274            let env = EthEvmEnv::new(
1275                SpecId::FRONTIER,
1276                BlockEnv::<BaseEvmTypes> {
1277                    number: U256::from(1),
1278                    gas_limit: U256::from(30_000),
1279                    beneficiary: Address::with_last_byte(0xbe),
1280                    ..Default::default()
1281                },
1282                1,
1283            );
1284            let evm = factory.evm_with_env(Db::new(database), env);
1285            let mut executor = factory.create_executor(evm, segment(0, 1, B256::ZERO).ctx);
1286            executor.apply_pre_execution_changes().unwrap();
1287            executor.finish_with_block_access_list().unwrap().0
1288        };
1289
1290        // Absent accounts stay absent, and accounts that are empty after the drain are removed,
1291        // even before EIP-161.
1292        let output = execute(&[
1293            (empty, AccountInfo::default()),
1294            (empty_with_balance, AccountInfo::default().with_balance(U256::from(7))),
1295            (nonce_only, AccountInfo { nonce: 1, balance: U256::from(3), ..Default::default() }),
1296        ]);
1297        assert_eq!(output.account(&absent), None);
1298        assert_eq!(output.account(&empty), Some(None));
1299        assert_eq!(output.account(&empty_with_balance), Some(None));
1300        let nonce_only_account = output.account(&nonce_only).unwrap().unwrap();
1301        assert_eq!((nonce_only_account.nonce, nonce_only_account.balance), (1, U256::ZERO));
1302        assert_eq!(output.account(&beneficiary).unwrap().unwrap().balance, U256::from(10));
1303
1304        // Nothing is drained, but the zero increment is still applied to the beneficiary, which
1305        // removes it because it is empty.
1306        let output = execute(&[(beneficiary, AccountInfo::default())]);
1307        assert_eq!(output.account(&beneficiary), Some(None));
1308    }
1309
1310    #[test]
1311    fn big_block_worker_switches_segments_from_bal_index() {
1312        let from = address!("0000000000000000000000000000000000000001");
1313        let target = address!("0000000000000000000000000000000000000010");
1314        let mut database = TestDatabase::default();
1315        database
1316            .accounts
1317            .insert(from, AccountInfo::default().with_balance(U256::from(1_000_000u64)));
1318
1319        let mut first = segment(0, 1, B256::with_last_byte(1));
1320        first.evm_env = EthEvmEnv::new(SpecId::FRONTIER, first.evm_env.block, 1);
1321        let mut second = segment(1, 2, B256::with_last_byte(2));
1322        second.evm_env = EthEvmEnv::new(SpecId::LONDON, second.evm_env.block, 1);
1323        let plan = EthBigBlockPlan::new(vec![first, second], Vec::new(), 2);
1324        let chain_spec =
1325            ChainSpecBuilder::mainnet().chain(Chain::mainnet()).paris_activated().build();
1326        let factory = super::super::factory::EthBigBlockExecutorFactory::new(
1327            super::super::factory::EthBlockExecutorFactory::new(Arc::new(chain_spec)),
1328        );
1329        let evm = factory.evm_with_env(Db::new(database), plan.segments[0].evm_env.clone());
1330        let mut executor = factory.create_executor(evm, plan);
1331
1332        executor.set_block_access_index(BlockAccessIndex::new(2));
1333        executor
1334            .execute_transaction_without_commit(transfer(from, target, 0))
1335            .expect("worker transaction in second segment");
1336        assert_eq!(executor.evm().block().number, U256::from(2));
1337
1338        executor.set_block_access_index(BlockAccessIndex::new(1));
1339        executor
1340            .execute_transaction_without_commit(transfer(from, target, 0))
1341            .expect("worker transaction in first segment");
1342        assert_eq!(executor.evm().block().number, U256::from(1));
1343    }
1344
1345    #[test]
1346    fn amsterdam_header_uses_bottleneck_gas_dimension() {
1347        assert_eq!(final_block_gas_used(true, 90, 70, 120), 120);
1348        assert_eq!(final_block_gas_used(true, 90, 130, 120), 130);
1349        assert_eq!(final_block_gas_used(false, 90, 130, 120), 90);
1350    }
1351
1352    #[test]
1353    fn amsterdam_segment_receipts_use_transaction_gas() {
1354        let sender = Address::with_last_byte(0x10);
1355        let mut database = TestDatabase::default();
1356        database.accounts.insert(
1357            sender,
1358            AccountInfo::default().with_balance(U256::from(1_000_000_000_000_000_000u64)),
1359        );
1360        let segments = (0..2)
1361            .map(|index| {
1362                let mut segment = segment(index, index as u64 + 1, B256::ZERO);
1363                segment.ctx.parent_beacon_block_root = Some(B256::ZERO);
1364                segment.evm_env = EthEvmEnv::new(
1365                    SpecId::AMSTERDAM,
1366                    BlockEnv::<BaseEvmTypes> {
1367                        number: U256::from(index + 1),
1368                        gas_limit: U256::from(30_000_000),
1369                        ..Default::default()
1370                    },
1371                    1,
1372                );
1373                segment
1374            })
1375            .collect();
1376        let plan = EthBigBlockPlan::new(segments, Vec::new(), 2);
1377        let factory = super::super::factory::EthBigBlockExecutorFactory::new(
1378            super::super::factory::EthBlockExecutorFactory::new(Arc::new(
1379                ChainSpecBuilder::mainnet().amsterdam_activated().build(),
1380            )),
1381        );
1382        let evm = factory.evm_with_env(Db::new(database), plan.segments[0].evm_env.clone());
1383        let mut executor = factory.create_executor(evm, plan);
1384        executor.apply_pre_execution_changes().unwrap();
1385        let mut receipt_gas = 0;
1386        let mut block_gas = 0;
1387        for nonce in 0..2 {
1388            // Creating a recipient charges state gas, separating receipt and block gas totals.
1389            let tx = TransactionSigned::Legacy(
1390                TxLegacy {
1391                    chain_id: Some(1),
1392                    nonce,
1393                    gas_limit: 1_000_000,
1394                    gas_price: 1,
1395                    to: TxKind::Call(Address::with_last_byte(0x20 + nonce as u8)),
1396                    value: U256::from(1),
1397                    ..Default::default()
1398                }
1399                .into_signed(Signature::test_signature()),
1400            );
1401            let gas = executor.execute_transaction(Recovered::new_unchecked(tx, sender)).unwrap();
1402            let segment_block_gas = gas.regular_gas_used().max(gas.state_gas_used());
1403            assert_ne!(gas.tx_gas_used(), segment_block_gas);
1404            receipt_gas += gas.tx_gas_used();
1405            block_gas += segment_block_gas;
1406            let receipt = executor.receipts().last().unwrap();
1407            assert!(receipt.success);
1408            assert_eq!(receipt.cumulative_gas_used, receipt_gas);
1409        }
1410        let (output, _) = executor.finish_with_block_access_list().unwrap();
1411        assert_eq!(output.result.gas_used, block_gas);
1412    }
1413
1414    #[test]
1415    fn big_block_segment_switch_installs_segment_blob_cap() {
1416        let sender = Address::with_last_byte(0x10);
1417        let mut database = TestDatabase::default();
1418        database.accounts.insert(
1419            sender,
1420            AccountInfo::default().with_balance(U256::from(1_000_000_000_000_000_000u64)),
1421        );
1422        let segments = [9, 6]
1423            .into_iter()
1424            .enumerate()
1425            .map(|(index, max_blobs_per_tx)| {
1426                let mut segment = segment(index, index as u64 + 1, B256::ZERO);
1427                segment.ctx.parent_beacon_block_root = Some(B256::ZERO);
1428                segment.evm_env = EthEvmEnv::new(
1429                    SpecId::PRAGUE,
1430                    BlockEnv::<BaseEvmTypes> {
1431                        number: U256::from(index + 1),
1432                        gas_limit: U256::from(30_000_000),
1433                        ..Default::default()
1434                    },
1435                    1,
1436                );
1437                segment.evm_env.version.max_blobs_per_tx = max_blobs_per_tx;
1438                segment
1439            })
1440            .collect();
1441        let plan = EthBigBlockPlan::new(segments, Vec::new(), 2);
1442        let factory = super::super::factory::EthBigBlockExecutorFactory::new(
1443            super::super::factory::EthBlockExecutorFactory::new(Arc::new(
1444                ChainSpecBuilder::mainnet().prague_activated().build(),
1445            )),
1446        );
1447        let evm = factory.evm_with_env(Db::new(database), plan.segments[0].evm_env.clone());
1448        let mut executor = factory.create_executor(evm, plan);
1449        executor.apply_pre_execution_changes().unwrap();
1450
1451        let blob_tx = |nonce| {
1452            let tx = TransactionSigned::Eip4844(
1453                TxEip4844 {
1454                    chain_id: 1,
1455                    nonce,
1456                    gas_limit: 100_000,
1457                    max_fee_per_gas: 1,
1458                    max_priority_fee_per_gas: 1,
1459                    to: Address::with_last_byte(0x20),
1460                    max_fee_per_blob_gas: 1,
1461                    blob_versioned_hashes: vec![B256::repeat_byte(1); 7],
1462                    ..Default::default()
1463                }
1464                .into_signed(Signature::test_signature()),
1465            );
1466            Recovered::new_unchecked(tx, sender)
1467        };
1468        executor.execute_transaction(blob_tx(0)).expect("seven blobs fit the first segment cap");
1469        let err = executor.execute_transaction(blob_tx(1)).unwrap_err();
1470        assert!(err.to_string().contains("too many blobs: have 7, max 6"), "{err}");
1471    }
1472}