Skip to main content

reth_evm_ethereum/
execution.rs

1//! EVM-backed Ethereum execution helpers.
2
3use crate::dao_fork;
4use alloc::{boxed::Box, string::ToString, vec::Vec};
5use alloy_consensus::{
6    constants::ETH_TO_WEI, transaction::Recovered, BlockHeader, Header, TxReceipt,
7};
8use alloy_eips::{
9    eip2718::Typed2718,
10    eip4895::Withdrawal,
11    eip6110::{DEPOSIT_REQUEST_TYPE, MAINNET_DEPOSIT_CONTRACT_ADDRESS},
12    eip7002::WITHDRAWAL_REQUEST_TYPE,
13    eip7251::CONSOLIDATION_REQUEST_TYPE,
14    eip7685::Requests,
15};
16use alloy_primitives::{map::AddressMap, Address, Bytes, Log, B256, U256};
17use alloy_sol_types::{sol, SolEvent};
18use core::any::Any;
19use evm2::{
20    evm::{
21        AccountChangeRef, AccountInfo, StateChangeSink, SystemTx, BEACON_ROOTS_ADDRESS,
22        BUILDER_DEPOSIT_REQUEST_ADDRESS, BUILDER_EXIT_REQUEST_ADDRESS,
23        CONSOLIDATION_REQUEST_ADDRESS, HISTORY_STORAGE_ADDRESS, WITHDRAWAL_REQUEST_ADDRESS,
24    },
25    interpreter::InstrStop,
26    registry::HandlerError,
27    Evm, EvmTypes, SpecId, TxResult, TxResultWithState,
28};
29use reth_ethereum_forks::EthereumHardforks;
30use reth_evm::{
31    execute::{map_database_error, map_handler_error},
32    BlockExecutionError, BlockValidationError, InternalBlockExecutionError, InvalidTxError,
33};
34use reth_execution_types::{BlockState, StateUpdate};
35
36const DEPOSIT_BYTES_SIZE: usize = 48 + 32 + 8 + 96 + 8;
37const BUILDER_DEPOSIT_REQUEST_TYPE: u8 = 0x03;
38const BUILDER_EXIT_REQUEST_TYPE: u8 = 0x04;
39
40sol! {
41    #[allow(missing_docs)]
42    event DepositEvent(
43        bytes pubkey,
44        bytes withdrawal_credentials,
45        bytes amount,
46        bytes signature,
47        bytes index
48    );
49}
50
51/// Ethereum transaction validation error returned by evm2 handlers.
52#[derive(Debug, Clone, PartialEq, Eq)]
53struct EthInvalidTxError(HandlerError);
54
55impl core::fmt::Display for EthInvalidTxError {
56    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
57        self.0.fmt(f)
58    }
59}
60
61impl core::error::Error for EthInvalidTxError {}
62
63impl InvalidTxError for EthInvalidTxError {
64    fn is_nonce_too_low(&self) -> bool {
65        matches!(self.0, HandlerError::InvalidNonce { expected, got } if got < expected)
66    }
67
68    fn is_gas_limit_too_high(&self) -> bool {
69        matches!(
70            self.0,
71            HandlerError::GasLimitMoreThanBlock { .. } |
72                HandlerError::TxGasLimitGreaterThanCap { .. }
73        )
74    }
75
76    fn is_gas_limit_too_low(&self) -> bool {
77        matches!(self.0, HandlerError::IntrinsicGasTooLow { .. })
78    }
79
80    /// Exposes the wrapped [`HandlerError`] so RPC layers can recover the structured error.
81    fn as_any(&self) -> &(dyn Any + 'static) {
82        &self.0
83    }
84}
85
86impl TryFrom<HandlerError> for EthInvalidTxError {
87    type Error = HandlerError;
88
89    fn try_from(error: HandlerError) -> Result<Self, Self::Error> {
90        match error {
91            HandlerError::Database(_) |
92            HandlerError::Fatal(_) |
93            HandlerError::WrongTransactionType { .. } => Err(error),
94            HandlerError::External(_) |
95            HandlerError::UnsupportedTransactionType(_) |
96            HandlerError::InvalidNonce { .. } |
97            HandlerError::InvalidChainId { .. } |
98            HandlerError::MissingChainId |
99            HandlerError::IntrinsicGasTooLow { .. } |
100            HandlerError::InsufficientFunds { .. } |
101            HandlerError::RejectCallerWithCode |
102            HandlerError::NonceOverflow |
103            HandlerError::GasLimitMoreThanBlock { .. } |
104            HandlerError::TxGasLimitGreaterThanCap { .. } |
105            HandlerError::CreateInitCodeSizeLimit { .. } |
106            HandlerError::FeeCapLessThanBaseFee { .. } |
107            HandlerError::EmptyAuthorizationList |
108            HandlerError::BlobFeeCapLessThanBlobBaseFee { .. } |
109            HandlerError::EmptyBlobs |
110            HandlerError::TooManyBlobs { .. } |
111            HandlerError::BlobVersionNotSupported |
112            HandlerError::PriorityFeeGreaterThanMaxFee => Ok(Self(error)),
113        }
114    }
115}
116
117/// Additional block-level execution context.
118#[derive(Debug, Clone, Copy, Default)]
119pub(crate) struct BlockExecutionContext<'a> {
120    /// Pre-block system calls to run before transaction execution.
121    pub system_calls: Option<BlockSystemCalls>,
122    /// Pre-merge ommer headers included in the block.
123    pub ommers: Option<&'a [Header]>,
124    /// Post-block withdrawals to apply after transaction execution.
125    pub withdrawals: Option<&'a [Withdrawal]>,
126    /// Deposit contract address used to derive EIP-6110 deposit requests from receipts.
127    pub deposit_contract_address: Option<Address>,
128}
129
130/// Inputs required by Ethereum pre-block system calls.
131#[derive(Debug, Clone, Copy)]
132pub(crate) struct BlockSystemCalls {
133    /// Parent block hash for EIP-2935 history storage.
134    pub parent_hash: B256,
135    /// Parent beacon block root for EIP-4788 beacon roots.
136    pub parent_beacon_block_root: Option<B256>,
137}
138
139pub(crate) fn map_transaction_error(error: HandlerError, hash: B256) -> BlockExecutionError {
140    let error = match EthInvalidTxError::try_from(error) {
141        Ok(error) => {
142            return BlockValidationError::InvalidTx { hash, error: Box::new(error) }.into();
143        }
144        Err(error) => error,
145    };
146    if let HandlerError::Database(database_error) = &error &&
147        !database_error.is_fatal()
148    {
149        BlockValidationError::EVM { hash, error: Box::new(error) }.into()
150    } else {
151        InternalBlockExecutionError::EVM { hash, error: Box::new(error) }.into()
152    }
153}
154
155fn send_state_update(state: StateUpdate, on_state_update: &mut impl FnMut(StateUpdate)) {
156    if !state.is_empty() {
157        on_state_update(state);
158    }
159}
160
161pub(crate) fn execute_transaction_with_condition<T: EvmTypes>(
162    evm: &mut Evm<'_, T>,
163    block_state: &mut BlockState,
164    stream_state: bool,
165    on_state_update: &mut impl FnMut(StateUpdate),
166    transaction: &Recovered<T::Tx>,
167    commit: impl FnOnce(&TxResult<T>, evm2::TxStateChanges<'_>) -> reth_evm::CommitChanges,
168) -> Result<Option<TxResult<T>>, HandlerError>
169where
170    T::Tx: Typed2718,
171{
172    let mut update = StateUpdate::default();
173    let result = match evm.transact(transaction) {
174        Ok(executed) => {
175            if commit(executed.result(), executed.changes()).should_commit() {
176                for changes in executed.account_changes() {
177                    block_state
178                        .commit_account_changes(changes, stream_state.then_some(&mut update));
179                }
180                Ok(Some(executed.commit()))
181            } else {
182                let _ = executed.discard();
183                Ok(None)
184            }
185        }
186        Err(error) => Err(error),
187    };
188    if stream_state {
189        send_state_update(update, on_state_update);
190    }
191    result
192}
193
194pub(crate) fn execute_transaction_without_commit<T: EvmTypes>(
195    evm: &mut Evm<'_, T>,
196    transaction: &Recovered<T::Tx>,
197) -> Result<TxResultWithState<T>, HandlerError>
198where
199    T::Tx: Typed2718,
200{
201    evm.transact(transaction).map(|executed| executed.detach())
202}
203
204pub(crate) fn commit_detached_transaction<T: EvmTypes>(
205    evm: &mut Evm<'_, T>,
206    block_state: &mut BlockState,
207    stream_state: bool,
208    on_state_update: &mut impl FnMut(StateUpdate),
209    output: TxResultWithState<T>,
210) -> TxResult<T> {
211    let TxResultWithState { result, pending_state, .. } = output;
212    accumulate_pending_state(block_state, stream_state, on_state_update, &pending_state);
213    // Reattach the finalized transaction so evm2 retains its account capacity and recycles
214    // storage maps for the next transaction instead of dropping the detached allocations.
215    evm.state_mut().set_pending_state(pending_state);
216    evm.state_mut().commit_transaction();
217    result
218}
219
220pub(crate) fn commit_pending_state<T: EvmTypes>(
221    evm: &mut Evm<'_, T>,
222    block_state: &mut BlockState,
223    stream_state: bool,
224    on_state_update: &mut impl FnMut(StateUpdate),
225    pending_state: &evm2::evm::PendingState,
226) {
227    accumulate_pending_state(block_state, stream_state, on_state_update, pending_state);
228    evm.overlay_db_mut().commit_pending(pending_state);
229}
230
231fn accumulate_pending_state(
232    block_state: &mut BlockState,
233    stream_state: bool,
234    on_state_update: &mut impl FnMut(StateUpdate),
235    pending_state: &evm2::evm::PendingState,
236) {
237    let mut update = StateUpdate::default();
238    for changes in pending_state.account_changes() {
239        block_state.commit_account_changes(changes, stream_state.then_some(&mut update));
240    }
241    if stream_state {
242        send_state_update(update, on_state_update);
243    }
244}
245
246pub(crate) fn pre_execution_system_call_state_changes<T: EvmTypes>(
247    evm: &mut Evm<'_, T>,
248    block_state: &mut BlockState,
249    stream_state: bool,
250    on_state_update: &mut impl FnMut(StateUpdate),
251    spec_id: SpecId,
252    block_number: u64,
253    context: BlockExecutionContext<'_>,
254) -> Result<(), BlockExecutionError> {
255    let Some(system_calls) = context.system_calls else {
256        return Ok(());
257    };
258
259    if spec_id.enables(SpecId::PRAGUE) && block_number != 0 {
260        let _ = execute_system_call(
261            evm,
262            block_state,
263            stream_state,
264            on_state_update,
265            HISTORY_STORAGE_ADDRESS,
266            system_calls.parent_hash.0.into(),
267        )?;
268    }
269
270    if spec_id.enables(SpecId::CANCUN) {
271        let parent_beacon_block_root = system_calls
272            .parent_beacon_block_root
273            .ok_or(BlockValidationError::MissingParentBeaconBlockRoot)?;
274
275        if block_number == 0 {
276            if parent_beacon_block_root != B256::ZERO {
277                return Err(BlockValidationError::CancunGenesisParentBeaconBlockRootNotZero {
278                    parent_beacon_block_root,
279                }
280                .into());
281            }
282        } else {
283            let _ = execute_system_call(
284                evm,
285                block_state,
286                stream_state,
287                on_state_update,
288                BEACON_ROOTS_ADDRESS,
289                parent_beacon_block_root.0.into(),
290            )?;
291        }
292    }
293
294    Ok(())
295}
296
297pub(crate) fn block_requests_from_receipts<R>(
298    spec_id: SpecId,
299    context: BlockExecutionContext<'_>,
300    receipts: &[R],
301) -> Result<Requests, BlockExecutionError>
302where
303    R: TxReceipt<Log = Log>,
304{
305    let mut requests = Requests::default();
306    if context.system_calls.is_none() || !spec_id.enables(SpecId::PRAGUE) {
307        return Ok(requests)
308    }
309
310    let deposit_requests = parse_deposit_requests_from_receipts(
311        context.deposit_contract_address.unwrap_or(MAINNET_DEPOSIT_CONTRACT_ADDRESS),
312        receipts,
313    )?;
314    requests.push_request_with_type(DEPOSIT_REQUEST_TYPE, deposit_requests);
315
316    Ok(requests)
317}
318
319// TODO: Replace the local deposit event and encoding with alloy-eips helpers once
320// https://github.com/alloy-rs/alloy/pull/4244 is released.
321fn parse_deposit_requests_from_receipts<R>(
322    deposit_contract_address: Address,
323    receipts: &[R],
324) -> Result<Vec<u8>, BlockExecutionError>
325where
326    R: TxReceipt<Log = Log>,
327{
328    let mut out = Vec::new();
329    for receipt in receipts {
330        for log in receipt.logs() {
331            if log.address != deposit_contract_address ||
332                log.topics().first() != Some(&DepositEvent::SIGNATURE_HASH)
333            {
334                continue
335            }
336
337            let decoded = DepositEvent::decode_log(log)
338                .map_err(|err| BlockValidationError::DepositRequestDecode(err.to_string()))?;
339            out.reserve(DEPOSIT_BYTES_SIZE);
340            out.extend_from_slice(decoded.pubkey.as_ref());
341            out.extend_from_slice(decoded.withdrawal_credentials.as_ref());
342            out.extend_from_slice(decoded.amount.as_ref());
343            out.extend_from_slice(decoded.signature.as_ref());
344            out.extend_from_slice(decoded.index.as_ref());
345        }
346    }
347
348    Ok(out)
349}
350
351pub(crate) fn post_execution_system_call_state_changes<T: EvmTypes>(
352    evm: &mut Evm<'_, T>,
353    block_state: &mut BlockState,
354    stream_state: bool,
355    on_state_update: &mut impl FnMut(StateUpdate),
356    spec_id: SpecId,
357    context: BlockExecutionContext<'_>,
358    requests: &mut Requests,
359) -> Result<(), BlockExecutionError> {
360    if context.system_calls.is_none() || !spec_id.enables(SpecId::PRAGUE) {
361        return Ok(());
362    }
363
364    let withdrawal_requests = execute_system_call(
365        evm,
366        block_state,
367        stream_state,
368        on_state_update,
369        WITHDRAWAL_REQUEST_ADDRESS,
370        Bytes::new(),
371    )?;
372    requests.push_request_with_type(
373        WITHDRAWAL_REQUEST_TYPE,
374        withdrawal_requests.output.iter().copied(),
375    );
376
377    let consolidation_requests = execute_system_call(
378        evm,
379        block_state,
380        stream_state,
381        on_state_update,
382        CONSOLIDATION_REQUEST_ADDRESS,
383        Bytes::new(),
384    )?;
385    requests.push_request_with_type(
386        CONSOLIDATION_REQUEST_TYPE,
387        consolidation_requests.output.iter().copied(),
388    );
389
390    if spec_id.enables(SpecId::AMSTERDAM) {
391        let builder_deposit_requests = execute_system_call(
392            evm,
393            block_state,
394            stream_state,
395            on_state_update,
396            BUILDER_DEPOSIT_REQUEST_ADDRESS,
397            Bytes::new(),
398        )?;
399        requests.push_request_with_type(
400            BUILDER_DEPOSIT_REQUEST_TYPE,
401            builder_deposit_requests.output.iter().copied(),
402        );
403
404        let builder_exit_requests = execute_system_call(
405            evm,
406            block_state,
407            stream_state,
408            on_state_update,
409            BUILDER_EXIT_REQUEST_ADDRESS,
410            Bytes::new(),
411        )?;
412        requests.push_request_with_type(
413            BUILDER_EXIT_REQUEST_TYPE,
414            builder_exit_requests.output.iter().copied(),
415        );
416    }
417
418    Ok(())
419}
420
421fn execute_system_call<T: EvmTypes>(
422    evm: &mut Evm<'_, T>,
423    block_state: &mut BlockState,
424    stream_state: bool,
425    on_state_update: &mut impl FnMut(StateUpdate),
426    address: Address,
427    data: Bytes,
428) -> Result<TxResult<T>, BlockExecutionError> {
429    let executed = evm.system_call(SystemTx::new(address, data)).map_err(map_handler_error)?;
430    // History and beacon-root calls accept reverts and halts, unlike request-producing calls.
431    if !executed.result().status &&
432        !matches!(address, BEACON_ROOTS_ADDRESS | HISTORY_STORAGE_ADDRESS)
433    {
434        let reason = executed.result().stop;
435        let _ = executed.discard();
436        return Err(match address {
437            WITHDRAWAL_REQUEST_ADDRESS => BlockValidationError::WithdrawalRequestsContractCall {
438                message: alloc::format!("{reason:?}"),
439            },
440            CONSOLIDATION_REQUEST_ADDRESS => {
441                BlockValidationError::ConsolidationRequestsContractCall {
442                    message: alloc::format!("{reason:?}"),
443                }
444            }
445            _ => BlockValidationError::Other(Box::new(SystemCallFailed { address, reason })),
446        }
447        .into());
448    }
449    let outcome = executed.detach();
450    Ok(commit_detached_transaction(evm, block_state, stream_state, on_state_update, outcome))
451}
452
453fn commit_state_changes<T: EvmTypes>(
454    evm: &mut Evm<'_, T>,
455    block_state: &mut BlockState,
456    stream_state: bool,
457    on_state_update: &mut impl FnMut(StateUpdate),
458    changes: &[(Address, Option<AccountInfo>, Option<AccountInfo>)],
459) {
460    let mut pending_state = evm2::evm::PendingState::default();
461    for (address, original, current) in changes {
462        let change = AccountChangeRef {
463            address: *address,
464            original: original.as_ref(),
465            current: current.as_ref(),
466            created: false,
467            selfdestructed: false,
468        };
469        let Ok(()) = evm.overlay_db_mut().account(change);
470        pending_state.insert_account(*address, original.clone(), current.clone());
471    }
472    accumulate_pending_state(block_state, stream_state, on_state_update, &pending_state);
473}
474
475#[expect(clippy::too_many_arguments)]
476pub(crate) fn post_block_balance_state_changes<T: EvmTypes>(
477    evm: &mut Evm<'_, T>,
478    block_state: &mut BlockState,
479    stream_state: bool,
480    on_state_update: &mut impl FnMut(StateUpdate),
481    base_block_reward: Option<u128>,
482    dao_fork_transition: bool,
483    block_number: u64,
484    block_beneficiary: Address,
485    ommers: Option<&[Header]>,
486    withdrawals: Option<&[Withdrawal]>,
487) -> Result<(), BlockExecutionError> {
488    let mut balance_increments = AddressMap::<U256>::default();
489
490    if let Some(base_block_reward) = base_block_reward {
491        let ommers = ommers.unwrap_or_default();
492        for ommer in ommers {
493            *balance_increments.entry(ommer.beneficiary()).or_default() +=
494                U256::from(ommer_reward(base_block_reward, block_number, ommer.number()));
495        }
496        *balance_increments.entry(block_beneficiary).or_default() +=
497            U256::from(block_reward(base_block_reward, ommers.len()));
498    }
499
500    for withdrawal in withdrawals.into_iter().flatten() {
501        *balance_increments.entry(withdrawal.address).or_default() += withdrawal.amount_wei();
502    }
503
504    let mut changes = Vec::new();
505
506    if dao_fork_transition {
507        core::hint::cold_path();
508        let mut drained_balance = U256::ZERO;
509        // Absent accounts stay absent, and an account that is empty once drained is removed, even
510        // before EIP-161.
511        for address in dao_fork::DAO_HARDFORK_ACCOUNTS {
512            let original = evm.read_account_info(&address).map_err(map_database_error)?;
513            let Some(original) = original else { continue };
514
515            drained_balance = drained_balance.saturating_add(original.balance);
516            let mut current = original.clone();
517            current.balance = U256::ZERO;
518            let current = (!current.is_empty()).then_some(current);
519            if current.as_ref() == Some(&original) {
520                continue
521            }
522            changes.push((address, Some(original), current));
523        }
524
525        *balance_increments.entry(dao_fork::DAO_HARDFORK_BENEFICIARY).or_default() +=
526            drained_balance;
527        // Reward recipients may also be drained accounts. Read their balances after the drain.
528        commit_state_changes(evm, block_state, stream_state, on_state_update, &changes);
529        changes.clear();
530    }
531
532    if balance_increments.is_empty() {
533        return Ok(());
534    }
535
536    for (address, increment) in balance_increments {
537        let original = evm.read_account_info(&address).map_err(map_database_error)?;
538        let current = if increment.is_zero() && original.as_ref().is_none_or(AccountInfo::is_empty)
539        {
540            None
541        } else {
542            let mut current = original.clone().unwrap_or_else(AccountInfo::empty);
543            current.balance = current.balance.saturating_add(increment);
544            Some(current)
545        };
546        if original == current {
547            // Zero-value withdrawals still count as account accesses in the BAL.
548            let Ok(()) = evm.overlay_db_mut().account_read(address, original.as_ref());
549            continue
550        }
551        changes.push((address, original, current));
552    }
553
554    commit_state_changes(evm, block_state, stream_state, on_state_update, &changes);
555
556    Ok(())
557}
558
559pub(crate) fn base_block_reward<C>(chain_spec: &C, block_number: u64) -> Option<u128>
560where
561    C: EthereumHardforks + ?Sized,
562{
563    if chain_spec.is_paris_active_at_block(block_number) {
564        None
565    } else if chain_spec.is_constantinople_active_at_block(block_number) {
566        Some(ETH_TO_WEI * 2)
567    } else if chain_spec.is_byzantium_active_at_block(block_number) {
568        Some(ETH_TO_WEI * 3)
569    } else {
570        Some(ETH_TO_WEI * 5)
571    }
572}
573
574const fn block_reward(base_block_reward: u128, ommers: usize) -> u128 {
575    base_block_reward + (base_block_reward >> 5) * ommers as u128
576}
577
578fn ommer_reward(base_block_reward: u128, block_number: u64, ommer_block_number: u64) -> u128 {
579    let distance = 8u64.saturating_add(ommer_block_number).saturating_sub(block_number);
580    (u128::from(distance) * base_block_reward) >> 3
581}
582
583/// A system contract reverted or halted without producing a successful result.
584#[derive(Debug)]
585struct SystemCallFailed {
586    address: Address,
587    reason: InstrStop,
588}
589
590impl core::fmt::Display for SystemCallFailed {
591    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
592        write!(f, "EVM system call to {} failed: {:?}", self.address, self.reason)
593    }
594}
595
596impl core::error::Error for SystemCallFailed {}
597
598#[cfg(test)]
599mod tests {
600    use super::*;
601    use alloy_consensus::{SignableTransaction, TxLegacy, TxType};
602    use alloy_genesis::Genesis;
603    use alloy_primitives::{address, Signature, TxKind};
604    use evm2::{
605        bytecode::Bytecode, env::BlockEnv as EvmBlockEnv, evm::InMemoryDB, interpreter::opcode::op,
606    };
607    use reth_chainspec::{Chain, ChainSpec, MAINNET};
608    use reth_ethereum_forks::{EthereumHardfork, ForkCondition};
609    use reth_ethereum_primitives::{Block, BlockBody, Receipt, TransactionSigned};
610    use reth_evm::{BlockExecutorFactory, ConfigureEvm, Executor};
611    use reth_execution_types::BlockExecutionOutput;
612    use reth_primitives_traits::RecoveredBlock;
613    use reth_trie_common::{HashedPostState, KeccakKeyHasher};
614    use std::sync::{mpsc, Arc};
615
616    type BlockEnv = EvmBlockEnv<evm2::BaseEvmTypes>;
617
618    #[test]
619    fn detached_commits_preserve_storage_hooks_and_bal() {
620        let caller = Address::with_last_byte(1);
621        let contract = address!("0000000000000000000000000000000000001000");
622        let mut database = InMemoryDB::default();
623        database.insert_account_info(
624            &caller,
625            AccountInfo::default().with_balance(U256::from(ETH_TO_WEI)),
626        );
627        database.insert_account_info(
628            &contract,
629            AccountInfo::default()
630                .with_nonce(1)
631                .with_code(Bytecode::new_raw(alloy_primitives::bytes!("5f546001015f5500"))),
632        );
633        let factory = crate::EthBlockExecutorFactory::new(MAINNET.clone());
634        let mut expected = None;
635        let mut expected_updates = None;
636        for mode in 0..3 {
637            for stream_state in [false, true] {
638                let env = crate::EthEvmEnv::new(
639                    SpecId::AMSTERDAM,
640                    BlockEnv { gas_limit: U256::from(10_000_000), ..Default::default() },
641                    1,
642                );
643                let mut evm = factory.evm_with_env(evm2::evm::Db::new(database.clone()), env);
644                evm.state_mut().enable_bal_builder();
645                let mut block = BlockState::new();
646                let mut updates = Vec::new();
647                for nonce in 0..2 {
648                    evm.state_mut().set_bal_index(alloy_eip7928::BlockAccessIndex::new(nonce + 1));
649                    let transaction = Recovered::new_unchecked(
650                        evm2::ethereum::TxEnvelope::Legacy(TxLegacy {
651                            nonce,
652                            gas_limit: 1_000_000,
653                            gas_price: 1,
654                            to: TxKind::Call(contract),
655                            ..Default::default()
656                        }),
657                        caller,
658                    );
659                    let mut hook = |state| updates.push(state);
660                    if mode == 0 {
661                        let result = execute_transaction_with_condition(
662                            &mut evm,
663                            &mut block,
664                            stream_state,
665                            &mut hook,
666                            &transaction,
667                            |_, _| reth_evm::CommitChanges::Yes,
668                        )
669                        .unwrap()
670                        .unwrap();
671                        assert!(result.status, "{result:?}");
672                    } else {
673                        let output =
674                            execute_transaction_without_commit(&mut evm, &transaction).unwrap();
675                        assert!(output.result.status, "{:?}", output.result);
676                        if mode == 1 {
677                            let _ = commit_detached_transaction(
678                                &mut evm,
679                                &mut block,
680                                stream_state,
681                                &mut hook,
682                                output,
683                            );
684                        } else {
685                            commit_pending_state(
686                                &mut evm,
687                                &mut block,
688                                stream_state,
689                                &mut hook,
690                                &output.pending_state,
691                            );
692                        }
693                    }
694                }
695                let bundle = block.into_bundle();
696                assert_eq!(bundle.storage(&contract, U256::ZERO), Some(U256::from(2)));
697                assert_eq!(updates.len(), if stream_state { 2 } else { 0 });
698                if stream_state {
699                    // Account order within an update follows map iteration and is unspecified.
700                    for update in &mut updates {
701                        update.accounts.sort_unstable_by_key(|account| account.address);
702                    }
703                    assert_eq!(expected_updates.get_or_insert_with(|| updates.clone()), &updates);
704                }
705                let output = (bundle, evm.state_mut().take_bal_builder());
706                if let Some(expected) = &expected {
707                    assert_eq!(&output, expected);
708                } else {
709                    expected = Some(output);
710                }
711            }
712        }
713    }
714
715    fn execute_block(
716        spec: SpecId,
717        env: BlockEnv,
718        database: InMemoryDB,
719        transactions: impl IntoIterator<Item = Recovered<TransactionSigned>>,
720        withdrawals: Option<&[Withdrawal]>,
721    ) -> Result<BlockExecutionOutput<Receipt>, BlockExecutionError> {
722        let builder = ChainSpec::builder().chain(Chain::mainnet()).genesis(Genesis::default());
723        let chain = match spec {
724            SpecId::LONDON => builder.london_activated(),
725            SpecId::SHANGHAI => builder.shanghai_activated(),
726            SpecId::PRAGUE => builder.prague_activated(),
727            SpecId::AMSTERDAM => builder.amsterdam_activated(),
728            _ => panic!("unsupported test fork"),
729        }
730        .build();
731        let (transactions, senders) = transactions.into_iter().map(Recovered::into_parts).unzip();
732        let block = RecoveredBlock::new_unhashed(
733            Block {
734                header: Header {
735                    number: 1,
736                    beneficiary: env.beneficiary,
737                    gas_limit: env.gas_limit.to(),
738                    timestamp: env.timestamp.to(),
739                    base_fee_per_gas: Some(env.basefee.to()),
740                    excess_blob_gas: Some(0),
741                    parent_beacon_block_root: Some(B256::ZERO),
742                    ..Default::default()
743                },
744                body: BlockBody {
745                    transactions,
746                    withdrawals: withdrawals.map(|withdrawals| withdrawals.to_vec().into()),
747                    ..Default::default()
748                },
749            },
750            senders,
751        );
752        let (tx, rx) = mpsc::channel();
753        let config = crate::EthEvmConfig::new(Arc::new(chain));
754        let without_hook = config.batch_executor(database.clone()).execute(&block)?;
755        let output = config
756            .batch_executor(database)
757            .execute_with_state_hook(&block, move |state| tx.send(state).unwrap())?;
758        assert_eq!(without_hook, output);
759        let mut streamed = HashedPostState::default();
760        for update in rx.try_iter() {
761            streamed.extend(reth_execution_types::state_update_to_hashed_post_state(&update));
762        }
763        let recomputed = HashedPostState::from_bundle_state::<KeccakKeyHasher>(&output.state.state);
764        assert_eq!(streamed.into_sorted(), recomputed.into_sorted());
765        Ok(output)
766    }
767
768    #[test]
769    fn charges_london_sstore_set_gas() {
770        let caller = address!("0000000000000000000000000000000000000001");
771        let contract = address!("0000000000000000000000000000000000001000");
772        let mut database = InMemoryDB::default();
773        database.insert_account_info(
774            &caller,
775            AccountInfo::default().with_balance(U256::from(ETH_TO_WEI)),
776        );
777        database.insert_account_info(
778            &contract,
779            AccountInfo::default().with_nonce(1).with_code(Bytecode::new_raw(Bytes::from(vec![
780                op::PUSH1,
781                10,
782                op::PUSH1,
783                0,
784                op::SSTORE,
785                op::STOP,
786            ]))),
787        );
788        let transaction = Recovered::new_unchecked(
789            TransactionSigned::Legacy(
790                TxLegacy {
791                    gas_price: 1_000_000_000,
792                    gas_limit: 100_000,
793                    to: TxKind::Call(contract),
794                    value: U256::from(1),
795                    ..Default::default()
796                }
797                .into_signed(Signature::test_signature()),
798            ),
799            caller,
800        );
801
802        let output = execute_block(
803            SpecId::LONDON,
804            BlockEnv {
805                gas_limit: U256::from(1_500_000),
806                basefee: U256::from(1),
807                ..Default::default()
808            },
809            database,
810            [transaction],
811            None,
812        )
813        .expect("EVM execution succeeds");
814
815        assert_eq!(output.result.receipts[0].cumulative_gas_used, 43_106);
816        assert!(output.result.receipts[0].success);
817        assert_eq!(output.account(&contract).unwrap().unwrap().balance, U256::from(1));
818        let sender = output.account(&caller).unwrap().unwrap();
819        assert_eq!(sender.nonce, 1);
820        assert_eq!(sender.balance, U256::from(ETH_TO_WEI) - U256::from(43_106_000_000_001u64));
821        assert_eq!(output.storage(&contract, U256::ZERO).unwrap(), U256::from(10));
822    }
823
824    #[test]
825    fn applies_withdrawals_to_block_output() {
826        let existing = address!("0000000000000000000000000000000000000001");
827        let new = address!("0000000000000000000000000000000000000002");
828        let mut database = InMemoryDB::default();
829        database
830            .insert_account_info(&existing, AccountInfo::default().with_balance(U256::from(100)));
831        let withdrawals = [
832            Withdrawal { index: 0, validator_index: 0, address: existing, amount: 1 },
833            Withdrawal { index: 1, validator_index: 1, address: new, amount: 2 },
834            Withdrawal { index: 2, validator_index: 2, address: new, amount: 3 },
835        ];
836
837        let output = execute_block(
838            SpecId::SHANGHAI,
839            BlockEnv::default(),
840            database,
841            core::iter::empty::<Recovered<TransactionSigned>>(),
842            Some(&withdrawals),
843        )
844        .expect("EVM execution succeeds");
845
846        assert!(output.result.receipts.is_empty());
847        assert_eq!(
848            output.account_state(&existing).unwrap().info.as_ref().unwrap().balance,
849            U256::from(1_000_000_100)
850        );
851        assert_eq!(
852            output.account_state(&new).unwrap().info.as_ref().unwrap().balance,
853            U256::from(5_000_000_000u64)
854        );
855    }
856
857    #[test]
858    fn zero_withdrawals_apply_eip161_state_clearing() {
859        let nonexistent = address!("0000000000000000000000000000000000000001");
860        let empty = address!("0000000000000000000000000000000000000002");
861        let contract = address!("0000000000000000000000000000000000000003");
862        let mut database = InMemoryDB::default();
863        database.insert_account_info(&empty, AccountInfo::default());
864        database.insert_account_info(&contract, AccountInfo::default().with_nonce(1));
865        let withdrawals = [
866            Withdrawal { index: 0, validator_index: 0, address: nonexistent, amount: 0 },
867            Withdrawal { index: 1, validator_index: 1, address: empty, amount: 0 },
868            Withdrawal { index: 2, validator_index: 2, address: contract, amount: 0 },
869        ];
870
871        let output = execute_block(
872            SpecId::SHANGHAI,
873            BlockEnv::default(),
874            database.clone(),
875            core::iter::empty::<Recovered<TransactionSigned>>(),
876            Some(&withdrawals),
877        )
878        .expect("EVM execution succeeds");
879
880        assert!(output.account_state(&nonexistent).is_none());
881        assert!(output.account_state(&empty).unwrap().info.is_none());
882        assert!(output.account_state(&contract).is_none());
883        let factory = crate::EthBlockExecutorFactory::new(MAINNET.clone());
884        let env = crate::EthEvmEnv::new(SpecId::AMSTERDAM, BlockEnv::default(), 1);
885        let mut evm = factory.evm_with_env(evm2::evm::Db::new(database), env);
886        evm.state_mut().enable_bal_builder();
887        post_block_balance_state_changes(
888            &mut evm,
889            &mut BlockState::new(),
890            false,
891            &mut |_| {},
892            None,
893            false,
894            1,
895            Address::ZERO,
896            None,
897            Some(&withdrawals),
898        )
899        .unwrap();
900        let bal = evm.state_mut().take_bal_builder().unwrap();
901        for withdrawal in withdrawals {
902            assert!(bal.accounts.contains_key(&withdrawal.address));
903        }
904    }
905
906    #[test]
907    fn test_calc_base_block_reward() {
908        // ((block number, td), reward)
909        let cases = [
910            // Pre-byzantium
911            ((0, U256::ZERO), Some(ETH_TO_WEI * 5)),
912            // Byzantium
913            ((4370000, U256::ZERO), Some(ETH_TO_WEI * 3)),
914            // Petersburg
915            ((7280000, U256::ZERO), Some(ETH_TO_WEI * 2)),
916            // Merge
917            ((15537394, U256::from(58_750_000_000_000_000_000_000_u128)), None),
918        ];
919
920        for ((block_number, _td), expected_reward) in cases {
921            assert_eq!(base_block_reward(&*MAINNET, block_number), expected_reward);
922        }
923    }
924
925    #[test]
926    fn test_calc_full_block_reward() {
927        let base_reward = ETH_TO_WEI;
928        let one_thirty_twoth_reward = base_reward >> 5;
929
930        // (num_ommers, reward)
931        let cases = [
932            (0, base_reward),
933            (1, base_reward + one_thirty_twoth_reward),
934            (2, base_reward + one_thirty_twoth_reward * 2),
935        ];
936
937        for (num_ommers, expected_reward) in cases {
938            assert_eq!(block_reward(base_reward, num_ommers), expected_reward);
939        }
940    }
941
942    #[test]
943    fn block_reward_uses_constantinople_activation() {
944        let chain_spec = ChainSpec::builder()
945            .chain(Chain::mainnet())
946            .genesis(Genesis::default())
947            .with_fork(EthereumHardfork::Byzantium, ForkCondition::Block(9))
948            .with_fork(EthereumHardfork::Constantinople, ForkCondition::Block(12))
949            .with_fork(EthereumHardfork::Petersburg, ForkCondition::Block(15))
950            .with_fork(EthereumHardfork::Paris, ForkCondition::Never)
951            .build();
952
953        assert_eq!(base_block_reward(&chain_spec, 11), Some(ETH_TO_WEI * 3));
954        let reward = base_block_reward(&chain_spec, 12).unwrap();
955        assert_eq!(reward, ETH_TO_WEI * 2);
956        assert_eq!(block_reward(reward, 1), ETH_TO_WEI * 2 + ((ETH_TO_WEI * 2) >> 5));
957        assert_eq!(ommer_reward(reward, 12, 11), ETH_TO_WEI * 7 / 4);
958        assert_eq!(base_block_reward(&chain_spec, 14), Some(ETH_TO_WEI * 2));
959    }
960
961    #[test]
962    fn collects_post_execution_system_call_requests() {
963        let mut database = InMemoryDB::default();
964        database.insert_account_info(
965            &WITHDRAWAL_REQUEST_ADDRESS,
966            AccountInfo::default().with_code(return_byte_code(0xaa)),
967        );
968        database.insert_account_info(
969            &CONSOLIDATION_REQUEST_ADDRESS,
970            AccountInfo::default().with_code(return_byte_code(0xbb)),
971        );
972
973        let output = execute_block(
974            SpecId::PRAGUE,
975            BlockEnv::default(),
976            database,
977            core::iter::empty::<Recovered<TransactionSigned>>(),
978            None,
979        )
980        .expect("system calls succeed");
981
982        assert_eq!(
983            output.result.requests.iter().cloned().collect::<Vec<_>>(),
984            vec![
985                Bytes::from_static(&[WITHDRAWAL_REQUEST_TYPE, 0xaa]),
986                Bytes::from_static(&[CONSOLIDATION_REQUEST_TYPE, 0xbb]),
987            ]
988        );
989        assert!(output.result.receipts.is_empty());
990    }
991
992    #[test]
993    fn collects_amsterdam_builder_requests() {
994        let mut database = InMemoryDB::default();
995        for (address, byte) in [
996            (WITHDRAWAL_REQUEST_ADDRESS, 0xaa),
997            (CONSOLIDATION_REQUEST_ADDRESS, 0xbb),
998            (BUILDER_DEPOSIT_REQUEST_ADDRESS, 0xcc),
999            (BUILDER_EXIT_REQUEST_ADDRESS, 0xdd),
1000        ] {
1001            database.insert_account_info(
1002                &address,
1003                AccountInfo::default().with_code(return_byte_code(byte)),
1004            );
1005        }
1006
1007        let output = execute_block(
1008            SpecId::AMSTERDAM,
1009            BlockEnv::default(),
1010            database,
1011            core::iter::empty::<Recovered<TransactionSigned>>(),
1012            None,
1013        )
1014        .expect("system calls succeed");
1015
1016        assert_eq!(
1017            output.result.requests.iter().cloned().collect::<Vec<_>>(),
1018            vec![
1019                Bytes::from_static(&[WITHDRAWAL_REQUEST_TYPE, 0xaa]),
1020                Bytes::from_static(&[CONSOLIDATION_REQUEST_TYPE, 0xbb]),
1021                Bytes::from_static(&[BUILDER_DEPOSIT_REQUEST_TYPE, 0xcc]),
1022                Bytes::from_static(&[BUILDER_EXIT_REQUEST_TYPE, 0xdd]),
1023            ]
1024        );
1025    }
1026
1027    #[test]
1028    fn system_call_failures_follow_contract_policy() {
1029        for address in [
1030            BEACON_ROOTS_ADDRESS,
1031            HISTORY_STORAGE_ADDRESS,
1032            WITHDRAWAL_REQUEST_ADDRESS,
1033            CONSOLIDATION_REQUEST_ADDRESS,
1034        ] {
1035            for code in [
1036                Bytes::from_static(&[op::PUSH0, op::PUSH0, op::REVERT]),
1037                Bytes::from_static(&[op::INVALID]),
1038            ] {
1039                let mut database = InMemoryDB::default();
1040                database.insert_account_info(
1041                    &address,
1042                    AccountInfo::default().with_code(Bytecode::new_legacy(code)),
1043                );
1044                let result = execute_block(
1045                    SpecId::PRAGUE,
1046                    BlockEnv { number: U256::from(1), ..Default::default() },
1047                    database,
1048                    core::iter::empty::<Recovered<TransactionSigned>>(),
1049                    None,
1050                );
1051                if matches!(address, BEACON_ROOTS_ADDRESS | HISTORY_STORAGE_ADDRESS) {
1052                    result.unwrap();
1053                } else {
1054                    let error = result.unwrap_err();
1055                    assert!(error.as_validation().is_some(), "{error:?}");
1056                }
1057            }
1058        }
1059    }
1060
1061    #[test]
1062    fn collects_deposit_requests_from_transaction_logs() {
1063        let deposit = DepositEvent {
1064            pubkey: Bytes::from(vec![0x11; 48]),
1065            withdrawal_credentials: Bytes::from(vec![0x22; 32]),
1066            amount: Bytes::from(vec![0x33; 8]),
1067            signature: Bytes::from(vec![0x44; 96]),
1068            index: Bytes::from(vec![0x55; 8]),
1069        };
1070        let log = DepositEvent::encode_log(&Log {
1071            address: MAINNET_DEPOSIT_CONTRACT_ADDRESS,
1072            data: deposit,
1073        });
1074        let receipt = Receipt { tx_type: TxType::Legacy, logs: vec![log], ..Default::default() };
1075
1076        let requests = block_requests_from_receipts(
1077            SpecId::PRAGUE,
1078            BlockExecutionContext {
1079                system_calls: Some(BlockSystemCalls {
1080                    parent_hash: B256::ZERO,
1081                    parent_beacon_block_root: Some(B256::ZERO),
1082                }),
1083                ommers: None,
1084                withdrawals: None,
1085                deposit_contract_address: None,
1086            },
1087            &[receipt],
1088        )
1089        .expect("deposit log decodes");
1090
1091        assert_eq!(requests.len(), 1);
1092        assert_eq!(requests[0][0], DEPOSIT_REQUEST_TYPE);
1093        assert_eq!(requests[0].len(), 1 + DEPOSIT_BYTES_SIZE);
1094    }
1095
1096    fn return_byte_code(value: u8) -> Bytecode {
1097        Bytecode::new_legacy(Bytes::from(vec![
1098            op::PUSH1,
1099            value,
1100            op::PUSH1,
1101            0,
1102            op::MSTORE8,
1103            op::PUSH1,
1104            1,
1105            op::PUSH1,
1106            0,
1107            op::RETURN,
1108        ]))
1109    }
1110}