1use 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#[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 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#[derive(Debug, Clone, Copy, Default)]
119pub(crate) struct BlockExecutionContext<'a> {
120 pub system_calls: Option<BlockSystemCalls>,
122 pub ommers: Option<&'a [Header]>,
124 pub withdrawals: Option<&'a [Withdrawal]>,
126 pub deposit_contract_address: Option<Address>,
128}
129
130#[derive(Debug, Clone, Copy)]
132pub(crate) struct BlockSystemCalls {
133 pub parent_hash: B256,
135 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 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
319fn 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 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 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 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 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#[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 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 let cases = [
910 ((0, U256::ZERO), Some(ETH_TO_WEI * 5)),
912 ((4370000, U256::ZERO), Some(ETH_TO_WEI * 3)),
914 ((7280000, U256::ZERO), Some(ETH_TO_WEI * 2)),
916 ((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 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}