1pub mod db;
2mod tracing;
3
4use super::{BlockExecutionResult, FrameValidationOutcome, TxGasBreakdown, compute_burned_fees};
5use crate::system_contracts::{
6 AMSTERDAM_REQUEST_PREDEPLOYS, BEACON_ROOTS_ADDRESS, BUILDER_DEPOSIT_CONTRACT_ADDRESS,
7 BUILDER_EXIT_CONTRACT_ADDRESS, CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
8 EXPIRY_VERIFIER_PREDEPLOY, EXPIRY_VERIFIER_RUNTIME_BYTECODE, HISTORY_STORAGE_ADDRESS,
9 PRAGUE_SYSTEM_CONTRACTS, SYSTEM_ADDRESS, WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
10};
11use crate::{EvmError, ExecutionResult};
12use bytes::Bytes;
13use ethrex_common::H256;
14#[cfg(feature = "rayon")]
15use ethrex_common::constants::EMPTY_KECCAK_HASH;
16use ethrex_common::types::Code;
17#[cfg(feature = "rayon")]
18use ethrex_common::types::TxType;
19use ethrex_common::types::block_access_list::BlockAccessList;
20#[cfg(feature = "rayon")]
21use ethrex_common::types::block_access_list::{
22 AccountChanges, BalAddressIndex, find_exact_change_balance, find_exact_change_code,
23 find_exact_change_nonce, find_exact_change_storage, has_exact_change_balance,
24 has_exact_change_code, has_exact_change_nonce, has_exact_change_storage,
25};
26use ethrex_common::types::fee_config::FeeConfig;
27use ethrex_common::types::{AuthorizationTuple, EIP7702Transaction};
28#[cfg(feature = "rayon")]
29use ethrex_common::utils::u256_from_big_endian_const;
30use ethrex_common::{
31 Address, U256,
32 types::{
33 AccessList, AccountUpdate, Block, BlockHeader, EIP1559Transaction, Fork, FrameReceipt,
34 GWEI_TO_WEI, GenericTransaction, INITIAL_BASE_FEE, Log, Receipt, Transaction, TxKind,
35 Withdrawal, requests::Requests,
36 },
37};
38#[cfg(feature = "rayon")]
39use ethrex_common::{
40 BigEndianHash, constants::AMSTERDAM_MAX_CODE_SIZE, validate_bal_code_sizes,
41 validate_block_access_list_size, validate_header_bal_indices,
42};
43use ethrex_crypto::Crypto;
44use ethrex_levm::EVMConfig;
45use ethrex_levm::StatelessValidator;
46#[cfg(feature = "rayon")]
47use ethrex_levm::account::{AccountStatus, LevmAccount};
48use ethrex_levm::call_frame::Stack;
49use ethrex_levm::constants::{
50 POST_OSAKA_GAS_LIMIT_CAP, STACK_LIMIT, SYS_CALL_GAS_LIMIT, TX_MAX_GAS_LIMIT_AMSTERDAM,
51};
52use ethrex_levm::db::gen_db::GeneralizedDatabase;
53#[cfg(feature = "rayon")]
54use ethrex_levm::db::gen_db::{
55 LazyBalCursor, code_from_bal, post_value_at_or_before, seed_one_address_info_from_bal,
56};
57#[cfg(feature = "rayon")]
58use ethrex_levm::db::{Database, gen_db::CacheDB};
59use ethrex_levm::errors::{InternalError, TxValidationError};
60use ethrex_levm::memory::Memory;
61#[cfg(feature = "perf_opcode_timings")]
62use ethrex_levm::timings::{OPCODE_TIMINGS, PRECOMPILES_TIMINGS};
63use ethrex_levm::tracing::LevmCallTracer;
64use ethrex_levm::utils::get_base_fee_per_blob_gas;
65use ethrex_levm::utils::intrinsic_gas_floor;
66use ethrex_levm::validation_observer::FrameSimViolation;
67use ethrex_levm::vm::VMType;
68use ethrex_levm::{
69 Environment,
70 errors::{ExecutionReport, TxResult, VMError},
71 vm::VM,
72};
73#[cfg(feature = "rayon")]
74use rayon::iter::{IntoParallelIterator, ParallelIterator};
75#[cfg(feature = "rayon")]
76use rayon::slice::ParallelSlice;
77#[cfg(feature = "rayon")]
78use rustc_hash::{FxHashMap, FxHashSet};
79use std::cmp::min;
80use std::sync::Arc;
81#[cfg(feature = "rayon")]
82use std::sync::atomic::AtomicBool;
83use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
84use std::sync::mpsc::Sender;
85
86#[cfg(feature = "rayon")]
90const BAL_WARM_CHUNK_ACCOUNTS: usize = 64;
91
92#[cfg(feature = "rayon")]
95const PRE_BLOCK_BAL_INDEX: u32 = 0;
96
97fn lstar_burned_fees(
105 chain_config: ðrex_common::types::ChainConfig,
106 header: ðrex_common::types::BlockHeader,
107 post_refund_gas: u64,
108) -> u64 {
109 let evm_cfg = EVMConfig::new_from_chain_config(chain_config, header);
110 let blob_base_fee = get_base_fee_per_blob_gas(header.excess_blob_gas, &evm_cfg)
111 .map(|v| u64::try_from(v).unwrap_or(u64::MAX))
112 .unwrap_or(0);
113 let blob_gas_used = header.blob_gas_used.unwrap_or(0);
114 compute_burned_fees(
115 header.base_fee_per_gas.unwrap_or(0),
116 post_refund_gas,
118 blob_base_fee,
119 blob_gas_used,
120 )
121}
122
123#[derive(Debug)]
129pub struct LEVM;
130
131fn frame_receipts_from(
135 frame_results: Option<Vec<(u8, u64, Vec<Log>)>>,
136) -> Option<Vec<FrameReceipt>> {
137 frame_results.map(|results| {
138 results
139 .into_iter()
140 .map(|(status, gas_used, logs)| FrameReceipt {
141 status,
142 gas_used,
143 logs,
144 })
145 .collect()
146 })
147}
148
149fn check_gas_limit(
151 block_gas_used: u64,
152 tx_gas_limit: u64,
153 block_gas_limit: u64,
154) -> Result<(), EvmError> {
155 if tx_gas_limit > block_gas_limit.saturating_sub(block_gas_used) {
156 return Err(EvmError::Transaction(format!(
157 "Gas allowance exceeded: \
158 used {block_gas_used} + tx limit {tx_gas_limit} > block limit {block_gas_limit}"
159 )));
160 }
161 Ok(())
162}
163
164pub fn block_work_budget(block_gas_limit: u64) -> u64 {
171 block_gas_limit.saturating_mul(2)
172}
173
174pub fn check_minimum_block_work<'a>(
187 txs_with_sender: impl IntoIterator<Item = (&'a Transaction, Address)>,
188 fork: Fork,
189 block_gas_limit: u64,
190) -> Result<(), EvmError> {
191 let budget = block_work_budget(block_gas_limit);
192 let mut floor_total = 0_u64;
193 for (tx, sender) in txs_with_sender {
194 let floor = intrinsic_gas_floor(tx, sender, fork)
198 .map_err(|e| EvmError::Transaction(format!("intrinsic gas floor: {e}")))?;
199 floor_total = floor_total.saturating_add(floor);
200 if floor_total > budget {
201 return Err(EvmError::Transaction(format!(
202 "Gas allowance exceeded: minimum gas of the block's transactions \
203 {floor_total} exceeds the block work budget {budget} \
204 (block_gas_limit={block_gas_limit})"
205 )));
206 }
207 }
208 Ok(())
209}
210
211#[derive(Default)]
222pub struct CompletedGas {
223 regular: AtomicU64,
224 state: AtomicU64,
225}
226
227impl CompletedGas {
228 pub fn check(&self, block_gas_limit: u64) -> Result<(), EvmError> {
230 let regular = self.regular.load(Ordering::Relaxed);
231 let state = self.state.load(Ordering::Relaxed);
232 if regular > block_gas_limit || state > block_gas_limit {
233 return Err(EvmError::Transaction(format!(
234 "Gas allowance exceeded: transactions completed during parallel execution \
235 used regular={regular} state={state}, over block_gas_limit={block_gas_limit}"
236 )));
237 }
238 Ok(())
239 }
240
241 pub fn record(
243 &self,
244 regular_gas: u64,
245 state_gas: u64,
246 block_gas_limit: u64,
247 ) -> Result<(), EvmError> {
248 self.regular.fetch_add(regular_gas, Ordering::Relaxed);
249 self.state.fetch_add(state_gas, Ordering::Relaxed);
250 self.check(block_gas_limit)
251 }
252}
253
254pub fn check_2d_gas_allowance(
273 tx: &Transaction,
274 block_gas_used_regular: u64,
275 block_gas_used_state: u64,
276 block_gas_limit: u64,
277) -> Result<(), EvmError> {
278 let tx_gas = tx.gas_limit();
279 let regular_available = block_gas_limit.saturating_sub(block_gas_used_regular);
280 let state_available = block_gas_limit.saturating_sub(block_gas_used_state);
281
282 let regular_contrib = tx_gas.min(TX_MAX_GAS_LIMIT_AMSTERDAM);
287 if regular_contrib > regular_available {
288 return Err(EvmError::Transaction(format!(
289 "Gas allowance exceeded: regular dim worst-case {regular_contrib} > \
290 available {regular_available} (block_gas_used_regular={block_gas_used_regular}, \
291 block_gas_limit={block_gas_limit})"
292 )));
293 }
294
295 let state_contrib = tx_gas;
297 if state_contrib > state_available {
298 return Err(EvmError::Transaction(format!(
299 "Gas allowance exceeded: state dim worst-case {state_contrib} > \
300 available {state_available} (block_gas_used_state={block_gas_used_state}, \
301 block_gas_limit={block_gas_limit})"
302 )));
303 }
304
305 Ok(())
306}
307
308#[cfg(feature = "rayon")]
314#[derive(Debug, thiserror::Error)]
315pub enum BalValidationError {
316 #[error("{0}")]
317 Mismatch(String),
318 #[error("{0}")]
319 Database(String),
320}
321
322impl LEVM {
323 pub fn execute_block(
328 block: &Block,
329 db: &mut GeneralizedDatabase,
330 vm_type: VMType,
331 crypto: &dyn Crypto,
332 stateless_validator: Option<&dyn StatelessValidator>,
333 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
334 let chain_config = db.store.get_chain_config()?;
335 let is_amsterdam = chain_config.is_amsterdam_activated(block.header.timestamp);
336 let is_lstar = chain_config.is_lstar_activated(block.header.timestamp);
338
339 debug_assert!(
344 block.body.transactions.len() < u32::MAX as usize,
345 "tx count overflows u32 BlockAccessIndex"
346 );
347
348 if is_amsterdam {
350 db.enable_bal_recording();
351 db.set_bal_index(0);
353 }
354
355 Self::prepare_block(block, db, vm_type, crypto)?;
356
357 let evm_config = EVMConfig::new_from_chain_config(&chain_config, &block.header);
361 let chain_id = chain_config.chain_id;
362 let base_blob_fee_per_gas =
363 get_base_fee_per_blob_gas(block.header.excess_blob_gas, &evm_config)?;
364 let mut shared_stack_pool = Vec::with_capacity(STACK_LIMIT);
366 let mut shared_memory_pool = Vec::with_capacity(1);
367
368 let n_txs = block.body.transactions.len();
369 let mut receipts = Vec::with_capacity(n_txs);
370 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(n_txs);
371 let mut cumulative_gas_used = 0_u64;
373 let mut block_gas_used = 0_u64;
375 let mut block_regular_gas_used = 0_u64;
377 let mut block_state_gas_used = 0_u64;
378 let transactions_with_sender =
379 block
380 .body
381 .get_transactions_with_sender(crypto)
382 .map_err(|error| {
383 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
384 })?;
385
386 for (tx_idx, (tx, tx_sender)) in transactions_with_sender.into_iter().enumerate() {
387 if !is_amsterdam {
394 check_gas_limit(cumulative_gas_used, tx.gas_limit(), block.header.gas_limit)?;
395 }
396
397 if is_amsterdam {
399 check_2d_gas_allowance(
400 tx,
401 block_regular_gas_used,
402 block_state_gas_used,
403 block.header.gas_limit,
404 )?;
405 }
406
407 if is_amsterdam {
409 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
410 db.set_bal_index(bal_index);
411
412 if let Some(recorder) = db.bal_recorder_mut() {
416 recorder.record_touched_address(tx_sender);
417 }
418 }
419
420 let report = Self::execute_tx_in_block(
421 tx,
422 tx_sender,
423 &block.header,
424 db,
425 vm_type,
426 base_blob_fee_per_gas,
427 &mut shared_stack_pool,
428 &mut shared_memory_pool,
429 false,
430 crypto,
431 evm_config,
432 chain_id,
433 stateless_validator,
434 )?;
435
436 tx_gas_breakdowns.push(TxGasBreakdown::from_report(
437 tx_idx,
438 tx.hash(crypto),
439 &report,
440 ));
441
442 cumulative_gas_used += report.gas_spent;
444
445 let tx_state_gas = report.state_gas_used;
448 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
449 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
450 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
451
452 if is_amsterdam {
453 block_gas_used = block_regular_gas_used.max(block_state_gas_used);
455 ::tracing::debug!(
456 "EIP-8037 validate tx[{tx_idx}]: regular={tx_regular_gas} state={tx_state_gas} gas_used={} gas_spent={} block_regular={block_regular_gas_used} block_state={block_state_gas_used} block_max={block_gas_used}",
457 report.gas_used,
458 report.gas_spent,
459 );
460
461 if block_regular_gas_used > block.header.gas_limit
466 || block_state_gas_used > block.header.gas_limit
467 {
468 return Err(EvmError::Transaction(format!(
469 "Gas allowance exceeded: Block gas used overflow: \
470 block_gas_used {block_gas_used} > block_gas_limit {}",
471 block.header.gas_limit
472 )));
473 }
474 } else {
475 block_gas_used = block_gas_used.saturating_add(report.gas_used);
476 }
477
478 let mut receipt = Receipt::new(
479 tx.tx_type(),
480 matches!(report.result, TxResult::Success),
481 cumulative_gas_used,
482 report.logs,
483 );
484
485 if matches!(tx, Transaction::FrameTransaction(_)) {
487 receipt.payer = report.payer_address;
488 receipt.frame_receipts = frame_receipts_from(report.frame_results);
489 }
490
491 receipts.push(receipt);
492 }
493
494 if is_amsterdam && block_gas_used > block.header.gas_limit {
498 return Err(EvmError::Transaction(format!(
499 "Gas allowance exceeded: Block gas used overflow: \
500 block_gas_used {block_gas_used} > block_gas_limit {}",
501 block.header.gas_limit
502 )));
503 }
504
505 if is_amsterdam {
518 let post_tx_index =
519 u32::try_from(block.body.transactions.len() + 1).unwrap_or(u32::MAX);
520 db.set_bal_index(post_tx_index);
521
522 if let Some(withdrawals) = &block.body.withdrawals
526 && let Some(recorder) = db.bal_recorder_mut()
527 {
528 recorder.extend_touched_addresses(withdrawals.iter().map(|w| w.address));
529 }
530 }
531
532 if let Some(withdrawals) = &block.body.withdrawals {
533 Self::process_withdrawals(db, withdrawals)?;
534 }
535
536 let requests = match vm_type {
540 VMType::L1 => extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?,
541 VMType::L2(_) => Default::default(),
542 };
543
544 let bal = db.take_bal();
546
547 Ok((
548 BlockExecutionResult {
549 receipts,
550 requests,
551 block_gas_used,
552 burned_fees: is_lstar
554 .then(|| lstar_burned_fees(&chain_config, &block.header, cumulative_gas_used)),
555 tx_gas_breakdowns,
556 },
557 bal,
558 ))
559 }
560
561 #[allow(clippy::too_many_arguments)]
565 pub fn execute_block_pipeline(
566 block: &Block,
567 db: &mut GeneralizedDatabase,
568 vm_type: VMType,
569 merkleizer: Option<Sender<Vec<AccountUpdate>>>,
570 queue_length: &AtomicUsize,
571 crypto: &dyn Crypto,
572 header_bal: Option<Arc<BlockAccessList>>,
573 bal_parallel_exec_enabled: bool,
574 stateless_validator: Option<&dyn StatelessValidator>,
575 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
576 let chain_config = db.store.get_chain_config()?;
577 let is_amsterdam = chain_config.is_amsterdam_activated(block.header.timestamp);
578 let is_lstar = chain_config.is_lstar_activated(block.header.timestamp);
579 let evm_config = EVMConfig::new_from_chain_config(&chain_config, &block.header);
582 let chain_id = chain_config.chain_id;
583
584 debug_assert!(
586 block.body.transactions.len() < u32::MAX as usize,
587 "tx count overflows u32 BlockAccessIndex"
588 );
589
590 let transactions_with_sender =
591 block
592 .body
593 .get_transactions_with_sender(crypto)
594 .map_err(|error| {
595 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
596 })?;
597
598 #[cfg(not(feature = "rayon"))]
599 let _ = (header_bal, bal_parallel_exec_enabled);
602 #[cfg(feature = "rayon")]
603 if let Some(bal) = header_bal
609 && is_amsterdam
610 && bal_parallel_exec_enabled
611 {
612 validate_header_bal_indices(&bal, block.body.transactions.len())
618 .map_err(|e| EvmError::Custom(e.to_string()))?;
619 validate_bal_code_sizes(&bal, AMSTERDAM_MAX_CODE_SIZE)
622 .map_err(|e| EvmError::Custom(format!("BAL validation failed: {e}")))?;
623
624 db.enable_bal_recording();
627 db.set_bal_index(PRE_BLOCK_BAL_INDEX);
628 let prepare_result = Self::prepare_block(block, db, vm_type, crypto);
629 let pre_block_bal = db.take_bal().unwrap_or_default();
630 prepare_result?;
631
632 let validation_index = Arc::new(bal.build_validation_index());
634
635 Self::validate_bal_pre_exec_index(db, &bal, &validation_index)?;
642
643 LEVM::get_state_transitions_tx(db)?;
645 let system_seed = Arc::new(std::mem::take(&mut db.initial_accounts_state));
646
647 let parallel_result = Self::execute_block_parallel(
648 block,
649 &transactions_with_sender,
650 db,
651 vm_type,
652 Arc::clone(&bal),
653 merkleizer.as_ref(),
654 queue_length,
655 system_seed,
656 &pre_block_bal,
657 crypto,
658 Arc::clone(&validation_index),
659 stateless_validator,
660 );
661
662 let (
667 receipts,
668 block_gas_used,
669 mut unread_storage_reads,
670 mut unaccessed_pure_accounts,
671 tx_gas_breakdowns,
672 ) = match parallel_result {
673 Ok(result) => result,
674 Err(parallel_err) => {
675 let last_tx_idx =
676 u32::try_from(block.body.transactions.len()).unwrap_or(u32::MAX);
677 if Self::seed_db_from_bal(
678 db,
679 &bal,
680 last_tx_idx,
681 &validation_index.accounts_by_min_index,
682 )
683 .is_ok()
684 && let VMType::L1 = vm_type
685 && let Err(e @ EvmError::SystemContractCallFailed(_)) =
686 extract_all_requests_levm(&[], db, &block.header, vm_type, crypto)
687 {
688 return Err(e);
689 }
690 return Err(parallel_err);
691 }
692 };
693
694 let last_tx_idx = u32::try_from(block.body.transactions.len()).unwrap_or(u32::MAX);
699 Self::seed_db_from_bal(
701 db,
702 &bal,
703 last_tx_idx,
704 &validation_index.accounts_by_min_index,
705 )?;
706
707 let withdrawal_bal_idx = u32::try_from(block.body.transactions.len())
708 .map(|n| n.saturating_add(1))
709 .unwrap_or(u32::MAX);
710 db.enable_bal_recording();
711 db.set_bal_index(withdrawal_bal_idx);
712
713 let post_block_result = (|| -> Result<Vec<Requests>, EvmError> {
718 if let Some(withdrawals) = &block.body.withdrawals {
719 Self::process_withdrawals(db, withdrawals)?;
720 }
721
722 let requests = match vm_type {
723 VMType::L1 => {
724 extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?
725 }
726 VMType::L2(_) => Default::default(),
727 };
728 Ok(requests)
729 })();
730 let post_block_bal = db.take_bal().unwrap_or_default();
731 let requests = post_block_result?;
732 let withdrawal_idx = u32::try_from(block.body.transactions.len())
740 .map(|n| n.saturating_add(1))
741 .unwrap_or(u32::MAX);
742 Self::validate_bal_withdrawal_index(db, &bal, withdrawal_idx, &validation_index)?;
743
744 if !unread_storage_reads.is_empty() {
746 for (addr, acct) in &db.current_accounts_state {
747 for key in acct.storage.keys() {
748 unread_storage_reads.remove(&(*addr, *key));
749 }
750 }
751 }
752
753 if !unaccessed_pure_accounts.is_empty() {
758 if let Some(withdrawals) = &block.body.withdrawals {
759 for w in withdrawals {
760 unaccessed_pure_accounts.remove(&w.address);
761 }
762 }
763 for canon in post_block_bal.accounts() {
764 if canon.address == SYSTEM_ADDRESS {
766 continue;
767 }
768 unaccessed_pure_accounts.remove(&canon.address);
769 }
770 }
771
772 Self::validate_bal_covers(
773 &db.current_accounts_state,
774 &post_block_bal,
775 &bal,
776 &validation_index,
777 "the withdrawal/request phase",
778 withdrawal_bal_idx,
779 )?;
780
781 if let Some(withdrawals) = &block.body.withdrawals {
784 for w in withdrawals {
785 if !validation_index.addr_to_idx.contains_key(&w.address) {
786 return Err(EvmError::Custom(format!(
787 "BAL validation failed: withdrawal recipient {:?} is missing \
788 from BAL",
789 w.address
790 )));
791 }
792 }
793 }
794
795 if let Some((addr, key)) = unread_storage_reads.iter().next() {
797 let slot = ethrex_common::BigEndianHash::into_uint(key);
798 return Err(EvmError::Custom(format!(
799 "BAL validation failed: storage_read for account {addr:?} slot \
800 {slot} was never actually read during block execution"
801 )));
802 }
803
804 if let Some(addr) = unaccessed_pure_accounts.iter().next() {
806 return Err(EvmError::Custom(format!(
807 "BAL validation failed: account {addr:?} has no mutations \
808 and no storage reads but was never accessed during block execution"
809 )));
810 }
811
812 bal.validate_ordering()
815 .map_err(|e| EvmError::Custom(format!("BAL validation failed: {e}")))?;
816
817 validate_block_access_list_size(&block.header, &chain_config, &bal)
820 .map_err(|e| EvmError::Custom(e.to_string()))?;
821
822 let post_refund_gas = receipts.last().map(|r| r.cumulative_gas_used).unwrap_or(0);
826 let burned_fees_par =
827 is_lstar.then(|| lstar_burned_fees(&chain_config, &block.header, post_refund_gas));
828
829 return Ok((
830 BlockExecutionResult {
831 receipts,
832 requests,
833 block_gas_used,
834 burned_fees: burned_fees_par,
835 tx_gas_breakdowns,
836 },
837 None,
838 ));
839 }
840
841 let Some(merkleizer) = merkleizer else {
847 return Err(EvmError::Custom(
848 "sequential execution path called without a merkleizer Sender".to_string(),
849 ));
850 };
851 if is_amsterdam {
852 db.enable_bal_recording();
853 db.set_bal_index(0);
855 }
856
857 Self::prepare_block(block, db, vm_type, crypto)?;
858
859 let base_blob_fee_per_gas =
861 get_base_fee_per_blob_gas(block.header.excess_blob_gas, &evm_config)?;
862
863 let mut shared_stack_pool = Vec::with_capacity(STACK_LIMIT);
864 let mut shared_memory_pool = Vec::with_capacity(1);
866
867 let n_txs = block.body.transactions.len();
868 let mut receipts = Vec::with_capacity(n_txs);
869 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(n_txs);
870 let mut cumulative_gas_used = 0_u64;
872 let mut block_gas_used = 0_u64;
874 let mut block_regular_gas_used = 0_u64;
876 let mut block_state_gas_used = 0_u64;
877 let mut tx_since_last_flush = 2;
880
881 for (tx_idx, (tx, tx_sender)) in transactions_with_sender.into_iter().enumerate() {
882 if !is_amsterdam {
889 check_gas_limit(cumulative_gas_used, tx.gas_limit(), block.header.gas_limit)?;
890 }
891
892 if is_amsterdam {
894 check_2d_gas_allowance(
895 tx,
896 block_regular_gas_used,
897 block_state_gas_used,
898 block.header.gas_limit,
899 )?;
900 }
901
902 if is_amsterdam {
904 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
905 db.set_bal_index(bal_index);
906
907 if let Some(recorder) = db.bal_recorder_mut() {
911 recorder.record_touched_address(tx_sender);
912 }
913 }
914
915 let report = Self::execute_tx_in_block(
916 tx,
917 tx_sender,
918 &block.header,
919 db,
920 vm_type,
921 base_blob_fee_per_gas,
922 &mut shared_stack_pool,
923 &mut shared_memory_pool,
924 false,
925 crypto,
926 evm_config,
927 chain_id,
928 stateless_validator,
929 )?;
930
931 tx_gas_breakdowns.push(TxGasBreakdown::from_report(
932 tx_idx,
933 tx.hash(crypto),
934 &report,
935 ));
936
937 if queue_length.load(Ordering::Relaxed) == 0 && tx_since_last_flush > 5 {
938 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
939 tx_since_last_flush = 0;
940 } else {
941 tx_since_last_flush += 1;
942 }
943
944 cumulative_gas_used += report.gas_spent;
946
947 let tx_state_gas = report.state_gas_used;
950 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
951 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
952 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
953
954 if is_amsterdam {
955 block_gas_used = block_regular_gas_used.max(block_state_gas_used);
957
958 if block_regular_gas_used > block.header.gas_limit
963 || block_state_gas_used > block.header.gas_limit
964 {
965 return Err(EvmError::Transaction(format!(
966 "Gas allowance exceeded: Block gas used overflow: \
967 block_gas_used {block_gas_used} > block_gas_limit {}",
968 block.header.gas_limit
969 )));
970 }
971 } else {
972 block_gas_used = block_gas_used.saturating_add(report.gas_used);
973 }
974
975 let mut receipt = Receipt::new(
976 tx.tx_type(),
977 matches!(report.result, TxResult::Success),
978 cumulative_gas_used,
979 report.logs,
980 );
981
982 if matches!(tx, Transaction::FrameTransaction(_)) {
984 receipt.payer = report.payer_address;
985 receipt.frame_receipts = frame_receipts_from(report.frame_results);
986 }
987
988 receipts.push(receipt);
989 }
990
991 if is_amsterdam && block_gas_used > block.header.gas_limit {
995 return Err(EvmError::Transaction(format!(
996 "Gas allowance exceeded: Block gas used overflow: \
997 block_gas_used {block_gas_used} > block_gas_limit {}",
998 block.header.gas_limit
999 )));
1000 }
1001
1002 #[cfg(feature = "perf_opcode_timings")]
1003 {
1004 let mut timings = OPCODE_TIMINGS.lock().expect("poison");
1005 timings.inc_tx_count(receipts.len());
1006 timings.inc_block_count();
1007 ::tracing::info!("{}", timings.info_pretty());
1008 let precompiles_timings = PRECOMPILES_TIMINGS.lock().expect("poison");
1009 ::tracing::info!("{}", precompiles_timings.info_pretty());
1010 }
1011
1012 if queue_length.load(Ordering::Relaxed) == 0 {
1013 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
1014 }
1015
1016 if is_amsterdam {
1019 let post_tx_index =
1020 u32::try_from(block.body.transactions.len() + 1).unwrap_or(u32::MAX);
1021 db.set_bal_index(post_tx_index);
1022
1023 if let Some(withdrawals) = &block.body.withdrawals
1025 && let Some(recorder) = db.bal_recorder_mut()
1026 {
1027 recorder.extend_touched_addresses(withdrawals.iter().map(|w| w.address));
1028 }
1029 }
1030
1031 if let Some(withdrawals) = &block.body.withdrawals {
1034 Self::process_withdrawals(db, withdrawals)?;
1035 }
1036
1037 let requests = match vm_type {
1041 VMType::L1 => extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?,
1042 VMType::L2(_) => Default::default(),
1043 };
1044 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
1045
1046 let bal = db.take_bal();
1048
1049 Ok((
1050 BlockExecutionResult {
1051 receipts,
1052 requests,
1053 block_gas_used,
1054 burned_fees: is_lstar
1056 .then(|| lstar_burned_fees(&chain_config, &block.header, cumulative_gas_used)),
1057 tx_gas_breakdowns,
1058 },
1059 bal,
1060 ))
1061 }
1062
1063 #[cfg(feature = "rayon")]
1068 fn bal_to_account_updates(
1069 bal: &BlockAccessList,
1070 store: &dyn Database,
1071 ) -> Result<Vec<AccountUpdate>, EvmError> {
1072 use ethrex_common::types::AccountInfo;
1073
1074 let mut updates = Vec::new();
1075
1076 let write_addrs: Vec<Address> = bal
1078 .accounts()
1079 .iter()
1080 .filter(|ac| {
1081 !ac.balance_changes.is_empty()
1082 || !ac.nonce_changes.is_empty()
1083 || !ac.code_changes.is_empty()
1084 || !ac.storage_changes.is_empty()
1085 })
1086 .map(|ac| ac.address)
1087 .collect();
1088 let _ = store.prefetch_accounts(&write_addrs);
1092
1093 for acct_changes in bal.accounts() {
1094 let addr = acct_changes.address;
1095
1096 let has_writes = !acct_changes.balance_changes.is_empty()
1098 || !acct_changes.nonce_changes.is_empty()
1099 || !acct_changes.code_changes.is_empty()
1100 || !acct_changes.storage_changes.is_empty();
1101 if !has_writes {
1102 continue;
1103 }
1104
1105 let prestate = store
1107 .get_account_state(addr)
1108 .map_err(|e| EvmError::Custom(format!("bal_to_account_updates: {e}")))?;
1109
1110 let balance = acct_changes
1112 .balance_changes
1113 .last()
1114 .map(|c| c.post_balance)
1115 .unwrap_or(prestate.balance);
1116
1117 let nonce = acct_changes
1119 .nonce_changes
1120 .last()
1121 .map(|c| c.post_nonce)
1122 .unwrap_or(prestate.nonce);
1123
1124 let (code_hash, code) = if let Some(c) = acct_changes.code_changes.last() {
1126 code_from_bal(&c.new_code)
1127 } else {
1128 (prestate.code_hash, None)
1129 };
1130
1131 let mut added_storage = FxHashMap::with_capacity_and_hasher(
1133 acct_changes.storage_changes.len(),
1134 Default::default(),
1135 );
1136 for slot_change in &acct_changes.storage_changes {
1137 if let Some(last) = slot_change.slot_changes.last() {
1138 let key = ethrex_common::utils::u256_to_h256(slot_change.slot);
1139 added_storage.insert(key, last.post_value);
1140 }
1141 }
1142
1143 let post_empty = balance.is_zero() && nonce == 0 && code_hash == *EMPTY_KECCAK_HASH;
1145 let pre_empty = prestate.balance.is_zero()
1146 && prestate.nonce == 0
1147 && prestate.code_hash == *EMPTY_KECCAK_HASH;
1148 let removed = post_empty && !pre_empty;
1149
1150 let balance_changed = acct_changes
1151 .balance_changes
1152 .last()
1153 .is_some_and(|c| c.post_balance != prestate.balance);
1154 let nonce_changed = acct_changes
1155 .nonce_changes
1156 .last()
1157 .is_some_and(|c| c.post_nonce != prestate.nonce);
1158 let code_changed = acct_changes.code_changes.last().is_some();
1159 let acc_info_updated = balance_changed || nonce_changed || code_changed;
1160
1161 if !removed && !acc_info_updated && added_storage.is_empty() {
1162 continue;
1163 }
1164
1165 let info = if acc_info_updated {
1166 Some(AccountInfo {
1167 code_hash,
1168 balance,
1169 nonce,
1170 })
1171 } else {
1172 None
1173 };
1174
1175 let update = AccountUpdate {
1176 address: addr,
1177 removed,
1178 info,
1179 code,
1180 added_storage,
1181 removed_storage: false,
1188 };
1189 updates.push(update);
1190 }
1191
1192 Ok(updates)
1193 }
1194
1195 #[cfg(feature = "rayon")]
1201 fn validate_bal_covers(
1202 cache: &CacheDB,
1203 phase_bal: &BlockAccessList,
1204 bal: &BlockAccessList,
1205 index: &BalAddressIndex,
1206 phase: &str,
1207 phase_idx: u32,
1208 ) -> Result<(), EvmError> {
1209 for canon in phase_bal.accounts() {
1212 let addr = canon.address;
1213 let Some(supplied) = index
1214 .addr_to_idx
1215 .get(&addr)
1216 .and_then(|i| bal.accounts().get(*i))
1217 else {
1218 return Err(EvmError::Custom(format!(
1219 "BAL validation failed: account {addr:?} was accessed during {phase} \
1220 but is missing from BAL"
1221 )));
1222 };
1223
1224 for canon_slot in &canon.storage_changes {
1226 let slot = canon_slot.slot;
1227 let supplied_changes = supplied
1228 .storage_changes
1229 .binary_search_by(|sc| sc.slot.cmp(&slot))
1230 .ok()
1231 .and_then(|i| supplied.storage_changes.get(i))
1232 .map(|sc| sc.slot_changes.as_slice())
1233 .unwrap_or_default();
1234 for canon_change in &canon_slot.slot_changes {
1235 let idx = canon_change.block_access_index;
1236 match find_exact_change_storage(supplied_changes, idx) {
1237 Some(v) if v == canon_change.post_value => {}
1238 Some(v) => {
1239 return Err(EvmError::Custom(format!(
1240 "BAL validation failed: account {addr:?} storage slot {slot} \
1241 was written during {phase} to {} but the BAL declares {v} \
1242 at index {idx}",
1243 canon_change.post_value
1244 )));
1245 }
1246 None => {
1247 return Err(EvmError::Custom(format!(
1248 "BAL validation failed: account {addr:?} storage slot {slot} \
1249 was written during {phase} to {} but the BAL declares no \
1250 change at index {idx}",
1251 canon_change.post_value
1252 )));
1253 }
1254 }
1255 }
1256 }
1257
1258 for canon_change in &canon.balance_changes {
1259 let idx = canon_change.block_access_index;
1260 let expected = canon_change.post_balance;
1261 if find_exact_change_balance(&supplied.balance_changes, idx) != Some(expected) {
1262 return Err(EvmError::Custom(format!(
1263 "BAL validation failed: account {addr:?} balance changed to {expected} \
1264 during {phase} but the BAL declares no matching change at index {idx}"
1265 )));
1266 }
1267 }
1268 for canon_change in &canon.nonce_changes {
1269 let idx = canon_change.block_access_index;
1270 let expected = canon_change.post_nonce;
1271 if find_exact_change_nonce(&supplied.nonce_changes, idx) != Some(expected) {
1272 return Err(EvmError::Custom(format!(
1273 "BAL validation failed: account {addr:?} nonce changed to {expected} \
1274 during {phase} but the BAL declares no matching change at index {idx}"
1275 )));
1276 }
1277 }
1278 for canon_change in &canon.code_changes {
1279 let idx = canon_change.block_access_index;
1280 if find_exact_change_code(&supplied.code_changes, idx)
1281 != Some(&canon_change.new_code)
1282 {
1283 return Err(EvmError::Custom(format!(
1284 "BAL validation failed: account {addr:?} code changed during {phase} \
1285 but the BAL declares no matching change at index {idx}"
1286 )));
1287 }
1288 }
1289
1290 for &slot in &canon.storage_reads {
1292 if supplied.storage_reads.binary_search(&slot).is_err()
1293 && supplied
1294 .storage_changes
1295 .binary_search_by(|sc| sc.slot.cmp(&slot))
1296 .is_err()
1297 {
1298 return Err(EvmError::Custom(format!(
1299 "BAL validation failed: storage slot {slot} of account {addr:?} was \
1300 read during {phase} but is missing from BAL"
1301 )));
1302 }
1303 }
1304 }
1305
1306 let canonical: FxHashMap<Address, &AccountChanges> = phase_bal
1309 .accounts()
1310 .iter()
1311 .map(|a| (a.address, a))
1312 .collect();
1313 for supplied in bal.accounts() {
1314 let addr = supplied.address;
1315 let canon = canonical.get(&addr).copied();
1316 for sc in &supplied.storage_changes {
1317 if find_exact_change_storage(&sc.slot_changes, phase_idx).is_none() {
1318 continue;
1319 }
1320 let declared_canonically = canon.is_some_and(|c| {
1321 c.storage_changes
1322 .binary_search_by(|cs| cs.slot.cmp(&sc.slot))
1323 .ok()
1324 .and_then(|i| c.storage_changes.get(i))
1325 .is_some_and(|cs| {
1326 find_exact_change_storage(&cs.slot_changes, phase_idx).is_some()
1327 })
1328 });
1329 if !declared_canonically {
1330 return Err(EvmError::Custom(format!(
1331 "BAL validation failed: account {addr:?} declares a storage change \
1332 for slot {} at index {phase_idx} but {phase} did not write it",
1333 sc.slot
1334 )));
1335 }
1336 }
1337 if find_exact_change_balance(&supplied.balance_changes, phase_idx).is_some()
1338 && canon.is_none_or(|c| {
1339 find_exact_change_balance(&c.balance_changes, phase_idx).is_none()
1340 })
1341 {
1342 return Err(EvmError::Custom(format!(
1343 "BAL validation failed: account {addr:?} declares a balance change at \
1344 index {phase_idx} but {phase} did not change its balance"
1345 )));
1346 }
1347 if find_exact_change_nonce(&supplied.nonce_changes, phase_idx).is_some()
1348 && canon
1349 .is_none_or(|c| find_exact_change_nonce(&c.nonce_changes, phase_idx).is_none())
1350 {
1351 return Err(EvmError::Custom(format!(
1352 "BAL validation failed: account {addr:?} declares a nonce change at \
1353 index {phase_idx} but {phase} did not change its nonce"
1354 )));
1355 }
1356 if find_exact_change_code(&supplied.code_changes, phase_idx).is_some()
1357 && canon
1358 .is_none_or(|c| find_exact_change_code(&c.code_changes, phase_idx).is_none())
1359 {
1360 return Err(EvmError::Custom(format!(
1361 "BAL validation failed: account {addr:?} declares a code change at \
1362 index {phase_idx} but {phase} did not change its code"
1363 )));
1364 }
1365 }
1366
1367 for (addr, acct) in cache {
1369 if *addr == SYSTEM_ADDRESS {
1370 continue;
1371 }
1372 let supplied = match index
1373 .addr_to_idx
1374 .get(addr)
1375 .and_then(|i| bal.accounts().get(*i))
1376 {
1377 Some(a) => a,
1378 None if !acct.storage.is_empty() => {
1380 return Err(EvmError::Custom(format!(
1381 "BAL validation failed: account {addr:?} was accessed during {phase} \
1382 but is missing from BAL"
1383 )));
1384 }
1385 None => continue,
1386 };
1387 debug_assert!(
1390 supplied.storage_reads.windows(2).all(|w| w[0] < w[1]),
1391 "storage_reads must be strictly ascending for binary_search"
1392 );
1393 for key in acct.storage.keys() {
1394 let slot = ethrex_common::BigEndianHash::into_uint(key);
1395 if supplied.storage_reads.binary_search(&slot).is_err()
1396 && supplied
1397 .storage_changes
1398 .binary_search_by(|sc| sc.slot.cmp(&slot))
1399 .is_err()
1400 {
1401 return Err(EvmError::Custom(format!(
1402 "BAL validation failed: storage slot {slot} of account {addr:?} was \
1403 read during {phase} but is missing from BAL"
1404 )));
1405 }
1406 }
1407 }
1408 Ok(())
1409 }
1410
1411 #[cfg(feature = "rayon")]
1428 fn seed_db_from_bal(
1429 db: &mut GeneralizedDatabase,
1430 bal: &BlockAccessList,
1431 max_idx: u32,
1432 accounts_by_min_index: &[(u32, usize)],
1433 ) -> Result<(), EvmError> {
1434 let end = accounts_by_min_index.partition_point(|(min_idx, _)| *min_idx <= max_idx);
1435 let bal_accounts = bal.accounts();
1436 for &(_, acct_idx) in &accounts_by_min_index[..end] {
1437 seed_one_address_info_from_bal(db, bal, acct_idx, max_idx)
1438 .map_err(|e| EvmError::Custom(format!("seed_db_from_bal: {e}")))?;
1439
1440 let acct_changes = &bal_accounts[acct_idx];
1441 if acct_changes.storage_changes.is_empty() {
1442 continue;
1443 }
1444 let any_storage = acct_changes.storage_changes.iter().any(|sc| {
1445 sc.slot_changes
1446 .first()
1447 .is_some_and(|c| c.block_access_index <= max_idx)
1448 });
1449 if !any_storage {
1450 continue;
1451 }
1452 let addr = acct_changes.address;
1453 if !db.current_accounts_state.contains_key(&addr) {
1454 db.get_account(addr)
1455 .map_err(|e| EvmError::Custom(format!("seed storage: {e}")))?;
1456 }
1457 let acc = db
1458 .get_account_mut(addr)
1459 .map_err(|e| EvmError::Custom(format!("seed storage mut: {e}")))?;
1460 for sc in &acct_changes.storage_changes {
1461 if let Some(value) = post_value_at_or_before(sc, max_idx) {
1462 acc.storage
1463 .insert(ethrex_common::utils::u256_to_h256(sc.slot), value);
1464 }
1465 }
1466 }
1467 Ok(())
1468 }
1469
1470 #[cfg(feature = "rayon")]
1477 #[allow(clippy::too_many_arguments, clippy::type_complexity)]
1478 fn execute_block_parallel(
1479 block: &Block,
1480 txs_with_sender: &[(&Transaction, Address)],
1481 db: &mut GeneralizedDatabase,
1482 vm_type: VMType,
1483 bal: Arc<BlockAccessList>,
1484 merkleizer: Option<&Sender<Vec<AccountUpdate>>>,
1485 queue_length: &AtomicUsize,
1486 system_seed: Arc<CacheDB>,
1487 pre_block_bal: &BlockAccessList,
1488 crypto: &dyn Crypto,
1489 validation_index: Arc<BalAddressIndex>,
1490 stateless_validator: Option<&dyn StatelessValidator>,
1491 ) -> Result<
1492 (
1493 Vec<Receipt>,
1494 u64,
1495 FxHashSet<(Address, H256)>,
1496 FxHashSet<Address>,
1497 Vec<TxGasBreakdown>,
1498 ),
1499 EvmError,
1500 > {
1501 let store = db.store.clone();
1502 let header = &block.header;
1503 let n_txs = txs_with_sender.len();
1504 let chain_config = store.get_chain_config()?;
1509 let is_amsterdam = chain_config.is_amsterdam_activated(header.timestamp);
1510 let evm_config = EVMConfig::new_from_chain_config(&chain_config, header);
1513 let chain_id = chain_config.chain_id;
1514 let base_blob_fee_per_gas = get_base_fee_per_blob_gas(header.excess_blob_gas, &evm_config)?;
1516 debug_assert!(
1517 is_amsterdam,
1518 "execute_block_parallel invoked on non-Amsterdam block"
1519 );
1520
1521 if let Some(merkleizer) = merkleizer {
1525 let account_updates = Self::bal_to_account_updates(&bal, store.as_ref())?;
1526 merkleizer
1527 .send(account_updates)
1528 .map_err(|e| EvmError::Custom(format!("merkleizer send failed: {e}")))?;
1529 queue_length.fetch_add(1, Ordering::Relaxed);
1530 }
1531
1532 let mut unread_storage_reads: FxHashSet<(Address, H256)> = FxHashSet::default();
1535 let mut unaccessed_pure_accounts: FxHashSet<Address> = FxHashSet::default();
1538 for acct in bal.accounts() {
1539 for &slot in &acct.storage_reads {
1540 let key = ethrex_common::utils::u256_to_h256(slot);
1541 unread_storage_reads.insert((acct.address, key));
1542 }
1543 let is_pure = acct.storage_changes.is_empty()
1544 && acct.storage_reads.is_empty()
1545 && acct.balance_changes.is_empty()
1546 && acct.nonce_changes.is_empty()
1547 && acct.code_changes.is_empty();
1548 if is_pure {
1549 unaccessed_pure_accounts.insert(acct.address);
1550 }
1551 }
1552
1553 for canon in pre_block_bal.accounts() {
1560 if canon.address == SYSTEM_ADDRESS {
1561 continue;
1562 }
1563 unaccessed_pure_accounts.remove(&canon.address);
1564 }
1565
1566 unread_storage_reads.retain(|(addr, key)| {
1568 !system_seed
1569 .get(addr)
1570 .is_some_and(|a| a.storage.contains_key(key))
1571 });
1572
1573 let arc_bal = bal;
1575 let arc_idx = validation_index;
1576
1577 type TxExecResult = (
1591 usize,
1592 TxType,
1593 ExecutionReport,
1594 FxHashSet<Address>, Vec<(Address, H256)>, Vec<Address>, Option<EvmError>, );
1599
1600 check_minimum_block_work(
1608 txs_with_sender.iter().map(|(tx, sender)| (*tx, *sender)),
1609 chain_config.fork(header.timestamp),
1610 header.gas_limit,
1611 )?;
1612
1613 let completed_gas = CompletedGas::default();
1616
1617 let exec_results: Result<Vec<TxExecResult>, EvmError> = (0..n_txs)
1618 .into_par_iter()
1619 .map(|tx_idx| -> Result<_, EvmError> {
1620 completed_gas.check(header.gas_limit)?;
1621 let (tx, sender) = &txs_with_sender[tx_idx];
1622 let mut tx_db = GeneralizedDatabase::new_with_shared_base_and_capacity(
1624 store.clone(),
1625 system_seed.clone(),
1626 32,
1627 );
1628 tx_db.lazy_bal = Some(LazyBalCursor {
1629 bal: arc_bal.clone(),
1630 bal_index: u32::try_from(tx_idx + 1).unwrap_or(u32::MAX),
1631 index: arc_idx.clone(),
1632 });
1633 let mut stack_pool = Vec::with_capacity(8);
1636 let mut memory_pool = Vec::with_capacity(1);
1638
1639 tx_db.accessed_accounts =
1643 Some(FxHashSet::with_capacity_and_hasher(16, Default::default()));
1644
1645 tx_db.enable_bal_recording();
1650 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
1651 tx_db.set_bal_index(bal_index);
1652 if let Some(recorder) = tx_db.bal_recorder_mut() {
1656 recorder.record_touched_address(*sender);
1657 }
1658
1659 let report = LEVM::execute_tx_in_block(
1660 tx,
1661 *sender,
1662 header,
1663 &mut tx_db,
1664 vm_type,
1665 base_blob_fee_per_gas,
1666 &mut stack_pool,
1667 &mut memory_pool,
1668 false,
1669 crypto,
1670 evm_config,
1671 chain_id,
1672 stateless_validator,
1673 )?;
1674 let mut report = report;
1675
1676 report.output = Bytes::new();
1680
1681 completed_gas.record(
1683 report.gas_used.saturating_sub(report.state_gas_used),
1684 report.state_gas_used,
1685 header.gas_limit,
1686 )?;
1687
1688 let current_state = std::mem::take(&mut tx_db.current_accounts_state);
1689 let codes = std::mem::take(&mut tx_db.codes);
1690 let mut tracked = tx_db.accessed_accounts.take().unwrap_or_default();
1691 let (shadow_touched, shadow_reads, tx_initial) = tx_db
1698 .bal_recorder
1699 .take()
1700 .map(|mut r| {
1701 (
1702 r.take_touched_addresses(),
1703 r.take_storage_reads(),
1704 r.take_tx_initial_storage(),
1705 )
1706 })
1707 .unwrap_or_default();
1708 let pure_reads: FxHashSet<(Address, U256)> = shadow_reads
1717 .iter()
1718 .copied()
1719 .filter(|k| !tx_initial.contains_key(k))
1720 .collect();
1721
1722 let mut reads_satisfied: Vec<(Address, H256)> =
1735 Vec::with_capacity(current_state.len() * 4);
1736 let mut destroyed: Vec<Address> = Vec::new();
1740 for (addr, acct) in ¤t_state {
1741 if matches!(
1742 acct.status,
1743 AccountStatus::Destroyed | AccountStatus::DestroyedModified
1744 ) {
1745 destroyed.push(*addr);
1746 } else {
1747 for key in acct.storage.keys() {
1748 reads_satisfied.push((*addr, *key));
1749 }
1750 }
1751 }
1752 for (addr, slot) in &shadow_reads {
1760 if destroyed.contains(addr) {
1761 reads_satisfied.push((*addr, ethrex_common::utils::u256_to_h256(*slot)));
1762 }
1763 }
1764
1765 let deferred_bal_err: Option<EvmError> = (|| -> Result<(), EvmError> {
1771 let bal_idx = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
1772 let seed_idx = u32::try_from(tx_idx).unwrap_or(u32::MAX);
1773 Self::validate_tx_execution(
1774 bal_idx,
1775 seed_idx,
1776 ¤t_state,
1777 &codes,
1778 &arc_bal,
1779 &arc_idx,
1780 &system_seed,
1781 &store,
1782 &pure_reads,
1783 &tx_initial,
1784 )
1785 .map_err(|e| match e {
1786 BalValidationError::Database(msg) => {
1790 EvmError::DB(format!("BAL validation for tx {tx_idx}: {msg}"))
1791 }
1792 BalValidationError::Mismatch(msg) => EvmError::Custom(format!(
1793 "BAL validation failed for tx {tx_idx}: {msg}"
1794 )),
1795 })?;
1796
1797 for addr in &shadow_touched {
1799 if !arc_idx.addr_to_idx.contains_key(addr) {
1800 return Err(EvmError::Custom(format!(
1801 "BAL validation failed for tx {tx_idx}: account {addr:?} was \
1802 accessed during execution but is missing from BAL"
1803 )));
1804 }
1805 }
1806 for (addr, slot) in &shadow_reads {
1807 let Some(&bal_acct_idx) = arc_idx.addr_to_idx.get(addr) else {
1808 continue;
1810 };
1811 let acct = &arc_bal.accounts()[bal_acct_idx];
1812 let in_changes = acct
1813 .storage_changes
1814 .binary_search_by(|sc| sc.slot.cmp(slot))
1815 .is_ok();
1816 debug_assert!(
1823 acct.storage_reads.windows(2).all(|w| w[0] < w[1]),
1824 "storage_reads must be strictly ascending for binary_search"
1825 );
1826 let in_reads = acct.storage_reads.binary_search(slot).is_ok();
1827 if !in_changes && !in_reads {
1828 return Err(EvmError::Custom(format!(
1829 "BAL validation failed for tx {tx_idx}: storage slot {slot} of \
1830 account {addr:?} was read during execution but is missing from \
1831 BAL (no storage_changes or storage_reads entry)"
1832 )));
1833 }
1834 }
1835 Ok(())
1836 })()
1837 .err();
1838
1839 drop(current_state);
1840 drop(codes);
1841
1842 tracked.extend(shadow_touched.iter().copied());
1851
1852 Ok((
1853 tx_idx,
1854 tx.tx_type(),
1855 report,
1856 tracked,
1857 reads_satisfied,
1858 destroyed,
1859 deferred_bal_err,
1860 ))
1861 })
1862 .collect();
1863
1864 let mut exec_results = exec_results?;
1865
1866 exec_results.sort_unstable_by_key(|(idx, _, _, _, _, _, _)| *idx);
1873
1874 let mut block_regular_gas_used = 0_u64;
1879 let mut block_state_gas_used = 0_u64;
1880 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(exec_results.len());
1881 for (tx_idx, _, report, _, _, _, _) in &exec_results {
1882 let (tx, _tx_sender) = txs_with_sender
1883 .get(*tx_idx)
1884 .ok_or_else(|| EvmError::Custom(format!("tx index {tx_idx} out of bounds")))?;
1885 if is_amsterdam {
1886 check_2d_gas_allowance(
1887 tx,
1888 block_regular_gas_used,
1889 block_state_gas_used,
1890 header.gas_limit,
1891 )?;
1892 }
1893
1894 tx_gas_breakdowns.push(TxGasBreakdown::from_report(
1895 *tx_idx,
1896 tx.hash(crypto),
1897 report,
1898 ));
1899
1900 let tx_state_gas = report.state_gas_used;
1901 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
1902 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
1903 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
1904 }
1905 let block_gas_used = block_regular_gas_used.max(block_state_gas_used);
1906 if block_gas_used > header.gas_limit {
1908 return Err(EvmError::Transaction(format!(
1909 "Gas allowance exceeded: Block gas used overflow: \
1910 block_gas_used {block_gas_used} > block_gas_limit {}",
1911 header.gas_limit
1912 )));
1913 }
1914
1915 Self::validate_bal_covers(
1918 &system_seed,
1919 pre_block_bal,
1920 &arc_bal,
1921 &arc_idx,
1922 "system calls",
1923 PRE_BLOCK_BAL_INDEX,
1924 )?;
1925
1926 for (_, _, _, _, _, _, deferred) in &mut exec_results {
1927 if let Some(err) = deferred.take() {
1928 return Err(err);
1929 }
1930 }
1931
1932 for (_, _, _, tracked_accounts, reads_satisfied, _, _) in &exec_results {
1941 if !unread_storage_reads.is_empty() {
1942 for pair in reads_satisfied {
1943 unread_storage_reads.remove(pair);
1944 }
1945 }
1946 if !unaccessed_pure_accounts.is_empty() {
1950 unaccessed_pure_accounts.remove(&header.coinbase);
1951 for addr in tracked_accounts {
1952 unaccessed_pure_accounts.remove(addr);
1953 }
1954 }
1955 }
1956
1957 let mut receipts = Vec::with_capacity(n_txs);
1959 let mut cumulative_gas_used = 0_u64;
1960 for (_, tx_type, report, _, _, _, _) in exec_results {
1961 cumulative_gas_used += report.gas_spent;
1962 let mut receipt = Receipt::new(
1963 tx_type,
1964 matches!(report.result, TxResult::Success),
1965 cumulative_gas_used,
1966 report.logs,
1967 );
1968 if tx_type == TxType::Frame {
1969 receipt.payer = report.payer_address;
1970 receipt.frame_receipts = frame_receipts_from(report.frame_results);
1971 }
1972 receipts.push(receipt);
1973 }
1974
1975 Ok((
1976 receipts,
1977 block_gas_used,
1978 unread_storage_reads,
1979 unaccessed_pure_accounts,
1980 tx_gas_breakdowns,
1981 ))
1982 }
1983
1984 #[cfg(feature = "rayon")]
1987 fn seeded_balance(
1988 seed_idx: u32,
1989 acct: ðrex_common::types::block_access_list::AccountChanges,
1990 system_seed: &CacheDB,
1991 store: &Arc<dyn Database>,
1992 ) -> Result<U256, BalValidationError> {
1993 let pos = acct
1994 .balance_changes
1995 .partition_point(|c| c.block_access_index <= seed_idx);
1996 if pos > 0 {
1997 Ok(acct.balance_changes[pos - 1].post_balance)
1998 } else if let Some(a) = system_seed.get(&acct.address) {
1999 Ok(a.info.balance)
2000 } else {
2001 store
2002 .get_account_state(acct.address)
2003 .map(|a| a.balance)
2004 .map_err(|e| {
2005 BalValidationError::Database(format!(
2006 "DB error reading balance for {:?}: {e}",
2007 acct.address
2008 ))
2009 })
2010 }
2011 }
2012
2013 #[cfg(feature = "rayon")]
2016 fn seeded_code_hash(
2017 seed_idx: u32,
2018 acct: ðrex_common::types::block_access_list::AccountChanges,
2019 system_seed: &CacheDB,
2020 store: &Arc<dyn Database>,
2021 ) -> Result<H256, BalValidationError> {
2022 let pos = acct
2023 .code_changes
2024 .partition_point(|c| c.block_access_index <= seed_idx);
2025 if pos > 0 {
2026 let seeded_code = &acct.code_changes[pos - 1].new_code;
2027 Ok(if seeded_code.is_empty() {
2028 *EMPTY_KECCAK_HASH
2029 } else {
2030 ethrex_common::utils::keccak(seeded_code)
2031 })
2032 } else if let Some(a) = system_seed.get(&acct.address) {
2033 Ok(a.info.code_hash)
2034 } else {
2035 store
2036 .get_account_state(acct.address)
2037 .map(|a| a.code_hash)
2038 .map_err(|e| {
2039 BalValidationError::Database(format!(
2040 "DB error reading account state for {:?}: {e}",
2041 acct.address
2042 ))
2043 })
2044 }
2045 }
2046
2047 #[cfg(feature = "rayon")]
2050 fn seeded_nonce(
2051 seed_idx: u32,
2052 acct: ðrex_common::types::block_access_list::AccountChanges,
2053 system_seed: &CacheDB,
2054 store: &Arc<dyn Database>,
2055 ) -> Result<u64, BalValidationError> {
2056 let pos = acct
2057 .nonce_changes
2058 .partition_point(|c| c.block_access_index <= seed_idx);
2059 if pos > 0 {
2060 Ok(acct.nonce_changes[pos - 1].post_nonce)
2061 } else if let Some(a) = system_seed.get(&acct.address) {
2062 Ok(a.info.nonce)
2063 } else {
2064 store
2065 .get_account_state(acct.address)
2066 .map(|a| a.nonce)
2067 .map_err(|e| {
2068 BalValidationError::Database(format!(
2069 "DB error reading nonce for {:?}: {e}",
2070 acct.address
2071 ))
2072 })
2073 }
2074 }
2075
2076 #[cfg(feature = "rayon")]
2084 fn seeded_account_triple(
2085 seed_idx: u32,
2086 acct: ðrex_common::types::block_access_list::AccountChanges,
2087 system_seed: &CacheDB,
2088 store: &Arc<dyn Database>,
2089 ) -> Result<(U256, u64, H256), BalValidationError> {
2090 let bpos = acct
2091 .balance_changes
2092 .partition_point(|c| c.block_access_index <= seed_idx);
2093 let npos = acct
2094 .nonce_changes
2095 .partition_point(|c| c.block_access_index <= seed_idx);
2096 let cpos = acct
2097 .code_changes
2098 .partition_point(|c| c.block_access_index <= seed_idx);
2099
2100 let mut balance = (bpos > 0).then(|| acct.balance_changes[bpos - 1].post_balance);
2101 let mut nonce = (npos > 0).then(|| acct.nonce_changes[npos - 1].post_nonce);
2102 let mut code_hash = (cpos > 0).then(|| {
2103 let c = &acct.code_changes[cpos - 1].new_code;
2104 if c.is_empty() {
2105 *EMPTY_KECCAK_HASH
2106 } else {
2107 ethrex_common::utils::keccak(c)
2108 }
2109 });
2110
2111 if let Some(a) = system_seed.get(&acct.address) {
2113 balance.get_or_insert(a.info.balance);
2114 nonce.get_or_insert(a.info.nonce);
2115 code_hash.get_or_insert(a.info.code_hash);
2116 }
2117
2118 if balance.is_none() || nonce.is_none() || code_hash.is_none() {
2120 let s = store.get_account_state(acct.address).map_err(|e| {
2121 BalValidationError::Database(format!(
2122 "DB error reading account state for {:?}: {e}",
2123 acct.address
2124 ))
2125 })?;
2126 balance.get_or_insert(s.balance);
2127 nonce.get_or_insert(s.nonce);
2128 code_hash.get_or_insert(s.code_hash);
2129 }
2130
2131 Ok((
2132 balance.unwrap_or_default(),
2133 nonce.unwrap_or_default(),
2134 code_hash.unwrap_or(*EMPTY_KECCAK_HASH),
2135 ))
2136 }
2137
2138 #[cfg(feature = "rayon")]
2143 fn seeded_storage_pre_value(
2144 addr: Address,
2145 key: H256,
2146 slot: U256,
2147 system_seed: &CacheDB,
2148 store: &Arc<dyn Database>,
2149 tx_initial: &FxHashMap<(Address, U256), U256>,
2150 ) -> Result<U256, BalValidationError> {
2151 if let Some(v) = tx_initial.get(&(addr, slot)) {
2152 return Ok(*v);
2153 }
2154 Self::storage_from_seed_or_store(addr, key, slot, system_seed, store)
2155 }
2156
2157 #[cfg(feature = "rayon")]
2160 fn storage_from_seed_or_store(
2161 addr: Address,
2162 key: H256,
2163 slot: U256,
2164 system_seed: &CacheDB,
2165 store: &Arc<dyn Database>,
2166 ) -> Result<U256, BalValidationError> {
2167 if let Some(v) = system_seed
2168 .get(&addr)
2169 .and_then(|a| a.storage.get(&key))
2170 .copied()
2171 {
2172 Ok(v)
2173 } else {
2174 store.get_storage_value(addr, key).map_err(|e| {
2175 BalValidationError::Database(format!(
2176 "DB error reading storage for {addr:?} slot {slot}: {e}"
2177 ))
2178 })
2179 }
2180 }
2181
2182 #[cfg(feature = "rayon")]
2189 fn seeded_storage(
2190 seed_idx: u32,
2191 sc: ðrex_common::types::block_access_list::SlotChange,
2192 addr: Address,
2193 key: H256,
2194 system_seed: &CacheDB,
2195 store: &Arc<dyn Database>,
2196 tx_initial: &FxHashMap<(Address, U256), U256>,
2197 ) -> Result<U256, BalValidationError> {
2198 if let Some(v) = tx_initial.get(&(addr, sc.slot)) {
2199 return Ok(*v);
2200 }
2201 let pos = sc
2202 .slot_changes
2203 .partition_point(|c| c.block_access_index <= seed_idx);
2204 if pos > 0 {
2205 Ok(sc.slot_changes[pos - 1].post_value)
2206 } else {
2207 Self::storage_from_seed_or_store(addr, key, sc.slot, system_seed, store)
2208 }
2209 }
2210
2211 #[cfg(feature = "rayon")]
2239 #[allow(clippy::too_many_arguments)]
2240 pub fn validate_tx_execution(
2241 bal_idx: u32,
2242 seed_idx: u32,
2243 current_state: &FxHashMap<Address, LevmAccount>,
2244 codes: &FxHashMap<H256, Code>,
2245 bal: &BlockAccessList,
2246 index: &BalAddressIndex,
2247 system_seed: &CacheDB,
2248 store: &Arc<dyn Database>,
2249 pure_reads: &FxHashSet<(Address, U256)>,
2250 tx_initial: &FxHashMap<(Address, U256), U256>,
2251 ) -> Result<(), BalValidationError> {
2252 if let Some(active_accounts) = index.tx_to_accounts.get(&bal_idx) {
2256 for &acct_inner_idx in active_accounts {
2257 let acct = &bal.accounts()[acct_inner_idx];
2258 let addr = acct.address;
2259 let actual = current_state.get(&addr);
2260 let mut seeded_info: Option<(U256, u64, H256)> = None;
2264
2265 if let Some(expected) = find_exact_change_balance(&acct.balance_changes, bal_idx) {
2267 match actual {
2268 Some(a) if a.info.balance == expected => {
2269 if seeded_info.is_none() {
2273 seeded_info = Some(Self::seeded_account_triple(
2274 seed_idx,
2275 acct,
2276 system_seed,
2277 store,
2278 )?);
2279 }
2280 let seeded = seeded_info.unwrap_or_default().0;
2281 if expected == seeded {
2282 return Err(BalValidationError::Mismatch(format!(
2283 "account {addr:?} has spurious no-op BAL balance change \
2284 at index {bal_idx}: post==pre=={expected}"
2285 )));
2286 }
2287 }
2288 Some(a) => {
2289 return Err(BalValidationError::Mismatch(format!(
2290 "account {addr:?} balance mismatch at index {bal_idx}: BAL={expected}, exec={} (diff={})",
2291 a.info.balance,
2292 describe_balance_diff(expected, a.info.balance),
2293 )));
2294 }
2295 None => {
2296 let seeded = Self::seeded_balance(seed_idx, acct, system_seed, store)?;
2303 if expected == seeded {
2304 return Err(BalValidationError::Mismatch(format!(
2305 "account {addr:?} has spurious no-op BAL balance change \
2306 at index {bal_idx} for an account absent from execution \
2307 state: post==pre=={expected}"
2308 )));
2309 }
2310 let all_bal_indices: Vec<u32> = acct
2312 .balance_changes
2313 .iter()
2314 .map(|c| c.block_access_index)
2315 .collect();
2316 let all_nonce_indices: Vec<u32> = acct
2317 .nonce_changes
2318 .iter()
2319 .map(|c| c.block_access_index)
2320 .collect();
2321 let all_storage_indices: Vec<(u32, u64)> = acct
2322 .storage_changes
2323 .iter()
2324 .flat_map(|sc| {
2325 sc.slot_changes
2326 .iter()
2327 .map(|c| (c.block_access_index, sc.slot.low_u64()))
2328 })
2329 .collect();
2330 let code_indices: Vec<u32> = acct
2331 .code_changes
2332 .iter()
2333 .map(|c| c.block_access_index)
2334 .collect();
2335 return Err(BalValidationError::Mismatch(format!(
2336 "account {addr:?} has BAL balance change at {bal_idx} \
2337 but not in execution state (expected={expected}, pre={seeded}, \
2338 all_bal_idx={all_bal_indices:?}, nonce_idx={all_nonce_indices:?}, \
2339 storage_idx={all_storage_indices:?}, code_idx={code_indices:?})"
2340 )));
2341 }
2342 }
2343 }
2344
2345 if let Some(expected) = find_exact_change_nonce(&acct.nonce_changes, bal_idx) {
2347 match actual {
2348 Some(a) if a.info.nonce == expected => {
2349 if seeded_info.is_none() {
2354 seeded_info = Some(Self::seeded_account_triple(
2355 seed_idx,
2356 acct,
2357 system_seed,
2358 store,
2359 )?);
2360 }
2361 let seeded = seeded_info.unwrap_or_default().1;
2362 if expected == seeded {
2363 return Err(BalValidationError::Mismatch(format!(
2364 "account {addr:?} has spurious no-op BAL nonce change \
2365 at index {bal_idx}: post==pre=={expected}"
2366 )));
2367 }
2368 }
2369 Some(a) => {
2370 return Err(BalValidationError::Mismatch(format!(
2371 "account {addr:?} nonce mismatch at index {bal_idx}: BAL={expected}, exec={}",
2372 a.info.nonce
2373 )));
2374 }
2375 None => {
2376 let seeded = Self::seeded_nonce(seed_idx, acct, system_seed, store)?;
2380 if expected == seeded {
2381 return Err(BalValidationError::Mismatch(format!(
2382 "account {addr:?} has spurious no-op BAL nonce change \
2383 at index {bal_idx} for an account absent from execution \
2384 state: post==pre=={expected}"
2385 )));
2386 }
2387 return Err(BalValidationError::Mismatch(format!(
2388 "account {addr:?} has BAL nonce change at {bal_idx} \
2389 but not in execution state (expected={expected}, pre={seeded})"
2390 )));
2391 }
2392 }
2393 }
2394
2395 if let Some(expected_code) = find_exact_change_code(&acct.code_changes, bal_idx) {
2397 match actual {
2398 Some(a) => {
2399 let actual_code = if let Some(c) = codes.get(&a.info.code_hash) {
2400 c.code_bytes()
2401 } else {
2402 let c = store.get_account_code(a.info.code_hash).map_err(|e| {
2403 BalValidationError::Database(format!(
2404 "DB error reading account code for {addr:?}: {e}"
2405 ))
2406 })?;
2407 c.code_bytes()
2408 };
2409 if actual_code != *expected_code {
2410 return Err(BalValidationError::Mismatch(format!(
2411 "account {addr:?} code mismatch at index {bal_idx}"
2412 )));
2413 }
2414 if seeded_info.is_none() {
2417 seeded_info = Some(Self::seeded_account_triple(
2418 seed_idx,
2419 acct,
2420 system_seed,
2421 store,
2422 )?);
2423 }
2424 let seeded_hash = seeded_info.unwrap_or_default().2;
2425 if a.info.code_hash == seeded_hash {
2426 return Err(BalValidationError::Mismatch(format!(
2427 "account {addr:?} has spurious no-op BAL code change \
2428 at index {bal_idx}"
2429 )));
2430 }
2431 }
2432 None => {
2433 let code_hash = if let Some(a) = system_seed.get(&addr) {
2437 a.info.code_hash
2438 } else {
2439 store
2440 .get_account_state(addr)
2441 .map(|a| a.code_hash)
2442 .map_err(|e| {
2443 BalValidationError::Database(format!(
2444 "DB error reading account state for {addr:?}: {e}"
2445 ))
2446 })?
2447 };
2448 let pre_code = if let Some(c) = codes.get(&code_hash) {
2449 c.code_bytes()
2450 } else {
2451 let c = store.get_account_code(code_hash).map_err(|e| {
2452 BalValidationError::Database(format!(
2453 "DB error reading account code for hash \
2454 {code_hash:?}: {e}"
2455 ))
2456 })?;
2457 c.code_bytes()
2458 };
2459 if *expected_code == pre_code {
2460 return Err(BalValidationError::Mismatch(format!(
2464 "account {addr:?} has spurious no-op BAL code change \
2465 at index {bal_idx} for an account absent from execution state"
2466 )));
2467 }
2468 return Err(BalValidationError::Mismatch(format!(
2469 "account {addr:?} has BAL code change at {bal_idx} \
2470 but not in execution state"
2471 )));
2472 }
2473 }
2474 }
2475
2476 for sc in &acct.storage_changes {
2478 if let Some(expected_value) =
2479 find_exact_change_storage(&sc.slot_changes, bal_idx)
2480 {
2481 let key = ethrex_common::utils::u256_to_h256(sc.slot);
2482 let actual_value = actual.and_then(|a| a.storage.get(&key)).copied();
2483 if actual_value != Some(expected_value) {
2484 if actual.is_none() || actual_value.is_none() {
2490 let seeded = Self::seeded_storage(
2491 seed_idx,
2492 sc,
2493 addr,
2494 key,
2495 system_seed,
2496 store,
2497 tx_initial,
2498 )?;
2499 if expected_value == seeded {
2500 return Err(BalValidationError::Mismatch(format!(
2501 "account {addr:?} has spurious no-op BAL storage \
2502 change for slot {} at index {bal_idx} for a slot \
2503 absent from execution state: post==pre=={expected_value}",
2504 sc.slot
2505 )));
2506 }
2507 }
2508 return Err(BalValidationError::Mismatch(format!(
2509 "account {addr:?} storage slot {} mismatch at index {bal_idx}: \
2510 BAL={expected_value}, exec={actual_value:?}",
2511 sc.slot
2512 )));
2513 }
2514 let seeded = Self::seeded_storage(
2518 seed_idx,
2519 sc,
2520 addr,
2521 key,
2522 system_seed,
2523 store,
2524 tx_initial,
2525 )?;
2526 if expected_value == seeded {
2527 return Err(BalValidationError::Mismatch(format!(
2528 "account {addr:?} has spurious no-op BAL storage change for \
2529 slot {} at index {bal_idx}",
2530 sc.slot
2531 )));
2532 }
2533 }
2534 }
2535 }
2536 }
2537
2538 for (addr, account) in current_state {
2541 if account.is_unmodified() {
2542 continue;
2543 }
2544
2545 let Some(&bal_acct_idx) = index.addr_to_idx.get(addr) else {
2546 let pre = system_seed
2550 .get(addr)
2551 .map(|a| (a.info.balance, a.info.nonce, a.info.code_hash))
2552 .or_else(|| {
2553 store
2554 .get_account_state(*addr)
2555 .ok()
2556 .map(|a| (a.balance, a.nonce, a.code_hash))
2557 })
2558 .unwrap_or_default();
2559 let post = (
2560 account.info.balance,
2561 account.info.nonce,
2562 account.info.code_hash,
2563 );
2564 if pre != post {
2565 return Err(BalValidationError::Mismatch(format!(
2566 "account {addr:?} was modified by execution but is absent from BAL"
2567 )));
2568 }
2569 continue;
2570 };
2571
2572 let acct = &bal.accounts()[bal_acct_idx];
2573
2574 if !has_exact_change_balance(&acct.balance_changes, bal_idx) {
2576 let seeded = Self::seeded_balance(seed_idx, acct, system_seed, store)?;
2577 if account.info.balance != seeded {
2578 return Err(BalValidationError::Mismatch(format!(
2579 "account {addr:?} balance changed by execution ({}) but BAL has no \
2580 balance change at index {bal_idx} (seeded={seeded})",
2581 account.info.balance
2582 )));
2583 }
2584 }
2585
2586 if !has_exact_change_nonce(&acct.nonce_changes, bal_idx) {
2588 let seeded = Self::seeded_nonce(seed_idx, acct, system_seed, store)?;
2589 if account.info.nonce != seeded {
2590 return Err(BalValidationError::Mismatch(format!(
2591 "account {addr:?} nonce changed by execution ({}) but BAL has no \
2592 nonce change at index {bal_idx} (seeded={seeded})",
2593 account.info.nonce
2594 )));
2595 }
2596 }
2597
2598 if !has_exact_change_code(&acct.code_changes, bal_idx) {
2602 let seeded_hash = Self::seeded_code_hash(seed_idx, acct, system_seed, store)?;
2603 if account.info.code_hash != seeded_hash {
2604 return Err(BalValidationError::Mismatch(format!(
2605 "account {addr:?} code changed by execution but BAL has no \
2606 code change at index {bal_idx} (seeded_hash={seeded_hash:?})"
2607 )));
2608 }
2609 }
2610
2611 for (key_h256, &value) in &account.storage {
2613 let slot_u256 = u256_from_big_endian_const(key_h256.0);
2614 let pos = acct
2616 .storage_changes
2617 .partition_point(|sc| sc.slot < slot_u256);
2618 if pos < acct.storage_changes.len() && acct.storage_changes[pos].slot == slot_u256 {
2619 let sc = &acct.storage_changes[pos];
2620 if !has_exact_change_storage(&sc.slot_changes, bal_idx) {
2621 let seeded = Self::seeded_storage(
2626 seed_idx,
2627 sc,
2628 *addr,
2629 *key_h256,
2630 system_seed,
2631 store,
2632 tx_initial,
2633 )?;
2634 if value != seeded {
2635 return Err(BalValidationError::Mismatch(format!(
2636 "account {addr:?} storage slot {slot_u256} changed by \
2637 execution ({value}) but BAL has no change at index \
2638 {bal_idx} (seeded={seeded})"
2639 )));
2640 }
2641 }
2642 }
2643 else if !pure_reads.contains(&(*addr, slot_u256)) {
2657 let pre_value = Self::seeded_storage_pre_value(
2662 *addr,
2663 *key_h256,
2664 slot_u256,
2665 system_seed,
2666 store,
2667 tx_initial,
2668 )?;
2669 if value != pre_value {
2670 let declared_as_read = acct.storage_reads.contains(&slot_u256);
2671 return Err(BalValidationError::Mismatch(format!(
2672 "account {addr:?} storage slot {slot_u256} was written by \
2673 execution ({value}) but has no storage_changes entry at index \
2674 {bal_idx} (pre_value={pre_value}, declared_as_read={declared_as_read})"
2675 )));
2676 }
2677 }
2678 }
2679 }
2680
2681 Ok(())
2682 }
2683
2684 #[cfg(feature = "rayon")]
2696 fn validate_bal_withdrawal_index(
2697 db: &GeneralizedDatabase,
2698 bal: &BlockAccessList,
2699 withdrawal_idx: u32,
2700 index: &BalAddressIndex,
2701 ) -> Result<(), EvmError> {
2702 for acct in bal.accounts() {
2705 let addr = acct.address;
2706 let actual = db.current_accounts_state.get(&addr);
2707
2708 if let Some(expected) = find_exact_change_balance(&acct.balance_changes, withdrawal_idx)
2710 {
2711 match actual {
2712 Some(a) if a.info.balance == expected => {}
2713 Some(a) => {
2714 return Err(EvmError::Custom(format!(
2715 "BAL validation failed for withdrawal: account {addr:?} balance \
2716 mismatch at index {withdrawal_idx}: BAL={expected}, actual={}",
2717 a.info.balance
2718 )));
2719 }
2720 None => {
2721 return Err(EvmError::Custom(format!(
2722 "BAL validation failed for withdrawal: account {addr:?} has \
2723 balance change at index {withdrawal_idx} but was not touched \
2724 by withdrawal/request phase"
2725 )));
2726 }
2727 }
2728 }
2729
2730 if let Some(expected) = find_exact_change_nonce(&acct.nonce_changes, withdrawal_idx) {
2732 match actual {
2733 Some(a) if a.info.nonce == expected => {}
2734 Some(a) => {
2735 return Err(EvmError::Custom(format!(
2736 "BAL validation failed for withdrawal: account {addr:?} nonce \
2737 mismatch at index {withdrawal_idx}: BAL={expected}, actual={}",
2738 a.info.nonce
2739 )));
2740 }
2741 None => {
2742 return Err(EvmError::Custom(format!(
2743 "BAL validation failed for withdrawal: account {addr:?} has \
2744 nonce change at index {withdrawal_idx} but was not touched \
2745 by withdrawal/request phase"
2746 )));
2747 }
2748 }
2749 }
2750
2751 if let Some(expected_code) = find_exact_change_code(&acct.code_changes, withdrawal_idx)
2753 {
2754 let code_hash = if expected_code.is_empty() {
2755 *EMPTY_KECCAK_HASH
2756 } else {
2757 ethrex_common::utils::keccak(expected_code)
2758 };
2759 match actual {
2760 Some(a) if a.info.code_hash == code_hash => {}
2761 Some(_) => {
2762 return Err(EvmError::Custom(format!(
2763 "BAL validation failed for withdrawal: account {addr:?} code \
2764 mismatch at index {withdrawal_idx}"
2765 )));
2766 }
2767 None => {
2768 return Err(EvmError::Custom(format!(
2769 "BAL validation failed for withdrawal: account {addr:?} has \
2770 code change at index {withdrawal_idx} but was not touched \
2771 by withdrawal/request phase"
2772 )));
2773 }
2774 }
2775 }
2776
2777 for sc in &acct.storage_changes {
2779 if let Some(expected_value) =
2780 find_exact_change_storage(&sc.slot_changes, withdrawal_idx)
2781 {
2782 let key = ethrex_common::utils::u256_to_h256(sc.slot);
2783 let actual_value = actual.and_then(|a| a.storage.get(&key)).copied();
2784 if actual_value != Some(expected_value) {
2785 return Err(EvmError::Custom(format!(
2786 "BAL validation failed for withdrawal: account {addr:?} storage \
2787 slot {} mismatch at index {withdrawal_idx}: BAL={expected_value}, \
2788 actual={actual_value:?}",
2789 sc.slot
2790 )));
2791 }
2792 }
2793 }
2794 }
2795
2796 for (addr, account) in &db.current_accounts_state {
2799 if account.is_unmodified() {
2800 continue;
2801 }
2802
2803 let Some(&bal_acct_idx) = index.addr_to_idx.get(addr) else {
2804 let pre_state = db.store.get_account_state(*addr).map_err(|e| {
2808 EvmError::Custom(format!(
2809 "BAL validation failed for withdrawal: db error reading \
2810 account {addr:?}: {e}"
2811 ))
2812 })?;
2813 let pre = (pre_state.balance, pre_state.nonce, pre_state.code_hash);
2814 let post = (
2815 account.info.balance,
2816 account.info.nonce,
2817 account.info.code_hash,
2818 );
2819 if pre != post {
2820 return Err(EvmError::Custom(format!(
2821 "BAL validation failed for withdrawal: account {addr:?} was modified \
2822 during withdrawal/request phase but is absent from BAL"
2823 )));
2824 }
2825 for (key_h256, &value) in &account.storage {
2828 let pre_value = db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
2829 EvmError::Custom(format!(
2830 "BAL validation failed for withdrawal: db error reading \
2831 storage {addr:?}[{}]: {e}",
2832 u256_from_big_endian_const(key_h256.0)
2833 ))
2834 })?;
2835 if value != pre_value {
2836 return Err(EvmError::Custom(format!(
2837 "BAL validation failed for withdrawal: account {addr:?} storage \
2838 slot {} changed during withdrawal/request phase but is absent \
2839 from BAL",
2840 u256_from_big_endian_const(key_h256.0)
2841 )));
2842 }
2843 }
2844 continue;
2845 };
2846
2847 let acct = &bal.accounts()[bal_acct_idx];
2848
2849 if !has_exact_change_balance(&acct.balance_changes, withdrawal_idx) {
2852 let seeded = match acct.balance_changes.last() {
2853 Some(c) => c.post_balance,
2854 None => {
2855 db.store
2856 .get_account_state(*addr)
2857 .map_err(|e| {
2858 EvmError::Custom(format!(
2859 "BAL validation failed for withdrawal: db error reading \
2860 account {addr:?}: {e}"
2861 ))
2862 })?
2863 .balance
2864 }
2865 };
2866 if account.info.balance != seeded {
2867 return Err(EvmError::Custom(format!(
2868 "BAL validation failed for withdrawal: account {addr:?} balance \
2869 changed during withdrawal/request phase ({}) but BAL has no \
2870 balance change at index {withdrawal_idx} (last_bal={seeded})",
2871 account.info.balance
2872 )));
2873 }
2874 }
2875
2876 if !has_exact_change_nonce(&acct.nonce_changes, withdrawal_idx) {
2878 let seeded = match acct.nonce_changes.last() {
2879 Some(c) => c.post_nonce,
2880 None => {
2881 db.store
2882 .get_account_state(*addr)
2883 .map_err(|e| {
2884 EvmError::Custom(format!(
2885 "BAL validation failed for withdrawal: db error reading \
2886 account {addr:?}: {e}"
2887 ))
2888 })?
2889 .nonce
2890 }
2891 };
2892 if account.info.nonce != seeded {
2893 return Err(EvmError::Custom(format!(
2894 "BAL validation failed for withdrawal: account {addr:?} nonce \
2895 changed during withdrawal/request phase ({}) but BAL has no \
2896 nonce change at index {withdrawal_idx} (last_bal={seeded})",
2897 account.info.nonce
2898 )));
2899 }
2900 }
2901
2902 if !has_exact_change_code(&acct.code_changes, withdrawal_idx) {
2904 let seeded_hash = match acct.code_changes.last() {
2905 Some(c) if c.new_code.is_empty() => *EMPTY_KECCAK_HASH,
2906 Some(c) => ethrex_common::utils::keccak(&c.new_code),
2907 None => {
2908 db.store
2909 .get_account_state(*addr)
2910 .map_err(|e| {
2911 EvmError::Custom(format!(
2912 "BAL validation failed for withdrawal: db error reading \
2913 account {addr:?}: {e}"
2914 ))
2915 })?
2916 .code_hash
2917 }
2918 };
2919 if account.info.code_hash != seeded_hash {
2920 return Err(EvmError::Custom(format!(
2921 "BAL validation failed for withdrawal: account {addr:?} code \
2922 changed during withdrawal/request phase but BAL has no \
2923 code change at index {withdrawal_idx} \
2924 (actual={:?}, last_bal={seeded_hash:?})",
2925 account.info.code_hash
2926 )));
2927 }
2928 }
2929
2930 for (key_h256, &value) in &account.storage {
2933 let slot_u256 = u256_from_big_endian_const(key_h256.0);
2934 let pos = acct
2935 .storage_changes
2936 .partition_point(|sc| sc.slot < slot_u256);
2937 if pos < acct.storage_changes.len() && acct.storage_changes[pos].slot == slot_u256 {
2938 let sc = &acct.storage_changes[pos];
2939 if !has_exact_change_storage(&sc.slot_changes, withdrawal_idx) {
2940 let seeded = match sc.slot_changes.last() {
2943 Some(c) => c.post_value,
2944 None => db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
2945 EvmError::Custom(format!(
2946 "BAL validation failed for withdrawal: db error reading \
2947 storage {addr:?}[{slot_u256}]: {e}"
2948 ))
2949 })?,
2950 };
2951 if value != seeded {
2952 return Err(EvmError::Custom(format!(
2953 "BAL validation failed for withdrawal: account {addr:?} \
2954 storage slot {slot_u256} changed during withdrawal/request \
2955 phase ({value}) but BAL has no change at index \
2956 {withdrawal_idx} (last_bal={seeded})"
2957 )));
2958 }
2959 }
2960 } else {
2961 let pre_value = db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
2964 EvmError::Custom(format!(
2965 "BAL validation failed for withdrawal: db error reading \
2966 storage {addr:?}[{slot_u256}]: {e}"
2967 ))
2968 })?;
2969 if value != pre_value {
2970 return Err(EvmError::Custom(format!(
2971 "BAL validation failed for withdrawal: account {addr:?} \
2972 storage slot {slot_u256} changed during withdrawal/request \
2973 phase ({value}) but slot is absent from BAL storage_changes \
2974 (pre={pre_value})"
2975 )));
2976 }
2977 }
2978 }
2979 }
2980
2981 Ok(())
2982 }
2983
2984 #[cfg(feature = "rayon")]
3002 fn validate_bal_pre_exec_index(
3003 db: &GeneralizedDatabase,
3004 bal: &BlockAccessList,
3005 index: &BalAddressIndex,
3006 ) -> Result<(), EvmError> {
3007 let Some(active_accounts) = index.tx_to_accounts.get(&PRE_BLOCK_BAL_INDEX) else {
3008 return Ok(());
3009 };
3010
3011 let pre_account = |addr: Address| -> Result<(U256, u64, H256), EvmError> {
3016 let s = db.store.get_account_state(addr).map_err(|e| {
3017 EvmError::Custom(format!(
3018 "BAL validation failed for system_tx: db error reading account {addr:?}: {e}"
3026 ))
3027 })?;
3028 Ok((s.balance, s.nonce, s.code_hash))
3029 };
3030
3031 for &acct_inner_idx in active_accounts {
3032 let acct = &bal.accounts()[acct_inner_idx];
3033 let addr = acct.address;
3034 let actual = db.current_accounts_state.get(&addr);
3035
3036 if let Some(expected) =
3038 find_exact_change_balance(&acct.balance_changes, PRE_BLOCK_BAL_INDEX)
3039 {
3040 match actual {
3041 Some(a) if a.info.balance == expected => {
3042 if expected == pre_account(addr)?.0 {
3043 return Err(EvmError::Custom(format!(
3044 "BAL validation failed for system_tx: account {addr:?} has spurious \
3045 no-op balance change at index 0: post==pre=={expected}"
3046 )));
3047 }
3048 }
3049 Some(a) => {
3050 return Err(EvmError::Custom(format!(
3051 "BAL validation failed for system_tx: account {addr:?} balance mismatch \
3052 at index 0: BAL={expected}, actual={}",
3053 a.info.balance
3054 )));
3055 }
3056 None => {
3057 return Err(EvmError::Custom(format!(
3058 "BAL validation failed for system_tx: account {addr:?} has balance \
3059 change at index 0 but was not touched by the pre-exec phase"
3060 )));
3061 }
3062 }
3063 }
3064
3065 if let Some(expected) =
3067 find_exact_change_nonce(&acct.nonce_changes, PRE_BLOCK_BAL_INDEX)
3068 {
3069 match actual {
3070 Some(a) if a.info.nonce == expected => {
3071 if expected == pre_account(addr)?.1 {
3072 return Err(EvmError::Custom(format!(
3073 "BAL validation failed for system_tx: account {addr:?} has spurious \
3074 no-op nonce change at index 0: post==pre=={expected}"
3075 )));
3076 }
3077 }
3078 Some(a) => {
3079 return Err(EvmError::Custom(format!(
3080 "BAL validation failed for system_tx: account {addr:?} nonce mismatch \
3081 at index 0: BAL={expected}, actual={}",
3082 a.info.nonce
3083 )));
3084 }
3085 None => {
3086 return Err(EvmError::Custom(format!(
3087 "BAL validation failed for system_tx: account {addr:?} has nonce \
3088 change at index 0 but was not touched by the pre-exec phase"
3089 )));
3090 }
3091 }
3092 }
3093
3094 if let Some(expected_code) =
3096 find_exact_change_code(&acct.code_changes, PRE_BLOCK_BAL_INDEX)
3097 {
3098 let expected_hash = if expected_code.is_empty() {
3099 *EMPTY_KECCAK_HASH
3100 } else {
3101 ethrex_common::utils::keccak(expected_code)
3102 };
3103 match actual {
3104 Some(a) if a.info.code_hash == expected_hash => {
3105 if expected_hash == pre_account(addr)?.2 {
3106 return Err(EvmError::Custom(format!(
3107 "BAL validation failed for system_tx: account {addr:?} has spurious \
3108 no-op code change at index 0"
3109 )));
3110 }
3111 }
3112 Some(_) => {
3113 return Err(EvmError::Custom(format!(
3114 "BAL validation failed for system_tx: account {addr:?} code mismatch \
3115 at index 0"
3116 )));
3117 }
3118 None => {
3119 return Err(EvmError::Custom(format!(
3120 "BAL validation failed for system_tx: account {addr:?} has code \
3121 change at index 0 but was not touched by the pre-exec phase"
3122 )));
3123 }
3124 }
3125 }
3126
3127 for sc in &acct.storage_changes {
3129 let Some(expected_value) =
3130 find_exact_change_storage(&sc.slot_changes, PRE_BLOCK_BAL_INDEX)
3131 else {
3132 continue;
3133 };
3134 let key = ethrex_common::utils::u256_to_h256(sc.slot);
3135 let actual_value = actual.and_then(|a| a.storage.get(&key)).copied();
3136 if actual_value != Some(expected_value) {
3137 return Err(EvmError::Custom(format!(
3138 "BAL validation failed for system_tx: account {addr:?} storage slot {} \
3139 mismatch at index 0: BAL={expected_value}, actual={actual_value:?}",
3140 sc.slot
3141 )));
3142 }
3143 let pre_value = db.store.get_storage_value(addr, key).map_err(|e| {
3144 EvmError::Custom(format!(
3145 "BAL validation failed for system_tx: db error reading storage {addr:?} \
3146 slot {}: {e}",
3147 sc.slot
3148 ))
3149 })?;
3150 if expected_value == pre_value {
3151 return Err(EvmError::Custom(format!(
3152 "BAL validation failed for system_tx: account {addr:?} has spurious no-op \
3153 storage change for slot {} at index 0: post==pre=={expected_value}",
3154 sc.slot
3155 )));
3156 }
3157 }
3158 }
3159
3160 Ok(())
3161 }
3162
3163 #[cfg(feature = "rayon")]
3169 pub fn warm_block(
3170 block: &Block,
3171 store: Arc<dyn Database>,
3172 vm_type: VMType,
3173 crypto: &dyn Crypto,
3174 cancelled: &AtomicBool,
3175 ) -> Result<(), EvmError> {
3176 let txs_with_sender = block
3177 .body
3178 .get_transactions_with_sender(crypto)
3179 .map_err(|error| {
3180 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
3181 })?;
3182
3183 Self::warm_txs(
3184 &txs_with_sender,
3185 &block.header,
3186 store.clone(),
3187 vm_type,
3188 crypto,
3189 &|| cancelled.load(Ordering::Relaxed),
3190 )?;
3191
3192 let mut db = GeneralizedDatabase::new(store.clone());
3193
3194 if cancelled.load(Ordering::Relaxed) {
3195 return Ok(());
3196 }
3197
3198 for withdrawal in block
3199 .body
3200 .withdrawals
3201 .iter()
3202 .flatten()
3203 .filter(|withdrawal| withdrawal.amount > 0)
3204 {
3205 db.get_account_mut(withdrawal.address).map_err(|_| {
3206 EvmError::DB(format!(
3207 "Withdrawal account {} not found",
3208 withdrawal.address
3209 ))
3210 })?;
3211 }
3212 Ok(())
3213 }
3214
3215 #[cfg(feature = "rayon")]
3223 pub fn warm_txs(
3224 txs_with_sender: &[(&Transaction, Address)],
3225 header: &BlockHeader,
3226 store: Arc<dyn Database>,
3227 vm_type: VMType,
3228 crypto: &dyn Crypto,
3229 should_stop: &(dyn Fn() -> bool + Sync),
3230 ) -> Result<(), EvmError> {
3231 let mut sender_groups: FxHashMap<Address, Vec<&Transaction>> = FxHashMap::default();
3234 for (tx, sender) in txs_with_sender {
3235 sender_groups.entry(*sender).or_default().push(tx);
3236 }
3237
3238 let chain_config = store.get_chain_config()?;
3241 let evm_config = EVMConfig::new_from_chain_config(&chain_config, header);
3242 let chain_id = chain_config.chain_id;
3243 let base_blob_fee_per_gas = get_base_fee_per_blob_gas(header.excess_blob_gas, &evm_config)?;
3245
3246 sender_groups.into_par_iter().for_each_with(
3249 Vec::with_capacity(STACK_LIMIT),
3250 |stack_pool, (sender, txs)| {
3251 if should_stop() {
3252 return;
3253 }
3254 let mut memory_pool = Vec::with_capacity(1);
3258 let mut group_db = GeneralizedDatabase::new(store.clone());
3260 for tx in txs {
3261 if should_stop() {
3262 return;
3263 }
3264 let _ = Self::execute_tx_in_block(
3265 tx,
3266 sender,
3267 header,
3268 &mut group_db,
3269 vm_type,
3270 base_blob_fee_per_gas,
3271 stack_pool,
3272 &mut memory_pool,
3273 true,
3274 crypto,
3275 evm_config,
3276 chain_id,
3277 None,
3280 );
3281 }
3282 },
3283 );
3284 Ok(())
3285 }
3286
3287 #[cfg(feature = "rayon")]
3290 pub fn bal_storage_slots(bal: &BlockAccessList) -> Vec<(Address, H256)> {
3291 bal.accounts()
3292 .iter()
3293 .flat_map(|ac| {
3294 ac.all_storage_slots()
3295 .map(move |slot| (ac.address, H256::from_uint(&slot)))
3296 })
3297 .collect()
3298 }
3299
3300 #[cfg(feature = "rayon")]
3309 pub fn warm_block_from_bal(
3310 bal: &BlockAccessList,
3311 store: Arc<dyn Database>,
3312 cancelled: &AtomicBool,
3313 ) -> Result<(), EvmError> {
3314 let accounts = bal.accounts();
3315 if accounts.is_empty() {
3316 return Ok(());
3317 }
3318
3319 accounts
3328 .par_chunks(BAL_WARM_CHUNK_ACCOUNTS)
3329 .try_for_each(|chunk| -> Result<(), EvmError> {
3330 if cancelled.load(Ordering::Relaxed) {
3331 return Ok(());
3332 }
3333 let addresses: Vec<Address> = chunk.iter().map(|ac| ac.address).collect();
3334 store
3335 .prefetch_accounts(&addresses)
3336 .map_err(|e| EvmError::Custom(format!("prefetch_accounts: {e}")))?;
3337 for address in addresses {
3338 if cancelled.load(Ordering::Relaxed) {
3339 return Ok(());
3340 }
3341 if let Ok(state) = store.get_account_state(address)
3342 && state.code_hash != *EMPTY_KECCAK_HASH
3343 {
3344 let _ = store.get_account_code(state.code_hash);
3345 }
3346 }
3347 Ok(())
3348 })
3349 }
3350
3351 fn send_state_transitions_tx(
3352 merkleizer: &Sender<Vec<AccountUpdate>>,
3353 db: &mut GeneralizedDatabase,
3354 queue_length: &AtomicUsize,
3355 ) -> Result<(), EvmError> {
3356 let transitions = LEVM::get_state_transitions_tx(db)?;
3357 merkleizer
3358 .send(transitions)
3359 .map_err(|e| EvmError::Custom(format!("send failed: {e}")))?;
3360 queue_length.fetch_add(1, Ordering::Relaxed);
3361 Ok(())
3362 }
3363
3364 fn setup_env(
3365 tx: &Transaction,
3366 tx_sender: Address,
3367 block_header: &BlockHeader,
3368 db: &GeneralizedDatabase,
3369 vm_type: VMType,
3370 ) -> Result<Environment, EvmError> {
3371 let chain_config = db.store.get_chain_config()?;
3375 let config = EVMConfig::new_from_chain_config(&chain_config, block_header);
3376 let base_blob_fee_per_gas =
3377 get_base_fee_per_blob_gas(block_header.excess_blob_gas, &config)?;
3378 Self::setup_env_with_config(
3379 tx,
3380 tx_sender,
3381 block_header,
3382 config,
3383 chain_config.chain_id,
3384 vm_type,
3385 base_blob_fee_per_gas,
3386 )
3387 }
3388
3389 fn setup_env_with_config(
3393 tx: &Transaction,
3394 tx_sender: Address,
3395 block_header: &BlockHeader,
3396 config: EVMConfig,
3397 chain_id: u64,
3398 vm_type: VMType,
3399 base_blob_fee_per_gas: U256,
3400 ) -> Result<Environment, EvmError> {
3401 let gas_price: U256 = calculate_gas_price_for_tx(
3402 tx,
3403 block_header.base_fee_per_gas.unwrap_or_default(),
3404 &vm_type,
3405 )?;
3406
3407 let block_excess_blob_gas = block_header.excess_blob_gas;
3408 let env = Environment {
3409 origin: tx_sender,
3410 gas_limit: tx.gas_limit(),
3411 config,
3412 block_number: block_header.number,
3413 coinbase: block_header.coinbase,
3414 timestamp: block_header.timestamp,
3415 prev_randao: Some(block_header.prev_randao),
3416 slot_number: block_header
3417 .slot_number
3418 .map(U256::from)
3419 .unwrap_or(U256::zero()),
3420 chain_id: chain_id.into(),
3421 base_fee_per_gas: block_header.base_fee_per_gas.unwrap_or_default().into(),
3422 base_blob_fee_per_gas,
3423 gas_price,
3424 block_excess_blob_gas,
3425 block_blob_gas_used: block_header.blob_gas_used,
3426 tx_blob_hashes: tx.blob_versioned_hashes(),
3427 tx_max_priority_fee_per_gas: tx.max_priority_fee().map(U256::from),
3428 tx_max_fee_per_gas: tx.max_fee_per_gas().map(U256::from),
3429 tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas(),
3430 tx_nonce: tx.nonce(),
3431 block_gas_limit: block_header.gas_limit,
3432 difficulty: block_header.difficulty,
3433 is_privileged: matches!(tx, Transaction::PrivilegedL2Transaction(_)),
3434 fee_token: tx.fee_token(),
3435 disable_balance_check: false,
3436 disable_nonce_check: false,
3437 disable_gas_allowance_check: false,
3438 disable_sender_eoa_check: false,
3439 is_system_call: false,
3440 };
3441
3442 Ok(env)
3443 }
3444
3445 pub fn execute_tx(
3446 tx: &Transaction,
3448 tx_sender: Address,
3450 block_header: &BlockHeader,
3452 db: &mut GeneralizedDatabase,
3453 vm_type: VMType,
3454 crypto: &dyn Crypto,
3455 stateless_validator: Option<&dyn StatelessValidator>,
3456 ) -> Result<ExecutionReport, EvmError> {
3457 let env = Self::setup_env(tx, tx_sender, block_header, db, vm_type)?;
3458 let mut vm = VM::new(
3459 env,
3460 db,
3461 tx,
3462 LevmCallTracer::disabled(),
3463 vm_type,
3464 crypto,
3465 stateless_validator,
3466 )?;
3467
3468 vm.execute().map_err(VMError::into)
3469 }
3470
3471 #[allow(clippy::too_many_arguments)]
3474 fn execute_tx_in_block(
3475 tx: &Transaction,
3477 tx_sender: Address,
3479 block_header: &BlockHeader,
3481 db: &mut GeneralizedDatabase,
3482 vm_type: VMType,
3483 base_blob_fee_per_gas: U256,
3484 stack_pool: &mut Vec<Stack>,
3485 memory_pool: &mut Vec<Memory>,
3486 disable_balance_check: bool,
3487 crypto: &dyn Crypto,
3488 config: EVMConfig,
3489 chain_id: u64,
3490 stateless_validator: Option<&dyn StatelessValidator>,
3491 ) -> Result<ExecutionReport, EvmError> {
3492 let mut env = Self::setup_env_with_config(
3493 tx,
3494 tx_sender,
3495 block_header,
3496 config,
3497 chain_id,
3498 vm_type,
3499 base_blob_fee_per_gas,
3500 )?;
3501 env.disable_balance_check = disable_balance_check;
3502 let mut vm = VM::new_pooled(
3506 env,
3507 db,
3508 tx,
3509 LevmCallTracer::disabled(),
3510 vm_type,
3511 crypto,
3512 stateless_validator,
3513 stack_pool,
3514 memory_pool,
3515 )?;
3516 let result = vm.execute().map_err(VMError::into);
3517 vm.reclaim_into(stack_pool, memory_pool);
3519 result
3520 }
3521
3522 pub fn undo_last_tx(db: &mut GeneralizedDatabase) -> Result<(), EvmError> {
3523 db.undo_last_transaction()?;
3524 Ok(())
3525 }
3526
3527 pub fn simulate_tx_from_generic(
3528 tx: &GenericTransaction,
3530 block_header: &BlockHeader,
3532 db: &mut GeneralizedDatabase,
3533 vm_type: VMType,
3534 crypto: &dyn Crypto,
3535 stateless_validator: Option<&dyn StatelessValidator>,
3536 ) -> Result<ExecutionResult, EvmError> {
3537 let mut env = env_from_generic(tx, block_header, db, vm_type)?;
3542
3543 adjust_disabled_base_fee(&mut env);
3544
3545 let converted_tx = generic_tx_to_transaction(tx)?;
3546 let mut vm = vm_from_generic(&converted_tx, env, db, vm_type, crypto, stateless_validator)?;
3547
3548 vm.execute()
3549 .map(|value| value.into())
3550 .map_err(VMError::into)
3551 }
3552
3553 #[allow(clippy::too_many_arguments)]
3568 pub fn simulate_frame_validation_prefix(
3569 tx: &Transaction,
3570 block_header: &BlockHeader,
3571 db: &mut GeneralizedDatabase,
3572 vm_type: VMType,
3573 crypto: &dyn Crypto,
3574 prefix: ðrex_common::types::ValidationPrefix,
3575 _canonical_paymaster_code_hash: Option<H256>,
3576 ) -> Result<FrameValidationOutcome, EvmError> {
3577 use ethrex_common::types::FRAME_TX_MAX_VERIFY_GAS;
3578
3579 let frame_tx = match tx {
3580 Transaction::FrameTransaction(ft) => ft,
3581 _ => {
3582 return Err(EvmError::Custom(
3583 "simulate_frame_validation_prefix requires a frame transaction".to_string(),
3584 ));
3585 }
3586 };
3587 let sender = frame_tx.sender;
3588
3589 let env = Self::setup_env(tx, sender, block_header, db, vm_type)?;
3590 let mut vm = VM::new(
3591 env,
3592 db,
3593 tx,
3594 LevmCallTracer::disabled(),
3595 vm_type,
3596 crypto,
3597 None,
3598 )?;
3599
3600 let canonical_pay_frame: Option<usize> = None;
3603
3604 let sim = match vm.run_frame_validation_prefix(
3605 &prefix.frame_indices,
3606 prefix.deploy_index,
3607 canonical_pay_frame,
3608 ) {
3609 Ok(sim) => sim,
3610 Err(err) => {
3611 return Ok(FrameValidationOutcome {
3615 passed: false,
3616 violation: Some(EvmError::from(err).to_string()),
3617 reservation_ceiling: Self::frame_tx_reservation_ceiling(frame_tx),
3618 accessed_paymaster: None,
3619 touched_sender_slots: Vec::new(),
3620 });
3621 }
3622 };
3623
3624 let reservation_ceiling = Self::frame_tx_reservation_ceiling(frame_tx);
3625 let touched_sender_slots = vm.validation_observer.touched_sender_slots.clone();
3626 let accessed_paymaster = sim.payer_address.map(|payer| (payer, false));
3633
3634 if let Some(violation) = &vm.validation_observer.violation {
3636 return Ok(FrameValidationOutcome {
3637 passed: false,
3638 violation: Some(format!("{violation:?}")),
3639 reservation_ceiling,
3640 accessed_paymaster,
3641 touched_sender_slots,
3642 });
3643 }
3644
3645 if sim.any_revert {
3647 return Ok(FrameValidationOutcome {
3648 passed: false,
3649 violation: Some("validation prefix frame reverted".to_string()),
3650 reservation_ceiling,
3651 accessed_paymaster,
3652 touched_sender_slots,
3653 });
3654 }
3655
3656 if sim.payer_address.is_none() {
3659 return Ok(FrameValidationOutcome {
3660 passed: false,
3661 violation: Some("validation prefix did not establish a payer".to_string()),
3662 reservation_ceiling,
3663 accessed_paymaster,
3664 touched_sender_slots,
3665 });
3666 }
3667
3668 if prefix.deploy_index.is_some() {
3670 let code = vm.db.get_account_code(sender).map_err(VMError::from)?;
3671 if code.is_empty() {
3672 return Ok(FrameValidationOutcome {
3673 passed: false,
3674 violation: Some(format!("{:?}", FrameSimViolation::DeployInstalledNoCode)),
3675 reservation_ceiling,
3676 accessed_paymaster,
3677 touched_sender_slots,
3678 });
3679 }
3680 }
3681
3682 if sim.total_gas_used > FRAME_TX_MAX_VERIFY_GAS {
3684 return Ok(FrameValidationOutcome {
3685 passed: false,
3686 violation: Some(format!(
3687 "validation prefix gas {} exceeds MAX_VERIFY_GAS {}",
3688 sim.total_gas_used, FRAME_TX_MAX_VERIFY_GAS
3689 )),
3690 reservation_ceiling,
3691 accessed_paymaster,
3692 touched_sender_slots,
3693 });
3694 }
3695
3696 Ok(FrameValidationOutcome {
3697 passed: true,
3698 violation: None,
3699 reservation_ceiling,
3700 accessed_paymaster,
3701 touched_sender_slots,
3702 })
3703 }
3704
3705 fn frame_tx_reservation_ceiling(frame_tx: ðrex_common::types::FrameTransaction) -> U256 {
3724 let gas_cost =
3725 U256::from(frame_tx.max_fee_per_gas).saturating_mul(U256::from(frame_tx.max_gas()));
3726 let blob_cost = U256::from(frame_tx.blob_versioned_hashes.len())
3727 .saturating_mul(U256::from(131072u64))
3728 .saturating_mul(frame_tx.max_fee_per_blob_gas);
3729 gas_cost.saturating_add(blob_cost)
3730 }
3731
3732 pub fn get_state_transitions(
3733 db: &mut GeneralizedDatabase,
3734 ) -> Result<Vec<AccountUpdate>, EvmError> {
3735 Ok(db.get_state_transitions()?)
3736 }
3737
3738 pub fn get_state_transitions_tx(
3739 db: &mut GeneralizedDatabase,
3740 ) -> Result<Vec<AccountUpdate>, EvmError> {
3741 Ok(db.get_state_transitions_tx()?)
3742 }
3743
3744 pub fn process_withdrawals(
3745 db: &mut GeneralizedDatabase,
3746 withdrawals: &[Withdrawal],
3747 ) -> Result<(), EvmError> {
3748 for (address, increment) in withdrawals
3750 .iter()
3751 .filter(|withdrawal| withdrawal.amount > 0)
3752 .map(|w| (w.address, u128::from(w.amount) * u128::from(GWEI_TO_WEI)))
3753 {
3754 let account = db
3755 .get_account_mut(address)
3756 .map_err(|_| EvmError::DB(format!("Withdrawal account {address} not found")))?;
3757
3758 let initial_balance = account.info.balance;
3759 account.info.balance += increment.into();
3760 let new_balance = account.info.balance;
3761
3762 if let Some(recorder) = db.bal_recorder_mut() {
3764 recorder.set_initial_balance(address, initial_balance);
3765 recorder.record_balance_change(address, new_balance);
3766 }
3767 }
3768 Ok(())
3769 }
3770
3771 pub fn beacon_root_contract_call(
3773 block_header: &BlockHeader,
3774 db: &mut GeneralizedDatabase,
3775 vm_type: VMType,
3776 crypto: &dyn Crypto,
3777 ) -> Result<(), EvmError> {
3778 if let VMType::L2(_) = vm_type {
3779 return Err(EvmError::InvalidEVM(
3780 "beacon_root_contract_call should not be called for L2 VM".to_string(),
3781 ));
3782 }
3783
3784 let beacon_root = block_header.parent_beacon_block_root.ok_or_else(|| {
3785 EvmError::Header("parent_beacon_block_root field is missing".to_string())
3786 })?;
3787
3788 generic_system_contract_levm(
3789 block_header,
3790 Bytes::copy_from_slice(beacon_root.as_bytes()),
3791 db,
3792 BEACON_ROOTS_ADDRESS.address,
3793 SYSTEM_ADDRESS,
3794 vm_type,
3795 crypto,
3796 )?;
3797 Ok(())
3798 }
3799
3800 pub fn process_block_hash_history(
3801 block_header: &BlockHeader,
3802 db: &mut GeneralizedDatabase,
3803 vm_type: VMType,
3804 crypto: &dyn Crypto,
3805 ) -> Result<(), EvmError> {
3806 if let VMType::L2(_) = vm_type {
3807 return Err(EvmError::InvalidEVM(
3808 "process_block_hash_history should not be called for L2 VM".to_string(),
3809 ));
3810 }
3811
3812 generic_system_contract_levm(
3813 block_header,
3814 Bytes::copy_from_slice(block_header.parent_hash.as_bytes()),
3815 db,
3816 HISTORY_STORAGE_ADDRESS.address,
3817 SYSTEM_ADDRESS,
3818 vm_type,
3819 crypto,
3820 )?;
3821 Ok(())
3822 }
3823
3824 pub fn install_expiry_verifier_code(
3830 db: &mut GeneralizedDatabase,
3831 crypto: &dyn Crypto,
3832 ) -> Result<(), EvmError> {
3833 const PREDEPLOY_NONCE: u64 = 1;
3835
3836 let current = db.get_account_code(EXPIRY_VERIFIER_PREDEPLOY.address)?;
3837 if current.code() == EXPIRY_VERIFIER_RUNTIME_BYTECODE.as_slice() {
3838 return Ok(());
3839 }
3840 let code = Code::from_bytecode(
3841 Bytes::from_static(&EXPIRY_VERIFIER_RUNTIME_BYTECODE),
3842 crypto,
3843 );
3844 let code_hash = code.hash;
3845 if let Some(recorder) = db.bal_recorder_mut() {
3849 recorder.record_code_change(EXPIRY_VERIFIER_PREDEPLOY.address, code.code_bytes());
3850 recorder.record_nonce_change(EXPIRY_VERIFIER_PREDEPLOY.address, PREDEPLOY_NONCE);
3851 }
3852 let acc = db
3853 .get_account_mut(EXPIRY_VERIFIER_PREDEPLOY.address)
3854 .map_err(EvmError::from)?;
3855 acc.info.code_hash = code_hash;
3856 acc.info.nonce = PREDEPLOY_NONCE;
3857 db.codes.entry(code_hash).or_insert(code);
3858 Ok(())
3859 }
3860
3861 pub(crate) fn read_withdrawal_requests(
3862 block_header: &BlockHeader,
3863 db: &mut GeneralizedDatabase,
3864 vm_type: VMType,
3865 crypto: &dyn Crypto,
3866 ) -> Result<ExecutionReport, EvmError> {
3867 if let VMType::L2(_) = vm_type {
3868 return Err(EvmError::InvalidEVM(
3869 "read_withdrawal_requests should not be called for L2 VM".to_string(),
3870 ));
3871 }
3872
3873 let report = generic_system_contract_levm(
3874 block_header,
3875 Bytes::new(),
3876 db,
3877 WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS.address,
3878 SYSTEM_ADDRESS,
3879 vm_type,
3880 crypto,
3881 )?;
3882
3883 match report.result {
3884 TxResult::Success => Ok(report),
3885 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
3887 "REVERT when reading withdrawal requests with error: {vm_error:?}. According to EIP-7002, the revert of this system call invalidates the block.",
3888 ))),
3889 }
3890 }
3891
3892 pub(crate) fn dequeue_consolidation_requests(
3893 block_header: &BlockHeader,
3894 db: &mut GeneralizedDatabase,
3895 vm_type: VMType,
3896 crypto: &dyn Crypto,
3897 ) -> Result<ExecutionReport, EvmError> {
3898 if let VMType::L2(_) = vm_type {
3899 return Err(EvmError::InvalidEVM(
3900 "dequeue_consolidation_requests should not be called for L2 VM".to_string(),
3901 ));
3902 }
3903
3904 let report = generic_system_contract_levm(
3905 block_header,
3906 Bytes::new(),
3907 db,
3908 CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS.address,
3909 SYSTEM_ADDRESS,
3910 vm_type,
3911 crypto,
3912 )?;
3913
3914 match report.result {
3915 TxResult::Success => Ok(report),
3916 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
3918 "REVERT when dequeuing consolidation requests with error: {vm_error:?}. According to EIP-7251, the revert of this system call invalidates the block.",
3919 ))),
3920 }
3921 }
3922
3923 pub(crate) fn read_builder_deposit_requests(
3924 block_header: &BlockHeader,
3925 db: &mut GeneralizedDatabase,
3926 vm_type: VMType,
3927 crypto: &dyn Crypto,
3928 ) -> Result<ExecutionReport, EvmError> {
3929 if let VMType::L2(_) = vm_type {
3930 return Err(EvmError::InvalidEVM(
3931 "read_builder_deposit_requests should not be called for L2 VM".to_string(),
3932 ));
3933 }
3934
3935 let report = generic_system_contract_levm(
3936 block_header,
3937 Bytes::new(),
3938 db,
3939 BUILDER_DEPOSIT_CONTRACT_ADDRESS.address,
3940 SYSTEM_ADDRESS,
3941 vm_type,
3942 crypto,
3943 )?;
3944
3945 match report.result {
3946 TxResult::Success => Ok(report),
3947 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
3949 "REVERT when reading builder deposit requests with error: {vm_error:?}. According to EIP-8282, the revert of this system call invalidates the block.",
3950 ))),
3951 }
3952 }
3953
3954 pub(crate) fn dequeue_builder_exit_requests(
3955 block_header: &BlockHeader,
3956 db: &mut GeneralizedDatabase,
3957 vm_type: VMType,
3958 crypto: &dyn Crypto,
3959 ) -> Result<ExecutionReport, EvmError> {
3960 if let VMType::L2(_) = vm_type {
3961 return Err(EvmError::InvalidEVM(
3962 "dequeue_builder_exit_requests should not be called for L2 VM".to_string(),
3963 ));
3964 }
3965
3966 let report = generic_system_contract_levm(
3967 block_header,
3968 Bytes::new(),
3969 db,
3970 BUILDER_EXIT_CONTRACT_ADDRESS.address,
3971 SYSTEM_ADDRESS,
3972 vm_type,
3973 crypto,
3974 )?;
3975
3976 match report.result {
3977 TxResult::Success => Ok(report),
3978 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
3980 "REVERT when dequeuing builder exit requests with error: {vm_error:?}. According to EIP-8282, the revert of this system call invalidates the block.",
3981 ))),
3982 }
3983 }
3984
3985 pub fn create_access_list(
3986 mut tx: GenericTransaction,
3987 header: &BlockHeader,
3988 db: &mut GeneralizedDatabase,
3989 vm_type: VMType,
3990 crypto: &dyn Crypto,
3991 ) -> Result<(ExecutionResult, AccessList), VMError> {
3992 let mut env = env_from_generic(&tx, header, db, vm_type)?;
3993
3994 adjust_disabled_base_fee(&mut env);
3995
3996 let converted_tx = generic_tx_to_transaction(&tx)?;
3997 let mut vm = vm_from_generic(&converted_tx, env.clone(), db, vm_type, crypto, None)?;
3998
3999 vm.stateless_execute()?;
4000
4001 tx.access_list = vm.substate.make_access_list();
4003 let converted_tx = generic_tx_to_transaction(&tx)?;
4004 let mut vm = vm_from_generic(&converted_tx, env, db, vm_type, crypto, None)?;
4005
4006 let report = vm.stateless_execute()?;
4007
4008 Ok((
4009 report.into(),
4010 tx.access_list
4011 .into_iter()
4012 .map(|x| (x.address, x.storage_keys))
4013 .collect(),
4014 ))
4015 }
4016
4017 pub fn prepare_block(
4018 block: &Block,
4019 db: &mut GeneralizedDatabase,
4020 vm_type: VMType,
4021 crypto: &dyn Crypto,
4022 ) -> Result<(), EvmError> {
4023 let chain_config = db.store.get_chain_config()?;
4024 let block_header = &block.header;
4025 let fork = chain_config.fork(block_header.timestamp);
4026
4027 if let VMType::L2(_) = vm_type {
4029 return Ok(());
4030 }
4031
4032 if fork >= Fork::Hegota {
4036 Self::install_expiry_verifier_code(db, crypto)?;
4037 }
4038
4039 if block_header.parent_beacon_block_root.is_some() && fork >= Fork::Cancun {
4040 Self::beacon_root_contract_call(block_header, db, vm_type, crypto)?;
4041 }
4042
4043 if fork >= Fork::Prague {
4044 Self::process_block_hash_history(block_header, db, vm_type, crypto)?;
4046 }
4047 Ok(())
4048 }
4049}
4050
4051pub fn generic_system_contract_levm(
4052 block_header: &BlockHeader,
4053 calldata: Bytes,
4054 db: &mut GeneralizedDatabase,
4055 contract_address: Address,
4056 system_address: Address,
4057 vm_type: VMType,
4058 crypto: &dyn Crypto,
4059) -> Result<ExecutionReport, EvmError> {
4060 let chain_config = db.store.get_chain_config()?;
4061 let config = EVMConfig::new_from_chain_config(&chain_config, block_header);
4062 let env = Environment {
4063 origin: system_address,
4064 gas_limit: SYS_CALL_GAS_LIMIT,
4075 block_number: block_header.number,
4076 coinbase: block_header.coinbase,
4077 timestamp: block_header.timestamp,
4078 prev_randao: Some(block_header.prev_randao),
4079 base_fee_per_gas: U256::zero(),
4080 gas_price: U256::zero(),
4081 block_excess_blob_gas: block_header.excess_blob_gas,
4082 block_blob_gas_used: block_header.blob_gas_used,
4083 block_gas_limit: block_header.gas_limit,
4088 is_system_call: true,
4089 config,
4090 ..Default::default()
4091 };
4092
4093 debug_assert!(
4099 env.gas_price.is_zero() && env.base_fee_per_gas.is_zero(),
4100 "system calls must run with a zero gas price"
4101 );
4102
4103 if PRAGUE_SYSTEM_CONTRACTS
4113 .iter()
4114 .chain(AMSTERDAM_REQUEST_PREDEPLOYS.iter())
4115 .any(|contract| contract.address == contract_address)
4116 && db.get_account_code(contract_address)?.is_empty()
4117 {
4118 return Err(EvmError::SystemContractCallFailed(format!(
4119 "System contract: {contract_address} has no code after deployment"
4120 )));
4121 };
4122
4123 let tx = &Transaction::EIP1559Transaction(EIP1559Transaction {
4124 to: TxKind::Call(contract_address),
4125 value: U256::zero(),
4126 data: calldata,
4127 ..Default::default()
4128 });
4129 if let Some(recorder) = db.bal_recorder.as_mut() {
4131 recorder.enter_system_call();
4132 }
4133
4134 let result = VM::new(
4135 env,
4136 db,
4137 tx,
4138 LevmCallTracer::disabled(),
4139 vm_type,
4140 crypto,
4141 None,
4142 )
4143 .and_then(|mut vm| vm.execute())
4144 .map_err(EvmError::from);
4145
4146 if let Some(recorder) = db.bal_recorder.as_mut() {
4148 recorder.exit_system_call();
4149 }
4150
4151 result
4152}
4153
4154#[allow(unreachable_code)]
4155#[allow(unused_variables)]
4156pub fn extract_all_requests_levm(
4157 receipts: &[Receipt],
4158 db: &mut GeneralizedDatabase,
4159 header: &BlockHeader,
4160 vm_type: VMType,
4161 crypto: &dyn Crypto,
4162) -> Result<Vec<Requests>, EvmError> {
4163 if let VMType::L2(_) = vm_type {
4164 return Err(EvmError::InvalidEVM(
4165 "extract_all_requests_levm should not be called for L2 VM".to_string(),
4166 ));
4167 }
4168
4169 let chain_config = db.store.get_chain_config()?;
4170 let fork = chain_config.fork(header.timestamp);
4171
4172 if fork < Fork::Prague {
4173 return Ok(Default::default());
4174 }
4175
4176 let withdrawals_data: Vec<u8> = LEVM::read_withdrawal_requests(header, db, vm_type, crypto)?
4177 .output
4178 .into();
4179 let consolidation_data: Vec<u8> =
4180 LEVM::dequeue_consolidation_requests(header, db, vm_type, crypto)?
4181 .output
4182 .into();
4183
4184 let deposits = Requests::from_deposit_receipts(chain_config.deposit_contract_address, receipts)
4185 .ok_or(EvmError::InvalidDepositRequest)?;
4186 let withdrawals = Requests::from_withdrawals_data(withdrawals_data);
4187 let consolidation = Requests::from_consolidation_data(consolidation_data);
4188
4189 let mut requests = vec![deposits, withdrawals, consolidation];
4190
4191 if fork >= Fork::Amsterdam {
4195 requests.reserve_exact(2);
4197 let builder_deposit_data: Vec<u8> =
4198 LEVM::read_builder_deposit_requests(header, db, vm_type, crypto)?
4199 .output
4200 .into();
4201 let builder_exit_data: Vec<u8> =
4202 LEVM::dequeue_builder_exit_requests(header, db, vm_type, crypto)?
4203 .output
4204 .into();
4205 requests.push(Requests::from_builder_deposit_data(builder_deposit_data));
4206 requests.push(Requests::from_builder_exit_data(builder_exit_data));
4207 }
4208
4209 Ok(requests)
4210}
4211
4212pub fn calculate_gas_price_for_generic(tx: &GenericTransaction, basefee: u64) -> U256 {
4215 if !tx.gas_price.is_zero() {
4216 tx.gas_price
4218 } else {
4219 min(
4221 tx.max_priority_fee_per_gas.unwrap_or(0) + basefee,
4222 tx.max_fee_per_gas.unwrap_or(0),
4223 )
4224 .into()
4225 }
4226}
4227
4228pub fn calculate_gas_price_for_tx(
4229 tx: &Transaction,
4230 mut fee_per_gas: u64,
4231 vm_type: &VMType,
4232) -> Result<U256, VMError> {
4233 let Some(max_priority_fee) = tx.max_priority_fee() else {
4234 return Ok(tx.gas_price());
4236 };
4237
4238 let max_fee_per_gas = tx.max_fee_per_gas().ok_or(VMError::TxValidation(
4239 TxValidationError::InsufficientMaxFeePerGas,
4240 ))?;
4241
4242 if let VMType::L2(fee_config) = vm_type
4243 && let Some(operator_fee_config) = &fee_config.operator_fee_config
4244 {
4245 fee_per_gas += operator_fee_config.operator_fee_per_gas;
4246 }
4247
4248 if fee_per_gas > max_fee_per_gas {
4249 return Err(VMError::TxValidation(
4250 TxValidationError::InsufficientMaxFeePerGas,
4251 ));
4252 }
4253
4254 Ok(min(max_priority_fee + fee_per_gas, max_fee_per_gas).into())
4255}
4256
4257pub(crate) fn adjust_disabled_base_fee(env: &mut Environment) {
4261 if env.gas_price == U256::zero() {
4262 env.base_fee_per_gas = U256::zero();
4263 }
4264 if env
4273 .tx_max_fee_per_blob_gas
4274 .is_some_and(|v| v == U256::zero())
4275 {
4276 env.base_blob_fee_per_gas = U256::zero();
4277 }
4278}
4279
4280fn adjust_disabled_l2_fees(env: &Environment, vm_type: VMType) -> VMType {
4282 if env.gas_price == U256::zero()
4283 && let VMType::L2(fee_config) = vm_type
4284 {
4285 return VMType::L2(FeeConfig {
4287 operator_fee_config: None,
4288 l1_fee_config: None,
4289 ..fee_config
4290 });
4291 }
4292 vm_type
4293}
4294
4295pub(crate) fn env_from_generic(
4296 tx: &GenericTransaction,
4297 header: &BlockHeader,
4298 db: &GeneralizedDatabase,
4299 vm_type: VMType,
4300) -> Result<Environment, VMError> {
4301 let chain_config = db.store.get_chain_config()?;
4302 let gas_price =
4303 calculate_gas_price_for_generic(tx, header.base_fee_per_gas.unwrap_or(INITIAL_BASE_FEE));
4304 let block_excess_blob_gas = header.excess_blob_gas;
4305 let config = EVMConfig::new_from_chain_config(&chain_config, header);
4306
4307 let slot_number = if let VMType::L2(_) = vm_type {
4321 U256::zero()
4322 } else {
4323 header.slot_number.map(U256::from).unwrap_or(U256::zero())
4324 };
4325
4326 Ok(Environment {
4327 origin: tx.from.0.into(),
4328 gas_limit: tx
4329 .gas
4330 .unwrap_or(get_max_allowed_gas_limit(header.gas_limit, config.fork)), config,
4332 block_number: header.number,
4333 coinbase: header.coinbase,
4334 timestamp: header.timestamp,
4335 prev_randao: Some(header.prev_randao),
4336 slot_number,
4337 chain_id: chain_config.chain_id.into(),
4338 base_fee_per_gas: header.base_fee_per_gas.unwrap_or_default().into(),
4339 base_blob_fee_per_gas: get_base_fee_per_blob_gas(block_excess_blob_gas, &config)?,
4340 gas_price,
4341 block_excess_blob_gas,
4342 block_blob_gas_used: header.blob_gas_used,
4343 tx_blob_hashes: tx.blob_versioned_hashes.clone(),
4344 tx_max_priority_fee_per_gas: tx.max_priority_fee_per_gas.map(U256::from),
4345 tx_max_fee_per_gas: tx.max_fee_per_gas.map(U256::from),
4346 tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
4347 tx_nonce: tx.nonce.unwrap_or_default(),
4348 block_gas_limit: header.gas_limit,
4349 difficulty: header.difficulty,
4350 is_privileged: false,
4351 fee_token: tx.fee_token,
4352 disable_balance_check: false,
4353 disable_nonce_check: true,
4359 disable_gas_allowance_check: true,
4365 disable_sender_eoa_check: true,
4368 is_system_call: false,
4369 })
4370}
4371
4372fn generic_tx_to_transaction(tx: &GenericTransaction) -> Result<Transaction, VMError> {
4387 let (signature_r, signature_s) = (U256::MAX, U256::MAX);
4390 let nonce = tx.nonce.unwrap_or_default();
4391 let gas_limit = tx.gas.unwrap_or_default();
4392 let max_fee_per_gas = tx.max_fee_per_gas.unwrap_or_default();
4393 let max_priority_fee_per_gas = tx.max_priority_fee_per_gas.unwrap_or_default();
4394 let chain_id = tx.chain_id.unwrap_or_default();
4395
4396 Ok(match &tx.authorization_list {
4397 Some(authorization_list) => Transaction::EIP7702Transaction(EIP7702Transaction {
4398 to: match tx.to {
4399 TxKind::Call(to) => to,
4400 TxKind::Create => {
4401 return Err(InternalError::msg("Generic Tx cannot be create type").into());
4402 }
4403 },
4404 value: tx.value,
4405 data: tx.input.clone(),
4406 access_list: tx
4407 .access_list
4408 .iter()
4409 .map(|list| (list.address, list.storage_keys.clone()))
4410 .collect(),
4411 authorization_list: authorization_list
4412 .iter()
4413 .map(|auth| Into::<AuthorizationTuple>::into(auth.clone()))
4414 .collect(),
4415 nonce,
4416 gas_limit,
4417 max_fee_per_gas,
4418 max_priority_fee_per_gas,
4419 chain_id,
4420 signature_r,
4421 signature_s,
4422 ..Default::default()
4423 }),
4424 None => Transaction::EIP1559Transaction(EIP1559Transaction {
4425 to: tx.to.clone(),
4426 value: tx.value,
4427 data: tx.input.clone(),
4428 access_list: tx
4429 .access_list
4430 .iter()
4431 .map(|list| (list.address, list.storage_keys.clone()))
4432 .collect(),
4433 nonce,
4434 gas_limit,
4435 max_fee_per_gas,
4436 max_priority_fee_per_gas,
4437 chain_id,
4438 signature_r,
4439 signature_s,
4440 ..Default::default()
4441 }),
4442 })
4443}
4444
4445fn vm_from_generic<'a>(
4446 tx: &'a Transaction,
4447 env: Environment,
4448 db: &'a mut GeneralizedDatabase,
4449 vm_type: VMType,
4450 crypto: &'a dyn Crypto,
4451 stateless_validator: Option<&'a dyn StatelessValidator>,
4452) -> Result<VM<'a>, VMError> {
4453 let vm_type = adjust_disabled_l2_fees(&env, vm_type);
4454 VM::new(
4455 env,
4456 db,
4457 tx,
4458 LevmCallTracer::disabled(),
4459 vm_type,
4460 crypto,
4461 stateless_validator,
4462 )
4463}
4464
4465pub fn get_max_allowed_gas_limit(block_gas_limit: u64, fork: Fork) -> u64 {
4466 if fork >= Fork::Osaka && fork < Fork::Amsterdam {
4474 POST_OSAKA_GAS_LIMIT_CAP
4475 } else {
4476 block_gas_limit
4477 }
4478}
4479
4480#[allow(dead_code)]
4488fn describe_balance_diff(expected: U256, actual: U256) -> String {
4489 let (sign, mag) = if expected >= actual {
4490 ("+", expected - actual)
4491 } else {
4492 ("-", actual - expected)
4493 };
4494 let Ok(mag_u128) = u128::try_from(mag) else {
4495 return format!("{sign}{mag}");
4496 };
4497 if mag_u128 == 0 {
4498 return "0".to_string();
4499 }
4500 let cpsb: u128 = 1530;
4501 let consts = [
4503 ("NEW_ACCOUNT", 120u128),
4504 ("STORAGE_SET", 64),
4505 ("AUTH_BASE", 23),
4506 ("AUTH_TOTAL", 143),
4507 ];
4508 for &gp in &[10u128, 1, 7, 100, 1000, 1_000_000_000] {
4510 if !mag_u128.is_multiple_of(gp) {
4511 continue;
4512 }
4513 let gas = mag_u128 / gp;
4514 if !gas.is_multiple_of(cpsb) {
4515 continue;
4516 }
4517 let bytes = gas / cpsb;
4518 for (name, c) in consts {
4519 if bytes.is_multiple_of(c) {
4520 let n = bytes / c;
4521 return format!(
4522 "{sign}{mag_u128} wei (= {gas} gas at {gp} wei/gas = {n}× {name}_state_gas)"
4523 );
4524 }
4525 }
4526 }
4527 format!("{sign}{mag_u128} wei")
4528}
4529
4530#[cfg(all(test, feature = "rayon"))]
4533mod bal_tests {
4534 use super::*;
4535 use ethrex_common::H256;
4536 use ethrex_common::types::AccountState;
4537 use ethrex_common::types::block_access_list::{
4538 AccountChanges, BalanceChange, NonceChange, SlotChange, StorageChange,
4539 };
4540 use ethrex_levm::errors::DatabaseError;
4541
4542 fn addr(byte: u8) -> Address {
4543 let mut a = Address::zero();
4544 a.0[19] = byte;
4545 a
4546 }
4547
4548 struct MockStore {
4550 accounts: FxHashMap<Address, AccountState>,
4551 }
4552
4553 impl MockStore {
4554 fn new() -> Self {
4555 Self {
4556 accounts: FxHashMap::default(),
4557 }
4558 }
4559
4560 fn with_account(mut self, address: Address, state: AccountState) -> Self {
4561 self.accounts.insert(address, state);
4562 self
4563 }
4564 }
4565
4566 impl Database for MockStore {
4567 fn get_account_state(&self, address: Address) -> Result<AccountState, DatabaseError> {
4568 Ok(self.accounts.get(&address).copied().unwrap_or_default())
4569 }
4570 fn get_storage_value(&self, _: Address, _: H256) -> Result<U256, DatabaseError> {
4571 Ok(U256::zero())
4572 }
4573 fn get_block_hash(&self, _: u64) -> Result<H256, DatabaseError> {
4574 Ok(H256::zero())
4575 }
4576 fn get_chain_config(&self) -> Result<ethrex_common::types::ChainConfig, DatabaseError> {
4577 Err(DatabaseError::Custom("not implemented".into()))
4578 }
4579 fn get_account_code(&self, _: H256) -> Result<ethrex_common::types::Code, DatabaseError> {
4580 Ok(ethrex_common::types::Code::from_bytecode(
4581 Bytes::new(),
4582 ðrex_crypto::NativeCrypto,
4583 ))
4584 }
4585 fn get_code_metadata(
4586 &self,
4587 _: H256,
4588 ) -> Result<ethrex_common::types::CodeMetadata, DatabaseError> {
4589 Ok(ethrex_common::types::CodeMetadata { length: 0 })
4590 }
4591 }
4592
4593 #[test]
4594 fn test_bal_to_account_updates_basic() {
4595 let address = addr(1);
4597 let store = MockStore::new().with_account(
4598 address,
4599 AccountState {
4600 balance: U256::from(100),
4601 nonce: 5,
4602 code_hash: *EMPTY_KECCAK_HASH,
4603 storage_root: H256::zero(),
4604 },
4605 );
4606
4607 let bal = BlockAccessList::from_accounts(vec![
4608 AccountChanges::new(address)
4609 .with_balance_changes(vec![
4610 BalanceChange::new(1, U256::from(90)),
4611 BalanceChange::new(2, U256::from(80)),
4612 ])
4613 .with_nonce_changes(vec![NonceChange::new(1, 6)])
4614 .with_storage_changes(vec![SlotChange::with_changes(
4615 U256::from(42),
4616 vec![StorageChange::new(1, U256::from(999))],
4617 )]),
4618 ]);
4619
4620 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
4621 assert_eq!(updates.len(), 1);
4622 let u = &updates[0];
4623 assert_eq!(u.address, address);
4624 assert!(!u.removed);
4625 let info = u.info.as_ref().unwrap();
4626 assert_eq!(info.balance, U256::from(80));
4628 assert_eq!(info.nonce, 6);
4629 assert_eq!(info.code_hash, *EMPTY_KECCAK_HASH);
4630 let key = ethrex_common::utils::u256_to_h256(U256::from(42));
4632 assert_eq!(*u.added_storage.get(&key).unwrap(), U256::from(999));
4633 }
4634
4635 #[test]
4636 fn test_bal_to_account_updates_highest_index_wins() {
4637 let address = addr(2);
4639 let store = MockStore::new().with_account(
4640 address,
4641 AccountState {
4642 balance: U256::from(1000),
4643 nonce: 0,
4644 code_hash: *EMPTY_KECCAK_HASH,
4645 storage_root: H256::zero(),
4646 },
4647 );
4648
4649 let bal = BlockAccessList::from_accounts(vec![
4650 AccountChanges::new(address).with_balance_changes(vec![
4651 BalanceChange::new(1, U256::from(900)),
4652 BalanceChange::new(2, U256::from(800)),
4653 BalanceChange::new(3, U256::from(700)),
4654 ]),
4655 ]);
4656
4657 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
4658 assert_eq!(updates.len(), 1);
4659 assert_eq!(updates[0].info.as_ref().unwrap().balance, U256::from(700));
4660 }
4661
4662 #[test]
4663 fn test_bal_to_account_updates_reads_only_skipped() {
4664 let address = addr(3);
4666 let store = MockStore::new();
4667
4668 let bal = BlockAccessList::from_accounts(vec![
4669 AccountChanges::new(address).with_storage_reads(vec![U256::from(1)]),
4670 ]);
4671
4672 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
4673 assert!(updates.is_empty());
4674 }
4675
4676 #[test]
4677 fn test_bal_to_account_updates_removal() {
4678 let address = addr(4);
4680 let store = MockStore::new().with_account(
4681 address,
4682 AccountState {
4683 balance: U256::from(50),
4684 nonce: 1,
4685 code_hash: *EMPTY_KECCAK_HASH,
4686 storage_root: H256::zero(),
4687 },
4688 );
4689
4690 let bal = BlockAccessList::from_accounts(vec![
4691 AccountChanges::new(address)
4692 .with_balance_changes(vec![BalanceChange::new(1, U256::zero())])
4693 .with_nonce_changes(vec![NonceChange::new(1, 0)]),
4694 ]);
4695
4696 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
4697 assert_eq!(updates.len(), 1);
4698 assert!(updates[0].removed);
4699 }
4700
4701 #[test]
4702 fn test_bal_to_account_updates_storage_zero() {
4703 let address = addr(5);
4705 let store = MockStore::new();
4706
4707 let bal = BlockAccessList::from_accounts(vec![
4708 AccountChanges::new(address).with_storage_changes(vec![SlotChange::with_changes(
4709 U256::from(7),
4710 vec![StorageChange::new(1, U256::zero())],
4711 )]),
4712 ]);
4713
4714 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
4715 assert_eq!(updates.len(), 1);
4716 let key = ethrex_common::utils::u256_to_h256(U256::from(7));
4717 assert_eq!(*updates[0].added_storage.get(&key).unwrap(), U256::zero());
4718 }
4719
4720 #[test]
4721 fn test_bal_to_account_updates_code_deployment() {
4722 let address = addr(6);
4724 let store = MockStore::new();
4725 let code = Bytes::from(vec![0x60, 0x00, 0x60, 0x00, 0xf3]); let expected_hash =
4727 ethrex_common::types::Code::from_bytecode(code.clone(), ðrex_crypto::NativeCrypto)
4728 .hash;
4729
4730 let bal = BlockAccessList::from_accounts(vec![
4731 AccountChanges::new(address)
4732 .with_code_changes(vec![
4733 ethrex_common::types::block_access_list::CodeChange::new(1, code.clone()),
4734 ])
4735 .with_nonce_changes(vec![NonceChange::new(1, 1)]),
4736 ]);
4737
4738 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
4739 assert_eq!(updates.len(), 1);
4740 let u = &updates[0];
4741 assert_eq!(u.info.as_ref().unwrap().code_hash, expected_hash);
4742 assert_eq!(u.code.as_ref().unwrap().code(), &code[..]);
4743 }
4744}
4745
4746#[cfg(test)]
4747mod system_call_coinbase_tests {
4748 use super::*;
4754 use ethrex_common::types::{AccountState, AccountUpdate, ChainConfig, Code, CodeMetadata};
4755 use ethrex_crypto::NativeCrypto;
4756 use ethrex_levm::db::Database;
4757 use ethrex_levm::errors::DatabaseError;
4758 use std::sync::Arc;
4759
4760 const HISTORY_RUNTIME_CODE: &str = concat!(
4762 "3373fffffffffffffffffffffffffffffffffffffffe1460465760203603604257",
4763 "5f35600143038111604257611fff81430311604257611fff900654",
4764 "5f5260205ff35b5f5ffd5b5f35611fff60014303065500",
4765 );
4766
4767 struct Store {
4768 chain_config: ChainConfig,
4769 history_code: Code,
4770 }
4771
4772 impl Database for Store {
4773 fn get_account_state(&self, address: Address) -> Result<AccountState, DatabaseError> {
4774 if address == HISTORY_STORAGE_ADDRESS.address {
4775 return Ok(AccountState {
4776 nonce: 1,
4777 code_hash: self.history_code.hash,
4778 ..Default::default()
4779 });
4780 }
4781 Ok(AccountState::default())
4782 }
4783 fn get_storage_value(&self, _: Address, _: H256) -> Result<U256, DatabaseError> {
4784 Ok(U256::zero())
4785 }
4786 fn get_block_hash(&self, _: u64) -> Result<H256, DatabaseError> {
4787 Ok(H256::zero())
4788 }
4789 fn get_chain_config(&self) -> Result<ChainConfig, DatabaseError> {
4790 Ok(self.chain_config)
4791 }
4792 fn get_account_code(&self, code_hash: H256) -> Result<Code, DatabaseError> {
4793 if code_hash == self.history_code.hash {
4794 return Ok(self.history_code.clone());
4795 }
4796 Ok(Code::default())
4797 }
4798 fn get_code_metadata(&self, code_hash: H256) -> Result<CodeMetadata, DatabaseError> {
4799 let length = if code_hash == self.history_code.hash {
4800 self.history_code.len() as u64
4801 } else {
4802 0
4803 };
4804 Ok(CodeMetadata { length })
4805 }
4806 }
4807
4808 fn history_code() -> Code {
4809 let bytes = hex::decode(HISTORY_RUNTIME_CODE).expect("history runtime code is valid hex");
4810 Code::from_bytecode(Bytes::from(bytes), &NativeCrypto)
4811 }
4812
4813 fn prague_db() -> GeneralizedDatabase {
4814 GeneralizedDatabase::new(Arc::new(Store {
4815 chain_config: ChainConfig {
4816 prague_time: Some(0),
4817 ..Default::default()
4818 },
4819 history_code: history_code(),
4820 }))
4821 }
4822
4823 fn parent_hash_value(parent_hash: H256) -> U256 {
4824 U256::from_big_endian(parent_hash.as_bytes())
4825 }
4826
4827 fn history_slot(block_number: u64) -> H256 {
4828 H256::from_low_u64_be((block_number - 1) % 8191)
4829 }
4830
4831 fn run_history_update(coinbase: Address) -> (Option<U256>, Vec<AccountUpdate>, H256) {
4835 let mut db = prague_db();
4836 let parent_hash = H256::from_low_u64_be(0x2935);
4837 let block_number = 42;
4838 let header = BlockHeader {
4839 parent_hash,
4840 coinbase,
4841 number: block_number,
4842 timestamp: 1,
4843 ..Default::default()
4844 };
4845
4846 LEVM::process_block_hash_history(&header, &mut db, VMType::L1, &NativeCrypto)
4847 .expect("history system call executes");
4848
4849 let slot = history_slot(block_number);
4850 let stored_value = db
4851 .current_accounts_state
4852 .get(&HISTORY_STORAGE_ADDRESS.address)
4853 .and_then(|account| account.storage.get(&slot).copied());
4854 let updates =
4855 LEVM::get_state_transitions(&mut db).expect("state transitions are generated");
4856
4857 (stored_value, updates, parent_hash)
4858 }
4859
4860 fn assert_history_write_emitted(coinbase: Address) {
4861 let (stored_value, updates, parent_hash) = run_history_update(coinbase);
4862 let slot = history_slot(42);
4863 assert_eq!(
4864 stored_value,
4865 Some(parent_hash_value(parent_hash)),
4866 "history storage must hold the parent hash after the system call"
4867 );
4868 assert!(
4869 updates.iter().any(|update| {
4870 update.address == HISTORY_STORAGE_ADDRESS.address
4871 && update.added_storage.get(&slot) == Some(&parent_hash_value(parent_hash))
4872 }),
4873 "the history-contract storage write must be emitted as a state update"
4874 );
4875 }
4876
4877 #[test]
4878 fn ordinary_coinbase_preserves_history_storage_write() {
4879 assert_history_write_emitted(Address::from_low_u64_be(0xbeef));
4880 }
4881
4882 #[test]
4885 fn history_address_coinbase_preserves_history_storage_write() {
4886 assert_history_write_emitted(HISTORY_STORAGE_ADDRESS.address);
4887 }
4888}
4889
4890#[cfg(all(test, feature = "rayon"))]
4895mod burned_fees_tests {
4896 use super::*;
4897 use ethrex_common::{
4898 constants::{DEFAULT_OMMERS_HASH, DEFAULT_REQUESTS_HASH},
4899 types::{AccountState, Block, BlockBody, BlockHeader, ChainConfig, Code, CodeMetadata},
4900 utils::keccak,
4901 };
4902 use ethrex_crypto::NativeCrypto;
4903 use ethrex_levm::errors::DatabaseError;
4904 use ethrex_rlp::encode::PayloadRLPEncode;
4905
4906 fn lstar_chain_config() -> ChainConfig {
4909 ChainConfig {
4910 chain_id: 1,
4911 homestead_block: Some(0),
4912 eip150_block: Some(0),
4913 eip155_block: Some(0),
4914 eip158_block: Some(0),
4915 byzantium_block: Some(0),
4916 constantinople_block: Some(0),
4917 petersburg_block: Some(0),
4918 istanbul_block: Some(0),
4919 berlin_block: Some(0),
4920 london_block: Some(0),
4921 terminal_total_difficulty: Some(0),
4922 terminal_total_difficulty_passed: true,
4923 shanghai_time: Some(0),
4924 cancun_time: Some(0),
4925 prague_time: Some(0),
4926 amsterdam_time: Some(0),
4927 lstar_time: Some(0),
4928 ..Default::default()
4929 }
4930 }
4931
4932 fn amsterdam_chain_config() -> ChainConfig {
4933 ChainConfig {
4934 chain_id: 1,
4935 homestead_block: Some(0),
4936 eip150_block: Some(0),
4937 eip155_block: Some(0),
4938 eip158_block: Some(0),
4939 byzantium_block: Some(0),
4940 constantinople_block: Some(0),
4941 petersburg_block: Some(0),
4942 istanbul_block: Some(0),
4943 berlin_block: Some(0),
4944 london_block: Some(0),
4945 terminal_total_difficulty: Some(0),
4946 terminal_total_difficulty_passed: true,
4947 shanghai_time: Some(0),
4948 cancun_time: Some(0),
4949 prague_time: Some(0),
4950 amsterdam_time: Some(0),
4951 lstar_time: None, ..Default::default()
4953 }
4954 }
4955
4956 struct MockDB {
4959 chain_config: ChainConfig,
4960 accounts: FxHashMap<Address, (AccountState, Bytes)>,
4961 storage: FxHashMap<(Address, H256), U256>,
4963 }
4964
4965 impl MockDB {
4966 fn new(chain_config: ChainConfig) -> Self {
4967 Self {
4968 chain_config,
4969 accounts: FxHashMap::default(),
4970 storage: FxHashMap::default(),
4971 }
4972 }
4973
4974 fn with_account(mut self, address: Address, balance: U256, code: Option<Bytes>) -> Self {
4975 let code_bytes = code.unwrap_or_default();
4976 let code_hash = if code_bytes.is_empty() {
4977 *EMPTY_KECCAK_HASH
4978 } else {
4979 keccak(&code_bytes)
4980 };
4981 let state = AccountState {
4982 nonce: 0,
4983 balance,
4984 storage_root: H256::zero(),
4985 code_hash,
4986 };
4987 self.accounts.insert(address, (state, code_bytes));
4988 self
4989 }
4990
4991 fn with_storage(mut self, address: Address, slot: H256, value: U256) -> Self {
4992 self.storage.insert((address, slot), value);
4993 self
4994 }
4995 }
4996
4997 impl Database for MockDB {
4998 fn get_account_state(&self, address: Address) -> Result<AccountState, DatabaseError> {
4999 Ok(self
5000 .accounts
5001 .get(&address)
5002 .map(|(s, _)| *s)
5003 .unwrap_or_default())
5004 }
5005
5006 fn get_storage_value(&self, address: Address, slot: H256) -> Result<U256, DatabaseError> {
5007 Ok(self
5008 .storage
5009 .get(&(address, slot))
5010 .copied()
5011 .unwrap_or_default())
5012 }
5013
5014 fn get_block_hash(&self, _: u64) -> Result<H256, DatabaseError> {
5015 Ok(H256::zero())
5016 }
5017
5018 fn get_chain_config(&self) -> Result<ChainConfig, DatabaseError> {
5019 Ok(self.chain_config)
5020 }
5021
5022 fn get_account_code(&self, code_hash: H256) -> Result<Code, DatabaseError> {
5023 let crypto = NativeCrypto;
5024 for (state, bytecode) in self.accounts.values() {
5025 if state.code_hash == code_hash && !bytecode.is_empty() {
5026 return Ok(Code::from_bytecode(bytecode.clone(), &crypto));
5027 }
5028 }
5029 Ok(Code::from_bytecode(Bytes::new(), &crypto))
5030 }
5031
5032 fn get_code_metadata(&self, code_hash: H256) -> Result<CodeMetadata, DatabaseError> {
5033 for (state, bytecode) in self.accounts.values() {
5034 if state.code_hash == code_hash {
5035 return Ok(CodeMetadata {
5036 length: bytecode.len() as u64,
5037 });
5038 }
5039 }
5040 Ok(CodeMetadata { length: 0 })
5041 }
5042 }
5043
5044 const PRIVATE_KEY: [u8; 32] = [
5047 0x4c, 0x08, 0x83, 0xa6, 0x91, 0x02, 0x93, 0x7d, 0x62, 0x31, 0x47, 0x1b, 0x5d, 0xbb, 0x62,
5048 0x04, 0xfe, 0x51, 0x29, 0x61, 0x70, 0x82, 0x79, 0x2a, 0xe4, 0x68, 0xd0, 0x1a, 0x3f, 0x36,
5049 0x23, 0x18,
5050 ];
5051
5052 fn sender_address() -> Address {
5053 use k256::ecdsa::SigningKey;
5054 let signing_key = SigningKey::from_bytes(&PRIVATE_KEY.into()).expect("valid key");
5055 let uncompressed_pub = signing_key.verifying_key().to_encoded_point(false);
5056 let pub_hash = keccak(&uncompressed_pub.as_bytes()[1..]);
5057 Address::from_slice(&pub_hash.0[12..])
5058 }
5059
5060 fn signed_eth_transfer(to: Address, nonce: u64, base_fee: u64) -> Transaction {
5063 use k256::ecdsa::SigningKey;
5064
5065 let tx = EIP1559Transaction {
5066 chain_id: 1,
5067 nonce,
5068 max_priority_fee_per_gas: 1,
5069 max_fee_per_gas: base_fee + 10,
5070 gas_limit: 21_000,
5071 to: TxKind::Call(to),
5072 value: U256::zero(),
5073 ..Default::default()
5074 };
5075
5076 let mut buf = vec![0x02u8];
5078 tx.encode_payload(&mut buf);
5079 let hash = keccak(&buf);
5080
5081 let signing_key = SigningKey::from_bytes(&PRIVATE_KEY.into()).expect("valid key");
5082 let (sig, rid) = signing_key
5083 .sign_prehash_recoverable(&hash.0)
5084 .expect("sign ok");
5085 let sig_bytes = sig.to_bytes();
5086
5087 Transaction::EIP1559Transaction(EIP1559Transaction {
5088 signature_r: U256::from_big_endian(&sig_bytes[..32]),
5089 signature_s: U256::from_big_endian(&sig_bytes[32..]),
5090 signature_y_parity: rid.to_byte() != 0,
5091 ..tx
5092 })
5093 }
5094
5095 const STOP_BYTECODE: &[u8] = &[0x00];
5097
5098 fn make_block_with_tx(tx: Transaction, base_fee: u64) -> Block {
5099 let header = BlockHeader {
5100 parent_hash: H256::zero(),
5101 ommers_hash: *DEFAULT_OMMERS_HASH,
5102 number: 1,
5103 gas_limit: 1_000_000,
5104 timestamp: 1,
5105 base_fee_per_gas: Some(base_fee),
5106 withdrawals_root: Some(H256::zero()),
5107 requests_hash: Some(*DEFAULT_REQUESTS_HASH),
5108 parent_beacon_block_root: None,
5110 ..Default::default()
5111 };
5112 let body = BlockBody {
5113 transactions: vec![tx],
5114 ommers: vec![],
5115 withdrawals: Some(vec![]),
5116 };
5117 Block::new(header, body)
5118 }
5119
5120 fn make_gen_db(chain_config: ChainConfig) -> GeneralizedDatabase {
5121 let sender = sender_address();
5122 let stop = Bytes::from_static(STOP_BYTECODE);
5123 let mock = MockDB::new(chain_config)
5124 .with_account(sender, U256::from(1_000_000_000u64), None)
5126 .with_account(
5128 WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS.address,
5129 U256::zero(),
5130 Some(stop.clone()),
5131 )
5132 .with_account(
5133 CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS.address,
5134 U256::zero(),
5135 Some(stop.clone()),
5136 )
5137 .with_account(
5139 BUILDER_DEPOSIT_CONTRACT_ADDRESS.address,
5140 U256::zero(),
5141 Some(stop.clone()),
5142 )
5143 .with_account(
5144 BUILDER_EXIT_CONTRACT_ADDRESS.address,
5145 U256::zero(),
5146 Some(stop),
5147 );
5148 GeneralizedDatabase::new(Arc::new(mock))
5149 }
5150
5151 const SSTORE_CLEAR_BYTECODE: &[u8] = &[0x60, 0x00, 0x60, 0x00, 0x55, 0x00];
5156
5157 fn signed_call_tx(to: Address, nonce: u64, base_fee: u64, gas_limit: u64) -> Transaction {
5159 use k256::ecdsa::SigningKey;
5160
5161 let tx = EIP1559Transaction {
5162 chain_id: 1,
5163 nonce,
5164 max_priority_fee_per_gas: 1,
5165 max_fee_per_gas: base_fee + 10,
5166 gas_limit,
5167 to: TxKind::Call(to),
5168 value: U256::zero(),
5169 ..Default::default()
5170 };
5171
5172 let mut buf = vec![0x02u8];
5173 tx.encode_payload(&mut buf);
5174 let hash = keccak(&buf);
5175
5176 let signing_key = SigningKey::from_bytes(&PRIVATE_KEY.into()).expect("valid key");
5177 let (sig, rid) = signing_key
5178 .sign_prehash_recoverable(&hash.0)
5179 .expect("sign ok");
5180 let sig_bytes = sig.to_bytes();
5181
5182 Transaction::EIP1559Transaction(EIP1559Transaction {
5183 signature_r: U256::from_big_endian(&sig_bytes[..32]),
5184 signature_s: U256::from_big_endian(&sig_bytes[32..]),
5185 signature_y_parity: rid.to_byte() != 0,
5186 ..tx
5187 })
5188 }
5189
5190 fn make_gen_db_sstore_clear(
5193 chain_config: ChainConfig,
5194 contract_addr: Address,
5195 ) -> GeneralizedDatabase {
5196 let sender = sender_address();
5197 let stop = Bytes::from_static(STOP_BYTECODE);
5198 let mock = MockDB::new(chain_config)
5199 .with_account(sender, U256::from(100_000_000_000u64), None)
5200 .with_account(
5201 WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS.address,
5202 U256::zero(),
5203 Some(stop.clone()),
5204 )
5205 .with_account(
5206 CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS.address,
5207 U256::zero(),
5208 Some(stop.clone()),
5209 )
5210 .with_account(
5212 BUILDER_DEPOSIT_CONTRACT_ADDRESS.address,
5213 U256::zero(),
5214 Some(stop.clone()),
5215 )
5216 .with_account(
5217 BUILDER_EXIT_CONTRACT_ADDRESS.address,
5218 U256::zero(),
5219 Some(stop),
5220 )
5221 .with_account(
5222 contract_addr,
5223 U256::zero(),
5224 Some(Bytes::from_static(SSTORE_CLEAR_BYTECODE)),
5225 )
5226 .with_storage(contract_addr, H256::zero(), U256::one());
5228 GeneralizedDatabase::new(Arc::new(mock))
5229 }
5230
5231 #[test]
5234 fn test_burned_fees_block_aggregate() {
5235 use std::sync::atomic::AtomicUsize;
5236 use std::sync::mpsc;
5237
5238 const BASE_FEE: u64 = 10;
5239 let recipient = Address::from_low_u64_be(0xBEEF);
5247 let tx = signed_eth_transfer(recipient, 0, BASE_FEE);
5248 let block = make_block_with_tx(tx, BASE_FEE);
5249 let crypto = NativeCrypto;
5250
5251 let mut db1 = make_gen_db(lstar_chain_config());
5253 let (result, _bal) = LEVM::execute_block(&block, &mut db1, VMType::L1, &crypto, None)
5254 .expect("execute_block succeeded");
5255 let cumulative = result
5256 .receipts
5257 .last()
5258 .map(|r| r.cumulative_gas_used)
5259 .unwrap_or(0);
5260 let expected = compute_burned_fees(BASE_FEE, cumulative, 0, 0);
5261 assert_eq!(
5262 result.burned_fees,
5263 Some(expected),
5264 "execute_block: burned_fees must equal base_fee * post-refund gas ({cumulative})"
5265 );
5266
5267 let mut db2 = make_gen_db(lstar_chain_config());
5269 let (tx_sender, _rx) = mpsc::channel();
5270 let queue_len = AtomicUsize::new(0);
5271 let tx2 = signed_eth_transfer(recipient, 0, BASE_FEE);
5272 let block2 = make_block_with_tx(tx2, BASE_FEE);
5273 let (result_pipeline, _bal) = LEVM::execute_block_pipeline(
5274 &block2,
5275 &mut db2,
5276 VMType::L1,
5277 Some(tx_sender),
5278 &queue_len,
5279 &crypto,
5280 None, false,
5282 None,
5283 )
5284 .expect("execute_block_pipeline succeeded");
5285 assert_eq!(
5286 result_pipeline.burned_fees, result.burned_fees,
5287 "execute_block_pipeline: burned_fees must match execute_block (production/verification consistency)"
5288 );
5289
5290 let tx3 = signed_eth_transfer(recipient, 0, BASE_FEE);
5292 let block3 = make_block_with_tx(tx3, BASE_FEE);
5293 let mut db3 = make_gen_db(amsterdam_chain_config());
5294 let (result_pre_lstar, _) =
5295 LEVM::execute_block(&block3, &mut db3, VMType::L1, &crypto, None)
5296 .expect("pre-LStar execute_block succeeded");
5297 assert_eq!(
5298 result_pre_lstar.burned_fees, None,
5299 "pre-LStar: burned_fees must be None"
5300 );
5301 }
5302
5303 #[test]
5320 fn test_burned_fees_post_refund_discriminating() {
5321 use std::sync::atomic::AtomicUsize;
5322 use std::sync::mpsc;
5323
5324 const BASE_FEE: u64 = 10;
5325 const TX_GAS_LIMIT: u64 = 100_000;
5327
5328 let contract_addr = Address::from_low_u64_be(0xC0DEBEEF);
5329 let crypto = NativeCrypto;
5330
5331 let tx_a = signed_call_tx(contract_addr, 0, BASE_FEE, TX_GAS_LIMIT);
5333 let block_a = make_block_with_tx(tx_a, BASE_FEE);
5334 let mut db_a = make_gen_db_sstore_clear(lstar_chain_config(), contract_addr);
5335 let (result_a, _) = LEVM::execute_block(&block_a, &mut db_a, VMType::L1, &crypto, None)
5336 .expect("execute_block with SSTORE clear succeeded");
5337
5338 let cumulative_a = result_a.receipts[0].cumulative_gas_used;
5339 assert!(
5341 result_a.block_gas_used > cumulative_a,
5342 "SSTORE refund must cause block_gas_used ({}) > cumulative_gas_used ({})",
5343 result_a.block_gas_used,
5344 cumulative_a,
5345 );
5346 assert_eq!(
5348 result_a.burned_fees,
5349 Some(BASE_FEE * cumulative_a),
5350 "execute_block: burned_fees must use post-refund gas (cumulative={}, block_gas_used={})",
5351 cumulative_a,
5352 result_a.block_gas_used,
5353 );
5354
5355 let tx_b = signed_call_tx(contract_addr, 0, BASE_FEE, TX_GAS_LIMIT);
5357 let block_b = make_block_with_tx(tx_b, BASE_FEE);
5358 let mut db_b = make_gen_db_sstore_clear(lstar_chain_config(), contract_addr);
5359 let (tx_sender_b, _rx_b) = mpsc::channel();
5360 let queue_len_b = AtomicUsize::new(0);
5361 let (result_b, _) = LEVM::execute_block_pipeline(
5362 &block_b,
5363 &mut db_b,
5364 VMType::L1,
5365 Some(tx_sender_b),
5366 &queue_len_b,
5367 &crypto,
5368 None, false,
5370 None,
5371 )
5372 .expect("execute_block_pipeline sequential with SSTORE clear succeeded");
5373
5374 let cumulative_b = result_b.receipts[0].cumulative_gas_used;
5375 assert!(
5376 result_b.block_gas_used > cumulative_b,
5377 "sequential pipeline: SSTORE refund must be present",
5378 );
5379 assert_eq!(
5380 result_b.burned_fees,
5381 Some(BASE_FEE * cumulative_b),
5382 "execute_block_pipeline (sequential): burned_fees must use post-refund gas",
5383 );
5384
5385 let tx_c = signed_call_tx(contract_addr, 0, BASE_FEE, TX_GAS_LIMIT);
5388 let block_c = make_block_with_tx(tx_c, BASE_FEE);
5389 let mut db_c1 = make_gen_db_sstore_clear(lstar_chain_config(), contract_addr);
5390 let (_, bal_c) = LEVM::execute_block(&block_c, &mut db_c1, VMType::L1, &crypto, None)
5391 .expect("BAL generation succeeded");
5392 let bal_c = bal_c.expect("LStar must produce a BAL");
5393
5394 let mut db_c2 = make_gen_db_sstore_clear(lstar_chain_config(), contract_addr);
5395 let (tx_sender_c, _rx_c) = mpsc::channel();
5396 let queue_len_c = AtomicUsize::new(0);
5397 let (result_c, _) = LEVM::execute_block_pipeline(
5398 &block_c,
5399 &mut db_c2,
5400 VMType::L1,
5401 Some(tx_sender_c),
5402 &queue_len_c,
5403 &crypto,
5404 Some(std::sync::Arc::new(bal_c)), true,
5406 None,
5407 )
5408 .expect("execute_block_pipeline parallel with SSTORE clear succeeded");
5409
5410 let cumulative_c = result_c.receipts[0].cumulative_gas_used;
5411 assert!(
5412 result_c.block_gas_used > cumulative_c,
5413 "parallel pipeline: SSTORE refund must be present",
5414 );
5415 assert_eq!(
5416 result_c.burned_fees,
5417 Some(BASE_FEE * cumulative_c),
5418 "execute_block_pipeline (parallel): burned_fees must use post-refund gas",
5419 );
5420
5421 assert_eq!(
5423 result_a.burned_fees, result_b.burned_fees,
5424 "execute_block vs sequential pipeline: burned_fees must match",
5425 );
5426 assert_eq!(
5427 result_a.burned_fees, result_c.burned_fees,
5428 "execute_block vs parallel pipeline: burned_fees must match",
5429 );
5430
5431 let tx_d = signed_call_tx(contract_addr, 0, BASE_FEE, TX_GAS_LIMIT);
5433 let block_d = make_block_with_tx(tx_d, BASE_FEE);
5434 let mut db_d = make_gen_db_sstore_clear(amsterdam_chain_config(), contract_addr);
5435 let (result_d, _) = LEVM::execute_block(&block_d, &mut db_d, VMType::L1, &crypto, None)
5436 .expect("pre-LStar with SSTORE clear succeeded");
5437 assert_eq!(
5438 result_d.burned_fees, None,
5439 "pre-LStar: burned_fees must be None"
5440 );
5441 }
5442}
5443
5444#[cfg(test)]
5445mod simulated_tx_encoding_tests {
5446 use super::*;
5456 use ethrex_common::types::GenericTransaction;
5457 use ethrex_rlp::encode::RLPEncode;
5458
5459 fn signed_equivalent(gas_limit: u64, data: Bytes) -> Transaction {
5462 Transaction::EIP1559Transaction(EIP1559Transaction {
5463 chain_id: 9_999,
5464 nonce: 42,
5465 max_priority_fee_per_gas: 1_000_000_000,
5466 max_fee_per_gas: 2_000_000_000,
5467 gas_limit,
5468 to: TxKind::Create,
5469 value: U256::from(1u64),
5470 data,
5471 access_list: vec![],
5472 signature_y_parity: true,
5473 signature_r: U256::MAX,
5474 signature_s: U256::MAX,
5475 ..Default::default()
5476 })
5477 }
5478
5479 fn generic(gas_limit: u64, data: Bytes) -> GenericTransaction {
5480 GenericTransaction {
5481 to: TxKind::Create,
5482 value: U256::from(1u64),
5483 input: data,
5484 nonce: Some(42),
5485 gas: Some(gas_limit),
5486 max_fee_per_gas: Some(2_000_000_000),
5487 max_priority_fee_per_gas: Some(1_000_000_000),
5488 chain_id: Some(9_999),
5489 ..Default::default()
5490 }
5491 }
5492
5493 #[test]
5494 fn simulated_tx_never_encodes_shorter_than_the_signed_one() {
5495 for (gas_limit, data) in [
5496 (21_000u64, Bytes::new()),
5497 (150_000, Bytes::from_static(&[0x60, 0x00, 0x60, 0x00])),
5498 (5_000_000, Bytes::from(vec![0xab; 4_096])),
5499 ] {
5500 let simulated = generic_tx_to_transaction(&generic(gas_limit, data.clone()))
5501 .expect("conversion should succeed");
5502 let signed = signed_equivalent(gas_limit, data.clone());
5503 assert!(
5504 simulated.length() >= signed.length(),
5505 "simulated tx encodes {} bytes, signed one {} — the L2 would under-reserve \
5506 the L1 fee by the difference (gas_limit {gas_limit}, {} data bytes)",
5507 simulated.length(),
5508 signed.length(),
5509 data.len(),
5510 );
5511 }
5512 }
5513}