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, IntoParallelRefIterator, ParallelIterator};
75#[cfg(feature = "rayon")]
76use rustc_hash::{FxHashMap, FxHashSet};
77use std::cmp::min;
78use std::sync::Arc;
79#[cfg(feature = "rayon")]
80use std::sync::atomic::AtomicBool;
81use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
82use std::sync::mpsc::Sender;
83
84#[cfg(feature = "rayon")]
87const PRE_BLOCK_BAL_INDEX: u32 = 0;
88
89fn lstar_burned_fees(
97 chain_config: ðrex_common::types::ChainConfig,
98 header: ðrex_common::types::BlockHeader,
99 post_refund_gas: u64,
100) -> u64 {
101 let evm_cfg = EVMConfig::new_from_chain_config(chain_config, header);
102 let blob_base_fee = get_base_fee_per_blob_gas(header.excess_blob_gas, &evm_cfg)
103 .map(|v| u64::try_from(v).unwrap_or(u64::MAX))
104 .unwrap_or(0);
105 let blob_gas_used = header.blob_gas_used.unwrap_or(0);
106 compute_burned_fees(
107 header.base_fee_per_gas.unwrap_or(0),
108 post_refund_gas,
110 blob_base_fee,
111 blob_gas_used,
112 )
113}
114
115#[derive(Debug)]
121pub struct LEVM;
122
123fn frame_receipts_from(
127 frame_results: Option<Vec<(u8, u64, Vec<Log>)>>,
128) -> Option<Vec<FrameReceipt>> {
129 frame_results.map(|results| {
130 results
131 .into_iter()
132 .map(|(status, gas_used, logs)| FrameReceipt {
133 status,
134 gas_used,
135 logs,
136 })
137 .collect()
138 })
139}
140
141fn check_gas_limit(
143 block_gas_used: u64,
144 tx_gas_limit: u64,
145 block_gas_limit: u64,
146) -> Result<(), EvmError> {
147 if tx_gas_limit > block_gas_limit.saturating_sub(block_gas_used) {
148 return Err(EvmError::Transaction(format!(
149 "Gas allowance exceeded: \
150 used {block_gas_used} + tx limit {tx_gas_limit} > block limit {block_gas_limit}"
151 )));
152 }
153 Ok(())
154}
155
156pub fn block_work_budget(block_gas_limit: u64) -> u64 {
163 block_gas_limit.saturating_mul(2)
164}
165
166pub fn check_minimum_block_work<'a>(
179 txs_with_sender: impl IntoIterator<Item = (&'a Transaction, Address)>,
180 fork: Fork,
181 block_gas_limit: u64,
182) -> Result<(), EvmError> {
183 let budget = block_work_budget(block_gas_limit);
184 let mut floor_total = 0_u64;
185 for (tx, sender) in txs_with_sender {
186 let floor = intrinsic_gas_floor(tx, sender, fork)
190 .map_err(|e| EvmError::Transaction(format!("intrinsic gas floor: {e}")))?;
191 floor_total = floor_total.saturating_add(floor);
192 if floor_total > budget {
193 return Err(EvmError::Transaction(format!(
194 "Gas allowance exceeded: minimum gas of the block's transactions \
195 {floor_total} exceeds the block work budget {budget} \
196 (block_gas_limit={block_gas_limit})"
197 )));
198 }
199 }
200 Ok(())
201}
202
203#[derive(Default)]
214pub struct CompletedGas {
215 regular: AtomicU64,
216 state: AtomicU64,
217}
218
219impl CompletedGas {
220 pub fn check(&self, block_gas_limit: u64) -> Result<(), EvmError> {
222 let regular = self.regular.load(Ordering::Relaxed);
223 let state = self.state.load(Ordering::Relaxed);
224 if regular > block_gas_limit || state > block_gas_limit {
225 return Err(EvmError::Transaction(format!(
226 "Gas allowance exceeded: transactions completed during parallel execution \
227 used regular={regular} state={state}, over block_gas_limit={block_gas_limit}"
228 )));
229 }
230 Ok(())
231 }
232
233 pub fn record(
235 &self,
236 regular_gas: u64,
237 state_gas: u64,
238 block_gas_limit: u64,
239 ) -> Result<(), EvmError> {
240 self.regular.fetch_add(regular_gas, Ordering::Relaxed);
241 self.state.fetch_add(state_gas, Ordering::Relaxed);
242 self.check(block_gas_limit)
243 }
244}
245
246pub fn check_2d_gas_allowance(
265 tx: &Transaction,
266 block_gas_used_regular: u64,
267 block_gas_used_state: u64,
268 block_gas_limit: u64,
269) -> Result<(), EvmError> {
270 let tx_gas = tx.gas_limit();
271 let regular_available = block_gas_limit.saturating_sub(block_gas_used_regular);
272 let state_available = block_gas_limit.saturating_sub(block_gas_used_state);
273
274 let regular_contrib = tx_gas.min(TX_MAX_GAS_LIMIT_AMSTERDAM);
279 if regular_contrib > regular_available {
280 return Err(EvmError::Transaction(format!(
281 "Gas allowance exceeded: regular dim worst-case {regular_contrib} > \
282 available {regular_available} (block_gas_used_regular={block_gas_used_regular}, \
283 block_gas_limit={block_gas_limit})"
284 )));
285 }
286
287 let state_contrib = tx_gas;
289 if state_contrib > state_available {
290 return Err(EvmError::Transaction(format!(
291 "Gas allowance exceeded: state dim worst-case {state_contrib} > \
292 available {state_available} (block_gas_used_state={block_gas_used_state}, \
293 block_gas_limit={block_gas_limit})"
294 )));
295 }
296
297 Ok(())
298}
299
300#[cfg(feature = "rayon")]
306#[derive(Debug, thiserror::Error)]
307pub enum BalValidationError {
308 #[error("{0}")]
309 Mismatch(String),
310 #[error("{0}")]
311 Database(String),
312}
313
314impl LEVM {
315 pub fn execute_block(
320 block: &Block,
321 db: &mut GeneralizedDatabase,
322 vm_type: VMType,
323 crypto: &dyn Crypto,
324 stateless_validator: Option<&dyn StatelessValidator>,
325 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
326 let chain_config = db.store.get_chain_config()?;
327 let is_amsterdam = chain_config.is_amsterdam_activated(block.header.timestamp);
328 let is_lstar = chain_config.is_lstar_activated(block.header.timestamp);
330
331 debug_assert!(
336 block.body.transactions.len() < u32::MAX as usize,
337 "tx count overflows u32 BlockAccessIndex"
338 );
339
340 if is_amsterdam {
342 db.enable_bal_recording();
343 db.set_bal_index(0);
345 }
346
347 Self::prepare_block(block, db, vm_type, crypto)?;
348
349 let evm_config = EVMConfig::new_from_chain_config(&chain_config, &block.header);
353 let chain_id = chain_config.chain_id;
354 let base_blob_fee_per_gas =
355 get_base_fee_per_blob_gas(block.header.excess_blob_gas, &evm_config)?;
356 let mut shared_stack_pool = Vec::with_capacity(STACK_LIMIT);
358 let mut shared_memory_pool = Vec::with_capacity(1);
359
360 let n_txs = block.body.transactions.len();
361 let mut receipts = Vec::with_capacity(n_txs);
362 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(n_txs);
363 let mut cumulative_gas_used = 0_u64;
365 let mut block_gas_used = 0_u64;
367 let mut block_regular_gas_used = 0_u64;
369 let mut block_state_gas_used = 0_u64;
370 let transactions_with_sender =
371 block
372 .body
373 .get_transactions_with_sender(crypto)
374 .map_err(|error| {
375 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
376 })?;
377
378 for (tx_idx, (tx, tx_sender)) in transactions_with_sender.into_iter().enumerate() {
379 if !is_amsterdam {
386 check_gas_limit(cumulative_gas_used, tx.gas_limit(), block.header.gas_limit)?;
387 }
388
389 if is_amsterdam {
391 check_2d_gas_allowance(
392 tx,
393 block_regular_gas_used,
394 block_state_gas_used,
395 block.header.gas_limit,
396 )?;
397 }
398
399 if is_amsterdam {
401 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
402 db.set_bal_index(bal_index);
403
404 if let Some(recorder) = db.bal_recorder_mut() {
408 recorder.record_touched_address(tx_sender);
409 }
410 }
411
412 let report = Self::execute_tx_in_block(
413 tx,
414 tx_sender,
415 &block.header,
416 db,
417 vm_type,
418 base_blob_fee_per_gas,
419 &mut shared_stack_pool,
420 &mut shared_memory_pool,
421 false,
422 crypto,
423 evm_config,
424 chain_id,
425 stateless_validator,
426 )?;
427
428 tx_gas_breakdowns.push(TxGasBreakdown::from_report(
429 tx_idx,
430 tx.hash(crypto),
431 &report,
432 ));
433
434 cumulative_gas_used += report.gas_spent;
436
437 let tx_state_gas = report.state_gas_used;
440 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
441 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
442 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
443
444 if is_amsterdam {
445 block_gas_used = block_regular_gas_used.max(block_state_gas_used);
447 ::tracing::debug!(
448 "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}",
449 report.gas_used,
450 report.gas_spent,
451 );
452
453 if block_regular_gas_used > block.header.gas_limit
458 || block_state_gas_used > block.header.gas_limit
459 {
460 return Err(EvmError::Transaction(format!(
461 "Gas allowance exceeded: Block gas used overflow: \
462 block_gas_used {block_gas_used} > block_gas_limit {}",
463 block.header.gas_limit
464 )));
465 }
466 } else {
467 block_gas_used = block_gas_used.saturating_add(report.gas_used);
468 }
469
470 let mut receipt = Receipt::new(
471 tx.tx_type(),
472 matches!(report.result, TxResult::Success),
473 cumulative_gas_used,
474 report.logs,
475 );
476
477 if matches!(tx, Transaction::FrameTransaction(_)) {
479 receipt.payer = report.payer_address;
480 receipt.frame_receipts = frame_receipts_from(report.frame_results);
481 }
482
483 receipts.push(receipt);
484 }
485
486 if is_amsterdam && block_gas_used > block.header.gas_limit {
490 return Err(EvmError::Transaction(format!(
491 "Gas allowance exceeded: Block gas used overflow: \
492 block_gas_used {block_gas_used} > block_gas_limit {}",
493 block.header.gas_limit
494 )));
495 }
496
497 if is_amsterdam {
510 let post_tx_index =
511 u32::try_from(block.body.transactions.len() + 1).unwrap_or(u32::MAX);
512 db.set_bal_index(post_tx_index);
513
514 if let Some(withdrawals) = &block.body.withdrawals
518 && let Some(recorder) = db.bal_recorder_mut()
519 {
520 recorder.extend_touched_addresses(withdrawals.iter().map(|w| w.address));
521 }
522 }
523
524 if let Some(withdrawals) = &block.body.withdrawals {
525 Self::process_withdrawals(db, withdrawals)?;
526 }
527
528 let requests = match vm_type {
532 VMType::L1 => extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?,
533 VMType::L2(_) => Default::default(),
534 };
535
536 let bal = db.take_bal();
538
539 Ok((
540 BlockExecutionResult {
541 receipts,
542 requests,
543 block_gas_used,
544 burned_fees: is_lstar
546 .then(|| lstar_burned_fees(&chain_config, &block.header, cumulative_gas_used)),
547 tx_gas_breakdowns,
548 },
549 bal,
550 ))
551 }
552
553 #[allow(clippy::too_many_arguments)]
557 pub fn execute_block_pipeline(
558 block: &Block,
559 db: &mut GeneralizedDatabase,
560 vm_type: VMType,
561 merkleizer: Option<Sender<Vec<AccountUpdate>>>,
562 queue_length: &AtomicUsize,
563 crypto: &dyn Crypto,
564 header_bal: Option<Arc<BlockAccessList>>,
565 bal_parallel_exec_enabled: bool,
566 stateless_validator: Option<&dyn StatelessValidator>,
567 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
568 let chain_config = db.store.get_chain_config()?;
569 let is_amsterdam = chain_config.is_amsterdam_activated(block.header.timestamp);
570 let is_lstar = chain_config.is_lstar_activated(block.header.timestamp);
571 let evm_config = EVMConfig::new_from_chain_config(&chain_config, &block.header);
574 let chain_id = chain_config.chain_id;
575
576 debug_assert!(
578 block.body.transactions.len() < u32::MAX as usize,
579 "tx count overflows u32 BlockAccessIndex"
580 );
581
582 let transactions_with_sender =
583 block
584 .body
585 .get_transactions_with_sender(crypto)
586 .map_err(|error| {
587 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
588 })?;
589
590 #[cfg(not(feature = "rayon"))]
591 let _ = (header_bal, bal_parallel_exec_enabled);
594 #[cfg(feature = "rayon")]
595 if let Some(bal) = header_bal
601 && is_amsterdam
602 && bal_parallel_exec_enabled
603 {
604 validate_header_bal_indices(&bal, block.body.transactions.len())
610 .map_err(|e| EvmError::Custom(e.to_string()))?;
611 validate_bal_code_sizes(&bal, AMSTERDAM_MAX_CODE_SIZE)
614 .map_err(|e| EvmError::Custom(format!("BAL validation failed: {e}")))?;
615
616 db.enable_bal_recording();
619 db.set_bal_index(PRE_BLOCK_BAL_INDEX);
620 let prepare_result = Self::prepare_block(block, db, vm_type, crypto);
621 let pre_block_bal = db.take_bal().unwrap_or_default();
622 prepare_result?;
623
624 let validation_index = Arc::new(bal.build_validation_index());
626
627 Self::validate_bal_pre_exec_index(db, &bal, &validation_index)?;
634
635 LEVM::get_state_transitions_tx(db)?;
637 let system_seed = Arc::new(std::mem::take(&mut db.initial_accounts_state));
638
639 let parallel_result = Self::execute_block_parallel(
640 block,
641 &transactions_with_sender,
642 db,
643 vm_type,
644 Arc::clone(&bal),
645 merkleizer.as_ref(),
646 queue_length,
647 system_seed,
648 &pre_block_bal,
649 crypto,
650 Arc::clone(&validation_index),
651 stateless_validator,
652 );
653
654 let (
659 receipts,
660 block_gas_used,
661 mut unread_storage_reads,
662 mut unaccessed_pure_accounts,
663 tx_gas_breakdowns,
664 ) = match parallel_result {
665 Ok(result) => result,
666 Err(parallel_err) => {
667 let last_tx_idx =
668 u32::try_from(block.body.transactions.len()).unwrap_or(u32::MAX);
669 if Self::seed_db_from_bal(
670 db,
671 &bal,
672 last_tx_idx,
673 &validation_index.accounts_by_min_index,
674 )
675 .is_ok()
676 && let VMType::L1 = vm_type
677 && let Err(e @ EvmError::SystemContractCallFailed(_)) =
678 extract_all_requests_levm(&[], db, &block.header, vm_type, crypto)
679 {
680 return Err(e);
681 }
682 return Err(parallel_err);
683 }
684 };
685
686 let last_tx_idx = u32::try_from(block.body.transactions.len()).unwrap_or(u32::MAX);
691 Self::seed_db_from_bal(
693 db,
694 &bal,
695 last_tx_idx,
696 &validation_index.accounts_by_min_index,
697 )?;
698
699 let withdrawal_bal_idx = u32::try_from(block.body.transactions.len())
700 .map(|n| n.saturating_add(1))
701 .unwrap_or(u32::MAX);
702 db.enable_bal_recording();
703 db.set_bal_index(withdrawal_bal_idx);
704
705 let post_block_result = (|| -> Result<Vec<Requests>, EvmError> {
710 if let Some(withdrawals) = &block.body.withdrawals {
711 Self::process_withdrawals(db, withdrawals)?;
712 }
713
714 let requests = match vm_type {
715 VMType::L1 => {
716 extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?
717 }
718 VMType::L2(_) => Default::default(),
719 };
720 Ok(requests)
721 })();
722 let post_block_bal = db.take_bal().unwrap_or_default();
723 let requests = post_block_result?;
724 let withdrawal_idx = u32::try_from(block.body.transactions.len())
732 .map(|n| n.saturating_add(1))
733 .unwrap_or(u32::MAX);
734 Self::validate_bal_withdrawal_index(db, &bal, withdrawal_idx, &validation_index)?;
735
736 if !unread_storage_reads.is_empty() {
738 for (addr, acct) in &db.current_accounts_state {
739 for key in acct.storage.keys() {
740 unread_storage_reads.remove(&(*addr, *key));
741 }
742 }
743 }
744
745 if !unaccessed_pure_accounts.is_empty() {
750 if let Some(withdrawals) = &block.body.withdrawals {
751 for w in withdrawals {
752 unaccessed_pure_accounts.remove(&w.address);
753 }
754 }
755 for canon in post_block_bal.accounts() {
756 if canon.address == SYSTEM_ADDRESS {
758 continue;
759 }
760 unaccessed_pure_accounts.remove(&canon.address);
761 }
762 }
763
764 Self::validate_bal_covers(
765 &db.current_accounts_state,
766 &post_block_bal,
767 &bal,
768 &validation_index,
769 "the withdrawal/request phase",
770 withdrawal_bal_idx,
771 )?;
772
773 if let Some(withdrawals) = &block.body.withdrawals {
776 for w in withdrawals {
777 if !validation_index.addr_to_idx.contains_key(&w.address) {
778 return Err(EvmError::Custom(format!(
779 "BAL validation failed: withdrawal recipient {:?} is missing \
780 from BAL",
781 w.address
782 )));
783 }
784 }
785 }
786
787 if let Some((addr, key)) = unread_storage_reads.iter().next() {
789 let slot = ethrex_common::BigEndianHash::into_uint(key);
790 return Err(EvmError::Custom(format!(
791 "BAL validation failed: storage_read for account {addr:?} slot \
792 {slot} was never actually read during block execution"
793 )));
794 }
795
796 if let Some(addr) = unaccessed_pure_accounts.iter().next() {
798 return Err(EvmError::Custom(format!(
799 "BAL validation failed: account {addr:?} has no mutations \
800 and no storage reads but was never accessed during block execution"
801 )));
802 }
803
804 bal.validate_ordering()
807 .map_err(|e| EvmError::Custom(format!("BAL validation failed: {e}")))?;
808
809 validate_block_access_list_size(&block.header, &chain_config, &bal)
812 .map_err(|e| EvmError::Custom(e.to_string()))?;
813
814 let post_refund_gas = receipts.last().map(|r| r.cumulative_gas_used).unwrap_or(0);
818 let burned_fees_par =
819 is_lstar.then(|| lstar_burned_fees(&chain_config, &block.header, post_refund_gas));
820
821 return Ok((
822 BlockExecutionResult {
823 receipts,
824 requests,
825 block_gas_used,
826 burned_fees: burned_fees_par,
827 tx_gas_breakdowns,
828 },
829 None,
830 ));
831 }
832
833 let Some(merkleizer) = merkleizer else {
839 return Err(EvmError::Custom(
840 "sequential execution path called without a merkleizer Sender".to_string(),
841 ));
842 };
843 if is_amsterdam {
844 db.enable_bal_recording();
845 db.set_bal_index(0);
847 }
848
849 Self::prepare_block(block, db, vm_type, crypto)?;
850
851 let base_blob_fee_per_gas =
853 get_base_fee_per_blob_gas(block.header.excess_blob_gas, &evm_config)?;
854
855 let mut shared_stack_pool = Vec::with_capacity(STACK_LIMIT);
856 let mut shared_memory_pool = Vec::with_capacity(1);
858
859 let n_txs = block.body.transactions.len();
860 let mut receipts = Vec::with_capacity(n_txs);
861 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(n_txs);
862 let mut cumulative_gas_used = 0_u64;
864 let mut block_gas_used = 0_u64;
866 let mut block_regular_gas_used = 0_u64;
868 let mut block_state_gas_used = 0_u64;
869 let mut tx_since_last_flush = 2;
872
873 for (tx_idx, (tx, tx_sender)) in transactions_with_sender.into_iter().enumerate() {
874 if !is_amsterdam {
881 check_gas_limit(cumulative_gas_used, tx.gas_limit(), block.header.gas_limit)?;
882 }
883
884 if is_amsterdam {
886 check_2d_gas_allowance(
887 tx,
888 block_regular_gas_used,
889 block_state_gas_used,
890 block.header.gas_limit,
891 )?;
892 }
893
894 if is_amsterdam {
896 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
897 db.set_bal_index(bal_index);
898
899 if let Some(recorder) = db.bal_recorder_mut() {
903 recorder.record_touched_address(tx_sender);
904 }
905 }
906
907 let report = Self::execute_tx_in_block(
908 tx,
909 tx_sender,
910 &block.header,
911 db,
912 vm_type,
913 base_blob_fee_per_gas,
914 &mut shared_stack_pool,
915 &mut shared_memory_pool,
916 false,
917 crypto,
918 evm_config,
919 chain_id,
920 stateless_validator,
921 )?;
922
923 tx_gas_breakdowns.push(TxGasBreakdown::from_report(
924 tx_idx,
925 tx.hash(crypto),
926 &report,
927 ));
928
929 if queue_length.load(Ordering::Relaxed) == 0 && tx_since_last_flush > 5 {
930 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
931 tx_since_last_flush = 0;
932 } else {
933 tx_since_last_flush += 1;
934 }
935
936 cumulative_gas_used += report.gas_spent;
938
939 let tx_state_gas = report.state_gas_used;
942 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
943 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
944 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
945
946 if is_amsterdam {
947 block_gas_used = block_regular_gas_used.max(block_state_gas_used);
949
950 if block_regular_gas_used > block.header.gas_limit
955 || block_state_gas_used > block.header.gas_limit
956 {
957 return Err(EvmError::Transaction(format!(
958 "Gas allowance exceeded: Block gas used overflow: \
959 block_gas_used {block_gas_used} > block_gas_limit {}",
960 block.header.gas_limit
961 )));
962 }
963 } else {
964 block_gas_used = block_gas_used.saturating_add(report.gas_used);
965 }
966
967 let mut receipt = Receipt::new(
968 tx.tx_type(),
969 matches!(report.result, TxResult::Success),
970 cumulative_gas_used,
971 report.logs,
972 );
973
974 if matches!(tx, Transaction::FrameTransaction(_)) {
976 receipt.payer = report.payer_address;
977 receipt.frame_receipts = frame_receipts_from(report.frame_results);
978 }
979
980 receipts.push(receipt);
981 }
982
983 if is_amsterdam && block_gas_used > block.header.gas_limit {
987 return Err(EvmError::Transaction(format!(
988 "Gas allowance exceeded: Block gas used overflow: \
989 block_gas_used {block_gas_used} > block_gas_limit {}",
990 block.header.gas_limit
991 )));
992 }
993
994 #[cfg(feature = "perf_opcode_timings")]
995 {
996 let mut timings = OPCODE_TIMINGS.lock().expect("poison");
997 timings.inc_tx_count(receipts.len());
998 timings.inc_block_count();
999 ::tracing::info!("{}", timings.info_pretty());
1000 let precompiles_timings = PRECOMPILES_TIMINGS.lock().expect("poison");
1001 ::tracing::info!("{}", precompiles_timings.info_pretty());
1002 }
1003
1004 if queue_length.load(Ordering::Relaxed) == 0 {
1005 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
1006 }
1007
1008 if is_amsterdam {
1011 let post_tx_index =
1012 u32::try_from(block.body.transactions.len() + 1).unwrap_or(u32::MAX);
1013 db.set_bal_index(post_tx_index);
1014
1015 if let Some(withdrawals) = &block.body.withdrawals
1017 && let Some(recorder) = db.bal_recorder_mut()
1018 {
1019 recorder.extend_touched_addresses(withdrawals.iter().map(|w| w.address));
1020 }
1021 }
1022
1023 if let Some(withdrawals) = &block.body.withdrawals {
1026 Self::process_withdrawals(db, withdrawals)?;
1027 }
1028
1029 let requests = match vm_type {
1033 VMType::L1 => extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?,
1034 VMType::L2(_) => Default::default(),
1035 };
1036 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
1037
1038 let bal = db.take_bal();
1040
1041 Ok((
1042 BlockExecutionResult {
1043 receipts,
1044 requests,
1045 block_gas_used,
1046 burned_fees: is_lstar
1048 .then(|| lstar_burned_fees(&chain_config, &block.header, cumulative_gas_used)),
1049 tx_gas_breakdowns,
1050 },
1051 bal,
1052 ))
1053 }
1054
1055 #[cfg(feature = "rayon")]
1060 fn bal_to_account_updates(
1061 bal: &BlockAccessList,
1062 store: &dyn Database,
1063 ) -> Result<Vec<AccountUpdate>, EvmError> {
1064 use ethrex_common::types::AccountInfo;
1065
1066 let mut updates = Vec::new();
1067
1068 let write_addrs: Vec<Address> = bal
1070 .accounts()
1071 .iter()
1072 .filter(|ac| {
1073 !ac.balance_changes.is_empty()
1074 || !ac.nonce_changes.is_empty()
1075 || !ac.code_changes.is_empty()
1076 || !ac.storage_changes.is_empty()
1077 })
1078 .map(|ac| ac.address)
1079 .collect();
1080 let _ = store.prefetch_accounts(&write_addrs);
1084
1085 for acct_changes in bal.accounts() {
1086 let addr = acct_changes.address;
1087
1088 let has_writes = !acct_changes.balance_changes.is_empty()
1090 || !acct_changes.nonce_changes.is_empty()
1091 || !acct_changes.code_changes.is_empty()
1092 || !acct_changes.storage_changes.is_empty();
1093 if !has_writes {
1094 continue;
1095 }
1096
1097 let prestate = store
1099 .get_account_state(addr)
1100 .map_err(|e| EvmError::Custom(format!("bal_to_account_updates: {e}")))?;
1101
1102 let balance = acct_changes
1104 .balance_changes
1105 .last()
1106 .map(|c| c.post_balance)
1107 .unwrap_or(prestate.balance);
1108
1109 let nonce = acct_changes
1111 .nonce_changes
1112 .last()
1113 .map(|c| c.post_nonce)
1114 .unwrap_or(prestate.nonce);
1115
1116 let (code_hash, code) = if let Some(c) = acct_changes.code_changes.last() {
1118 code_from_bal(&c.new_code)
1119 } else {
1120 (prestate.code_hash, None)
1121 };
1122
1123 let mut added_storage = FxHashMap::with_capacity_and_hasher(
1125 acct_changes.storage_changes.len(),
1126 Default::default(),
1127 );
1128 for slot_change in &acct_changes.storage_changes {
1129 if let Some(last) = slot_change.slot_changes.last() {
1130 let key = ethrex_common::utils::u256_to_h256(slot_change.slot);
1131 added_storage.insert(key, last.post_value);
1132 }
1133 }
1134
1135 let post_empty = balance.is_zero() && nonce == 0 && code_hash == *EMPTY_KECCAK_HASH;
1137 let pre_empty = prestate.balance.is_zero()
1138 && prestate.nonce == 0
1139 && prestate.code_hash == *EMPTY_KECCAK_HASH;
1140 let removed = post_empty && !pre_empty;
1141
1142 let balance_changed = acct_changes
1143 .balance_changes
1144 .last()
1145 .is_some_and(|c| c.post_balance != prestate.balance);
1146 let nonce_changed = acct_changes
1147 .nonce_changes
1148 .last()
1149 .is_some_and(|c| c.post_nonce != prestate.nonce);
1150 let code_changed = acct_changes.code_changes.last().is_some();
1151 let acc_info_updated = balance_changed || nonce_changed || code_changed;
1152
1153 if !removed && !acc_info_updated && added_storage.is_empty() {
1154 continue;
1155 }
1156
1157 let info = if acc_info_updated {
1158 Some(AccountInfo {
1159 code_hash,
1160 balance,
1161 nonce,
1162 })
1163 } else {
1164 None
1165 };
1166
1167 let update = AccountUpdate {
1168 address: addr,
1169 removed,
1170 info,
1171 code,
1172 added_storage,
1173 removed_storage: false,
1180 };
1181 updates.push(update);
1182 }
1183
1184 Ok(updates)
1185 }
1186
1187 #[cfg(feature = "rayon")]
1193 fn validate_bal_covers(
1194 cache: &CacheDB,
1195 phase_bal: &BlockAccessList,
1196 bal: &BlockAccessList,
1197 index: &BalAddressIndex,
1198 phase: &str,
1199 phase_idx: u32,
1200 ) -> Result<(), EvmError> {
1201 for canon in phase_bal.accounts() {
1204 let addr = canon.address;
1205 let Some(supplied) = index
1206 .addr_to_idx
1207 .get(&addr)
1208 .and_then(|i| bal.accounts().get(*i))
1209 else {
1210 return Err(EvmError::Custom(format!(
1211 "BAL validation failed: account {addr:?} was accessed during {phase} \
1212 but is missing from BAL"
1213 )));
1214 };
1215
1216 for canon_slot in &canon.storage_changes {
1218 let slot = canon_slot.slot;
1219 let supplied_changes = supplied
1220 .storage_changes
1221 .binary_search_by(|sc| sc.slot.cmp(&slot))
1222 .ok()
1223 .and_then(|i| supplied.storage_changes.get(i))
1224 .map(|sc| sc.slot_changes.as_slice())
1225 .unwrap_or_default();
1226 for canon_change in &canon_slot.slot_changes {
1227 let idx = canon_change.block_access_index;
1228 match find_exact_change_storage(supplied_changes, idx) {
1229 Some(v) if v == canon_change.post_value => {}
1230 Some(v) => {
1231 return Err(EvmError::Custom(format!(
1232 "BAL validation failed: account {addr:?} storage slot {slot} \
1233 was written during {phase} to {} but the BAL declares {v} \
1234 at index {idx}",
1235 canon_change.post_value
1236 )));
1237 }
1238 None => {
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 no \
1242 change at index {idx}",
1243 canon_change.post_value
1244 )));
1245 }
1246 }
1247 }
1248 }
1249
1250 for canon_change in &canon.balance_changes {
1251 let idx = canon_change.block_access_index;
1252 let expected = canon_change.post_balance;
1253 if find_exact_change_balance(&supplied.balance_changes, idx) != Some(expected) {
1254 return Err(EvmError::Custom(format!(
1255 "BAL validation failed: account {addr:?} balance changed to {expected} \
1256 during {phase} but the BAL declares no matching change at index {idx}"
1257 )));
1258 }
1259 }
1260 for canon_change in &canon.nonce_changes {
1261 let idx = canon_change.block_access_index;
1262 let expected = canon_change.post_nonce;
1263 if find_exact_change_nonce(&supplied.nonce_changes, idx) != Some(expected) {
1264 return Err(EvmError::Custom(format!(
1265 "BAL validation failed: account {addr:?} nonce changed to {expected} \
1266 during {phase} but the BAL declares no matching change at index {idx}"
1267 )));
1268 }
1269 }
1270 for canon_change in &canon.code_changes {
1271 let idx = canon_change.block_access_index;
1272 if find_exact_change_code(&supplied.code_changes, idx)
1273 != Some(&canon_change.new_code)
1274 {
1275 return Err(EvmError::Custom(format!(
1276 "BAL validation failed: account {addr:?} code changed during {phase} \
1277 but the BAL declares no matching change at index {idx}"
1278 )));
1279 }
1280 }
1281
1282 for &slot in &canon.storage_reads {
1284 if supplied.storage_reads.binary_search(&slot).is_err()
1285 && supplied
1286 .storage_changes
1287 .binary_search_by(|sc| sc.slot.cmp(&slot))
1288 .is_err()
1289 {
1290 return Err(EvmError::Custom(format!(
1291 "BAL validation failed: storage slot {slot} of account {addr:?} was \
1292 read during {phase} but is missing from BAL"
1293 )));
1294 }
1295 }
1296 }
1297
1298 let canonical: FxHashMap<Address, &AccountChanges> = phase_bal
1301 .accounts()
1302 .iter()
1303 .map(|a| (a.address, a))
1304 .collect();
1305 for supplied in bal.accounts() {
1306 let addr = supplied.address;
1307 let canon = canonical.get(&addr).copied();
1308 for sc in &supplied.storage_changes {
1309 if find_exact_change_storage(&sc.slot_changes, phase_idx).is_none() {
1310 continue;
1311 }
1312 let declared_canonically = canon.is_some_and(|c| {
1313 c.storage_changes
1314 .binary_search_by(|cs| cs.slot.cmp(&sc.slot))
1315 .ok()
1316 .and_then(|i| c.storage_changes.get(i))
1317 .is_some_and(|cs| {
1318 find_exact_change_storage(&cs.slot_changes, phase_idx).is_some()
1319 })
1320 });
1321 if !declared_canonically {
1322 return Err(EvmError::Custom(format!(
1323 "BAL validation failed: account {addr:?} declares a storage change \
1324 for slot {} at index {phase_idx} but {phase} did not write it",
1325 sc.slot
1326 )));
1327 }
1328 }
1329 if find_exact_change_balance(&supplied.balance_changes, phase_idx).is_some()
1330 && canon.is_none_or(|c| {
1331 find_exact_change_balance(&c.balance_changes, phase_idx).is_none()
1332 })
1333 {
1334 return Err(EvmError::Custom(format!(
1335 "BAL validation failed: account {addr:?} declares a balance change at \
1336 index {phase_idx} but {phase} did not change its balance"
1337 )));
1338 }
1339 if find_exact_change_nonce(&supplied.nonce_changes, phase_idx).is_some()
1340 && canon
1341 .is_none_or(|c| find_exact_change_nonce(&c.nonce_changes, phase_idx).is_none())
1342 {
1343 return Err(EvmError::Custom(format!(
1344 "BAL validation failed: account {addr:?} declares a nonce change at \
1345 index {phase_idx} but {phase} did not change its nonce"
1346 )));
1347 }
1348 if find_exact_change_code(&supplied.code_changes, phase_idx).is_some()
1349 && canon
1350 .is_none_or(|c| find_exact_change_code(&c.code_changes, phase_idx).is_none())
1351 {
1352 return Err(EvmError::Custom(format!(
1353 "BAL validation failed: account {addr:?} declares a code change at \
1354 index {phase_idx} but {phase} did not change its code"
1355 )));
1356 }
1357 }
1358
1359 for (addr, acct) in cache {
1361 if *addr == SYSTEM_ADDRESS {
1362 continue;
1363 }
1364 let supplied = match index
1365 .addr_to_idx
1366 .get(addr)
1367 .and_then(|i| bal.accounts().get(*i))
1368 {
1369 Some(a) => a,
1370 None if !acct.storage.is_empty() => {
1372 return Err(EvmError::Custom(format!(
1373 "BAL validation failed: account {addr:?} was accessed during {phase} \
1374 but is missing from BAL"
1375 )));
1376 }
1377 None => continue,
1378 };
1379 debug_assert!(
1382 supplied.storage_reads.windows(2).all(|w| w[0] < w[1]),
1383 "storage_reads must be strictly ascending for binary_search"
1384 );
1385 for key in acct.storage.keys() {
1386 let slot = ethrex_common::BigEndianHash::into_uint(key);
1387 if supplied.storage_reads.binary_search(&slot).is_err()
1388 && supplied
1389 .storage_changes
1390 .binary_search_by(|sc| sc.slot.cmp(&slot))
1391 .is_err()
1392 {
1393 return Err(EvmError::Custom(format!(
1394 "BAL validation failed: storage slot {slot} of account {addr:?} was \
1395 read during {phase} but is missing from BAL"
1396 )));
1397 }
1398 }
1399 }
1400 Ok(())
1401 }
1402
1403 #[cfg(feature = "rayon")]
1420 fn seed_db_from_bal(
1421 db: &mut GeneralizedDatabase,
1422 bal: &BlockAccessList,
1423 max_idx: u32,
1424 accounts_by_min_index: &[(u32, usize)],
1425 ) -> Result<(), EvmError> {
1426 let end = accounts_by_min_index.partition_point(|(min_idx, _)| *min_idx <= max_idx);
1427 let bal_accounts = bal.accounts();
1428 for &(_, acct_idx) in &accounts_by_min_index[..end] {
1429 seed_one_address_info_from_bal(db, bal, acct_idx, max_idx)
1430 .map_err(|e| EvmError::Custom(format!("seed_db_from_bal: {e}")))?;
1431
1432 let acct_changes = &bal_accounts[acct_idx];
1433 if acct_changes.storage_changes.is_empty() {
1434 continue;
1435 }
1436 let any_storage = acct_changes.storage_changes.iter().any(|sc| {
1437 sc.slot_changes
1438 .first()
1439 .is_some_and(|c| c.block_access_index <= max_idx)
1440 });
1441 if !any_storage {
1442 continue;
1443 }
1444 let addr = acct_changes.address;
1445 if !db.current_accounts_state.contains_key(&addr) {
1446 db.get_account(addr)
1447 .map_err(|e| EvmError::Custom(format!("seed storage: {e}")))?;
1448 }
1449 let acc = db
1450 .get_account_mut(addr)
1451 .map_err(|e| EvmError::Custom(format!("seed storage mut: {e}")))?;
1452 for sc in &acct_changes.storage_changes {
1453 if let Some(value) = post_value_at_or_before(sc, max_idx) {
1454 acc.storage
1455 .insert(ethrex_common::utils::u256_to_h256(sc.slot), value);
1456 }
1457 }
1458 }
1459 Ok(())
1460 }
1461
1462 #[cfg(feature = "rayon")]
1469 #[allow(clippy::too_many_arguments, clippy::type_complexity)]
1470 fn execute_block_parallel(
1471 block: &Block,
1472 txs_with_sender: &[(&Transaction, Address)],
1473 db: &mut GeneralizedDatabase,
1474 vm_type: VMType,
1475 bal: Arc<BlockAccessList>,
1476 merkleizer: Option<&Sender<Vec<AccountUpdate>>>,
1477 queue_length: &AtomicUsize,
1478 system_seed: Arc<CacheDB>,
1479 pre_block_bal: &BlockAccessList,
1480 crypto: &dyn Crypto,
1481 validation_index: Arc<BalAddressIndex>,
1482 stateless_validator: Option<&dyn StatelessValidator>,
1483 ) -> Result<
1484 (
1485 Vec<Receipt>,
1486 u64,
1487 FxHashSet<(Address, H256)>,
1488 FxHashSet<Address>,
1489 Vec<TxGasBreakdown>,
1490 ),
1491 EvmError,
1492 > {
1493 let store = db.store.clone();
1494 let header = &block.header;
1495 let n_txs = txs_with_sender.len();
1496 let chain_config = store.get_chain_config()?;
1501 let is_amsterdam = chain_config.is_amsterdam_activated(header.timestamp);
1502 let evm_config = EVMConfig::new_from_chain_config(&chain_config, header);
1505 let chain_id = chain_config.chain_id;
1506 let base_blob_fee_per_gas = get_base_fee_per_blob_gas(header.excess_blob_gas, &evm_config)?;
1508 debug_assert!(
1509 is_amsterdam,
1510 "execute_block_parallel invoked on non-Amsterdam block"
1511 );
1512
1513 if let Some(merkleizer) = merkleizer {
1517 let account_updates = Self::bal_to_account_updates(&bal, store.as_ref())?;
1518 merkleizer
1519 .send(account_updates)
1520 .map_err(|e| EvmError::Custom(format!("merkleizer send failed: {e}")))?;
1521 queue_length.fetch_add(1, Ordering::Relaxed);
1522 }
1523
1524 let mut unread_storage_reads: FxHashSet<(Address, H256)> = FxHashSet::default();
1527 let mut unaccessed_pure_accounts: FxHashSet<Address> = FxHashSet::default();
1530 for acct in bal.accounts() {
1531 for &slot in &acct.storage_reads {
1532 let key = ethrex_common::utils::u256_to_h256(slot);
1533 unread_storage_reads.insert((acct.address, key));
1534 }
1535 let is_pure = acct.storage_changes.is_empty()
1536 && acct.storage_reads.is_empty()
1537 && acct.balance_changes.is_empty()
1538 && acct.nonce_changes.is_empty()
1539 && acct.code_changes.is_empty();
1540 if is_pure {
1541 unaccessed_pure_accounts.insert(acct.address);
1542 }
1543 }
1544
1545 for canon in pre_block_bal.accounts() {
1552 if canon.address == SYSTEM_ADDRESS {
1553 continue;
1554 }
1555 unaccessed_pure_accounts.remove(&canon.address);
1556 }
1557
1558 unread_storage_reads.retain(|(addr, key)| {
1560 !system_seed
1561 .get(addr)
1562 .is_some_and(|a| a.storage.contains_key(key))
1563 });
1564
1565 let arc_bal = bal;
1567 let arc_idx = validation_index;
1568
1569 type TxExecResult = (
1583 usize,
1584 TxType,
1585 ExecutionReport,
1586 FxHashSet<Address>, Vec<(Address, H256)>, Vec<Address>, Option<EvmError>, );
1591
1592 check_minimum_block_work(
1600 txs_with_sender.iter().map(|(tx, sender)| (*tx, *sender)),
1601 chain_config.fork(header.timestamp),
1602 header.gas_limit,
1603 )?;
1604
1605 let completed_gas = CompletedGas::default();
1608
1609 let exec_results: Result<Vec<TxExecResult>, EvmError> = (0..n_txs)
1610 .into_par_iter()
1611 .map(|tx_idx| -> Result<_, EvmError> {
1612 completed_gas.check(header.gas_limit)?;
1613 let (tx, sender) = &txs_with_sender[tx_idx];
1614 let mut tx_db = GeneralizedDatabase::new_with_shared_base_and_capacity(
1616 store.clone(),
1617 system_seed.clone(),
1618 32,
1619 );
1620 tx_db.lazy_bal = Some(LazyBalCursor {
1621 bal: arc_bal.clone(),
1622 bal_index: u32::try_from(tx_idx + 1).unwrap_or(u32::MAX),
1623 index: arc_idx.clone(),
1624 });
1625 let mut stack_pool = Vec::with_capacity(8);
1628 let mut memory_pool = Vec::with_capacity(1);
1630
1631 tx_db.accessed_accounts =
1635 Some(FxHashSet::with_capacity_and_hasher(16, Default::default()));
1636
1637 tx_db.enable_bal_recording();
1642 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
1643 tx_db.set_bal_index(bal_index);
1644 if let Some(recorder) = tx_db.bal_recorder_mut() {
1648 recorder.record_touched_address(*sender);
1649 }
1650
1651 let report = LEVM::execute_tx_in_block(
1652 tx,
1653 *sender,
1654 header,
1655 &mut tx_db,
1656 vm_type,
1657 base_blob_fee_per_gas,
1658 &mut stack_pool,
1659 &mut memory_pool,
1660 false,
1661 crypto,
1662 evm_config,
1663 chain_id,
1664 stateless_validator,
1665 )?;
1666 let mut report = report;
1667
1668 report.output = Bytes::new();
1672
1673 completed_gas.record(
1675 report.gas_used.saturating_sub(report.state_gas_used),
1676 report.state_gas_used,
1677 header.gas_limit,
1678 )?;
1679
1680 let current_state = std::mem::take(&mut tx_db.current_accounts_state);
1681 let codes = std::mem::take(&mut tx_db.codes);
1682 let mut tracked = tx_db.accessed_accounts.take().unwrap_or_default();
1683 let (shadow_touched, shadow_reads, tx_initial) = tx_db
1690 .bal_recorder
1691 .take()
1692 .map(|mut r| {
1693 (
1694 r.take_touched_addresses(),
1695 r.take_storage_reads(),
1696 r.take_tx_initial_storage(),
1697 )
1698 })
1699 .unwrap_or_default();
1700 let pure_reads: FxHashSet<(Address, U256)> = shadow_reads
1709 .iter()
1710 .copied()
1711 .filter(|k| !tx_initial.contains_key(k))
1712 .collect();
1713
1714 let mut reads_satisfied: Vec<(Address, H256)> =
1727 Vec::with_capacity(current_state.len() * 4);
1728 let mut destroyed: Vec<Address> = Vec::new();
1732 for (addr, acct) in ¤t_state {
1733 if matches!(
1734 acct.status,
1735 AccountStatus::Destroyed | AccountStatus::DestroyedModified
1736 ) {
1737 destroyed.push(*addr);
1738 } else {
1739 for key in acct.storage.keys() {
1740 reads_satisfied.push((*addr, *key));
1741 }
1742 }
1743 }
1744 for (addr, slot) in &shadow_reads {
1752 if destroyed.contains(addr) {
1753 reads_satisfied.push((*addr, ethrex_common::utils::u256_to_h256(*slot)));
1754 }
1755 }
1756
1757 let deferred_bal_err: Option<EvmError> = (|| -> Result<(), EvmError> {
1763 let bal_idx = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
1764 let seed_idx = u32::try_from(tx_idx).unwrap_or(u32::MAX);
1765 Self::validate_tx_execution(
1766 bal_idx,
1767 seed_idx,
1768 ¤t_state,
1769 &codes,
1770 &arc_bal,
1771 &arc_idx,
1772 &system_seed,
1773 &store,
1774 &pure_reads,
1775 &tx_initial,
1776 )
1777 .map_err(|e| match e {
1778 BalValidationError::Database(msg) => {
1782 EvmError::DB(format!("BAL validation for tx {tx_idx}: {msg}"))
1783 }
1784 BalValidationError::Mismatch(msg) => EvmError::Custom(format!(
1785 "BAL validation failed for tx {tx_idx}: {msg}"
1786 )),
1787 })?;
1788
1789 for addr in &shadow_touched {
1791 if !arc_idx.addr_to_idx.contains_key(addr) {
1792 return Err(EvmError::Custom(format!(
1793 "BAL validation failed for tx {tx_idx}: account {addr:?} was \
1794 accessed during execution but is missing from BAL"
1795 )));
1796 }
1797 }
1798 for (addr, slot) in &shadow_reads {
1799 let Some(&bal_acct_idx) = arc_idx.addr_to_idx.get(addr) else {
1800 continue;
1802 };
1803 let acct = &arc_bal.accounts()[bal_acct_idx];
1804 let in_changes = acct
1805 .storage_changes
1806 .binary_search_by(|sc| sc.slot.cmp(slot))
1807 .is_ok();
1808 debug_assert!(
1815 acct.storage_reads.windows(2).all(|w| w[0] < w[1]),
1816 "storage_reads must be strictly ascending for binary_search"
1817 );
1818 let in_reads = acct.storage_reads.binary_search(slot).is_ok();
1819 if !in_changes && !in_reads {
1820 return Err(EvmError::Custom(format!(
1821 "BAL validation failed for tx {tx_idx}: storage slot {slot} of \
1822 account {addr:?} was read during execution but is missing from \
1823 BAL (no storage_changes or storage_reads entry)"
1824 )));
1825 }
1826 }
1827 Ok(())
1828 })()
1829 .err();
1830
1831 drop(current_state);
1832 drop(codes);
1833
1834 tracked.extend(shadow_touched.iter().copied());
1843
1844 Ok((
1845 tx_idx,
1846 tx.tx_type(),
1847 report,
1848 tracked,
1849 reads_satisfied,
1850 destroyed,
1851 deferred_bal_err,
1852 ))
1853 })
1854 .collect();
1855
1856 let mut exec_results = exec_results?;
1857
1858 exec_results.sort_unstable_by_key(|(idx, _, _, _, _, _, _)| *idx);
1865
1866 let mut block_regular_gas_used = 0_u64;
1871 let mut block_state_gas_used = 0_u64;
1872 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(exec_results.len());
1873 for (tx_idx, _, report, _, _, _, _) in &exec_results {
1874 let (tx, _tx_sender) = txs_with_sender
1875 .get(*tx_idx)
1876 .ok_or_else(|| EvmError::Custom(format!("tx index {tx_idx} out of bounds")))?;
1877 if is_amsterdam {
1878 check_2d_gas_allowance(
1879 tx,
1880 block_regular_gas_used,
1881 block_state_gas_used,
1882 header.gas_limit,
1883 )?;
1884 }
1885
1886 tx_gas_breakdowns.push(TxGasBreakdown::from_report(
1887 *tx_idx,
1888 tx.hash(crypto),
1889 report,
1890 ));
1891
1892 let tx_state_gas = report.state_gas_used;
1893 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
1894 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
1895 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
1896 }
1897 let block_gas_used = block_regular_gas_used.max(block_state_gas_used);
1898 if block_gas_used > header.gas_limit {
1900 return Err(EvmError::Transaction(format!(
1901 "Gas allowance exceeded: Block gas used overflow: \
1902 block_gas_used {block_gas_used} > block_gas_limit {}",
1903 header.gas_limit
1904 )));
1905 }
1906
1907 Self::validate_bal_covers(
1910 &system_seed,
1911 pre_block_bal,
1912 &arc_bal,
1913 &arc_idx,
1914 "system calls",
1915 PRE_BLOCK_BAL_INDEX,
1916 )?;
1917
1918 for (_, _, _, _, _, _, deferred) in &mut exec_results {
1919 if let Some(err) = deferred.take() {
1920 return Err(err);
1921 }
1922 }
1923
1924 for (_, _, _, tracked_accounts, reads_satisfied, _, _) in &exec_results {
1933 if !unread_storage_reads.is_empty() {
1934 for pair in reads_satisfied {
1935 unread_storage_reads.remove(pair);
1936 }
1937 }
1938 if !unaccessed_pure_accounts.is_empty() {
1942 unaccessed_pure_accounts.remove(&header.coinbase);
1943 for addr in tracked_accounts {
1944 unaccessed_pure_accounts.remove(addr);
1945 }
1946 }
1947 }
1948
1949 let mut receipts = Vec::with_capacity(n_txs);
1951 let mut cumulative_gas_used = 0_u64;
1952 for (_, tx_type, report, _, _, _, _) in exec_results {
1953 cumulative_gas_used += report.gas_spent;
1954 let mut receipt = Receipt::new(
1955 tx_type,
1956 matches!(report.result, TxResult::Success),
1957 cumulative_gas_used,
1958 report.logs,
1959 );
1960 if tx_type == TxType::Frame {
1961 receipt.payer = report.payer_address;
1962 receipt.frame_receipts = frame_receipts_from(report.frame_results);
1963 }
1964 receipts.push(receipt);
1965 }
1966
1967 Ok((
1968 receipts,
1969 block_gas_used,
1970 unread_storage_reads,
1971 unaccessed_pure_accounts,
1972 tx_gas_breakdowns,
1973 ))
1974 }
1975
1976 #[cfg(feature = "rayon")]
1979 fn seeded_balance(
1980 seed_idx: u32,
1981 acct: ðrex_common::types::block_access_list::AccountChanges,
1982 system_seed: &CacheDB,
1983 store: &Arc<dyn Database>,
1984 ) -> Result<U256, BalValidationError> {
1985 let pos = acct
1986 .balance_changes
1987 .partition_point(|c| c.block_access_index <= seed_idx);
1988 if pos > 0 {
1989 Ok(acct.balance_changes[pos - 1].post_balance)
1990 } else if let Some(a) = system_seed.get(&acct.address) {
1991 Ok(a.info.balance)
1992 } else {
1993 store
1994 .get_account_state(acct.address)
1995 .map(|a| a.balance)
1996 .map_err(|e| {
1997 BalValidationError::Database(format!(
1998 "DB error reading balance for {:?}: {e}",
1999 acct.address
2000 ))
2001 })
2002 }
2003 }
2004
2005 #[cfg(feature = "rayon")]
2008 fn seeded_code_hash(
2009 seed_idx: u32,
2010 acct: ðrex_common::types::block_access_list::AccountChanges,
2011 system_seed: &CacheDB,
2012 store: &Arc<dyn Database>,
2013 ) -> Result<H256, BalValidationError> {
2014 let pos = acct
2015 .code_changes
2016 .partition_point(|c| c.block_access_index <= seed_idx);
2017 if pos > 0 {
2018 let seeded_code = &acct.code_changes[pos - 1].new_code;
2019 Ok(if seeded_code.is_empty() {
2020 *EMPTY_KECCAK_HASH
2021 } else {
2022 ethrex_common::utils::keccak(seeded_code)
2023 })
2024 } else if let Some(a) = system_seed.get(&acct.address) {
2025 Ok(a.info.code_hash)
2026 } else {
2027 store
2028 .get_account_state(acct.address)
2029 .map(|a| a.code_hash)
2030 .map_err(|e| {
2031 BalValidationError::Database(format!(
2032 "DB error reading account state for {:?}: {e}",
2033 acct.address
2034 ))
2035 })
2036 }
2037 }
2038
2039 #[cfg(feature = "rayon")]
2042 fn seeded_nonce(
2043 seed_idx: u32,
2044 acct: ðrex_common::types::block_access_list::AccountChanges,
2045 system_seed: &CacheDB,
2046 store: &Arc<dyn Database>,
2047 ) -> Result<u64, BalValidationError> {
2048 let pos = acct
2049 .nonce_changes
2050 .partition_point(|c| c.block_access_index <= seed_idx);
2051 if pos > 0 {
2052 Ok(acct.nonce_changes[pos - 1].post_nonce)
2053 } else if let Some(a) = system_seed.get(&acct.address) {
2054 Ok(a.info.nonce)
2055 } else {
2056 store
2057 .get_account_state(acct.address)
2058 .map(|a| a.nonce)
2059 .map_err(|e| {
2060 BalValidationError::Database(format!(
2061 "DB error reading nonce for {:?}: {e}",
2062 acct.address
2063 ))
2064 })
2065 }
2066 }
2067
2068 #[cfg(feature = "rayon")]
2076 fn seeded_account_triple(
2077 seed_idx: u32,
2078 acct: ðrex_common::types::block_access_list::AccountChanges,
2079 system_seed: &CacheDB,
2080 store: &Arc<dyn Database>,
2081 ) -> Result<(U256, u64, H256), BalValidationError> {
2082 let bpos = acct
2083 .balance_changes
2084 .partition_point(|c| c.block_access_index <= seed_idx);
2085 let npos = acct
2086 .nonce_changes
2087 .partition_point(|c| c.block_access_index <= seed_idx);
2088 let cpos = acct
2089 .code_changes
2090 .partition_point(|c| c.block_access_index <= seed_idx);
2091
2092 let mut balance = (bpos > 0).then(|| acct.balance_changes[bpos - 1].post_balance);
2093 let mut nonce = (npos > 0).then(|| acct.nonce_changes[npos - 1].post_nonce);
2094 let mut code_hash = (cpos > 0).then(|| {
2095 let c = &acct.code_changes[cpos - 1].new_code;
2096 if c.is_empty() {
2097 *EMPTY_KECCAK_HASH
2098 } else {
2099 ethrex_common::utils::keccak(c)
2100 }
2101 });
2102
2103 if let Some(a) = system_seed.get(&acct.address) {
2105 balance.get_or_insert(a.info.balance);
2106 nonce.get_or_insert(a.info.nonce);
2107 code_hash.get_or_insert(a.info.code_hash);
2108 }
2109
2110 if balance.is_none() || nonce.is_none() || code_hash.is_none() {
2112 let s = store.get_account_state(acct.address).map_err(|e| {
2113 BalValidationError::Database(format!(
2114 "DB error reading account state for {:?}: {e}",
2115 acct.address
2116 ))
2117 })?;
2118 balance.get_or_insert(s.balance);
2119 nonce.get_or_insert(s.nonce);
2120 code_hash.get_or_insert(s.code_hash);
2121 }
2122
2123 Ok((
2124 balance.unwrap_or_default(),
2125 nonce.unwrap_or_default(),
2126 code_hash.unwrap_or(*EMPTY_KECCAK_HASH),
2127 ))
2128 }
2129
2130 #[cfg(feature = "rayon")]
2135 fn seeded_storage_pre_value(
2136 addr: Address,
2137 key: H256,
2138 slot: U256,
2139 system_seed: &CacheDB,
2140 store: &Arc<dyn Database>,
2141 tx_initial: &FxHashMap<(Address, U256), U256>,
2142 ) -> Result<U256, BalValidationError> {
2143 if let Some(v) = tx_initial.get(&(addr, slot)) {
2144 return Ok(*v);
2145 }
2146 Self::storage_from_seed_or_store(addr, key, slot, system_seed, store)
2147 }
2148
2149 #[cfg(feature = "rayon")]
2152 fn storage_from_seed_or_store(
2153 addr: Address,
2154 key: H256,
2155 slot: U256,
2156 system_seed: &CacheDB,
2157 store: &Arc<dyn Database>,
2158 ) -> Result<U256, BalValidationError> {
2159 if let Some(v) = system_seed
2160 .get(&addr)
2161 .and_then(|a| a.storage.get(&key))
2162 .copied()
2163 {
2164 Ok(v)
2165 } else {
2166 store.get_storage_value(addr, key).map_err(|e| {
2167 BalValidationError::Database(format!(
2168 "DB error reading storage for {addr:?} slot {slot}: {e}"
2169 ))
2170 })
2171 }
2172 }
2173
2174 #[cfg(feature = "rayon")]
2181 fn seeded_storage(
2182 seed_idx: u32,
2183 sc: ðrex_common::types::block_access_list::SlotChange,
2184 addr: Address,
2185 key: H256,
2186 system_seed: &CacheDB,
2187 store: &Arc<dyn Database>,
2188 tx_initial: &FxHashMap<(Address, U256), U256>,
2189 ) -> Result<U256, BalValidationError> {
2190 if let Some(v) = tx_initial.get(&(addr, sc.slot)) {
2191 return Ok(*v);
2192 }
2193 let pos = sc
2194 .slot_changes
2195 .partition_point(|c| c.block_access_index <= seed_idx);
2196 if pos > 0 {
2197 Ok(sc.slot_changes[pos - 1].post_value)
2198 } else {
2199 Self::storage_from_seed_or_store(addr, key, sc.slot, system_seed, store)
2200 }
2201 }
2202
2203 #[cfg(feature = "rayon")]
2231 #[allow(clippy::too_many_arguments)]
2232 pub fn validate_tx_execution(
2233 bal_idx: u32,
2234 seed_idx: u32,
2235 current_state: &FxHashMap<Address, LevmAccount>,
2236 codes: &FxHashMap<H256, Code>,
2237 bal: &BlockAccessList,
2238 index: &BalAddressIndex,
2239 system_seed: &CacheDB,
2240 store: &Arc<dyn Database>,
2241 pure_reads: &FxHashSet<(Address, U256)>,
2242 tx_initial: &FxHashMap<(Address, U256), U256>,
2243 ) -> Result<(), BalValidationError> {
2244 if let Some(active_accounts) = index.tx_to_accounts.get(&bal_idx) {
2248 for &acct_inner_idx in active_accounts {
2249 let acct = &bal.accounts()[acct_inner_idx];
2250 let addr = acct.address;
2251 let actual = current_state.get(&addr);
2252 let mut seeded_info: Option<(U256, u64, H256)> = None;
2256
2257 if let Some(expected) = find_exact_change_balance(&acct.balance_changes, bal_idx) {
2259 match actual {
2260 Some(a) if a.info.balance == expected => {
2261 if seeded_info.is_none() {
2265 seeded_info = Some(Self::seeded_account_triple(
2266 seed_idx,
2267 acct,
2268 system_seed,
2269 store,
2270 )?);
2271 }
2272 let seeded = seeded_info.unwrap_or_default().0;
2273 if expected == seeded {
2274 return Err(BalValidationError::Mismatch(format!(
2275 "account {addr:?} has spurious no-op BAL balance change \
2276 at index {bal_idx}: post==pre=={expected}"
2277 )));
2278 }
2279 }
2280 Some(a) => {
2281 return Err(BalValidationError::Mismatch(format!(
2282 "account {addr:?} balance mismatch at index {bal_idx}: BAL={expected}, exec={} (diff={})",
2283 a.info.balance,
2284 describe_balance_diff(expected, a.info.balance),
2285 )));
2286 }
2287 None => {
2288 let seeded = Self::seeded_balance(seed_idx, acct, system_seed, store)?;
2295 if expected == seeded {
2296 return Err(BalValidationError::Mismatch(format!(
2297 "account {addr:?} has spurious no-op BAL balance change \
2298 at index {bal_idx} for an account absent from execution \
2299 state: post==pre=={expected}"
2300 )));
2301 }
2302 let all_bal_indices: Vec<u32> = acct
2304 .balance_changes
2305 .iter()
2306 .map(|c| c.block_access_index)
2307 .collect();
2308 let all_nonce_indices: Vec<u32> = acct
2309 .nonce_changes
2310 .iter()
2311 .map(|c| c.block_access_index)
2312 .collect();
2313 let all_storage_indices: Vec<(u32, u64)> = acct
2314 .storage_changes
2315 .iter()
2316 .flat_map(|sc| {
2317 sc.slot_changes
2318 .iter()
2319 .map(|c| (c.block_access_index, sc.slot.low_u64()))
2320 })
2321 .collect();
2322 let code_indices: Vec<u32> = acct
2323 .code_changes
2324 .iter()
2325 .map(|c| c.block_access_index)
2326 .collect();
2327 return Err(BalValidationError::Mismatch(format!(
2328 "account {addr:?} has BAL balance change at {bal_idx} \
2329 but not in execution state (expected={expected}, pre={seeded}, \
2330 all_bal_idx={all_bal_indices:?}, nonce_idx={all_nonce_indices:?}, \
2331 storage_idx={all_storage_indices:?}, code_idx={code_indices:?})"
2332 )));
2333 }
2334 }
2335 }
2336
2337 if let Some(expected) = find_exact_change_nonce(&acct.nonce_changes, bal_idx) {
2339 match actual {
2340 Some(a) if a.info.nonce == expected => {
2341 if seeded_info.is_none() {
2346 seeded_info = Some(Self::seeded_account_triple(
2347 seed_idx,
2348 acct,
2349 system_seed,
2350 store,
2351 )?);
2352 }
2353 let seeded = seeded_info.unwrap_or_default().1;
2354 if expected == seeded {
2355 return Err(BalValidationError::Mismatch(format!(
2356 "account {addr:?} has spurious no-op BAL nonce change \
2357 at index {bal_idx}: post==pre=={expected}"
2358 )));
2359 }
2360 }
2361 Some(a) => {
2362 return Err(BalValidationError::Mismatch(format!(
2363 "account {addr:?} nonce mismatch at index {bal_idx}: BAL={expected}, exec={}",
2364 a.info.nonce
2365 )));
2366 }
2367 None => {
2368 let seeded = Self::seeded_nonce(seed_idx, acct, system_seed, store)?;
2372 if expected == seeded {
2373 return Err(BalValidationError::Mismatch(format!(
2374 "account {addr:?} has spurious no-op BAL nonce change \
2375 at index {bal_idx} for an account absent from execution \
2376 state: post==pre=={expected}"
2377 )));
2378 }
2379 return Err(BalValidationError::Mismatch(format!(
2380 "account {addr:?} has BAL nonce change at {bal_idx} \
2381 but not in execution state (expected={expected}, pre={seeded})"
2382 )));
2383 }
2384 }
2385 }
2386
2387 if let Some(expected_code) = find_exact_change_code(&acct.code_changes, bal_idx) {
2389 match actual {
2390 Some(a) => {
2391 let actual_code = if let Some(c) = codes.get(&a.info.code_hash) {
2392 c.code_bytes()
2393 } else {
2394 let c = store.get_account_code(a.info.code_hash).map_err(|e| {
2395 BalValidationError::Database(format!(
2396 "DB error reading account code for {addr:?}: {e}"
2397 ))
2398 })?;
2399 c.code_bytes()
2400 };
2401 if actual_code != *expected_code {
2402 return Err(BalValidationError::Mismatch(format!(
2403 "account {addr:?} code mismatch at index {bal_idx}"
2404 )));
2405 }
2406 if seeded_info.is_none() {
2409 seeded_info = Some(Self::seeded_account_triple(
2410 seed_idx,
2411 acct,
2412 system_seed,
2413 store,
2414 )?);
2415 }
2416 let seeded_hash = seeded_info.unwrap_or_default().2;
2417 if a.info.code_hash == seeded_hash {
2418 return Err(BalValidationError::Mismatch(format!(
2419 "account {addr:?} has spurious no-op BAL code change \
2420 at index {bal_idx}"
2421 )));
2422 }
2423 }
2424 None => {
2425 let code_hash = if let Some(a) = system_seed.get(&addr) {
2429 a.info.code_hash
2430 } else {
2431 store
2432 .get_account_state(addr)
2433 .map(|a| a.code_hash)
2434 .map_err(|e| {
2435 BalValidationError::Database(format!(
2436 "DB error reading account state for {addr:?}: {e}"
2437 ))
2438 })?
2439 };
2440 let pre_code = if let Some(c) = codes.get(&code_hash) {
2441 c.code_bytes()
2442 } else {
2443 let c = store.get_account_code(code_hash).map_err(|e| {
2444 BalValidationError::Database(format!(
2445 "DB error reading account code for hash \
2446 {code_hash:?}: {e}"
2447 ))
2448 })?;
2449 c.code_bytes()
2450 };
2451 if *expected_code == pre_code {
2452 return Err(BalValidationError::Mismatch(format!(
2456 "account {addr:?} has spurious no-op BAL code change \
2457 at index {bal_idx} for an account absent from execution state"
2458 )));
2459 }
2460 return Err(BalValidationError::Mismatch(format!(
2461 "account {addr:?} has BAL code change at {bal_idx} \
2462 but not in execution state"
2463 )));
2464 }
2465 }
2466 }
2467
2468 for sc in &acct.storage_changes {
2470 if let Some(expected_value) =
2471 find_exact_change_storage(&sc.slot_changes, bal_idx)
2472 {
2473 let key = ethrex_common::utils::u256_to_h256(sc.slot);
2474 let actual_value = actual.and_then(|a| a.storage.get(&key)).copied();
2475 if actual_value != Some(expected_value) {
2476 if actual.is_none() || actual_value.is_none() {
2482 let seeded = Self::seeded_storage(
2483 seed_idx,
2484 sc,
2485 addr,
2486 key,
2487 system_seed,
2488 store,
2489 tx_initial,
2490 )?;
2491 if expected_value == seeded {
2492 return Err(BalValidationError::Mismatch(format!(
2493 "account {addr:?} has spurious no-op BAL storage \
2494 change for slot {} at index {bal_idx} for a slot \
2495 absent from execution state: post==pre=={expected_value}",
2496 sc.slot
2497 )));
2498 }
2499 }
2500 return Err(BalValidationError::Mismatch(format!(
2501 "account {addr:?} storage slot {} mismatch at index {bal_idx}: \
2502 BAL={expected_value}, exec={actual_value:?}",
2503 sc.slot
2504 )));
2505 }
2506 let seeded = Self::seeded_storage(
2510 seed_idx,
2511 sc,
2512 addr,
2513 key,
2514 system_seed,
2515 store,
2516 tx_initial,
2517 )?;
2518 if expected_value == seeded {
2519 return Err(BalValidationError::Mismatch(format!(
2520 "account {addr:?} has spurious no-op BAL storage change for \
2521 slot {} at index {bal_idx}",
2522 sc.slot
2523 )));
2524 }
2525 }
2526 }
2527 }
2528 }
2529
2530 for (addr, account) in current_state {
2533 if account.is_unmodified() {
2534 continue;
2535 }
2536
2537 let Some(&bal_acct_idx) = index.addr_to_idx.get(addr) else {
2538 let pre = system_seed
2542 .get(addr)
2543 .map(|a| (a.info.balance, a.info.nonce, a.info.code_hash))
2544 .or_else(|| {
2545 store
2546 .get_account_state(*addr)
2547 .ok()
2548 .map(|a| (a.balance, a.nonce, a.code_hash))
2549 })
2550 .unwrap_or_default();
2551 let post = (
2552 account.info.balance,
2553 account.info.nonce,
2554 account.info.code_hash,
2555 );
2556 if pre != post {
2557 return Err(BalValidationError::Mismatch(format!(
2558 "account {addr:?} was modified by execution but is absent from BAL"
2559 )));
2560 }
2561 continue;
2562 };
2563
2564 let acct = &bal.accounts()[bal_acct_idx];
2565
2566 if !has_exact_change_balance(&acct.balance_changes, bal_idx) {
2568 let seeded = Self::seeded_balance(seed_idx, acct, system_seed, store)?;
2569 if account.info.balance != seeded {
2570 return Err(BalValidationError::Mismatch(format!(
2571 "account {addr:?} balance changed by execution ({}) but BAL has no \
2572 balance change at index {bal_idx} (seeded={seeded})",
2573 account.info.balance
2574 )));
2575 }
2576 }
2577
2578 if !has_exact_change_nonce(&acct.nonce_changes, bal_idx) {
2580 let seeded = Self::seeded_nonce(seed_idx, acct, system_seed, store)?;
2581 if account.info.nonce != seeded {
2582 return Err(BalValidationError::Mismatch(format!(
2583 "account {addr:?} nonce changed by execution ({}) but BAL has no \
2584 nonce change at index {bal_idx} (seeded={seeded})",
2585 account.info.nonce
2586 )));
2587 }
2588 }
2589
2590 if !has_exact_change_code(&acct.code_changes, bal_idx) {
2594 let seeded_hash = Self::seeded_code_hash(seed_idx, acct, system_seed, store)?;
2595 if account.info.code_hash != seeded_hash {
2596 return Err(BalValidationError::Mismatch(format!(
2597 "account {addr:?} code changed by execution but BAL has no \
2598 code change at index {bal_idx} (seeded_hash={seeded_hash:?})"
2599 )));
2600 }
2601 }
2602
2603 for (key_h256, &value) in &account.storage {
2605 let slot_u256 = u256_from_big_endian_const(key_h256.0);
2606 let pos = acct
2608 .storage_changes
2609 .partition_point(|sc| sc.slot < slot_u256);
2610 if pos < acct.storage_changes.len() && acct.storage_changes[pos].slot == slot_u256 {
2611 let sc = &acct.storage_changes[pos];
2612 if !has_exact_change_storage(&sc.slot_changes, bal_idx) {
2613 let seeded = Self::seeded_storage(
2618 seed_idx,
2619 sc,
2620 *addr,
2621 *key_h256,
2622 system_seed,
2623 store,
2624 tx_initial,
2625 )?;
2626 if value != seeded {
2627 return Err(BalValidationError::Mismatch(format!(
2628 "account {addr:?} storage slot {slot_u256} changed by \
2629 execution ({value}) but BAL has no change at index \
2630 {bal_idx} (seeded={seeded})"
2631 )));
2632 }
2633 }
2634 }
2635 else if !pure_reads.contains(&(*addr, slot_u256)) {
2649 let pre_value = Self::seeded_storage_pre_value(
2654 *addr,
2655 *key_h256,
2656 slot_u256,
2657 system_seed,
2658 store,
2659 tx_initial,
2660 )?;
2661 if value != pre_value {
2662 let declared_as_read = acct.storage_reads.contains(&slot_u256);
2663 return Err(BalValidationError::Mismatch(format!(
2664 "account {addr:?} storage slot {slot_u256} was written by \
2665 execution ({value}) but has no storage_changes entry at index \
2666 {bal_idx} (pre_value={pre_value}, declared_as_read={declared_as_read})"
2667 )));
2668 }
2669 }
2670 }
2671 }
2672
2673 Ok(())
2674 }
2675
2676 #[cfg(feature = "rayon")]
2688 fn validate_bal_withdrawal_index(
2689 db: &GeneralizedDatabase,
2690 bal: &BlockAccessList,
2691 withdrawal_idx: u32,
2692 index: &BalAddressIndex,
2693 ) -> Result<(), EvmError> {
2694 for acct in bal.accounts() {
2697 let addr = acct.address;
2698 let actual = db.current_accounts_state.get(&addr);
2699
2700 if let Some(expected) = find_exact_change_balance(&acct.balance_changes, withdrawal_idx)
2702 {
2703 match actual {
2704 Some(a) if a.info.balance == expected => {}
2705 Some(a) => {
2706 return Err(EvmError::Custom(format!(
2707 "BAL validation failed for withdrawal: account {addr:?} balance \
2708 mismatch at index {withdrawal_idx}: BAL={expected}, actual={}",
2709 a.info.balance
2710 )));
2711 }
2712 None => {
2713 return Err(EvmError::Custom(format!(
2714 "BAL validation failed for withdrawal: account {addr:?} has \
2715 balance change at index {withdrawal_idx} but was not touched \
2716 by withdrawal/request phase"
2717 )));
2718 }
2719 }
2720 }
2721
2722 if let Some(expected) = find_exact_change_nonce(&acct.nonce_changes, withdrawal_idx) {
2724 match actual {
2725 Some(a) if a.info.nonce == expected => {}
2726 Some(a) => {
2727 return Err(EvmError::Custom(format!(
2728 "BAL validation failed for withdrawal: account {addr:?} nonce \
2729 mismatch at index {withdrawal_idx}: BAL={expected}, actual={}",
2730 a.info.nonce
2731 )));
2732 }
2733 None => {
2734 return Err(EvmError::Custom(format!(
2735 "BAL validation failed for withdrawal: account {addr:?} has \
2736 nonce change at index {withdrawal_idx} but was not touched \
2737 by withdrawal/request phase"
2738 )));
2739 }
2740 }
2741 }
2742
2743 if let Some(expected_code) = find_exact_change_code(&acct.code_changes, withdrawal_idx)
2745 {
2746 let code_hash = if expected_code.is_empty() {
2747 *EMPTY_KECCAK_HASH
2748 } else {
2749 ethrex_common::utils::keccak(expected_code)
2750 };
2751 match actual {
2752 Some(a) if a.info.code_hash == code_hash => {}
2753 Some(_) => {
2754 return Err(EvmError::Custom(format!(
2755 "BAL validation failed for withdrawal: account {addr:?} code \
2756 mismatch at index {withdrawal_idx}"
2757 )));
2758 }
2759 None => {
2760 return Err(EvmError::Custom(format!(
2761 "BAL validation failed for withdrawal: account {addr:?} has \
2762 code change at index {withdrawal_idx} but was not touched \
2763 by withdrawal/request phase"
2764 )));
2765 }
2766 }
2767 }
2768
2769 for sc in &acct.storage_changes {
2771 if let Some(expected_value) =
2772 find_exact_change_storage(&sc.slot_changes, withdrawal_idx)
2773 {
2774 let key = ethrex_common::utils::u256_to_h256(sc.slot);
2775 let actual_value = actual.and_then(|a| a.storage.get(&key)).copied();
2776 if actual_value != Some(expected_value) {
2777 return Err(EvmError::Custom(format!(
2778 "BAL validation failed for withdrawal: account {addr:?} storage \
2779 slot {} mismatch at index {withdrawal_idx}: BAL={expected_value}, \
2780 actual={actual_value:?}",
2781 sc.slot
2782 )));
2783 }
2784 }
2785 }
2786 }
2787
2788 for (addr, account) in &db.current_accounts_state {
2791 if account.is_unmodified() {
2792 continue;
2793 }
2794
2795 let Some(&bal_acct_idx) = index.addr_to_idx.get(addr) else {
2796 let pre_state = db.store.get_account_state(*addr).map_err(|e| {
2800 EvmError::Custom(format!(
2801 "BAL validation failed for withdrawal: db error reading \
2802 account {addr:?}: {e}"
2803 ))
2804 })?;
2805 let pre = (pre_state.balance, pre_state.nonce, pre_state.code_hash);
2806 let post = (
2807 account.info.balance,
2808 account.info.nonce,
2809 account.info.code_hash,
2810 );
2811 if pre != post {
2812 return Err(EvmError::Custom(format!(
2813 "BAL validation failed for withdrawal: account {addr:?} was modified \
2814 during withdrawal/request phase but is absent from BAL"
2815 )));
2816 }
2817 for (key_h256, &value) in &account.storage {
2820 let pre_value = db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
2821 EvmError::Custom(format!(
2822 "BAL validation failed for withdrawal: db error reading \
2823 storage {addr:?}[{}]: {e}",
2824 u256_from_big_endian_const(key_h256.0)
2825 ))
2826 })?;
2827 if value != pre_value {
2828 return Err(EvmError::Custom(format!(
2829 "BAL validation failed for withdrawal: account {addr:?} storage \
2830 slot {} changed during withdrawal/request phase but is absent \
2831 from BAL",
2832 u256_from_big_endian_const(key_h256.0)
2833 )));
2834 }
2835 }
2836 continue;
2837 };
2838
2839 let acct = &bal.accounts()[bal_acct_idx];
2840
2841 if !has_exact_change_balance(&acct.balance_changes, withdrawal_idx) {
2844 let seeded = match acct.balance_changes.last() {
2845 Some(c) => c.post_balance,
2846 None => {
2847 db.store
2848 .get_account_state(*addr)
2849 .map_err(|e| {
2850 EvmError::Custom(format!(
2851 "BAL validation failed for withdrawal: db error reading \
2852 account {addr:?}: {e}"
2853 ))
2854 })?
2855 .balance
2856 }
2857 };
2858 if account.info.balance != seeded {
2859 return Err(EvmError::Custom(format!(
2860 "BAL validation failed for withdrawal: account {addr:?} balance \
2861 changed during withdrawal/request phase ({}) but BAL has no \
2862 balance change at index {withdrawal_idx} (last_bal={seeded})",
2863 account.info.balance
2864 )));
2865 }
2866 }
2867
2868 if !has_exact_change_nonce(&acct.nonce_changes, withdrawal_idx) {
2870 let seeded = match acct.nonce_changes.last() {
2871 Some(c) => c.post_nonce,
2872 None => {
2873 db.store
2874 .get_account_state(*addr)
2875 .map_err(|e| {
2876 EvmError::Custom(format!(
2877 "BAL validation failed for withdrawal: db error reading \
2878 account {addr:?}: {e}"
2879 ))
2880 })?
2881 .nonce
2882 }
2883 };
2884 if account.info.nonce != seeded {
2885 return Err(EvmError::Custom(format!(
2886 "BAL validation failed for withdrawal: account {addr:?} nonce \
2887 changed during withdrawal/request phase ({}) but BAL has no \
2888 nonce change at index {withdrawal_idx} (last_bal={seeded})",
2889 account.info.nonce
2890 )));
2891 }
2892 }
2893
2894 if !has_exact_change_code(&acct.code_changes, withdrawal_idx) {
2896 let seeded_hash = match acct.code_changes.last() {
2897 Some(c) if c.new_code.is_empty() => *EMPTY_KECCAK_HASH,
2898 Some(c) => ethrex_common::utils::keccak(&c.new_code),
2899 None => {
2900 db.store
2901 .get_account_state(*addr)
2902 .map_err(|e| {
2903 EvmError::Custom(format!(
2904 "BAL validation failed for withdrawal: db error reading \
2905 account {addr:?}: {e}"
2906 ))
2907 })?
2908 .code_hash
2909 }
2910 };
2911 if account.info.code_hash != seeded_hash {
2912 return Err(EvmError::Custom(format!(
2913 "BAL validation failed for withdrawal: account {addr:?} code \
2914 changed during withdrawal/request phase but BAL has no \
2915 code change at index {withdrawal_idx} \
2916 (actual={:?}, last_bal={seeded_hash:?})",
2917 account.info.code_hash
2918 )));
2919 }
2920 }
2921
2922 for (key_h256, &value) in &account.storage {
2925 let slot_u256 = u256_from_big_endian_const(key_h256.0);
2926 let pos = acct
2927 .storage_changes
2928 .partition_point(|sc| sc.slot < slot_u256);
2929 if pos < acct.storage_changes.len() && acct.storage_changes[pos].slot == slot_u256 {
2930 let sc = &acct.storage_changes[pos];
2931 if !has_exact_change_storage(&sc.slot_changes, withdrawal_idx) {
2932 let seeded = match sc.slot_changes.last() {
2935 Some(c) => c.post_value,
2936 None => db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
2937 EvmError::Custom(format!(
2938 "BAL validation failed for withdrawal: db error reading \
2939 storage {addr:?}[{slot_u256}]: {e}"
2940 ))
2941 })?,
2942 };
2943 if value != seeded {
2944 return Err(EvmError::Custom(format!(
2945 "BAL validation failed for withdrawal: account {addr:?} \
2946 storage slot {slot_u256} changed during withdrawal/request \
2947 phase ({value}) but BAL has no change at index \
2948 {withdrawal_idx} (last_bal={seeded})"
2949 )));
2950 }
2951 }
2952 } else {
2953 let pre_value = db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
2956 EvmError::Custom(format!(
2957 "BAL validation failed for withdrawal: db error reading \
2958 storage {addr:?}[{slot_u256}]: {e}"
2959 ))
2960 })?;
2961 if value != pre_value {
2962 return Err(EvmError::Custom(format!(
2963 "BAL validation failed for withdrawal: account {addr:?} \
2964 storage slot {slot_u256} changed during withdrawal/request \
2965 phase ({value}) but slot is absent from BAL storage_changes \
2966 (pre={pre_value})"
2967 )));
2968 }
2969 }
2970 }
2971 }
2972
2973 Ok(())
2974 }
2975
2976 #[cfg(feature = "rayon")]
2994 fn validate_bal_pre_exec_index(
2995 db: &GeneralizedDatabase,
2996 bal: &BlockAccessList,
2997 index: &BalAddressIndex,
2998 ) -> Result<(), EvmError> {
2999 let Some(active_accounts) = index.tx_to_accounts.get(&PRE_BLOCK_BAL_INDEX) else {
3000 return Ok(());
3001 };
3002
3003 let pre_account = |addr: Address| -> Result<(U256, u64, H256), EvmError> {
3008 let s = db.store.get_account_state(addr).map_err(|e| {
3009 EvmError::Custom(format!(
3010 "BAL validation failed for system_tx: db error reading account {addr:?}: {e}"
3018 ))
3019 })?;
3020 Ok((s.balance, s.nonce, s.code_hash))
3021 };
3022
3023 for &acct_inner_idx in active_accounts {
3024 let acct = &bal.accounts()[acct_inner_idx];
3025 let addr = acct.address;
3026 let actual = db.current_accounts_state.get(&addr);
3027
3028 if let Some(expected) =
3030 find_exact_change_balance(&acct.balance_changes, PRE_BLOCK_BAL_INDEX)
3031 {
3032 match actual {
3033 Some(a) if a.info.balance == expected => {
3034 if expected == pre_account(addr)?.0 {
3035 return Err(EvmError::Custom(format!(
3036 "BAL validation failed for system_tx: account {addr:?} has spurious \
3037 no-op balance change at index 0: post==pre=={expected}"
3038 )));
3039 }
3040 }
3041 Some(a) => {
3042 return Err(EvmError::Custom(format!(
3043 "BAL validation failed for system_tx: account {addr:?} balance mismatch \
3044 at index 0: BAL={expected}, actual={}",
3045 a.info.balance
3046 )));
3047 }
3048 None => {
3049 return Err(EvmError::Custom(format!(
3050 "BAL validation failed for system_tx: account {addr:?} has balance \
3051 change at index 0 but was not touched by the pre-exec phase"
3052 )));
3053 }
3054 }
3055 }
3056
3057 if let Some(expected) =
3059 find_exact_change_nonce(&acct.nonce_changes, PRE_BLOCK_BAL_INDEX)
3060 {
3061 match actual {
3062 Some(a) if a.info.nonce == expected => {
3063 if expected == pre_account(addr)?.1 {
3064 return Err(EvmError::Custom(format!(
3065 "BAL validation failed for system_tx: account {addr:?} has spurious \
3066 no-op nonce change at index 0: post==pre=={expected}"
3067 )));
3068 }
3069 }
3070 Some(a) => {
3071 return Err(EvmError::Custom(format!(
3072 "BAL validation failed for system_tx: account {addr:?} nonce mismatch \
3073 at index 0: BAL={expected}, actual={}",
3074 a.info.nonce
3075 )));
3076 }
3077 None => {
3078 return Err(EvmError::Custom(format!(
3079 "BAL validation failed for system_tx: account {addr:?} has nonce \
3080 change at index 0 but was not touched by the pre-exec phase"
3081 )));
3082 }
3083 }
3084 }
3085
3086 if let Some(expected_code) =
3088 find_exact_change_code(&acct.code_changes, PRE_BLOCK_BAL_INDEX)
3089 {
3090 let expected_hash = if expected_code.is_empty() {
3091 *EMPTY_KECCAK_HASH
3092 } else {
3093 ethrex_common::utils::keccak(expected_code)
3094 };
3095 match actual {
3096 Some(a) if a.info.code_hash == expected_hash => {
3097 if expected_hash == pre_account(addr)?.2 {
3098 return Err(EvmError::Custom(format!(
3099 "BAL validation failed for system_tx: account {addr:?} has spurious \
3100 no-op code change at index 0"
3101 )));
3102 }
3103 }
3104 Some(_) => {
3105 return Err(EvmError::Custom(format!(
3106 "BAL validation failed for system_tx: account {addr:?} code mismatch \
3107 at index 0"
3108 )));
3109 }
3110 None => {
3111 return Err(EvmError::Custom(format!(
3112 "BAL validation failed for system_tx: account {addr:?} has code \
3113 change at index 0 but was not touched by the pre-exec phase"
3114 )));
3115 }
3116 }
3117 }
3118
3119 for sc in &acct.storage_changes {
3121 let Some(expected_value) =
3122 find_exact_change_storage(&sc.slot_changes, PRE_BLOCK_BAL_INDEX)
3123 else {
3124 continue;
3125 };
3126 let key = ethrex_common::utils::u256_to_h256(sc.slot);
3127 let actual_value = actual.and_then(|a| a.storage.get(&key)).copied();
3128 if actual_value != Some(expected_value) {
3129 return Err(EvmError::Custom(format!(
3130 "BAL validation failed for system_tx: account {addr:?} storage slot {} \
3131 mismatch at index 0: BAL={expected_value}, actual={actual_value:?}",
3132 sc.slot
3133 )));
3134 }
3135 let pre_value = db.store.get_storage_value(addr, key).map_err(|e| {
3136 EvmError::Custom(format!(
3137 "BAL validation failed for system_tx: db error reading storage {addr:?} \
3138 slot {}: {e}",
3139 sc.slot
3140 ))
3141 })?;
3142 if expected_value == pre_value {
3143 return Err(EvmError::Custom(format!(
3144 "BAL validation failed for system_tx: account {addr:?} has spurious no-op \
3145 storage change for slot {} at index 0: post==pre=={expected_value}",
3146 sc.slot
3147 )));
3148 }
3149 }
3150 }
3151
3152 Ok(())
3153 }
3154
3155 #[cfg(feature = "rayon")]
3161 pub fn warm_block(
3162 block: &Block,
3163 store: Arc<dyn Database>,
3164 vm_type: VMType,
3165 crypto: &dyn Crypto,
3166 cancelled: &AtomicBool,
3167 ) -> Result<(), EvmError> {
3168 let txs_with_sender = block
3169 .body
3170 .get_transactions_with_sender(crypto)
3171 .map_err(|error| {
3172 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
3173 })?;
3174
3175 Self::warm_txs(
3176 &txs_with_sender,
3177 &block.header,
3178 store.clone(),
3179 vm_type,
3180 crypto,
3181 &|| cancelled.load(Ordering::Relaxed),
3182 )?;
3183
3184 let mut db = GeneralizedDatabase::new(store.clone());
3185
3186 if cancelled.load(Ordering::Relaxed) {
3187 return Ok(());
3188 }
3189
3190 for withdrawal in block
3191 .body
3192 .withdrawals
3193 .iter()
3194 .flatten()
3195 .filter(|withdrawal| withdrawal.amount > 0)
3196 {
3197 db.get_account_mut(withdrawal.address).map_err(|_| {
3198 EvmError::DB(format!(
3199 "Withdrawal account {} not found",
3200 withdrawal.address
3201 ))
3202 })?;
3203 }
3204 Ok(())
3205 }
3206
3207 #[cfg(feature = "rayon")]
3215 pub fn warm_txs(
3216 txs_with_sender: &[(&Transaction, Address)],
3217 header: &BlockHeader,
3218 store: Arc<dyn Database>,
3219 vm_type: VMType,
3220 crypto: &dyn Crypto,
3221 should_stop: &(dyn Fn() -> bool + Sync),
3222 ) -> Result<(), EvmError> {
3223 let mut sender_groups: FxHashMap<Address, Vec<&Transaction>> = FxHashMap::default();
3226 for (tx, sender) in txs_with_sender {
3227 sender_groups.entry(*sender).or_default().push(tx);
3228 }
3229
3230 let chain_config = store.get_chain_config()?;
3233 let evm_config = EVMConfig::new_from_chain_config(&chain_config, header);
3234 let chain_id = chain_config.chain_id;
3235 let base_blob_fee_per_gas = get_base_fee_per_blob_gas(header.excess_blob_gas, &evm_config)?;
3237
3238 sender_groups.into_par_iter().for_each_with(
3241 Vec::with_capacity(STACK_LIMIT),
3242 |stack_pool, (sender, txs)| {
3243 if should_stop() {
3244 return;
3245 }
3246 let mut memory_pool = Vec::with_capacity(1);
3250 let mut group_db = GeneralizedDatabase::new(store.clone());
3252 for tx in txs {
3253 if should_stop() {
3254 return;
3255 }
3256 let _ = Self::execute_tx_in_block(
3257 tx,
3258 sender,
3259 header,
3260 &mut group_db,
3261 vm_type,
3262 base_blob_fee_per_gas,
3263 stack_pool,
3264 &mut memory_pool,
3265 true,
3266 crypto,
3267 evm_config,
3268 chain_id,
3269 None,
3272 );
3273 }
3274 },
3275 );
3276 Ok(())
3277 }
3278
3279 #[cfg(feature = "rayon")]
3282 pub fn bal_storage_slots(bal: &BlockAccessList) -> Vec<(Address, H256)> {
3283 bal.accounts()
3284 .iter()
3285 .flat_map(|ac| {
3286 ac.all_storage_slots()
3287 .map(move |slot| (ac.address, H256::from_uint(&slot)))
3288 })
3289 .collect()
3290 }
3291
3292 #[cfg(feature = "rayon")]
3301 pub fn warm_block_from_bal(
3302 bal: &BlockAccessList,
3303 store: Arc<dyn Database>,
3304 cancelled: &AtomicBool,
3305 ) -> Result<(), EvmError> {
3306 let accounts = bal.accounts();
3307 if accounts.is_empty() {
3308 return Ok(());
3309 }
3310
3311 let account_addresses: Vec<Address> = accounts.iter().map(|ac| ac.address).collect();
3317 store
3318 .prefetch_accounts(&account_addresses)
3319 .map_err(|e| EvmError::Custom(format!("prefetch_accounts: {e}")))?;
3320
3321 if cancelled.load(Ordering::Relaxed) {
3322 return Ok(());
3323 }
3324
3325 let code_hashes: Vec<ethrex_common::H256> = accounts
3329 .par_iter()
3330 .filter_map(|ac| {
3331 store
3332 .get_account_state(ac.address)
3333 .ok()
3334 .filter(|s| s.code_hash != *EMPTY_KECCAK_HASH)
3335 .map(|s| s.code_hash)
3336 })
3337 .collect();
3338 code_hashes.par_iter().for_each(|&h| {
3339 let _ = store.get_account_code(h);
3340 });
3341
3342 Ok(())
3343 }
3344
3345 fn send_state_transitions_tx(
3346 merkleizer: &Sender<Vec<AccountUpdate>>,
3347 db: &mut GeneralizedDatabase,
3348 queue_length: &AtomicUsize,
3349 ) -> Result<(), EvmError> {
3350 let transitions = LEVM::get_state_transitions_tx(db)?;
3351 merkleizer
3352 .send(transitions)
3353 .map_err(|e| EvmError::Custom(format!("send failed: {e}")))?;
3354 queue_length.fetch_add(1, Ordering::Relaxed);
3355 Ok(())
3356 }
3357
3358 fn setup_env(
3359 tx: &Transaction,
3360 tx_sender: Address,
3361 block_header: &BlockHeader,
3362 db: &GeneralizedDatabase,
3363 vm_type: VMType,
3364 ) -> Result<Environment, EvmError> {
3365 let chain_config = db.store.get_chain_config()?;
3369 let config = EVMConfig::new_from_chain_config(&chain_config, block_header);
3370 let base_blob_fee_per_gas =
3371 get_base_fee_per_blob_gas(block_header.excess_blob_gas, &config)?;
3372 Self::setup_env_with_config(
3373 tx,
3374 tx_sender,
3375 block_header,
3376 config,
3377 chain_config.chain_id,
3378 vm_type,
3379 base_blob_fee_per_gas,
3380 )
3381 }
3382
3383 fn setup_env_with_config(
3387 tx: &Transaction,
3388 tx_sender: Address,
3389 block_header: &BlockHeader,
3390 config: EVMConfig,
3391 chain_id: u64,
3392 vm_type: VMType,
3393 base_blob_fee_per_gas: U256,
3394 ) -> Result<Environment, EvmError> {
3395 let gas_price: U256 = calculate_gas_price_for_tx(
3396 tx,
3397 block_header.base_fee_per_gas.unwrap_or_default(),
3398 &vm_type,
3399 )?;
3400
3401 let block_excess_blob_gas = block_header.excess_blob_gas;
3402 let env = Environment {
3403 origin: tx_sender,
3404 gas_limit: tx.gas_limit(),
3405 config,
3406 block_number: block_header.number,
3407 coinbase: block_header.coinbase,
3408 timestamp: block_header.timestamp,
3409 prev_randao: Some(block_header.prev_randao),
3410 slot_number: block_header
3411 .slot_number
3412 .map(U256::from)
3413 .unwrap_or(U256::zero()),
3414 chain_id: chain_id.into(),
3415 base_fee_per_gas: block_header.base_fee_per_gas.unwrap_or_default().into(),
3416 base_blob_fee_per_gas,
3417 gas_price,
3418 block_excess_blob_gas,
3419 block_blob_gas_used: block_header.blob_gas_used,
3420 tx_blob_hashes: tx.blob_versioned_hashes(),
3421 tx_max_priority_fee_per_gas: tx.max_priority_fee().map(U256::from),
3422 tx_max_fee_per_gas: tx.max_fee_per_gas().map(U256::from),
3423 tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas(),
3424 tx_nonce: tx.nonce(),
3425 block_gas_limit: block_header.gas_limit,
3426 difficulty: block_header.difficulty,
3427 is_privileged: matches!(tx, Transaction::PrivilegedL2Transaction(_)),
3428 fee_token: tx.fee_token(),
3429 disable_balance_check: false,
3430 disable_nonce_check: false,
3431 disable_gas_allowance_check: false,
3432 disable_sender_eoa_check: false,
3433 is_system_call: false,
3434 };
3435
3436 Ok(env)
3437 }
3438
3439 pub fn execute_tx(
3440 tx: &Transaction,
3442 tx_sender: Address,
3444 block_header: &BlockHeader,
3446 db: &mut GeneralizedDatabase,
3447 vm_type: VMType,
3448 crypto: &dyn Crypto,
3449 stateless_validator: Option<&dyn StatelessValidator>,
3450 ) -> Result<ExecutionReport, EvmError> {
3451 let env = Self::setup_env(tx, tx_sender, block_header, db, vm_type)?;
3452 let mut vm = VM::new(
3453 env,
3454 db,
3455 tx,
3456 LevmCallTracer::disabled(),
3457 vm_type,
3458 crypto,
3459 stateless_validator,
3460 )?;
3461
3462 vm.execute().map_err(VMError::into)
3463 }
3464
3465 #[allow(clippy::too_many_arguments)]
3468 fn execute_tx_in_block(
3469 tx: &Transaction,
3471 tx_sender: Address,
3473 block_header: &BlockHeader,
3475 db: &mut GeneralizedDatabase,
3476 vm_type: VMType,
3477 base_blob_fee_per_gas: U256,
3478 stack_pool: &mut Vec<Stack>,
3479 memory_pool: &mut Vec<Memory>,
3480 disable_balance_check: bool,
3481 crypto: &dyn Crypto,
3482 config: EVMConfig,
3483 chain_id: u64,
3484 stateless_validator: Option<&dyn StatelessValidator>,
3485 ) -> Result<ExecutionReport, EvmError> {
3486 let mut env = Self::setup_env_with_config(
3487 tx,
3488 tx_sender,
3489 block_header,
3490 config,
3491 chain_id,
3492 vm_type,
3493 base_blob_fee_per_gas,
3494 )?;
3495 env.disable_balance_check = disable_balance_check;
3496 let mut vm = VM::new_pooled(
3500 env,
3501 db,
3502 tx,
3503 LevmCallTracer::disabled(),
3504 vm_type,
3505 crypto,
3506 stateless_validator,
3507 stack_pool,
3508 memory_pool,
3509 )?;
3510 let result = vm.execute().map_err(VMError::into);
3511 vm.reclaim_into(stack_pool, memory_pool);
3513 result
3514 }
3515
3516 pub fn undo_last_tx(db: &mut GeneralizedDatabase) -> Result<(), EvmError> {
3517 db.undo_last_transaction()?;
3518 Ok(())
3519 }
3520
3521 pub fn simulate_tx_from_generic(
3522 tx: &GenericTransaction,
3524 block_header: &BlockHeader,
3526 db: &mut GeneralizedDatabase,
3527 vm_type: VMType,
3528 crypto: &dyn Crypto,
3529 stateless_validator: Option<&dyn StatelessValidator>,
3530 ) -> Result<ExecutionResult, EvmError> {
3531 let mut env = env_from_generic(tx, block_header, db, vm_type)?;
3536
3537 adjust_disabled_base_fee(&mut env);
3538
3539 let converted_tx = generic_tx_to_transaction(tx)?;
3540 let mut vm = vm_from_generic(&converted_tx, env, db, vm_type, crypto, stateless_validator)?;
3541
3542 vm.execute()
3543 .map(|value| value.into())
3544 .map_err(VMError::into)
3545 }
3546
3547 #[allow(clippy::too_many_arguments)]
3562 pub fn simulate_frame_validation_prefix(
3563 tx: &Transaction,
3564 block_header: &BlockHeader,
3565 db: &mut GeneralizedDatabase,
3566 vm_type: VMType,
3567 crypto: &dyn Crypto,
3568 prefix: ðrex_common::types::ValidationPrefix,
3569 _canonical_paymaster_code_hash: Option<H256>,
3570 ) -> Result<FrameValidationOutcome, EvmError> {
3571 use ethrex_common::types::FRAME_TX_MAX_VERIFY_GAS;
3572
3573 let frame_tx = match tx {
3574 Transaction::FrameTransaction(ft) => ft,
3575 _ => {
3576 return Err(EvmError::Custom(
3577 "simulate_frame_validation_prefix requires a frame transaction".to_string(),
3578 ));
3579 }
3580 };
3581 let sender = frame_tx.sender;
3582
3583 let env = Self::setup_env(tx, sender, block_header, db, vm_type)?;
3584 let mut vm = VM::new(
3585 env,
3586 db,
3587 tx,
3588 LevmCallTracer::disabled(),
3589 vm_type,
3590 crypto,
3591 None,
3592 )?;
3593
3594 let canonical_pay_frame: Option<usize> = None;
3597
3598 let sim = match vm.run_frame_validation_prefix(
3599 &prefix.frame_indices,
3600 prefix.deploy_index,
3601 canonical_pay_frame,
3602 ) {
3603 Ok(sim) => sim,
3604 Err(err) => {
3605 return Ok(FrameValidationOutcome {
3609 passed: false,
3610 violation: Some(EvmError::from(err).to_string()),
3611 reservation_ceiling: Self::frame_tx_reservation_ceiling(frame_tx),
3612 accessed_paymaster: None,
3613 touched_sender_slots: Vec::new(),
3614 });
3615 }
3616 };
3617
3618 let reservation_ceiling = Self::frame_tx_reservation_ceiling(frame_tx);
3619 let touched_sender_slots = vm.validation_observer.touched_sender_slots.clone();
3620 let accessed_paymaster = sim.payer_address.map(|payer| (payer, false));
3627
3628 if let Some(violation) = &vm.validation_observer.violation {
3630 return Ok(FrameValidationOutcome {
3631 passed: false,
3632 violation: Some(format!("{violation:?}")),
3633 reservation_ceiling,
3634 accessed_paymaster,
3635 touched_sender_slots,
3636 });
3637 }
3638
3639 if sim.any_revert {
3641 return Ok(FrameValidationOutcome {
3642 passed: false,
3643 violation: Some("validation prefix frame reverted".to_string()),
3644 reservation_ceiling,
3645 accessed_paymaster,
3646 touched_sender_slots,
3647 });
3648 }
3649
3650 if sim.payer_address.is_none() {
3653 return Ok(FrameValidationOutcome {
3654 passed: false,
3655 violation: Some("validation prefix did not establish a payer".to_string()),
3656 reservation_ceiling,
3657 accessed_paymaster,
3658 touched_sender_slots,
3659 });
3660 }
3661
3662 if prefix.deploy_index.is_some() {
3664 let code = vm.db.get_account_code(sender).map_err(VMError::from)?;
3665 if code.is_empty() {
3666 return Ok(FrameValidationOutcome {
3667 passed: false,
3668 violation: Some(format!("{:?}", FrameSimViolation::DeployInstalledNoCode)),
3669 reservation_ceiling,
3670 accessed_paymaster,
3671 touched_sender_slots,
3672 });
3673 }
3674 }
3675
3676 if sim.total_gas_used > FRAME_TX_MAX_VERIFY_GAS {
3678 return Ok(FrameValidationOutcome {
3679 passed: false,
3680 violation: Some(format!(
3681 "validation prefix gas {} exceeds MAX_VERIFY_GAS {}",
3682 sim.total_gas_used, FRAME_TX_MAX_VERIFY_GAS
3683 )),
3684 reservation_ceiling,
3685 accessed_paymaster,
3686 touched_sender_slots,
3687 });
3688 }
3689
3690 Ok(FrameValidationOutcome {
3691 passed: true,
3692 violation: None,
3693 reservation_ceiling,
3694 accessed_paymaster,
3695 touched_sender_slots,
3696 })
3697 }
3698
3699 fn frame_tx_reservation_ceiling(frame_tx: ðrex_common::types::FrameTransaction) -> U256 {
3718 let gas_cost =
3719 U256::from(frame_tx.max_fee_per_gas).saturating_mul(U256::from(frame_tx.max_gas()));
3720 let blob_cost = U256::from(frame_tx.blob_versioned_hashes.len())
3721 .saturating_mul(U256::from(131072u64))
3722 .saturating_mul(frame_tx.max_fee_per_blob_gas);
3723 gas_cost.saturating_add(blob_cost)
3724 }
3725
3726 pub fn get_state_transitions(
3727 db: &mut GeneralizedDatabase,
3728 ) -> Result<Vec<AccountUpdate>, EvmError> {
3729 Ok(db.get_state_transitions()?)
3730 }
3731
3732 pub fn get_state_transitions_tx(
3733 db: &mut GeneralizedDatabase,
3734 ) -> Result<Vec<AccountUpdate>, EvmError> {
3735 Ok(db.get_state_transitions_tx()?)
3736 }
3737
3738 pub fn process_withdrawals(
3739 db: &mut GeneralizedDatabase,
3740 withdrawals: &[Withdrawal],
3741 ) -> Result<(), EvmError> {
3742 for (address, increment) in withdrawals
3744 .iter()
3745 .filter(|withdrawal| withdrawal.amount > 0)
3746 .map(|w| (w.address, u128::from(w.amount) * u128::from(GWEI_TO_WEI)))
3747 {
3748 let account = db
3749 .get_account_mut(address)
3750 .map_err(|_| EvmError::DB(format!("Withdrawal account {address} not found")))?;
3751
3752 let initial_balance = account.info.balance;
3753 account.info.balance += increment.into();
3754 let new_balance = account.info.balance;
3755
3756 if let Some(recorder) = db.bal_recorder_mut() {
3758 recorder.set_initial_balance(address, initial_balance);
3759 recorder.record_balance_change(address, new_balance);
3760 }
3761 }
3762 Ok(())
3763 }
3764
3765 pub fn beacon_root_contract_call(
3767 block_header: &BlockHeader,
3768 db: &mut GeneralizedDatabase,
3769 vm_type: VMType,
3770 crypto: &dyn Crypto,
3771 ) -> Result<(), EvmError> {
3772 if let VMType::L2(_) = vm_type {
3773 return Err(EvmError::InvalidEVM(
3774 "beacon_root_contract_call should not be called for L2 VM".to_string(),
3775 ));
3776 }
3777
3778 let beacon_root = block_header.parent_beacon_block_root.ok_or_else(|| {
3779 EvmError::Header("parent_beacon_block_root field is missing".to_string())
3780 })?;
3781
3782 generic_system_contract_levm(
3783 block_header,
3784 Bytes::copy_from_slice(beacon_root.as_bytes()),
3785 db,
3786 BEACON_ROOTS_ADDRESS.address,
3787 SYSTEM_ADDRESS,
3788 vm_type,
3789 crypto,
3790 )?;
3791 Ok(())
3792 }
3793
3794 pub fn process_block_hash_history(
3795 block_header: &BlockHeader,
3796 db: &mut GeneralizedDatabase,
3797 vm_type: VMType,
3798 crypto: &dyn Crypto,
3799 ) -> Result<(), EvmError> {
3800 if let VMType::L2(_) = vm_type {
3801 return Err(EvmError::InvalidEVM(
3802 "process_block_hash_history should not be called for L2 VM".to_string(),
3803 ));
3804 }
3805
3806 generic_system_contract_levm(
3807 block_header,
3808 Bytes::copy_from_slice(block_header.parent_hash.as_bytes()),
3809 db,
3810 HISTORY_STORAGE_ADDRESS.address,
3811 SYSTEM_ADDRESS,
3812 vm_type,
3813 crypto,
3814 )?;
3815 Ok(())
3816 }
3817
3818 pub fn install_expiry_verifier_code(
3824 db: &mut GeneralizedDatabase,
3825 crypto: &dyn Crypto,
3826 ) -> Result<(), EvmError> {
3827 const PREDEPLOY_NONCE: u64 = 1;
3829
3830 let current = db.get_account_code(EXPIRY_VERIFIER_PREDEPLOY.address)?;
3831 if current.code() == EXPIRY_VERIFIER_RUNTIME_BYTECODE.as_slice() {
3832 return Ok(());
3833 }
3834 let code = Code::from_bytecode(
3835 Bytes::from_static(&EXPIRY_VERIFIER_RUNTIME_BYTECODE),
3836 crypto,
3837 );
3838 let code_hash = code.hash;
3839 if let Some(recorder) = db.bal_recorder_mut() {
3843 recorder.record_code_change(EXPIRY_VERIFIER_PREDEPLOY.address, code.code_bytes());
3844 recorder.record_nonce_change(EXPIRY_VERIFIER_PREDEPLOY.address, PREDEPLOY_NONCE);
3845 }
3846 let acc = db
3847 .get_account_mut(EXPIRY_VERIFIER_PREDEPLOY.address)
3848 .map_err(EvmError::from)?;
3849 acc.info.code_hash = code_hash;
3850 acc.info.nonce = PREDEPLOY_NONCE;
3851 db.codes.entry(code_hash).or_insert(code);
3852 Ok(())
3853 }
3854
3855 pub(crate) fn read_withdrawal_requests(
3856 block_header: &BlockHeader,
3857 db: &mut GeneralizedDatabase,
3858 vm_type: VMType,
3859 crypto: &dyn Crypto,
3860 ) -> Result<ExecutionReport, EvmError> {
3861 if let VMType::L2(_) = vm_type {
3862 return Err(EvmError::InvalidEVM(
3863 "read_withdrawal_requests should not be called for L2 VM".to_string(),
3864 ));
3865 }
3866
3867 let report = generic_system_contract_levm(
3868 block_header,
3869 Bytes::new(),
3870 db,
3871 WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS.address,
3872 SYSTEM_ADDRESS,
3873 vm_type,
3874 crypto,
3875 )?;
3876
3877 match report.result {
3878 TxResult::Success => Ok(report),
3879 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
3881 "REVERT when reading withdrawal requests with error: {vm_error:?}. According to EIP-7002, the revert of this system call invalidates the block.",
3882 ))),
3883 }
3884 }
3885
3886 pub(crate) fn dequeue_consolidation_requests(
3887 block_header: &BlockHeader,
3888 db: &mut GeneralizedDatabase,
3889 vm_type: VMType,
3890 crypto: &dyn Crypto,
3891 ) -> Result<ExecutionReport, EvmError> {
3892 if let VMType::L2(_) = vm_type {
3893 return Err(EvmError::InvalidEVM(
3894 "dequeue_consolidation_requests should not be called for L2 VM".to_string(),
3895 ));
3896 }
3897
3898 let report = generic_system_contract_levm(
3899 block_header,
3900 Bytes::new(),
3901 db,
3902 CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS.address,
3903 SYSTEM_ADDRESS,
3904 vm_type,
3905 crypto,
3906 )?;
3907
3908 match report.result {
3909 TxResult::Success => Ok(report),
3910 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
3912 "REVERT when dequeuing consolidation requests with error: {vm_error:?}. According to EIP-7251, the revert of this system call invalidates the block.",
3913 ))),
3914 }
3915 }
3916
3917 pub(crate) fn read_builder_deposit_requests(
3918 block_header: &BlockHeader,
3919 db: &mut GeneralizedDatabase,
3920 vm_type: VMType,
3921 crypto: &dyn Crypto,
3922 ) -> Result<ExecutionReport, EvmError> {
3923 if let VMType::L2(_) = vm_type {
3924 return Err(EvmError::InvalidEVM(
3925 "read_builder_deposit_requests should not be called for L2 VM".to_string(),
3926 ));
3927 }
3928
3929 let report = generic_system_contract_levm(
3930 block_header,
3931 Bytes::new(),
3932 db,
3933 BUILDER_DEPOSIT_CONTRACT_ADDRESS.address,
3934 SYSTEM_ADDRESS,
3935 vm_type,
3936 crypto,
3937 )?;
3938
3939 match report.result {
3940 TxResult::Success => Ok(report),
3941 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
3943 "REVERT when reading builder deposit requests with error: {vm_error:?}. According to EIP-8282, the revert of this system call invalidates the block.",
3944 ))),
3945 }
3946 }
3947
3948 pub(crate) fn dequeue_builder_exit_requests(
3949 block_header: &BlockHeader,
3950 db: &mut GeneralizedDatabase,
3951 vm_type: VMType,
3952 crypto: &dyn Crypto,
3953 ) -> Result<ExecutionReport, EvmError> {
3954 if let VMType::L2(_) = vm_type {
3955 return Err(EvmError::InvalidEVM(
3956 "dequeue_builder_exit_requests should not be called for L2 VM".to_string(),
3957 ));
3958 }
3959
3960 let report = generic_system_contract_levm(
3961 block_header,
3962 Bytes::new(),
3963 db,
3964 BUILDER_EXIT_CONTRACT_ADDRESS.address,
3965 SYSTEM_ADDRESS,
3966 vm_type,
3967 crypto,
3968 )?;
3969
3970 match report.result {
3971 TxResult::Success => Ok(report),
3972 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
3974 "REVERT when dequeuing builder exit requests with error: {vm_error:?}. According to EIP-8282, the revert of this system call invalidates the block.",
3975 ))),
3976 }
3977 }
3978
3979 pub fn create_access_list(
3980 mut tx: GenericTransaction,
3981 header: &BlockHeader,
3982 db: &mut GeneralizedDatabase,
3983 vm_type: VMType,
3984 crypto: &dyn Crypto,
3985 ) -> Result<(ExecutionResult, AccessList), VMError> {
3986 let mut env = env_from_generic(&tx, header, db, vm_type)?;
3987
3988 adjust_disabled_base_fee(&mut env);
3989
3990 let converted_tx = generic_tx_to_transaction(&tx)?;
3991 let mut vm = vm_from_generic(&converted_tx, env.clone(), db, vm_type, crypto, None)?;
3992
3993 vm.stateless_execute()?;
3994
3995 tx.access_list = vm.substate.make_access_list();
3997 let converted_tx = generic_tx_to_transaction(&tx)?;
3998 let mut vm = vm_from_generic(&converted_tx, env, db, vm_type, crypto, None)?;
3999
4000 let report = vm.stateless_execute()?;
4001
4002 Ok((
4003 report.into(),
4004 tx.access_list
4005 .into_iter()
4006 .map(|x| (x.address, x.storage_keys))
4007 .collect(),
4008 ))
4009 }
4010
4011 pub fn prepare_block(
4012 block: &Block,
4013 db: &mut GeneralizedDatabase,
4014 vm_type: VMType,
4015 crypto: &dyn Crypto,
4016 ) -> Result<(), EvmError> {
4017 let chain_config = db.store.get_chain_config()?;
4018 let block_header = &block.header;
4019 let fork = chain_config.fork(block_header.timestamp);
4020
4021 if let VMType::L2(_) = vm_type {
4023 return Ok(());
4024 }
4025
4026 if fork >= Fork::Hegota {
4030 Self::install_expiry_verifier_code(db, crypto)?;
4031 }
4032
4033 if block_header.parent_beacon_block_root.is_some() && fork >= Fork::Cancun {
4034 Self::beacon_root_contract_call(block_header, db, vm_type, crypto)?;
4035 }
4036
4037 if fork >= Fork::Prague {
4038 Self::process_block_hash_history(block_header, db, vm_type, crypto)?;
4040 }
4041 Ok(())
4042 }
4043}
4044
4045pub fn generic_system_contract_levm(
4046 block_header: &BlockHeader,
4047 calldata: Bytes,
4048 db: &mut GeneralizedDatabase,
4049 contract_address: Address,
4050 system_address: Address,
4051 vm_type: VMType,
4052 crypto: &dyn Crypto,
4053) -> Result<ExecutionReport, EvmError> {
4054 let chain_config = db.store.get_chain_config()?;
4055 let config = EVMConfig::new_from_chain_config(&chain_config, block_header);
4056 let env = Environment {
4057 origin: system_address,
4058 gas_limit: SYS_CALL_GAS_LIMIT,
4069 block_number: block_header.number,
4070 coinbase: block_header.coinbase,
4071 timestamp: block_header.timestamp,
4072 prev_randao: Some(block_header.prev_randao),
4073 base_fee_per_gas: U256::zero(),
4074 gas_price: U256::zero(),
4075 block_excess_blob_gas: block_header.excess_blob_gas,
4076 block_blob_gas_used: block_header.blob_gas_used,
4077 block_gas_limit: block_header.gas_limit,
4082 is_system_call: true,
4083 config,
4084 ..Default::default()
4085 };
4086
4087 debug_assert!(
4093 env.gas_price.is_zero() && env.base_fee_per_gas.is_zero(),
4094 "system calls must run with a zero gas price"
4095 );
4096
4097 if PRAGUE_SYSTEM_CONTRACTS
4107 .iter()
4108 .chain(AMSTERDAM_REQUEST_PREDEPLOYS.iter())
4109 .any(|contract| contract.address == contract_address)
4110 && db.get_account_code(contract_address)?.is_empty()
4111 {
4112 return Err(EvmError::SystemContractCallFailed(format!(
4113 "System contract: {contract_address} has no code after deployment"
4114 )));
4115 };
4116
4117 let tx = &Transaction::EIP1559Transaction(EIP1559Transaction {
4118 to: TxKind::Call(contract_address),
4119 value: U256::zero(),
4120 data: calldata,
4121 ..Default::default()
4122 });
4123 if let Some(recorder) = db.bal_recorder.as_mut() {
4125 recorder.enter_system_call();
4126 }
4127
4128 let result = VM::new(
4129 env,
4130 db,
4131 tx,
4132 LevmCallTracer::disabled(),
4133 vm_type,
4134 crypto,
4135 None,
4136 )
4137 .and_then(|mut vm| vm.execute())
4138 .map_err(EvmError::from);
4139
4140 if let Some(recorder) = db.bal_recorder.as_mut() {
4142 recorder.exit_system_call();
4143 }
4144
4145 result
4146}
4147
4148#[allow(unreachable_code)]
4149#[allow(unused_variables)]
4150pub fn extract_all_requests_levm(
4151 receipts: &[Receipt],
4152 db: &mut GeneralizedDatabase,
4153 header: &BlockHeader,
4154 vm_type: VMType,
4155 crypto: &dyn Crypto,
4156) -> Result<Vec<Requests>, EvmError> {
4157 if let VMType::L2(_) = vm_type {
4158 return Err(EvmError::InvalidEVM(
4159 "extract_all_requests_levm should not be called for L2 VM".to_string(),
4160 ));
4161 }
4162
4163 let chain_config = db.store.get_chain_config()?;
4164 let fork = chain_config.fork(header.timestamp);
4165
4166 if fork < Fork::Prague {
4167 return Ok(Default::default());
4168 }
4169
4170 let withdrawals_data: Vec<u8> = LEVM::read_withdrawal_requests(header, db, vm_type, crypto)?
4171 .output
4172 .into();
4173 let consolidation_data: Vec<u8> =
4174 LEVM::dequeue_consolidation_requests(header, db, vm_type, crypto)?
4175 .output
4176 .into();
4177
4178 let deposits = Requests::from_deposit_receipts(chain_config.deposit_contract_address, receipts)
4179 .ok_or(EvmError::InvalidDepositRequest)?;
4180 let withdrawals = Requests::from_withdrawals_data(withdrawals_data);
4181 let consolidation = Requests::from_consolidation_data(consolidation_data);
4182
4183 let mut requests = vec![deposits, withdrawals, consolidation];
4184
4185 if fork >= Fork::Amsterdam {
4189 requests.reserve_exact(2);
4191 let builder_deposit_data: Vec<u8> =
4192 LEVM::read_builder_deposit_requests(header, db, vm_type, crypto)?
4193 .output
4194 .into();
4195 let builder_exit_data: Vec<u8> =
4196 LEVM::dequeue_builder_exit_requests(header, db, vm_type, crypto)?
4197 .output
4198 .into();
4199 requests.push(Requests::from_builder_deposit_data(builder_deposit_data));
4200 requests.push(Requests::from_builder_exit_data(builder_exit_data));
4201 }
4202
4203 Ok(requests)
4204}
4205
4206pub fn calculate_gas_price_for_generic(tx: &GenericTransaction, basefee: u64) -> U256 {
4209 if !tx.gas_price.is_zero() {
4210 tx.gas_price
4212 } else {
4213 min(
4215 tx.max_priority_fee_per_gas.unwrap_or(0) + basefee,
4216 tx.max_fee_per_gas.unwrap_or(0),
4217 )
4218 .into()
4219 }
4220}
4221
4222pub fn calculate_gas_price_for_tx(
4223 tx: &Transaction,
4224 mut fee_per_gas: u64,
4225 vm_type: &VMType,
4226) -> Result<U256, VMError> {
4227 let Some(max_priority_fee) = tx.max_priority_fee() else {
4228 return Ok(tx.gas_price());
4230 };
4231
4232 let max_fee_per_gas = tx.max_fee_per_gas().ok_or(VMError::TxValidation(
4233 TxValidationError::InsufficientMaxFeePerGas,
4234 ))?;
4235
4236 if let VMType::L2(fee_config) = vm_type
4237 && let Some(operator_fee_config) = &fee_config.operator_fee_config
4238 {
4239 fee_per_gas += operator_fee_config.operator_fee_per_gas;
4240 }
4241
4242 if fee_per_gas > max_fee_per_gas {
4243 return Err(VMError::TxValidation(
4244 TxValidationError::InsufficientMaxFeePerGas,
4245 ));
4246 }
4247
4248 Ok(min(max_priority_fee + fee_per_gas, max_fee_per_gas).into())
4249}
4250
4251pub(crate) fn adjust_disabled_base_fee(env: &mut Environment) {
4255 if env.gas_price == U256::zero() {
4256 env.base_fee_per_gas = U256::zero();
4257 }
4258 if env
4267 .tx_max_fee_per_blob_gas
4268 .is_some_and(|v| v == U256::zero())
4269 {
4270 env.base_blob_fee_per_gas = U256::zero();
4271 }
4272}
4273
4274fn adjust_disabled_l2_fees(env: &Environment, vm_type: VMType) -> VMType {
4276 if env.gas_price == U256::zero()
4277 && let VMType::L2(fee_config) = vm_type
4278 {
4279 return VMType::L2(FeeConfig {
4281 operator_fee_config: None,
4282 l1_fee_config: None,
4283 ..fee_config
4284 });
4285 }
4286 vm_type
4287}
4288
4289pub(crate) fn env_from_generic(
4290 tx: &GenericTransaction,
4291 header: &BlockHeader,
4292 db: &GeneralizedDatabase,
4293 vm_type: VMType,
4294) -> Result<Environment, VMError> {
4295 let chain_config = db.store.get_chain_config()?;
4296 let gas_price =
4297 calculate_gas_price_for_generic(tx, header.base_fee_per_gas.unwrap_or(INITIAL_BASE_FEE));
4298 let block_excess_blob_gas = header.excess_blob_gas;
4299 let config = EVMConfig::new_from_chain_config(&chain_config, header);
4300
4301 let slot_number = if let VMType::L2(_) = vm_type {
4315 U256::zero()
4316 } else {
4317 header.slot_number.map(U256::from).unwrap_or(U256::zero())
4318 };
4319
4320 Ok(Environment {
4321 origin: tx.from.0.into(),
4322 gas_limit: tx
4323 .gas
4324 .unwrap_or(get_max_allowed_gas_limit(header.gas_limit, config.fork)), config,
4326 block_number: header.number,
4327 coinbase: header.coinbase,
4328 timestamp: header.timestamp,
4329 prev_randao: Some(header.prev_randao),
4330 slot_number,
4331 chain_id: chain_config.chain_id.into(),
4332 base_fee_per_gas: header.base_fee_per_gas.unwrap_or_default().into(),
4333 base_blob_fee_per_gas: get_base_fee_per_blob_gas(block_excess_blob_gas, &config)?,
4334 gas_price,
4335 block_excess_blob_gas,
4336 block_blob_gas_used: header.blob_gas_used,
4337 tx_blob_hashes: tx.blob_versioned_hashes.clone(),
4338 tx_max_priority_fee_per_gas: tx.max_priority_fee_per_gas.map(U256::from),
4339 tx_max_fee_per_gas: tx.max_fee_per_gas.map(U256::from),
4340 tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
4341 tx_nonce: tx.nonce.unwrap_or_default(),
4342 block_gas_limit: header.gas_limit,
4343 difficulty: header.difficulty,
4344 is_privileged: false,
4345 fee_token: tx.fee_token,
4346 disable_balance_check: false,
4347 disable_nonce_check: true,
4353 disable_gas_allowance_check: true,
4359 disable_sender_eoa_check: true,
4362 is_system_call: false,
4363 })
4364}
4365
4366fn generic_tx_to_transaction(tx: &GenericTransaction) -> Result<Transaction, VMError> {
4381 let (signature_r, signature_s) = (U256::MAX, U256::MAX);
4384 let nonce = tx.nonce.unwrap_or_default();
4385 let gas_limit = tx.gas.unwrap_or_default();
4386 let max_fee_per_gas = tx.max_fee_per_gas.unwrap_or_default();
4387 let max_priority_fee_per_gas = tx.max_priority_fee_per_gas.unwrap_or_default();
4388 let chain_id = tx.chain_id.unwrap_or_default();
4389
4390 Ok(match &tx.authorization_list {
4391 Some(authorization_list) => Transaction::EIP7702Transaction(EIP7702Transaction {
4392 to: match tx.to {
4393 TxKind::Call(to) => to,
4394 TxKind::Create => {
4395 return Err(InternalError::msg("Generic Tx cannot be create type").into());
4396 }
4397 },
4398 value: tx.value,
4399 data: tx.input.clone(),
4400 access_list: tx
4401 .access_list
4402 .iter()
4403 .map(|list| (list.address, list.storage_keys.clone()))
4404 .collect(),
4405 authorization_list: authorization_list
4406 .iter()
4407 .map(|auth| Into::<AuthorizationTuple>::into(auth.clone()))
4408 .collect(),
4409 nonce,
4410 gas_limit,
4411 max_fee_per_gas,
4412 max_priority_fee_per_gas,
4413 chain_id,
4414 signature_r,
4415 signature_s,
4416 ..Default::default()
4417 }),
4418 None => Transaction::EIP1559Transaction(EIP1559Transaction {
4419 to: tx.to.clone(),
4420 value: tx.value,
4421 data: tx.input.clone(),
4422 access_list: tx
4423 .access_list
4424 .iter()
4425 .map(|list| (list.address, list.storage_keys.clone()))
4426 .collect(),
4427 nonce,
4428 gas_limit,
4429 max_fee_per_gas,
4430 max_priority_fee_per_gas,
4431 chain_id,
4432 signature_r,
4433 signature_s,
4434 ..Default::default()
4435 }),
4436 })
4437}
4438
4439fn vm_from_generic<'a>(
4440 tx: &'a Transaction,
4441 env: Environment,
4442 db: &'a mut GeneralizedDatabase,
4443 vm_type: VMType,
4444 crypto: &'a dyn Crypto,
4445 stateless_validator: Option<&'a dyn StatelessValidator>,
4446) -> Result<VM<'a>, VMError> {
4447 let vm_type = adjust_disabled_l2_fees(&env, vm_type);
4448 VM::new(
4449 env,
4450 db,
4451 tx,
4452 LevmCallTracer::disabled(),
4453 vm_type,
4454 crypto,
4455 stateless_validator,
4456 )
4457}
4458
4459pub fn get_max_allowed_gas_limit(block_gas_limit: u64, fork: Fork) -> u64 {
4460 if fork >= Fork::Osaka && fork < Fork::Amsterdam {
4468 POST_OSAKA_GAS_LIMIT_CAP
4469 } else {
4470 block_gas_limit
4471 }
4472}
4473
4474#[allow(dead_code)]
4482fn describe_balance_diff(expected: U256, actual: U256) -> String {
4483 let (sign, mag) = if expected >= actual {
4484 ("+", expected - actual)
4485 } else {
4486 ("-", actual - expected)
4487 };
4488 let Ok(mag_u128) = u128::try_from(mag) else {
4489 return format!("{sign}{mag}");
4490 };
4491 if mag_u128 == 0 {
4492 return "0".to_string();
4493 }
4494 let cpsb: u128 = 1530;
4495 let consts = [
4497 ("NEW_ACCOUNT", 120u128),
4498 ("STORAGE_SET", 64),
4499 ("AUTH_BASE", 23),
4500 ("AUTH_TOTAL", 143),
4501 ];
4502 for &gp in &[10u128, 1, 7, 100, 1000, 1_000_000_000] {
4504 if !mag_u128.is_multiple_of(gp) {
4505 continue;
4506 }
4507 let gas = mag_u128 / gp;
4508 if !gas.is_multiple_of(cpsb) {
4509 continue;
4510 }
4511 let bytes = gas / cpsb;
4512 for (name, c) in consts {
4513 if bytes.is_multiple_of(c) {
4514 let n = bytes / c;
4515 return format!(
4516 "{sign}{mag_u128} wei (= {gas} gas at {gp} wei/gas = {n}× {name}_state_gas)"
4517 );
4518 }
4519 }
4520 }
4521 format!("{sign}{mag_u128} wei")
4522}
4523
4524#[cfg(all(test, feature = "rayon"))]
4527mod bal_tests {
4528 use super::*;
4529 use ethrex_common::H256;
4530 use ethrex_common::types::AccountState;
4531 use ethrex_common::types::block_access_list::{
4532 AccountChanges, BalanceChange, NonceChange, SlotChange, StorageChange,
4533 };
4534 use ethrex_levm::errors::DatabaseError;
4535
4536 fn addr(byte: u8) -> Address {
4537 let mut a = Address::zero();
4538 a.0[19] = byte;
4539 a
4540 }
4541
4542 struct MockStore {
4544 accounts: FxHashMap<Address, AccountState>,
4545 }
4546
4547 impl MockStore {
4548 fn new() -> Self {
4549 Self {
4550 accounts: FxHashMap::default(),
4551 }
4552 }
4553
4554 fn with_account(mut self, address: Address, state: AccountState) -> Self {
4555 self.accounts.insert(address, state);
4556 self
4557 }
4558 }
4559
4560 impl Database for MockStore {
4561 fn get_account_state(&self, address: Address) -> Result<AccountState, DatabaseError> {
4562 Ok(self.accounts.get(&address).copied().unwrap_or_default())
4563 }
4564 fn get_storage_value(&self, _: Address, _: H256) -> Result<U256, DatabaseError> {
4565 Ok(U256::zero())
4566 }
4567 fn get_block_hash(&self, _: u64) -> Result<H256, DatabaseError> {
4568 Ok(H256::zero())
4569 }
4570 fn get_chain_config(&self) -> Result<ethrex_common::types::ChainConfig, DatabaseError> {
4571 Err(DatabaseError::Custom("not implemented".into()))
4572 }
4573 fn get_account_code(&self, _: H256) -> Result<ethrex_common::types::Code, DatabaseError> {
4574 Ok(ethrex_common::types::Code::from_bytecode(
4575 Bytes::new(),
4576 ðrex_crypto::NativeCrypto,
4577 ))
4578 }
4579 fn get_code_metadata(
4580 &self,
4581 _: H256,
4582 ) -> Result<ethrex_common::types::CodeMetadata, DatabaseError> {
4583 Ok(ethrex_common::types::CodeMetadata { length: 0 })
4584 }
4585 }
4586
4587 #[test]
4588 fn test_bal_to_account_updates_basic() {
4589 let address = addr(1);
4591 let store = MockStore::new().with_account(
4592 address,
4593 AccountState {
4594 balance: U256::from(100),
4595 nonce: 5,
4596 code_hash: *EMPTY_KECCAK_HASH,
4597 storage_root: H256::zero(),
4598 },
4599 );
4600
4601 let bal = BlockAccessList::from_accounts(vec![
4602 AccountChanges::new(address)
4603 .with_balance_changes(vec![
4604 BalanceChange::new(1, U256::from(90)),
4605 BalanceChange::new(2, U256::from(80)),
4606 ])
4607 .with_nonce_changes(vec![NonceChange::new(1, 6)])
4608 .with_storage_changes(vec![SlotChange::with_changes(
4609 U256::from(42),
4610 vec![StorageChange::new(1, U256::from(999))],
4611 )]),
4612 ]);
4613
4614 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
4615 assert_eq!(updates.len(), 1);
4616 let u = &updates[0];
4617 assert_eq!(u.address, address);
4618 assert!(!u.removed);
4619 let info = u.info.as_ref().unwrap();
4620 assert_eq!(info.balance, U256::from(80));
4622 assert_eq!(info.nonce, 6);
4623 assert_eq!(info.code_hash, *EMPTY_KECCAK_HASH);
4624 let key = ethrex_common::utils::u256_to_h256(U256::from(42));
4626 assert_eq!(*u.added_storage.get(&key).unwrap(), U256::from(999));
4627 }
4628
4629 #[test]
4630 fn test_bal_to_account_updates_highest_index_wins() {
4631 let address = addr(2);
4633 let store = MockStore::new().with_account(
4634 address,
4635 AccountState {
4636 balance: U256::from(1000),
4637 nonce: 0,
4638 code_hash: *EMPTY_KECCAK_HASH,
4639 storage_root: H256::zero(),
4640 },
4641 );
4642
4643 let bal = BlockAccessList::from_accounts(vec![
4644 AccountChanges::new(address).with_balance_changes(vec![
4645 BalanceChange::new(1, U256::from(900)),
4646 BalanceChange::new(2, U256::from(800)),
4647 BalanceChange::new(3, U256::from(700)),
4648 ]),
4649 ]);
4650
4651 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
4652 assert_eq!(updates.len(), 1);
4653 assert_eq!(updates[0].info.as_ref().unwrap().balance, U256::from(700));
4654 }
4655
4656 #[test]
4657 fn test_bal_to_account_updates_reads_only_skipped() {
4658 let address = addr(3);
4660 let store = MockStore::new();
4661
4662 let bal = BlockAccessList::from_accounts(vec![
4663 AccountChanges::new(address).with_storage_reads(vec![U256::from(1)]),
4664 ]);
4665
4666 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
4667 assert!(updates.is_empty());
4668 }
4669
4670 #[test]
4671 fn test_bal_to_account_updates_removal() {
4672 let address = addr(4);
4674 let store = MockStore::new().with_account(
4675 address,
4676 AccountState {
4677 balance: U256::from(50),
4678 nonce: 1,
4679 code_hash: *EMPTY_KECCAK_HASH,
4680 storage_root: H256::zero(),
4681 },
4682 );
4683
4684 let bal = BlockAccessList::from_accounts(vec![
4685 AccountChanges::new(address)
4686 .with_balance_changes(vec![BalanceChange::new(1, U256::zero())])
4687 .with_nonce_changes(vec![NonceChange::new(1, 0)]),
4688 ]);
4689
4690 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
4691 assert_eq!(updates.len(), 1);
4692 assert!(updates[0].removed);
4693 }
4694
4695 #[test]
4696 fn test_bal_to_account_updates_storage_zero() {
4697 let address = addr(5);
4699 let store = MockStore::new();
4700
4701 let bal = BlockAccessList::from_accounts(vec![
4702 AccountChanges::new(address).with_storage_changes(vec![SlotChange::with_changes(
4703 U256::from(7),
4704 vec![StorageChange::new(1, U256::zero())],
4705 )]),
4706 ]);
4707
4708 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
4709 assert_eq!(updates.len(), 1);
4710 let key = ethrex_common::utils::u256_to_h256(U256::from(7));
4711 assert_eq!(*updates[0].added_storage.get(&key).unwrap(), U256::zero());
4712 }
4713
4714 #[test]
4715 fn test_bal_to_account_updates_code_deployment() {
4716 let address = addr(6);
4718 let store = MockStore::new();
4719 let code = Bytes::from(vec![0x60, 0x00, 0x60, 0x00, 0xf3]); let expected_hash =
4721 ethrex_common::types::Code::from_bytecode(code.clone(), ðrex_crypto::NativeCrypto)
4722 .hash;
4723
4724 let bal = BlockAccessList::from_accounts(vec![
4725 AccountChanges::new(address)
4726 .with_code_changes(vec![
4727 ethrex_common::types::block_access_list::CodeChange::new(1, code.clone()),
4728 ])
4729 .with_nonce_changes(vec![NonceChange::new(1, 1)]),
4730 ]);
4731
4732 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
4733 assert_eq!(updates.len(), 1);
4734 let u = &updates[0];
4735 assert_eq!(u.info.as_ref().unwrap().code_hash, expected_hash);
4736 assert_eq!(u.code.as_ref().unwrap().code(), &code[..]);
4737 }
4738}
4739
4740#[cfg(test)]
4741mod system_call_coinbase_tests {
4742 use super::*;
4748 use ethrex_common::types::{AccountState, AccountUpdate, ChainConfig, Code, CodeMetadata};
4749 use ethrex_crypto::NativeCrypto;
4750 use ethrex_levm::db::Database;
4751 use ethrex_levm::errors::DatabaseError;
4752 use std::sync::Arc;
4753
4754 const HISTORY_RUNTIME_CODE: &str = concat!(
4756 "3373fffffffffffffffffffffffffffffffffffffffe1460465760203603604257",
4757 "5f35600143038111604257611fff81430311604257611fff900654",
4758 "5f5260205ff35b5f5ffd5b5f35611fff60014303065500",
4759 );
4760
4761 struct Store {
4762 chain_config: ChainConfig,
4763 history_code: Code,
4764 }
4765
4766 impl Database for Store {
4767 fn get_account_state(&self, address: Address) -> Result<AccountState, DatabaseError> {
4768 if address == HISTORY_STORAGE_ADDRESS.address {
4769 return Ok(AccountState {
4770 nonce: 1,
4771 code_hash: self.history_code.hash,
4772 ..Default::default()
4773 });
4774 }
4775 Ok(AccountState::default())
4776 }
4777 fn get_storage_value(&self, _: Address, _: H256) -> Result<U256, DatabaseError> {
4778 Ok(U256::zero())
4779 }
4780 fn get_block_hash(&self, _: u64) -> Result<H256, DatabaseError> {
4781 Ok(H256::zero())
4782 }
4783 fn get_chain_config(&self) -> Result<ChainConfig, DatabaseError> {
4784 Ok(self.chain_config)
4785 }
4786 fn get_account_code(&self, code_hash: H256) -> Result<Code, DatabaseError> {
4787 if code_hash == self.history_code.hash {
4788 return Ok(self.history_code.clone());
4789 }
4790 Ok(Code::default())
4791 }
4792 fn get_code_metadata(&self, code_hash: H256) -> Result<CodeMetadata, DatabaseError> {
4793 let length = if code_hash == self.history_code.hash {
4794 self.history_code.len() as u64
4795 } else {
4796 0
4797 };
4798 Ok(CodeMetadata { length })
4799 }
4800 }
4801
4802 fn history_code() -> Code {
4803 let bytes = hex::decode(HISTORY_RUNTIME_CODE).expect("history runtime code is valid hex");
4804 Code::from_bytecode(Bytes::from(bytes), &NativeCrypto)
4805 }
4806
4807 fn prague_db() -> GeneralizedDatabase {
4808 GeneralizedDatabase::new(Arc::new(Store {
4809 chain_config: ChainConfig {
4810 prague_time: Some(0),
4811 ..Default::default()
4812 },
4813 history_code: history_code(),
4814 }))
4815 }
4816
4817 fn parent_hash_value(parent_hash: H256) -> U256 {
4818 U256::from_big_endian(parent_hash.as_bytes())
4819 }
4820
4821 fn history_slot(block_number: u64) -> H256 {
4822 H256::from_low_u64_be((block_number - 1) % 8191)
4823 }
4824
4825 fn run_history_update(coinbase: Address) -> (Option<U256>, Vec<AccountUpdate>, H256) {
4829 let mut db = prague_db();
4830 let parent_hash = H256::from_low_u64_be(0x2935);
4831 let block_number = 42;
4832 let header = BlockHeader {
4833 parent_hash,
4834 coinbase,
4835 number: block_number,
4836 timestamp: 1,
4837 ..Default::default()
4838 };
4839
4840 LEVM::process_block_hash_history(&header, &mut db, VMType::L1, &NativeCrypto)
4841 .expect("history system call executes");
4842
4843 let slot = history_slot(block_number);
4844 let stored_value = db
4845 .current_accounts_state
4846 .get(&HISTORY_STORAGE_ADDRESS.address)
4847 .and_then(|account| account.storage.get(&slot).copied());
4848 let updates =
4849 LEVM::get_state_transitions(&mut db).expect("state transitions are generated");
4850
4851 (stored_value, updates, parent_hash)
4852 }
4853
4854 fn assert_history_write_emitted(coinbase: Address) {
4855 let (stored_value, updates, parent_hash) = run_history_update(coinbase);
4856 let slot = history_slot(42);
4857 assert_eq!(
4858 stored_value,
4859 Some(parent_hash_value(parent_hash)),
4860 "history storage must hold the parent hash after the system call"
4861 );
4862 assert!(
4863 updates.iter().any(|update| {
4864 update.address == HISTORY_STORAGE_ADDRESS.address
4865 && update.added_storage.get(&slot) == Some(&parent_hash_value(parent_hash))
4866 }),
4867 "the history-contract storage write must be emitted as a state update"
4868 );
4869 }
4870
4871 #[test]
4872 fn ordinary_coinbase_preserves_history_storage_write() {
4873 assert_history_write_emitted(Address::from_low_u64_be(0xbeef));
4874 }
4875
4876 #[test]
4879 fn history_address_coinbase_preserves_history_storage_write() {
4880 assert_history_write_emitted(HISTORY_STORAGE_ADDRESS.address);
4881 }
4882}
4883
4884#[cfg(all(test, feature = "rayon"))]
4889mod burned_fees_tests {
4890 use super::*;
4891 use ethrex_common::{
4892 constants::{DEFAULT_OMMERS_HASH, DEFAULT_REQUESTS_HASH},
4893 types::{AccountState, Block, BlockBody, BlockHeader, ChainConfig, Code, CodeMetadata},
4894 utils::keccak,
4895 };
4896 use ethrex_crypto::NativeCrypto;
4897 use ethrex_levm::errors::DatabaseError;
4898 use ethrex_rlp::encode::PayloadRLPEncode;
4899
4900 fn lstar_chain_config() -> ChainConfig {
4903 ChainConfig {
4904 chain_id: 1,
4905 homestead_block: Some(0),
4906 eip150_block: Some(0),
4907 eip155_block: Some(0),
4908 eip158_block: Some(0),
4909 byzantium_block: Some(0),
4910 constantinople_block: Some(0),
4911 petersburg_block: Some(0),
4912 istanbul_block: Some(0),
4913 berlin_block: Some(0),
4914 london_block: Some(0),
4915 terminal_total_difficulty: Some(0),
4916 terminal_total_difficulty_passed: true,
4917 shanghai_time: Some(0),
4918 cancun_time: Some(0),
4919 prague_time: Some(0),
4920 amsterdam_time: Some(0),
4921 lstar_time: Some(0),
4922 ..Default::default()
4923 }
4924 }
4925
4926 fn amsterdam_chain_config() -> ChainConfig {
4927 ChainConfig {
4928 chain_id: 1,
4929 homestead_block: Some(0),
4930 eip150_block: Some(0),
4931 eip155_block: Some(0),
4932 eip158_block: Some(0),
4933 byzantium_block: Some(0),
4934 constantinople_block: Some(0),
4935 petersburg_block: Some(0),
4936 istanbul_block: Some(0),
4937 berlin_block: Some(0),
4938 london_block: Some(0),
4939 terminal_total_difficulty: Some(0),
4940 terminal_total_difficulty_passed: true,
4941 shanghai_time: Some(0),
4942 cancun_time: Some(0),
4943 prague_time: Some(0),
4944 amsterdam_time: Some(0),
4945 lstar_time: None, ..Default::default()
4947 }
4948 }
4949
4950 struct MockDB {
4953 chain_config: ChainConfig,
4954 accounts: FxHashMap<Address, (AccountState, Bytes)>,
4955 storage: FxHashMap<(Address, H256), U256>,
4957 }
4958
4959 impl MockDB {
4960 fn new(chain_config: ChainConfig) -> Self {
4961 Self {
4962 chain_config,
4963 accounts: FxHashMap::default(),
4964 storage: FxHashMap::default(),
4965 }
4966 }
4967
4968 fn with_account(mut self, address: Address, balance: U256, code: Option<Bytes>) -> Self {
4969 let code_bytes = code.unwrap_or_default();
4970 let code_hash = if code_bytes.is_empty() {
4971 *EMPTY_KECCAK_HASH
4972 } else {
4973 keccak(&code_bytes)
4974 };
4975 let state = AccountState {
4976 nonce: 0,
4977 balance,
4978 storage_root: H256::zero(),
4979 code_hash,
4980 };
4981 self.accounts.insert(address, (state, code_bytes));
4982 self
4983 }
4984
4985 fn with_storage(mut self, address: Address, slot: H256, value: U256) -> Self {
4986 self.storage.insert((address, slot), value);
4987 self
4988 }
4989 }
4990
4991 impl Database for MockDB {
4992 fn get_account_state(&self, address: Address) -> Result<AccountState, DatabaseError> {
4993 Ok(self
4994 .accounts
4995 .get(&address)
4996 .map(|(s, _)| *s)
4997 .unwrap_or_default())
4998 }
4999
5000 fn get_storage_value(&self, address: Address, slot: H256) -> Result<U256, DatabaseError> {
5001 Ok(self
5002 .storage
5003 .get(&(address, slot))
5004 .copied()
5005 .unwrap_or_default())
5006 }
5007
5008 fn get_block_hash(&self, _: u64) -> Result<H256, DatabaseError> {
5009 Ok(H256::zero())
5010 }
5011
5012 fn get_chain_config(&self) -> Result<ChainConfig, DatabaseError> {
5013 Ok(self.chain_config)
5014 }
5015
5016 fn get_account_code(&self, code_hash: H256) -> Result<Code, DatabaseError> {
5017 let crypto = NativeCrypto;
5018 for (state, bytecode) in self.accounts.values() {
5019 if state.code_hash == code_hash && !bytecode.is_empty() {
5020 return Ok(Code::from_bytecode(bytecode.clone(), &crypto));
5021 }
5022 }
5023 Ok(Code::from_bytecode(Bytes::new(), &crypto))
5024 }
5025
5026 fn get_code_metadata(&self, code_hash: H256) -> Result<CodeMetadata, DatabaseError> {
5027 for (state, bytecode) in self.accounts.values() {
5028 if state.code_hash == code_hash {
5029 return Ok(CodeMetadata {
5030 length: bytecode.len() as u64,
5031 });
5032 }
5033 }
5034 Ok(CodeMetadata { length: 0 })
5035 }
5036 }
5037
5038 const PRIVATE_KEY: [u8; 32] = [
5041 0x4c, 0x08, 0x83, 0xa6, 0x91, 0x02, 0x93, 0x7d, 0x62, 0x31, 0x47, 0x1b, 0x5d, 0xbb, 0x62,
5042 0x04, 0xfe, 0x51, 0x29, 0x61, 0x70, 0x82, 0x79, 0x2a, 0xe4, 0x68, 0xd0, 0x1a, 0x3f, 0x36,
5043 0x23, 0x18,
5044 ];
5045
5046 fn sender_address() -> Address {
5047 use k256::ecdsa::SigningKey;
5048 let signing_key = SigningKey::from_bytes(&PRIVATE_KEY.into()).expect("valid key");
5049 let uncompressed_pub = signing_key.verifying_key().to_encoded_point(false);
5050 let pub_hash = keccak(&uncompressed_pub.as_bytes()[1..]);
5051 Address::from_slice(&pub_hash.0[12..])
5052 }
5053
5054 fn signed_eth_transfer(to: Address, nonce: u64, base_fee: u64) -> Transaction {
5057 use k256::ecdsa::SigningKey;
5058
5059 let tx = EIP1559Transaction {
5060 chain_id: 1,
5061 nonce,
5062 max_priority_fee_per_gas: 1,
5063 max_fee_per_gas: base_fee + 10,
5064 gas_limit: 21_000,
5065 to: TxKind::Call(to),
5066 value: U256::zero(),
5067 ..Default::default()
5068 };
5069
5070 let mut buf = vec![0x02u8];
5072 tx.encode_payload(&mut buf);
5073 let hash = keccak(&buf);
5074
5075 let signing_key = SigningKey::from_bytes(&PRIVATE_KEY.into()).expect("valid key");
5076 let (sig, rid) = signing_key
5077 .sign_prehash_recoverable(&hash.0)
5078 .expect("sign ok");
5079 let sig_bytes = sig.to_bytes();
5080
5081 Transaction::EIP1559Transaction(EIP1559Transaction {
5082 signature_r: U256::from_big_endian(&sig_bytes[..32]),
5083 signature_s: U256::from_big_endian(&sig_bytes[32..]),
5084 signature_y_parity: rid.to_byte() != 0,
5085 ..tx
5086 })
5087 }
5088
5089 const STOP_BYTECODE: &[u8] = &[0x00];
5091
5092 fn make_block_with_tx(tx: Transaction, base_fee: u64) -> Block {
5093 let header = BlockHeader {
5094 parent_hash: H256::zero(),
5095 ommers_hash: *DEFAULT_OMMERS_HASH,
5096 number: 1,
5097 gas_limit: 1_000_000,
5098 timestamp: 1,
5099 base_fee_per_gas: Some(base_fee),
5100 withdrawals_root: Some(H256::zero()),
5101 requests_hash: Some(*DEFAULT_REQUESTS_HASH),
5102 parent_beacon_block_root: None,
5104 ..Default::default()
5105 };
5106 let body = BlockBody {
5107 transactions: vec![tx],
5108 ommers: vec![],
5109 withdrawals: Some(vec![]),
5110 };
5111 Block::new(header, body)
5112 }
5113
5114 fn make_gen_db(chain_config: ChainConfig) -> GeneralizedDatabase {
5115 let sender = sender_address();
5116 let stop = Bytes::from_static(STOP_BYTECODE);
5117 let mock = MockDB::new(chain_config)
5118 .with_account(sender, U256::from(1_000_000_000u64), None)
5120 .with_account(
5122 WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS.address,
5123 U256::zero(),
5124 Some(stop.clone()),
5125 )
5126 .with_account(
5127 CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS.address,
5128 U256::zero(),
5129 Some(stop.clone()),
5130 )
5131 .with_account(
5133 BUILDER_DEPOSIT_CONTRACT_ADDRESS.address,
5134 U256::zero(),
5135 Some(stop.clone()),
5136 )
5137 .with_account(
5138 BUILDER_EXIT_CONTRACT_ADDRESS.address,
5139 U256::zero(),
5140 Some(stop),
5141 );
5142 GeneralizedDatabase::new(Arc::new(mock))
5143 }
5144
5145 const SSTORE_CLEAR_BYTECODE: &[u8] = &[0x60, 0x00, 0x60, 0x00, 0x55, 0x00];
5150
5151 fn signed_call_tx(to: Address, nonce: u64, base_fee: u64, gas_limit: u64) -> Transaction {
5153 use k256::ecdsa::SigningKey;
5154
5155 let tx = EIP1559Transaction {
5156 chain_id: 1,
5157 nonce,
5158 max_priority_fee_per_gas: 1,
5159 max_fee_per_gas: base_fee + 10,
5160 gas_limit,
5161 to: TxKind::Call(to),
5162 value: U256::zero(),
5163 ..Default::default()
5164 };
5165
5166 let mut buf = vec![0x02u8];
5167 tx.encode_payload(&mut buf);
5168 let hash = keccak(&buf);
5169
5170 let signing_key = SigningKey::from_bytes(&PRIVATE_KEY.into()).expect("valid key");
5171 let (sig, rid) = signing_key
5172 .sign_prehash_recoverable(&hash.0)
5173 .expect("sign ok");
5174 let sig_bytes = sig.to_bytes();
5175
5176 Transaction::EIP1559Transaction(EIP1559Transaction {
5177 signature_r: U256::from_big_endian(&sig_bytes[..32]),
5178 signature_s: U256::from_big_endian(&sig_bytes[32..]),
5179 signature_y_parity: rid.to_byte() != 0,
5180 ..tx
5181 })
5182 }
5183
5184 fn make_gen_db_sstore_clear(
5187 chain_config: ChainConfig,
5188 contract_addr: Address,
5189 ) -> GeneralizedDatabase {
5190 let sender = sender_address();
5191 let stop = Bytes::from_static(STOP_BYTECODE);
5192 let mock = MockDB::new(chain_config)
5193 .with_account(sender, U256::from(100_000_000_000u64), None)
5194 .with_account(
5195 WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS.address,
5196 U256::zero(),
5197 Some(stop.clone()),
5198 )
5199 .with_account(
5200 CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS.address,
5201 U256::zero(),
5202 Some(stop.clone()),
5203 )
5204 .with_account(
5206 BUILDER_DEPOSIT_CONTRACT_ADDRESS.address,
5207 U256::zero(),
5208 Some(stop.clone()),
5209 )
5210 .with_account(
5211 BUILDER_EXIT_CONTRACT_ADDRESS.address,
5212 U256::zero(),
5213 Some(stop),
5214 )
5215 .with_account(
5216 contract_addr,
5217 U256::zero(),
5218 Some(Bytes::from_static(SSTORE_CLEAR_BYTECODE)),
5219 )
5220 .with_storage(contract_addr, H256::zero(), U256::one());
5222 GeneralizedDatabase::new(Arc::new(mock))
5223 }
5224
5225 #[test]
5228 fn test_burned_fees_block_aggregate() {
5229 use std::sync::atomic::AtomicUsize;
5230 use std::sync::mpsc;
5231
5232 const BASE_FEE: u64 = 10;
5233 let recipient = Address::from_low_u64_be(0xBEEF);
5241 let tx = signed_eth_transfer(recipient, 0, BASE_FEE);
5242 let block = make_block_with_tx(tx, BASE_FEE);
5243 let crypto = NativeCrypto;
5244
5245 let mut db1 = make_gen_db(lstar_chain_config());
5247 let (result, _bal) = LEVM::execute_block(&block, &mut db1, VMType::L1, &crypto, None)
5248 .expect("execute_block succeeded");
5249 let cumulative = result
5250 .receipts
5251 .last()
5252 .map(|r| r.cumulative_gas_used)
5253 .unwrap_or(0);
5254 let expected = compute_burned_fees(BASE_FEE, cumulative, 0, 0);
5255 assert_eq!(
5256 result.burned_fees,
5257 Some(expected),
5258 "execute_block: burned_fees must equal base_fee * post-refund gas ({cumulative})"
5259 );
5260
5261 let mut db2 = make_gen_db(lstar_chain_config());
5263 let (tx_sender, _rx) = mpsc::channel();
5264 let queue_len = AtomicUsize::new(0);
5265 let tx2 = signed_eth_transfer(recipient, 0, BASE_FEE);
5266 let block2 = make_block_with_tx(tx2, BASE_FEE);
5267 let (result_pipeline, _bal) = LEVM::execute_block_pipeline(
5268 &block2,
5269 &mut db2,
5270 VMType::L1,
5271 Some(tx_sender),
5272 &queue_len,
5273 &crypto,
5274 None, false,
5276 None,
5277 )
5278 .expect("execute_block_pipeline succeeded");
5279 assert_eq!(
5280 result_pipeline.burned_fees, result.burned_fees,
5281 "execute_block_pipeline: burned_fees must match execute_block (production/verification consistency)"
5282 );
5283
5284 let tx3 = signed_eth_transfer(recipient, 0, BASE_FEE);
5286 let block3 = make_block_with_tx(tx3, BASE_FEE);
5287 let mut db3 = make_gen_db(amsterdam_chain_config());
5288 let (result_pre_lstar, _) =
5289 LEVM::execute_block(&block3, &mut db3, VMType::L1, &crypto, None)
5290 .expect("pre-LStar execute_block succeeded");
5291 assert_eq!(
5292 result_pre_lstar.burned_fees, None,
5293 "pre-LStar: burned_fees must be None"
5294 );
5295 }
5296
5297 #[test]
5314 fn test_burned_fees_post_refund_discriminating() {
5315 use std::sync::atomic::AtomicUsize;
5316 use std::sync::mpsc;
5317
5318 const BASE_FEE: u64 = 10;
5319 const TX_GAS_LIMIT: u64 = 100_000;
5321
5322 let contract_addr = Address::from_low_u64_be(0xC0DEBEEF);
5323 let crypto = NativeCrypto;
5324
5325 let tx_a = signed_call_tx(contract_addr, 0, BASE_FEE, TX_GAS_LIMIT);
5327 let block_a = make_block_with_tx(tx_a, BASE_FEE);
5328 let mut db_a = make_gen_db_sstore_clear(lstar_chain_config(), contract_addr);
5329 let (result_a, _) = LEVM::execute_block(&block_a, &mut db_a, VMType::L1, &crypto, None)
5330 .expect("execute_block with SSTORE clear succeeded");
5331
5332 let cumulative_a = result_a.receipts[0].cumulative_gas_used;
5333 assert!(
5335 result_a.block_gas_used > cumulative_a,
5336 "SSTORE refund must cause block_gas_used ({}) > cumulative_gas_used ({})",
5337 result_a.block_gas_used,
5338 cumulative_a,
5339 );
5340 assert_eq!(
5342 result_a.burned_fees,
5343 Some(BASE_FEE * cumulative_a),
5344 "execute_block: burned_fees must use post-refund gas (cumulative={}, block_gas_used={})",
5345 cumulative_a,
5346 result_a.block_gas_used,
5347 );
5348
5349 let tx_b = signed_call_tx(contract_addr, 0, BASE_FEE, TX_GAS_LIMIT);
5351 let block_b = make_block_with_tx(tx_b, BASE_FEE);
5352 let mut db_b = make_gen_db_sstore_clear(lstar_chain_config(), contract_addr);
5353 let (tx_sender_b, _rx_b) = mpsc::channel();
5354 let queue_len_b = AtomicUsize::new(0);
5355 let (result_b, _) = LEVM::execute_block_pipeline(
5356 &block_b,
5357 &mut db_b,
5358 VMType::L1,
5359 Some(tx_sender_b),
5360 &queue_len_b,
5361 &crypto,
5362 None, false,
5364 None,
5365 )
5366 .expect("execute_block_pipeline sequential with SSTORE clear succeeded");
5367
5368 let cumulative_b = result_b.receipts[0].cumulative_gas_used;
5369 assert!(
5370 result_b.block_gas_used > cumulative_b,
5371 "sequential pipeline: SSTORE refund must be present",
5372 );
5373 assert_eq!(
5374 result_b.burned_fees,
5375 Some(BASE_FEE * cumulative_b),
5376 "execute_block_pipeline (sequential): burned_fees must use post-refund gas",
5377 );
5378
5379 let tx_c = signed_call_tx(contract_addr, 0, BASE_FEE, TX_GAS_LIMIT);
5382 let block_c = make_block_with_tx(tx_c, BASE_FEE);
5383 let mut db_c1 = make_gen_db_sstore_clear(lstar_chain_config(), contract_addr);
5384 let (_, bal_c) = LEVM::execute_block(&block_c, &mut db_c1, VMType::L1, &crypto, None)
5385 .expect("BAL generation succeeded");
5386 let bal_c = bal_c.expect("LStar must produce a BAL");
5387
5388 let mut db_c2 = make_gen_db_sstore_clear(lstar_chain_config(), contract_addr);
5389 let (tx_sender_c, _rx_c) = mpsc::channel();
5390 let queue_len_c = AtomicUsize::new(0);
5391 let (result_c, _) = LEVM::execute_block_pipeline(
5392 &block_c,
5393 &mut db_c2,
5394 VMType::L1,
5395 Some(tx_sender_c),
5396 &queue_len_c,
5397 &crypto,
5398 Some(std::sync::Arc::new(bal_c)), true,
5400 None,
5401 )
5402 .expect("execute_block_pipeline parallel with SSTORE clear succeeded");
5403
5404 let cumulative_c = result_c.receipts[0].cumulative_gas_used;
5405 assert!(
5406 result_c.block_gas_used > cumulative_c,
5407 "parallel pipeline: SSTORE refund must be present",
5408 );
5409 assert_eq!(
5410 result_c.burned_fees,
5411 Some(BASE_FEE * cumulative_c),
5412 "execute_block_pipeline (parallel): burned_fees must use post-refund gas",
5413 );
5414
5415 assert_eq!(
5417 result_a.burned_fees, result_b.burned_fees,
5418 "execute_block vs sequential pipeline: burned_fees must match",
5419 );
5420 assert_eq!(
5421 result_a.burned_fees, result_c.burned_fees,
5422 "execute_block vs parallel pipeline: burned_fees must match",
5423 );
5424
5425 let tx_d = signed_call_tx(contract_addr, 0, BASE_FEE, TX_GAS_LIMIT);
5427 let block_d = make_block_with_tx(tx_d, BASE_FEE);
5428 let mut db_d = make_gen_db_sstore_clear(amsterdam_chain_config(), contract_addr);
5429 let (result_d, _) = LEVM::execute_block(&block_d, &mut db_d, VMType::L1, &crypto, None)
5430 .expect("pre-LStar with SSTORE clear succeeded");
5431 assert_eq!(
5432 result_d.burned_fees, None,
5433 "pre-LStar: burned_fees must be None"
5434 );
5435 }
5436}
5437
5438#[cfg(test)]
5439mod simulated_tx_encoding_tests {
5440 use super::*;
5450 use ethrex_common::types::GenericTransaction;
5451 use ethrex_rlp::encode::RLPEncode;
5452
5453 fn signed_equivalent(gas_limit: u64, data: Bytes) -> Transaction {
5456 Transaction::EIP1559Transaction(EIP1559Transaction {
5457 chain_id: 9_999,
5458 nonce: 42,
5459 max_priority_fee_per_gas: 1_000_000_000,
5460 max_fee_per_gas: 2_000_000_000,
5461 gas_limit,
5462 to: TxKind::Create,
5463 value: U256::from(1u64),
5464 data,
5465 access_list: vec![],
5466 signature_y_parity: true,
5467 signature_r: U256::MAX,
5468 signature_s: U256::MAX,
5469 ..Default::default()
5470 })
5471 }
5472
5473 fn generic(gas_limit: u64, data: Bytes) -> GenericTransaction {
5474 GenericTransaction {
5475 to: TxKind::Create,
5476 value: U256::from(1u64),
5477 input: data,
5478 nonce: Some(42),
5479 gas: Some(gas_limit),
5480 max_fee_per_gas: Some(2_000_000_000),
5481 max_priority_fee_per_gas: Some(1_000_000_000),
5482 chain_id: Some(9_999),
5483 ..Default::default()
5484 }
5485 }
5486
5487 #[test]
5488 fn simulated_tx_never_encodes_shorter_than_the_signed_one() {
5489 for (gas_limit, data) in [
5490 (21_000u64, Bytes::new()),
5491 (150_000, Bytes::from_static(&[0x60, 0x00, 0x60, 0x00])),
5492 (5_000_000, Bytes::from(vec![0xab; 4_096])),
5493 ] {
5494 let simulated = generic_tx_to_transaction(&generic(gas_limit, data.clone()))
5495 .expect("conversion should succeed");
5496 let signed = signed_equivalent(gas_limit, data.clone());
5497 assert!(
5498 simulated.length() >= signed.length(),
5499 "simulated tx encodes {} bytes, signed one {} — the L2 would under-reserve \
5500 the L1 fee by the difference (gas_limit {gas_limit}, {} data bytes)",
5501 simulated.length(),
5502 signed.length(),
5503 data.len(),
5504 );
5505 }
5506 }
5507}