1pub mod db;
2mod tracing;
3
4use super::{BlockExecutionResult, FrameValidationOutcome, TxGasBreakdown};
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(all(feature = "rayon", not(feature = "eip-8025")))]
15use ethrex_common::constants::EMPTY_KECCAK_HASH;
16use ethrex_common::types::Code;
17#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
18use ethrex_common::types::TxType;
19use ethrex_common::types::block_access_list::BlockAccessList;
20#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
21use ethrex_common::types::block_access_list::{
22 BalAddressIndex, find_exact_change_balance, find_exact_change_code, find_exact_change_nonce,
23 find_exact_change_storage, has_exact_change_balance, has_exact_change_code,
24 has_exact_change_nonce, has_exact_change_storage,
25};
26use ethrex_common::types::fee_config::FeeConfig;
27use ethrex_common::types::{AuthorizationTuple, EIP7702Transaction};
28#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
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(all(feature = "rayon", not(feature = "eip-8025")))]
39use ethrex_common::{BigEndianHash, validate_block_access_list_size, validate_header_bal_indices};
40use ethrex_crypto::Crypto;
41use ethrex_levm::EVMConfig;
42#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
43use ethrex_levm::account::{AccountStatus, LevmAccount};
44use ethrex_levm::call_frame::Stack;
45use ethrex_levm::constants::{
46 POST_OSAKA_GAS_LIMIT_CAP, STACK_LIMIT, SYS_CALL_GAS_LIMIT, TX_MAX_GAS_LIMIT_AMSTERDAM,
47};
48use ethrex_levm::db::gen_db::GeneralizedDatabase;
49#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
50use ethrex_levm::db::gen_db::{
51 LazyBalCursor, code_from_bal, post_value_at_or_before, seed_one_address_info_from_bal,
52};
53#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
54use ethrex_levm::db::{Database, gen_db::CacheDB};
55use ethrex_levm::errors::{InternalError, TxValidationError};
56use ethrex_levm::memory::Memory;
57#[cfg(feature = "perf_opcode_timings")]
58use ethrex_levm::timings::{OPCODE_TIMINGS, PRECOMPILES_TIMINGS};
59use ethrex_levm::tracing::LevmCallTracer;
60use ethrex_levm::utils::get_base_fee_per_blob_gas;
61use ethrex_levm::validation_observer::FrameSimViolation;
62use ethrex_levm::vm::VMType;
63use ethrex_levm::{
64 Environment,
65 errors::{ExecutionReport, TxResult, VMError},
66 vm::VM,
67};
68#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
69use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
70#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
71use rustc_hash::{FxHashMap, FxHashSet};
72use std::cmp::min;
73use std::sync::Arc;
74#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
75use std::sync::atomic::AtomicBool;
76use std::sync::atomic::{AtomicUsize, Ordering};
77use std::sync::mpsc::Sender;
78
79#[derive(Debug)]
85pub struct LEVM;
86
87fn frame_receipts_from(
91 frame_results: Option<Vec<(u8, u64, Vec<Log>)>>,
92) -> Option<Vec<FrameReceipt>> {
93 frame_results.map(|results| {
94 results
95 .into_iter()
96 .map(|(status, gas_used, logs)| FrameReceipt {
97 status,
98 gas_used,
99 logs,
100 })
101 .collect()
102 })
103}
104
105fn check_gas_limit(
107 block_gas_used: u64,
108 tx_gas_limit: u64,
109 block_gas_limit: u64,
110) -> Result<(), EvmError> {
111 if tx_gas_limit > block_gas_limit.saturating_sub(block_gas_used) {
112 return Err(EvmError::Transaction(format!(
113 "Gas allowance exceeded: \
114 used {block_gas_used} + tx limit {tx_gas_limit} > block limit {block_gas_limit}"
115 )));
116 }
117 Ok(())
118}
119
120pub fn check_2d_gas_allowance(
139 tx: &Transaction,
140 block_gas_used_regular: u64,
141 block_gas_used_state: u64,
142 block_gas_limit: u64,
143) -> Result<(), EvmError> {
144 let tx_gas = tx.gas_limit();
145 let regular_available = block_gas_limit.saturating_sub(block_gas_used_regular);
146 let state_available = block_gas_limit.saturating_sub(block_gas_used_state);
147
148 let regular_contrib = tx_gas.min(TX_MAX_GAS_LIMIT_AMSTERDAM);
153 if regular_contrib > regular_available {
154 return Err(EvmError::Transaction(format!(
155 "Gas allowance exceeded: regular dim worst-case {regular_contrib} > \
156 available {regular_available} (block_gas_used_regular={block_gas_used_regular}, \
157 block_gas_limit={block_gas_limit})"
158 )));
159 }
160
161 let state_contrib = tx_gas;
163 if state_contrib > state_available {
164 return Err(EvmError::Transaction(format!(
165 "Gas allowance exceeded: state dim worst-case {state_contrib} > \
166 available {state_available} (block_gas_used_state={block_gas_used_state}, \
167 block_gas_limit={block_gas_limit})"
168 )));
169 }
170
171 Ok(())
172}
173
174#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
177#[derive(Debug, thiserror::Error)]
178enum BalValidationError {
179 #[error("{0}")]
180 Mismatch(String),
181 #[error("{0}")]
182 Database(String),
183}
184
185impl LEVM {
186 pub fn execute_block(
191 block: &Block,
192 db: &mut GeneralizedDatabase,
193 vm_type: VMType,
194 crypto: &dyn Crypto,
195 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
196 let chain_config = db.store.get_chain_config()?;
197 let is_amsterdam = chain_config.is_amsterdam_activated(block.header.timestamp);
198
199 debug_assert!(
204 block.body.transactions.len() < u32::MAX as usize,
205 "tx count overflows u32 BlockAccessIndex"
206 );
207
208 if is_amsterdam {
210 db.enable_bal_recording();
211 db.set_bal_index(0);
213 }
214
215 Self::prepare_block(block, db, vm_type, crypto)?;
216
217 let evm_config = EVMConfig::new_from_chain_config(&chain_config, &block.header);
221 let chain_id = chain_config.chain_id;
222 let base_blob_fee_per_gas =
223 get_base_fee_per_blob_gas(block.header.excess_blob_gas, &evm_config)?;
224 let mut shared_stack_pool = Vec::with_capacity(STACK_LIMIT);
226 let mut shared_memory_pool = Vec::with_capacity(1);
227
228 let n_txs = block.body.transactions.len();
229 let mut receipts = Vec::with_capacity(n_txs);
230 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(n_txs);
231 let mut cumulative_gas_used = 0_u64;
233 let mut block_gas_used = 0_u64;
235 let mut block_regular_gas_used = 0_u64;
237 let mut block_state_gas_used = 0_u64;
238 let transactions_with_sender =
239 block
240 .body
241 .get_transactions_with_sender(crypto)
242 .map_err(|error| {
243 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
244 })?;
245
246 for (tx_idx, (tx, tx_sender)) in transactions_with_sender.into_iter().enumerate() {
247 if !is_amsterdam {
254 check_gas_limit(cumulative_gas_used, tx.gas_limit(), block.header.gas_limit)?;
255 }
256
257 if is_amsterdam {
259 check_2d_gas_allowance(
260 tx,
261 block_regular_gas_used,
262 block_state_gas_used,
263 block.header.gas_limit,
264 )?;
265 }
266
267 if is_amsterdam {
269 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
270 db.set_bal_index(bal_index);
271
272 if let Some(recorder) = db.bal_recorder_mut() {
276 recorder.record_touched_address(tx_sender);
277 }
278 }
279
280 let report = Self::execute_tx_in_block(
281 tx,
282 tx_sender,
283 &block.header,
284 db,
285 vm_type,
286 base_blob_fee_per_gas,
287 &mut shared_stack_pool,
288 &mut shared_memory_pool,
289 false,
290 crypto,
291 evm_config,
292 chain_id,
293 )?;
294
295 tx_gas_breakdowns.push(TxGasBreakdown::from_report(
296 tx_idx,
297 tx.hash(crypto),
298 &report,
299 ));
300
301 cumulative_gas_used += report.gas_spent;
303
304 let tx_state_gas = report.state_gas_used;
307 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
308 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
309 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
310
311 if is_amsterdam {
312 block_gas_used = block_regular_gas_used.max(block_state_gas_used);
314 ::tracing::debug!(
315 "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}",
316 report.gas_used,
317 report.gas_spent,
318 );
319
320 if block_regular_gas_used > block.header.gas_limit
325 || block_state_gas_used > block.header.gas_limit
326 {
327 return Err(EvmError::Transaction(format!(
328 "Gas allowance exceeded: Block gas used overflow: \
329 block_gas_used {block_gas_used} > block_gas_limit {}",
330 block.header.gas_limit
331 )));
332 }
333 } else {
334 block_gas_used = block_gas_used.saturating_add(report.gas_used);
335 }
336
337 let mut receipt = Receipt::new(
338 tx.tx_type(),
339 matches!(report.result, TxResult::Success),
340 cumulative_gas_used,
341 report.logs,
342 );
343
344 if matches!(tx, Transaction::FrameTransaction(_)) {
346 receipt.payer = report.payer_address;
347 receipt.frame_receipts = frame_receipts_from(report.frame_results);
348 }
349
350 receipts.push(receipt);
351 }
352
353 if is_amsterdam && block_gas_used > block.header.gas_limit {
357 return Err(EvmError::Transaction(format!(
358 "Gas allowance exceeded: Block gas used overflow: \
359 block_gas_used {block_gas_used} > block_gas_limit {}",
360 block.header.gas_limit
361 )));
362 }
363
364 if is_amsterdam {
367 let post_tx_index =
368 u32::try_from(block.body.transactions.len() + 1).unwrap_or(u32::MAX);
369 db.set_bal_index(post_tx_index);
370
371 if let Some(withdrawals) = &block.body.withdrawals
375 && let Some(recorder) = db.bal_recorder_mut()
376 {
377 recorder.extend_touched_addresses(withdrawals.iter().map(|w| w.address));
378 }
379 }
380
381 let requests = match vm_type {
385 VMType::L1 => extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?,
386 VMType::L2(_) => Default::default(),
387 };
388
389 if let Some(withdrawals) = &block.body.withdrawals {
390 Self::process_withdrawals(db, withdrawals)?;
391 }
392
393 let bal = db.take_bal();
395
396 Ok((
397 BlockExecutionResult {
398 receipts,
399 requests,
400 block_gas_used,
401 tx_gas_breakdowns,
402 },
403 bal,
404 ))
405 }
406
407 #[allow(clippy::too_many_arguments)]
411 pub fn execute_block_pipeline(
412 block: &Block,
413 db: &mut GeneralizedDatabase,
414 vm_type: VMType,
415 merkleizer: Option<Sender<Vec<AccountUpdate>>>,
416 queue_length: &AtomicUsize,
417 crypto: &dyn Crypto,
418 header_bal: Option<Arc<BlockAccessList>>,
419 bal_parallel_exec_enabled: bool,
420 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
421 let chain_config = db.store.get_chain_config()?;
422 let is_amsterdam = chain_config.is_amsterdam_activated(block.header.timestamp);
423 let evm_config = EVMConfig::new_from_chain_config(&chain_config, &block.header);
426 let chain_id = chain_config.chain_id;
427
428 debug_assert!(
430 block.body.transactions.len() < u32::MAX as usize,
431 "tx count overflows u32 BlockAccessIndex"
432 );
433
434 let transactions_with_sender =
435 block
436 .body
437 .get_transactions_with_sender(crypto)
438 .map_err(|error| {
439 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
440 })?;
441
442 #[cfg(any(feature = "eip-8025", not(feature = "rayon")))]
443 let _ = (header_bal, bal_parallel_exec_enabled);
446 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
447 if let Some(bal) = header_bal
453 && is_amsterdam
454 && bal_parallel_exec_enabled
455 {
456 validate_header_bal_indices(&bal, block.body.transactions.len())
462 .map_err(|e| EvmError::Custom(e.to_string()))?;
463
464 Self::prepare_block(block, db, vm_type, crypto)?;
467
468 let validation_index = Arc::new(bal.build_validation_index());
470
471 LEVM::get_state_transitions_tx(db)?;
473 let system_seed = Arc::new(std::mem::take(&mut db.initial_accounts_state));
474
475 let parallel_result = Self::execute_block_parallel(
476 block,
477 &transactions_with_sender,
478 db,
479 vm_type,
480 Arc::clone(&bal),
481 merkleizer.as_ref(),
482 queue_length,
483 system_seed,
484 crypto,
485 Arc::clone(&validation_index),
486 );
487
488 let (
493 receipts,
494 block_gas_used,
495 mut unread_storage_reads,
496 mut unaccessed_pure_accounts,
497 tx_gas_breakdowns,
498 ) = match parallel_result {
499 Ok(result) => result,
500 Err(parallel_err) => {
501 let last_tx_idx =
502 u32::try_from(block.body.transactions.len()).unwrap_or(u32::MAX);
503 if Self::seed_db_from_bal(
504 db,
505 &bal,
506 last_tx_idx,
507 &validation_index.accounts_by_min_index,
508 )
509 .is_ok()
510 && let VMType::L1 = vm_type
511 && let Err(e @ EvmError::SystemContractCallFailed(_)) =
512 extract_all_requests_levm(&[], db, &block.header, vm_type, crypto)
513 {
514 return Err(e);
515 }
516 return Err(parallel_err);
517 }
518 };
519
520 let last_tx_idx = u32::try_from(block.body.transactions.len()).unwrap_or(u32::MAX);
525 Self::seed_db_from_bal(
527 db,
528 &bal,
529 last_tx_idx,
530 &validation_index.accounts_by_min_index,
531 )?;
532
533 let requests = match vm_type {
535 VMType::L1 => {
536 extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?
537 }
538 VMType::L2(_) => Default::default(),
539 };
540
541 if let Some(withdrawals) = &block.body.withdrawals {
542 Self::process_withdrawals(db, withdrawals)?;
543 }
544 let withdrawal_idx = u32::try_from(block.body.transactions.len())
552 .map(|n| n.saturating_add(1))
553 .unwrap_or(u32::MAX);
554 Self::validate_bal_withdrawal_index(db, &bal, withdrawal_idx, &validation_index)?;
555
556 if !unread_storage_reads.is_empty() {
558 for (addr, acct) in &db.current_accounts_state {
559 for key in acct.storage.keys() {
560 unread_storage_reads.remove(&(*addr, *key));
561 }
562 }
563 }
564
565 if !unaccessed_pure_accounts.is_empty() {
570 if let Some(withdrawals) = &block.body.withdrawals {
571 for w in withdrawals {
572 unaccessed_pure_accounts.remove(&w.address);
573 }
574 }
575 for addr in db.current_accounts_state.keys() {
576 if *addr == SYSTEM_ADDRESS {
580 continue;
581 }
582 unaccessed_pure_accounts.remove(addr);
583 }
584 }
585
586 if let Some((addr, key)) = unread_storage_reads.iter().next() {
588 let slot = ethrex_common::BigEndianHash::into_uint(key);
589 return Err(EvmError::Custom(format!(
590 "BAL validation failed: storage_read for account {addr:?} slot \
591 {slot} was never actually read during block execution"
592 )));
593 }
594
595 if let Some(addr) = unaccessed_pure_accounts.iter().next() {
597 return Err(EvmError::Custom(format!(
598 "BAL validation failed: account {addr:?} has no mutations \
599 and no storage reads but was never accessed during block execution"
600 )));
601 }
602
603 validate_block_access_list_size(&block.header, &chain_config, &bal)
606 .map_err(|e| EvmError::Custom(e.to_string()))?;
607
608 return Ok((
609 BlockExecutionResult {
610 receipts,
611 requests,
612 block_gas_used,
613 tx_gas_breakdowns,
614 },
615 None,
616 ));
617 }
618
619 let Some(merkleizer) = merkleizer else {
625 return Err(EvmError::Custom(
626 "sequential execution path called without a merkleizer Sender".to_string(),
627 ));
628 };
629 if is_amsterdam {
630 db.enable_bal_recording();
631 db.set_bal_index(0);
633 }
634
635 Self::prepare_block(block, db, vm_type, crypto)?;
636
637 let base_blob_fee_per_gas =
639 get_base_fee_per_blob_gas(block.header.excess_blob_gas, &evm_config)?;
640
641 let mut shared_stack_pool = Vec::with_capacity(STACK_LIMIT);
642 let mut shared_memory_pool = Vec::with_capacity(1);
644
645 let n_txs = block.body.transactions.len();
646 let mut receipts = Vec::with_capacity(n_txs);
647 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(n_txs);
648 let mut cumulative_gas_used = 0_u64;
650 let mut block_gas_used = 0_u64;
652 let mut block_regular_gas_used = 0_u64;
654 let mut block_state_gas_used = 0_u64;
655 let mut tx_since_last_flush = 2;
658
659 for (tx_idx, (tx, tx_sender)) in transactions_with_sender.into_iter().enumerate() {
660 if !is_amsterdam {
667 check_gas_limit(cumulative_gas_used, tx.gas_limit(), block.header.gas_limit)?;
668 }
669
670 if is_amsterdam {
672 check_2d_gas_allowance(
673 tx,
674 block_regular_gas_used,
675 block_state_gas_used,
676 block.header.gas_limit,
677 )?;
678 }
679
680 if is_amsterdam {
682 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
683 db.set_bal_index(bal_index);
684
685 if let Some(recorder) = db.bal_recorder_mut() {
689 recorder.record_touched_address(tx_sender);
690 }
691 }
692
693 let report = Self::execute_tx_in_block(
694 tx,
695 tx_sender,
696 &block.header,
697 db,
698 vm_type,
699 base_blob_fee_per_gas,
700 &mut shared_stack_pool,
701 &mut shared_memory_pool,
702 false,
703 crypto,
704 evm_config,
705 chain_id,
706 )?;
707
708 tx_gas_breakdowns.push(TxGasBreakdown::from_report(
709 tx_idx,
710 tx.hash(crypto),
711 &report,
712 ));
713
714 if queue_length.load(Ordering::Relaxed) == 0 && tx_since_last_flush > 5 {
715 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
716 tx_since_last_flush = 0;
717 } else {
718 tx_since_last_flush += 1;
719 }
720
721 cumulative_gas_used += report.gas_spent;
723
724 let tx_state_gas = report.state_gas_used;
727 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
728 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
729 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
730
731 if is_amsterdam {
732 block_gas_used = block_regular_gas_used.max(block_state_gas_used);
734
735 if block_regular_gas_used > block.header.gas_limit
740 || block_state_gas_used > block.header.gas_limit
741 {
742 return Err(EvmError::Transaction(format!(
743 "Gas allowance exceeded: Block gas used overflow: \
744 block_gas_used {block_gas_used} > block_gas_limit {}",
745 block.header.gas_limit
746 )));
747 }
748 } else {
749 block_gas_used = block_gas_used.saturating_add(report.gas_used);
750 }
751
752 let mut receipt = Receipt::new(
753 tx.tx_type(),
754 matches!(report.result, TxResult::Success),
755 cumulative_gas_used,
756 report.logs,
757 );
758
759 if matches!(tx, Transaction::FrameTransaction(_)) {
761 receipt.payer = report.payer_address;
762 receipt.frame_receipts = frame_receipts_from(report.frame_results);
763 }
764
765 receipts.push(receipt);
766 }
767
768 if is_amsterdam && block_gas_used > block.header.gas_limit {
772 return Err(EvmError::Transaction(format!(
773 "Gas allowance exceeded: Block gas used overflow: \
774 block_gas_used {block_gas_used} > block_gas_limit {}",
775 block.header.gas_limit
776 )));
777 }
778
779 #[cfg(feature = "perf_opcode_timings")]
780 {
781 let mut timings = OPCODE_TIMINGS.lock().expect("poison");
782 timings.inc_tx_count(receipts.len());
783 timings.inc_block_count();
784 ::tracing::info!("{}", timings.info_pretty());
785 let precompiles_timings = PRECOMPILES_TIMINGS.lock().expect("poison");
786 ::tracing::info!("{}", precompiles_timings.info_pretty());
787 }
788
789 if queue_length.load(Ordering::Relaxed) == 0 {
790 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
791 }
792
793 if is_amsterdam {
796 let post_tx_index =
797 u32::try_from(block.body.transactions.len() + 1).unwrap_or(u32::MAX);
798 db.set_bal_index(post_tx_index);
799
800 if let Some(withdrawals) = &block.body.withdrawals
802 && let Some(recorder) = db.bal_recorder_mut()
803 {
804 recorder.extend_touched_addresses(withdrawals.iter().map(|w| w.address));
805 }
806 }
807
808 let requests = match vm_type {
812 VMType::L1 => extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?,
813 VMType::L2(_) => Default::default(),
814 };
815
816 if let Some(withdrawals) = &block.body.withdrawals {
817 Self::process_withdrawals(db, withdrawals)?;
818 }
819 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
820
821 let bal = db.take_bal();
823
824 Ok((
825 BlockExecutionResult {
826 receipts,
827 requests,
828 block_gas_used,
829 tx_gas_breakdowns,
830 },
831 bal,
832 ))
833 }
834
835 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
840 fn bal_to_account_updates(
841 bal: &BlockAccessList,
842 store: &dyn Database,
843 ) -> Result<Vec<AccountUpdate>, EvmError> {
844 use ethrex_common::types::AccountInfo;
845
846 let mut updates = Vec::new();
847
848 let write_addrs: Vec<Address> = bal
850 .accounts()
851 .iter()
852 .filter(|ac| {
853 !ac.balance_changes.is_empty()
854 || !ac.nonce_changes.is_empty()
855 || !ac.code_changes.is_empty()
856 || !ac.storage_changes.is_empty()
857 })
858 .map(|ac| ac.address)
859 .collect();
860 store
861 .prefetch_accounts(&write_addrs)
862 .map_err(|e| EvmError::Custom(format!("bal_to_account_updates prefetch: {e}")))?;
863
864 for acct_changes in bal.accounts() {
865 let addr = acct_changes.address;
866
867 let has_writes = !acct_changes.balance_changes.is_empty()
869 || !acct_changes.nonce_changes.is_empty()
870 || !acct_changes.code_changes.is_empty()
871 || !acct_changes.storage_changes.is_empty();
872 if !has_writes {
873 continue;
874 }
875
876 let prestate = store
878 .get_account_state(addr)
879 .map_err(|e| EvmError::Custom(format!("bal_to_account_updates: {e}")))?;
880
881 let balance = acct_changes
883 .balance_changes
884 .last()
885 .map(|c| c.post_balance)
886 .unwrap_or(prestate.balance);
887
888 let nonce = acct_changes
890 .nonce_changes
891 .last()
892 .map(|c| c.post_nonce)
893 .unwrap_or(prestate.nonce);
894
895 let (code_hash, code) = if let Some(c) = acct_changes.code_changes.last() {
897 code_from_bal(&c.new_code)
898 } else {
899 (prestate.code_hash, None)
900 };
901
902 let mut added_storage = FxHashMap::with_capacity_and_hasher(
904 acct_changes.storage_changes.len(),
905 Default::default(),
906 );
907 for slot_change in &acct_changes.storage_changes {
908 if let Some(last) = slot_change.slot_changes.last() {
909 let key = ethrex_common::utils::u256_to_h256(slot_change.slot);
910 added_storage.insert(key, last.post_value);
911 }
912 }
913
914 let post_empty = balance.is_zero() && nonce == 0 && code_hash == *EMPTY_KECCAK_HASH;
916 let pre_empty = prestate.balance.is_zero()
917 && prestate.nonce == 0
918 && prestate.code_hash == *EMPTY_KECCAK_HASH;
919 let removed = post_empty && !pre_empty;
920
921 let balance_changed = acct_changes
922 .balance_changes
923 .last()
924 .is_some_and(|c| c.post_balance != prestate.balance);
925 let nonce_changed = acct_changes
926 .nonce_changes
927 .last()
928 .is_some_and(|c| c.post_nonce != prestate.nonce);
929 let code_changed = acct_changes.code_changes.last().is_some();
930 let acc_info_updated = balance_changed || nonce_changed || code_changed;
931
932 if !removed && !acc_info_updated && added_storage.is_empty() {
933 continue;
934 }
935
936 let info = if acc_info_updated {
937 Some(AccountInfo {
938 code_hash,
939 balance,
940 nonce,
941 })
942 } else {
943 None
944 };
945
946 let update = AccountUpdate {
947 address: addr,
948 removed,
949 info,
950 code,
951 added_storage,
952 removed_storage: false,
959 };
960 updates.push(update);
961 }
962
963 Ok(updates)
964 }
965
966 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
983 fn seed_db_from_bal(
984 db: &mut GeneralizedDatabase,
985 bal: &BlockAccessList,
986 max_idx: u32,
987 accounts_by_min_index: &[(u32, usize)],
988 ) -> Result<(), EvmError> {
989 let end = accounts_by_min_index.partition_point(|(min_idx, _)| *min_idx <= max_idx);
990 let bal_accounts = bal.accounts();
991 for &(_, acct_idx) in &accounts_by_min_index[..end] {
992 seed_one_address_info_from_bal(db, bal, acct_idx, max_idx)
993 .map_err(|e| EvmError::Custom(format!("seed_db_from_bal: {e}")))?;
994
995 let acct_changes = &bal_accounts[acct_idx];
996 if acct_changes.storage_changes.is_empty() {
997 continue;
998 }
999 let any_storage = acct_changes.storage_changes.iter().any(|sc| {
1000 sc.slot_changes
1001 .first()
1002 .is_some_and(|c| c.block_access_index <= max_idx)
1003 });
1004 if !any_storage {
1005 continue;
1006 }
1007 let addr = acct_changes.address;
1008 if !db.current_accounts_state.contains_key(&addr) {
1009 db.get_account(addr)
1010 .map_err(|e| EvmError::Custom(format!("seed storage: {e}")))?;
1011 }
1012 let acc = db
1013 .get_account_mut(addr)
1014 .map_err(|e| EvmError::Custom(format!("seed storage mut: {e}")))?;
1015 for sc in &acct_changes.storage_changes {
1016 if let Some(value) = post_value_at_or_before(sc, max_idx) {
1017 acc.storage
1018 .insert(ethrex_common::utils::u256_to_h256(sc.slot), value);
1019 }
1020 }
1021 }
1022 Ok(())
1023 }
1024
1025 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1032 #[allow(clippy::too_many_arguments, clippy::type_complexity)]
1033 fn execute_block_parallel(
1034 block: &Block,
1035 txs_with_sender: &[(&Transaction, Address)],
1036 db: &mut GeneralizedDatabase,
1037 vm_type: VMType,
1038 bal: Arc<BlockAccessList>,
1039 merkleizer: Option<&Sender<Vec<AccountUpdate>>>,
1040 queue_length: &AtomicUsize,
1041 system_seed: Arc<CacheDB>,
1042 crypto: &dyn Crypto,
1043 validation_index: Arc<BalAddressIndex>,
1044 ) -> Result<
1045 (
1046 Vec<Receipt>,
1047 u64,
1048 FxHashSet<(Address, H256)>,
1049 FxHashSet<Address>,
1050 Vec<TxGasBreakdown>,
1051 ),
1052 EvmError,
1053 > {
1054 let store = db.store.clone();
1055 let header = &block.header;
1056 let n_txs = txs_with_sender.len();
1057 let chain_config = store.get_chain_config()?;
1062 let is_amsterdam = chain_config.is_amsterdam_activated(header.timestamp);
1063 let evm_config = EVMConfig::new_from_chain_config(&chain_config, header);
1066 let chain_id = chain_config.chain_id;
1067 let base_blob_fee_per_gas = get_base_fee_per_blob_gas(header.excess_blob_gas, &evm_config)?;
1069 debug_assert!(
1070 is_amsterdam,
1071 "execute_block_parallel invoked on non-Amsterdam block"
1072 );
1073
1074 if let Some(merkleizer) = merkleizer {
1078 let account_updates = Self::bal_to_account_updates(&bal, store.as_ref())?;
1079 merkleizer
1080 .send(account_updates)
1081 .map_err(|e| EvmError::Custom(format!("merkleizer send failed: {e}")))?;
1082 queue_length.fetch_add(1, Ordering::Relaxed);
1083 }
1084
1085 let mut unread_storage_reads: FxHashSet<(Address, H256)> = FxHashSet::default();
1088 let mut unaccessed_pure_accounts: FxHashSet<Address> = FxHashSet::default();
1091 for acct in bal.accounts() {
1092 for &slot in &acct.storage_reads {
1093 let key = ethrex_common::utils::u256_to_h256(slot);
1094 unread_storage_reads.insert((acct.address, key));
1095 }
1096 let is_pure = acct.storage_changes.is_empty()
1097 && acct.storage_reads.is_empty()
1098 && acct.balance_changes.is_empty()
1099 && acct.nonce_changes.is_empty()
1100 && acct.code_changes.is_empty();
1101 if is_pure {
1102 unaccessed_pure_accounts.insert(acct.address);
1103 }
1104 }
1105
1106 for addr in system_seed.keys() {
1112 if *addr == SYSTEM_ADDRESS {
1113 continue;
1114 }
1115 unaccessed_pure_accounts.remove(addr);
1116 }
1117
1118 unread_storage_reads.retain(|(addr, key)| {
1120 !system_seed
1121 .get(addr)
1122 .is_some_and(|a| a.storage.contains_key(key))
1123 });
1124
1125 let arc_bal = bal;
1127 let arc_idx = validation_index;
1128
1129 type TxExecResult = (
1143 usize,
1144 TxType,
1145 ExecutionReport,
1146 FxHashSet<Address>, Vec<(Address, H256)>, Vec<Address>, Option<EvmError>, );
1151
1152 let exec_results: Result<Vec<TxExecResult>, EvmError> = (0..n_txs)
1153 .into_par_iter()
1154 .map(|tx_idx| -> Result<_, EvmError> {
1155 let (tx, sender) = &txs_with_sender[tx_idx];
1156 let mut tx_db = GeneralizedDatabase::new_with_shared_base_and_capacity(
1158 store.clone(),
1159 system_seed.clone(),
1160 32,
1161 );
1162 tx_db.lazy_bal = Some(LazyBalCursor {
1163 bal: arc_bal.clone(),
1164 bal_index: u32::try_from(tx_idx + 1).unwrap_or(u32::MAX),
1165 index: arc_idx.clone(),
1166 });
1167 let mut stack_pool = Vec::with_capacity(8);
1170 let mut memory_pool = Vec::with_capacity(1);
1172
1173 tx_db.accessed_accounts =
1177 Some(FxHashSet::with_capacity_and_hasher(16, Default::default()));
1178
1179 tx_db.enable_bal_recording();
1184 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
1185 tx_db.set_bal_index(bal_index);
1186 if let Some(recorder) = tx_db.bal_recorder_mut() {
1190 recorder.record_touched_address(*sender);
1191 }
1192
1193 let report = LEVM::execute_tx_in_block(
1194 tx,
1195 *sender,
1196 header,
1197 &mut tx_db,
1198 vm_type,
1199 base_blob_fee_per_gas,
1200 &mut stack_pool,
1201 &mut memory_pool,
1202 false,
1203 crypto,
1204 evm_config,
1205 chain_id,
1206 )?;
1207
1208 let current_state = std::mem::take(&mut tx_db.current_accounts_state);
1209 let codes = std::mem::take(&mut tx_db.codes);
1210 let mut tracked = tx_db.accessed_accounts.take().unwrap_or_default();
1211 let (shadow_touched, shadow_reads) = tx_db
1212 .bal_recorder
1213 .take()
1214 .map(|mut r| (r.take_touched_addresses(), r.take_storage_reads()))
1215 .unwrap_or_default();
1216
1217 let mut reads_satisfied: Vec<(Address, H256)> =
1225 Vec::with_capacity(current_state.len() * 4);
1226 let mut destroyed: Vec<Address> = Vec::new();
1230 for (addr, acct) in ¤t_state {
1231 if matches!(
1232 acct.status,
1233 AccountStatus::Destroyed | AccountStatus::DestroyedModified
1234 ) {
1235 destroyed.push(*addr);
1236 } else {
1237 for key in acct.storage.keys() {
1238 reads_satisfied.push((*addr, *key));
1239 }
1240 }
1241 }
1242
1243 let deferred_bal_err: Option<EvmError> = (|| -> Result<(), EvmError> {
1249 let bal_idx = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
1250 let seed_idx = u32::try_from(tx_idx).unwrap_or(u32::MAX);
1251 Self::validate_tx_execution(
1252 bal_idx,
1253 seed_idx,
1254 ¤t_state,
1255 &codes,
1256 &arc_bal,
1257 &arc_idx,
1258 &system_seed,
1259 &store,
1260 )
1261 .map_err(|e| {
1262 EvmError::Custom(format!("BAL validation failed for tx {tx_idx}: {e}"))
1263 })?;
1264
1265 for addr in &shadow_touched {
1267 if !arc_idx.addr_to_idx.contains_key(addr) {
1268 return Err(EvmError::Custom(format!(
1269 "BAL validation failed for tx {tx_idx}: account {addr:?} was \
1270 accessed during execution but is missing from BAL"
1271 )));
1272 }
1273 }
1274 for (addr, slot) in &shadow_reads {
1275 let Some(&bal_acct_idx) = arc_idx.addr_to_idx.get(addr) else {
1276 continue;
1278 };
1279 let acct = &arc_bal.accounts()[bal_acct_idx];
1280 let in_changes = acct
1281 .storage_changes
1282 .binary_search_by(|sc| sc.slot.cmp(slot))
1283 .is_ok();
1284 let in_reads = acct.storage_reads.contains(slot);
1285 if !in_changes && !in_reads {
1286 return Err(EvmError::Custom(format!(
1287 "BAL validation failed for tx {tx_idx}: storage slot {slot} of \
1288 account {addr:?} was read during execution but is missing from \
1289 BAL (no storage_changes or storage_reads entry)"
1290 )));
1291 }
1292 }
1293 Ok(())
1294 })()
1295 .err();
1296
1297 drop(current_state);
1298 drop(codes);
1299
1300 tracked.extend(shadow_touched.iter().copied());
1309
1310 Ok((
1311 tx_idx,
1312 tx.tx_type(),
1313 report,
1314 tracked,
1315 reads_satisfied,
1316 destroyed,
1317 deferred_bal_err,
1318 ))
1319 })
1320 .collect();
1321
1322 let mut exec_results = exec_results?;
1323
1324 exec_results.sort_unstable_by_key(|(idx, _, _, _, _, _, _)| *idx);
1331
1332 let mut block_regular_gas_used = 0_u64;
1337 let mut block_state_gas_used = 0_u64;
1338 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(exec_results.len());
1339 for (tx_idx, _, report, _, _, _, _) in &exec_results {
1340 let (tx, _tx_sender) = txs_with_sender
1341 .get(*tx_idx)
1342 .ok_or_else(|| EvmError::Custom(format!("tx index {tx_idx} out of bounds")))?;
1343 if is_amsterdam {
1344 check_2d_gas_allowance(
1345 tx,
1346 block_regular_gas_used,
1347 block_state_gas_used,
1348 header.gas_limit,
1349 )?;
1350 }
1351
1352 tx_gas_breakdowns.push(TxGasBreakdown::from_report(
1353 *tx_idx,
1354 tx.hash(crypto),
1355 report,
1356 ));
1357
1358 let tx_state_gas = report.state_gas_used;
1359 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
1360 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
1361 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
1362 }
1363 let block_gas_used = block_regular_gas_used.max(block_state_gas_used);
1364 if block_gas_used > header.gas_limit {
1366 return Err(EvmError::Transaction(format!(
1367 "Gas allowance exceeded: Block gas used overflow: \
1368 block_gas_used {block_gas_used} > block_gas_limit {}",
1369 header.gas_limit
1370 )));
1371 }
1372
1373 for (_, _, _, _, _, _, deferred) in &mut exec_results {
1376 if let Some(err) = deferred.take() {
1377 return Err(err);
1378 }
1379 }
1380
1381 for (_, _, _, tracked_accounts, reads_satisfied, destroyed, _) in &exec_results {
1384 if !unread_storage_reads.is_empty() {
1385 for addr in destroyed {
1386 unread_storage_reads.retain(|&(a, _)| a != *addr);
1387 }
1388 for pair in reads_satisfied {
1389 unread_storage_reads.remove(pair);
1390 }
1391 }
1392 if !unaccessed_pure_accounts.is_empty() {
1396 unaccessed_pure_accounts.remove(&header.coinbase);
1397 for addr in tracked_accounts {
1398 unaccessed_pure_accounts.remove(addr);
1399 }
1400 }
1401 }
1402
1403 let mut receipts = Vec::with_capacity(n_txs);
1405 let mut cumulative_gas_used = 0_u64;
1406 for (_, tx_type, report, _, _, _, _) in exec_results {
1407 cumulative_gas_used += report.gas_spent;
1408 let mut receipt = Receipt::new(
1409 tx_type,
1410 matches!(report.result, TxResult::Success),
1411 cumulative_gas_used,
1412 report.logs,
1413 );
1414 if tx_type == TxType::Frame {
1415 receipt.payer = report.payer_address;
1416 receipt.frame_receipts = frame_receipts_from(report.frame_results);
1417 }
1418 receipts.push(receipt);
1419 }
1420
1421 Ok((
1422 receipts,
1423 block_gas_used,
1424 unread_storage_reads,
1425 unaccessed_pure_accounts,
1426 tx_gas_breakdowns,
1427 ))
1428 }
1429
1430 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1433 fn seeded_balance(
1434 seed_idx: u32,
1435 acct: ðrex_common::types::block_access_list::AccountChanges,
1436 system_seed: &CacheDB,
1437 store: &Arc<dyn Database>,
1438 ) -> Result<U256, BalValidationError> {
1439 let pos = acct
1440 .balance_changes
1441 .partition_point(|c| c.block_access_index <= seed_idx);
1442 if pos > 0 {
1443 Ok(acct.balance_changes[pos - 1].post_balance)
1444 } else if let Some(a) = system_seed.get(&acct.address) {
1445 Ok(a.info.balance)
1446 } else {
1447 store
1448 .get_account_state(acct.address)
1449 .map(|a| a.balance)
1450 .map_err(|e| {
1451 BalValidationError::Database(format!(
1452 "DB error reading balance for {:?}: {e}",
1453 acct.address
1454 ))
1455 })
1456 }
1457 }
1458
1459 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1462 fn seeded_nonce(
1463 seed_idx: u32,
1464 acct: ðrex_common::types::block_access_list::AccountChanges,
1465 system_seed: &CacheDB,
1466 store: &Arc<dyn Database>,
1467 ) -> Result<u64, BalValidationError> {
1468 let pos = acct
1469 .nonce_changes
1470 .partition_point(|c| c.block_access_index <= seed_idx);
1471 if pos > 0 {
1472 Ok(acct.nonce_changes[pos - 1].post_nonce)
1473 } else if let Some(a) = system_seed.get(&acct.address) {
1474 Ok(a.info.nonce)
1475 } else {
1476 store
1477 .get_account_state(acct.address)
1478 .map(|a| a.nonce)
1479 .map_err(|e| {
1480 BalValidationError::Database(format!(
1481 "DB error reading nonce for {:?}: {e}",
1482 acct.address
1483 ))
1484 })
1485 }
1486 }
1487
1488 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1504 #[allow(clippy::too_many_arguments)]
1505 fn validate_tx_execution(
1506 bal_idx: u32,
1507 seed_idx: u32,
1508 current_state: &FxHashMap<Address, LevmAccount>,
1509 codes: &FxHashMap<H256, Code>,
1510 bal: &BlockAccessList,
1511 index: &BalAddressIndex,
1512 system_seed: &CacheDB,
1513 store: &Arc<dyn Database>,
1514 ) -> Result<(), BalValidationError> {
1515 if let Some(active_accounts) = index.tx_to_accounts.get(&bal_idx) {
1518 for &acct_inner_idx in active_accounts {
1519 let acct = &bal.accounts()[acct_inner_idx];
1520 let addr = acct.address;
1521 let actual = current_state.get(&addr);
1522
1523 if let Some(expected) = find_exact_change_balance(&acct.balance_changes, bal_idx) {
1525 match actual {
1526 Some(a) if a.info.balance == expected => {}
1527 Some(a) => {
1528 return Err(BalValidationError::Mismatch(format!(
1529 "account {addr:?} balance mismatch at index {bal_idx}: BAL={expected}, exec={} (diff={})",
1530 a.info.balance,
1531 describe_balance_diff(expected, a.info.balance),
1532 )));
1533 }
1534 None => {
1535 let seeded = Self::seeded_balance(seed_idx, acct, system_seed, store)?;
1540 if expected != seeded {
1541 let all_bal_indices: Vec<u32> = acct
1543 .balance_changes
1544 .iter()
1545 .map(|c| c.block_access_index)
1546 .collect();
1547 let all_nonce_indices: Vec<u32> = acct
1548 .nonce_changes
1549 .iter()
1550 .map(|c| c.block_access_index)
1551 .collect();
1552 let all_storage_indices: Vec<(u32, u64)> = acct
1553 .storage_changes
1554 .iter()
1555 .flat_map(|sc| {
1556 sc.slot_changes
1557 .iter()
1558 .map(|c| (c.block_access_index, sc.slot.low_u64()))
1559 })
1560 .collect();
1561 let code_indices: Vec<u32> = acct
1562 .code_changes
1563 .iter()
1564 .map(|c| c.block_access_index)
1565 .collect();
1566 return Err(BalValidationError::Mismatch(format!(
1567 "account {addr:?} has BAL balance change at {bal_idx} \
1568 but not in execution state (expected={expected}, pre={seeded}, \
1569 all_bal_idx={all_bal_indices:?}, nonce_idx={all_nonce_indices:?}, \
1570 storage_idx={all_storage_indices:?}, code_idx={code_indices:?})"
1571 )));
1572 }
1573 }
1574 }
1575 }
1576
1577 if let Some(expected) = find_exact_change_nonce(&acct.nonce_changes, bal_idx) {
1579 match actual {
1580 Some(a) if a.info.nonce == expected => {}
1581 Some(a) => {
1582 return Err(BalValidationError::Mismatch(format!(
1583 "account {addr:?} nonce mismatch at index {bal_idx}: BAL={expected}, exec={}",
1584 a.info.nonce
1585 )));
1586 }
1587 None => {
1588 let seeded = Self::seeded_nonce(seed_idx, acct, system_seed, store)?;
1589 if expected != seeded {
1590 return Err(BalValidationError::Mismatch(format!(
1591 "account {addr:?} has BAL nonce change at {bal_idx} \
1592 but not in execution state (expected={expected}, pre={seeded})"
1593 )));
1594 }
1595 }
1596 }
1597 }
1598
1599 if let Some(expected_code) = find_exact_change_code(&acct.code_changes, bal_idx) {
1601 match actual {
1602 Some(a) => {
1603 let actual_code = if let Some(c) = codes.get(&a.info.code_hash) {
1604 c.code_bytes()
1605 } else {
1606 let c = store.get_account_code(a.info.code_hash).map_err(|e| {
1607 BalValidationError::Database(format!(
1608 "DB error reading account code for {addr:?}: {e}"
1609 ))
1610 })?;
1611 c.code_bytes()
1612 };
1613 if actual_code != *expected_code {
1614 return Err(BalValidationError::Mismatch(format!(
1615 "account {addr:?} code mismatch at index {bal_idx}"
1616 )));
1617 }
1618 }
1619 None => {
1620 let code_hash = if let Some(a) = system_seed.get(&addr) {
1624 a.info.code_hash
1625 } else {
1626 store
1627 .get_account_state(addr)
1628 .map(|a| a.code_hash)
1629 .map_err(|e| {
1630 BalValidationError::Database(format!(
1631 "DB error reading account state for {addr:?}: {e}"
1632 ))
1633 })?
1634 };
1635 let pre_code = if let Some(c) = codes.get(&code_hash) {
1636 c.code_bytes()
1637 } else {
1638 let c = store.get_account_code(code_hash).map_err(|e| {
1639 BalValidationError::Database(format!(
1640 "DB error reading account code for hash \
1641 {code_hash:?}: {e}"
1642 ))
1643 })?;
1644 c.code_bytes()
1645 };
1646 if *expected_code != pre_code {
1647 return Err(BalValidationError::Mismatch(format!(
1648 "account {addr:?} has BAL code change at {bal_idx} \
1649 but not in execution state"
1650 )));
1651 }
1652 }
1653 }
1654 }
1655
1656 for sc in &acct.storage_changes {
1658 if let Some(expected_value) =
1659 find_exact_change_storage(&sc.slot_changes, bal_idx)
1660 {
1661 let key = ethrex_common::utils::u256_to_h256(sc.slot);
1662 let actual_value = actual.and_then(|a| a.storage.get(&key)).copied();
1663 if actual_value != Some(expected_value) {
1664 if actual.is_none() || actual_value.is_none() {
1666 let pre_value =
1667 store.get_storage_value(addr, key).map_err(|e| {
1668 BalValidationError::Database(format!(
1669 "DB error reading storage for {addr:?} slot {}: {e}",
1670 sc.slot
1671 ))
1672 })?;
1673 if expected_value == pre_value {
1674 continue; }
1676 }
1677 return Err(BalValidationError::Mismatch(format!(
1678 "account {addr:?} storage slot {} mismatch at index {bal_idx}: \
1679 BAL={expected_value}, exec={actual_value:?}",
1680 sc.slot
1681 )));
1682 }
1683 }
1684 }
1685 }
1686 }
1687
1688 for (addr, account) in current_state {
1691 if account.is_unmodified() {
1692 continue;
1693 }
1694
1695 let Some(&bal_acct_idx) = index.addr_to_idx.get(addr) else {
1696 let pre = system_seed
1700 .get(addr)
1701 .map(|a| (a.info.balance, a.info.nonce, a.info.code_hash))
1702 .or_else(|| {
1703 store
1704 .get_account_state(*addr)
1705 .ok()
1706 .map(|a| (a.balance, a.nonce, a.code_hash))
1707 })
1708 .unwrap_or_default();
1709 let post = (
1710 account.info.balance,
1711 account.info.nonce,
1712 account.info.code_hash,
1713 );
1714 if pre != post {
1715 return Err(BalValidationError::Mismatch(format!(
1716 "account {addr:?} was modified by execution but is absent from BAL"
1717 )));
1718 }
1719 continue;
1720 };
1721
1722 let acct = &bal.accounts()[bal_acct_idx];
1723
1724 if !has_exact_change_balance(&acct.balance_changes, bal_idx) {
1726 let seeded_pos = acct
1727 .balance_changes
1728 .partition_point(|c| c.block_access_index <= seed_idx);
1729 let seeded = if seeded_pos > 0 {
1730 acct.balance_changes[seeded_pos - 1].post_balance
1731 } else {
1732 system_seed
1734 .get(addr)
1735 .map(|a| a.info.balance)
1736 .unwrap_or_else(|| {
1737 store
1738 .get_account_state(*addr)
1739 .map(|a| a.balance)
1740 .unwrap_or_default()
1741 })
1742 };
1743 if account.info.balance != seeded {
1744 return Err(BalValidationError::Mismatch(format!(
1745 "account {addr:?} balance changed by execution ({}) but BAL has no \
1746 balance change at index {bal_idx} (seeded={seeded})",
1747 account.info.balance
1748 )));
1749 }
1750 }
1751
1752 if !has_exact_change_nonce(&acct.nonce_changes, bal_idx) {
1754 let seeded_pos = acct
1755 .nonce_changes
1756 .partition_point(|c| c.block_access_index <= seed_idx);
1757 let seeded = if seeded_pos > 0 {
1758 acct.nonce_changes[seeded_pos - 1].post_nonce
1759 } else {
1760 system_seed
1761 .get(addr)
1762 .map(|a| a.info.nonce)
1763 .unwrap_or_else(|| {
1764 store
1765 .get_account_state(*addr)
1766 .map(|a| a.nonce)
1767 .unwrap_or_default()
1768 })
1769 };
1770 if account.info.nonce != seeded {
1771 return Err(BalValidationError::Mismatch(format!(
1772 "account {addr:?} nonce changed by execution ({}) but BAL has no \
1773 nonce change at index {bal_idx} (seeded={seeded})",
1774 account.info.nonce
1775 )));
1776 }
1777 }
1778
1779 if !has_exact_change_code(&acct.code_changes, bal_idx) {
1783 let seeded_pos = acct
1784 .code_changes
1785 .partition_point(|c| c.block_access_index <= seed_idx);
1786 let seeded_hash = if seeded_pos > 0 {
1787 let seeded_code = &acct.code_changes[seeded_pos - 1].new_code;
1788 if seeded_code.is_empty() {
1789 *EMPTY_KECCAK_HASH
1790 } else {
1791 ethrex_common::utils::keccak(seeded_code)
1792 }
1793 } else {
1794 system_seed
1796 .get(addr)
1797 .map(|a| a.info.code_hash)
1798 .unwrap_or_else(|| {
1799 store
1800 .get_account_state(*addr)
1801 .map(|a| a.code_hash)
1802 .unwrap_or(*EMPTY_KECCAK_HASH)
1803 })
1804 };
1805 if account.info.code_hash != seeded_hash {
1806 return Err(BalValidationError::Mismatch(format!(
1807 "account {addr:?} code changed by execution but BAL has no \
1808 code change at index {bal_idx} (seeded_hash={seeded_hash:?})"
1809 )));
1810 }
1811 }
1812
1813 for (key_h256, &value) in &account.storage {
1815 let slot_u256 = u256_from_big_endian_const(key_h256.0);
1816 let pos = acct
1818 .storage_changes
1819 .partition_point(|sc| sc.slot < slot_u256);
1820 if pos < acct.storage_changes.len() && acct.storage_changes[pos].slot == slot_u256 {
1821 let sc = &acct.storage_changes[pos];
1822 if !has_exact_change_storage(&sc.slot_changes, bal_idx) {
1823 let seeded_pos = sc
1824 .slot_changes
1825 .partition_point(|c| c.block_access_index <= seed_idx);
1826 if seeded_pos > 0 {
1827 let seeded = sc.slot_changes[seeded_pos - 1].post_value;
1828 if value != seeded {
1829 return Err(BalValidationError::Mismatch(format!(
1830 "account {addr:?} storage slot {slot_u256} changed by \
1831 execution ({value}) but BAL has no change at index \
1832 {bal_idx} (seeded={seeded})"
1833 )));
1834 }
1835 }
1836 }
1837 }
1838 }
1841 }
1842
1843 Ok(())
1844 }
1845
1846 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1858 fn validate_bal_withdrawal_index(
1859 db: &GeneralizedDatabase,
1860 bal: &BlockAccessList,
1861 withdrawal_idx: u32,
1862 index: &BalAddressIndex,
1863 ) -> Result<(), EvmError> {
1864 for acct in bal.accounts() {
1867 let addr = acct.address;
1868 let actual = db.current_accounts_state.get(&addr);
1869
1870 if let Some(expected) = find_exact_change_balance(&acct.balance_changes, withdrawal_idx)
1872 {
1873 match actual {
1874 Some(a) if a.info.balance == expected => {}
1875 Some(a) => {
1876 return Err(EvmError::Custom(format!(
1877 "BAL validation failed for withdrawal: account {addr:?} balance \
1878 mismatch at index {withdrawal_idx}: BAL={expected}, actual={}",
1879 a.info.balance
1880 )));
1881 }
1882 None => {
1883 return Err(EvmError::Custom(format!(
1884 "BAL validation failed for withdrawal: account {addr:?} has \
1885 balance change at index {withdrawal_idx} but was not touched \
1886 by withdrawal/request phase"
1887 )));
1888 }
1889 }
1890 }
1891
1892 if let Some(expected) = find_exact_change_nonce(&acct.nonce_changes, withdrawal_idx) {
1894 match actual {
1895 Some(a) if a.info.nonce == expected => {}
1896 Some(a) => {
1897 return Err(EvmError::Custom(format!(
1898 "BAL validation failed for withdrawal: account {addr:?} nonce \
1899 mismatch at index {withdrawal_idx}: BAL={expected}, actual={}",
1900 a.info.nonce
1901 )));
1902 }
1903 None => {
1904 return Err(EvmError::Custom(format!(
1905 "BAL validation failed for withdrawal: account {addr:?} has \
1906 nonce change at index {withdrawal_idx} but was not touched \
1907 by withdrawal/request phase"
1908 )));
1909 }
1910 }
1911 }
1912
1913 if let Some(expected_code) = find_exact_change_code(&acct.code_changes, withdrawal_idx)
1915 {
1916 let code_hash = if expected_code.is_empty() {
1917 *EMPTY_KECCAK_HASH
1918 } else {
1919 ethrex_common::utils::keccak(expected_code)
1920 };
1921 match actual {
1922 Some(a) if a.info.code_hash == code_hash => {}
1923 Some(_) => {
1924 return Err(EvmError::Custom(format!(
1925 "BAL validation failed for withdrawal: account {addr:?} code \
1926 mismatch at index {withdrawal_idx}"
1927 )));
1928 }
1929 None => {
1930 return Err(EvmError::Custom(format!(
1931 "BAL validation failed for withdrawal: account {addr:?} has \
1932 code change at index {withdrawal_idx} but was not touched \
1933 by withdrawal/request phase"
1934 )));
1935 }
1936 }
1937 }
1938
1939 for sc in &acct.storage_changes {
1941 if let Some(expected_value) =
1942 find_exact_change_storage(&sc.slot_changes, withdrawal_idx)
1943 {
1944 let key = ethrex_common::utils::u256_to_h256(sc.slot);
1945 let actual_value = actual.and_then(|a| a.storage.get(&key)).copied();
1946 if actual_value != Some(expected_value) {
1947 return Err(EvmError::Custom(format!(
1948 "BAL validation failed for withdrawal: account {addr:?} storage \
1949 slot {} mismatch at index {withdrawal_idx}: BAL={expected_value}, \
1950 actual={actual_value:?}",
1951 sc.slot
1952 )));
1953 }
1954 }
1955 }
1956 }
1957
1958 for (addr, account) in &db.current_accounts_state {
1961 if account.is_unmodified() {
1962 continue;
1963 }
1964
1965 let Some(&bal_acct_idx) = index.addr_to_idx.get(addr) else {
1966 let pre_state = db.store.get_account_state(*addr).map_err(|e| {
1970 EvmError::Custom(format!(
1971 "BAL validation failed for withdrawal: db error reading \
1972 account {addr:?}: {e}"
1973 ))
1974 })?;
1975 let pre = (pre_state.balance, pre_state.nonce, pre_state.code_hash);
1976 let post = (
1977 account.info.balance,
1978 account.info.nonce,
1979 account.info.code_hash,
1980 );
1981 if pre != post {
1982 return Err(EvmError::Custom(format!(
1983 "BAL validation failed for withdrawal: account {addr:?} was modified \
1984 during withdrawal/request phase but is absent from BAL"
1985 )));
1986 }
1987 for (key_h256, &value) in &account.storage {
1990 let pre_value = db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
1991 EvmError::Custom(format!(
1992 "BAL validation failed for withdrawal: db error reading \
1993 storage {addr:?}[{}]: {e}",
1994 u256_from_big_endian_const(key_h256.0)
1995 ))
1996 })?;
1997 if value != pre_value {
1998 return Err(EvmError::Custom(format!(
1999 "BAL validation failed for withdrawal: account {addr:?} storage \
2000 slot {} changed during withdrawal/request phase but is absent \
2001 from BAL",
2002 u256_from_big_endian_const(key_h256.0)
2003 )));
2004 }
2005 }
2006 continue;
2007 };
2008
2009 let acct = &bal.accounts()[bal_acct_idx];
2010
2011 if !has_exact_change_balance(&acct.balance_changes, withdrawal_idx) {
2014 let seeded = match acct.balance_changes.last() {
2015 Some(c) => c.post_balance,
2016 None => {
2017 db.store
2018 .get_account_state(*addr)
2019 .map_err(|e| {
2020 EvmError::Custom(format!(
2021 "BAL validation failed for withdrawal: db error reading \
2022 account {addr:?}: {e}"
2023 ))
2024 })?
2025 .balance
2026 }
2027 };
2028 if account.info.balance != seeded {
2029 return Err(EvmError::Custom(format!(
2030 "BAL validation failed for withdrawal: account {addr:?} balance \
2031 changed during withdrawal/request phase ({}) but BAL has no \
2032 balance change at index {withdrawal_idx} (last_bal={seeded})",
2033 account.info.balance
2034 )));
2035 }
2036 }
2037
2038 if !has_exact_change_nonce(&acct.nonce_changes, withdrawal_idx) {
2040 let seeded = match acct.nonce_changes.last() {
2041 Some(c) => c.post_nonce,
2042 None => {
2043 db.store
2044 .get_account_state(*addr)
2045 .map_err(|e| {
2046 EvmError::Custom(format!(
2047 "BAL validation failed for withdrawal: db error reading \
2048 account {addr:?}: {e}"
2049 ))
2050 })?
2051 .nonce
2052 }
2053 };
2054 if account.info.nonce != seeded {
2055 return Err(EvmError::Custom(format!(
2056 "BAL validation failed for withdrawal: account {addr:?} nonce \
2057 changed during withdrawal/request phase ({}) but BAL has no \
2058 nonce change at index {withdrawal_idx} (last_bal={seeded})",
2059 account.info.nonce
2060 )));
2061 }
2062 }
2063
2064 if !has_exact_change_code(&acct.code_changes, withdrawal_idx) {
2066 let seeded_hash = match acct.code_changes.last() {
2067 Some(c) if c.new_code.is_empty() => *EMPTY_KECCAK_HASH,
2068 Some(c) => ethrex_common::utils::keccak(&c.new_code),
2069 None => {
2070 db.store
2071 .get_account_state(*addr)
2072 .map_err(|e| {
2073 EvmError::Custom(format!(
2074 "BAL validation failed for withdrawal: db error reading \
2075 account {addr:?}: {e}"
2076 ))
2077 })?
2078 .code_hash
2079 }
2080 };
2081 if account.info.code_hash != seeded_hash {
2082 return Err(EvmError::Custom(format!(
2083 "BAL validation failed for withdrawal: account {addr:?} code \
2084 changed during withdrawal/request phase but BAL has no \
2085 code change at index {withdrawal_idx} \
2086 (actual={:?}, last_bal={seeded_hash:?})",
2087 account.info.code_hash
2088 )));
2089 }
2090 }
2091
2092 for (key_h256, &value) in &account.storage {
2095 let slot_u256 = u256_from_big_endian_const(key_h256.0);
2096 let pos = acct
2097 .storage_changes
2098 .partition_point(|sc| sc.slot < slot_u256);
2099 if pos < acct.storage_changes.len() && acct.storage_changes[pos].slot == slot_u256 {
2100 let sc = &acct.storage_changes[pos];
2101 if !has_exact_change_storage(&sc.slot_changes, withdrawal_idx) {
2102 let seeded = match sc.slot_changes.last() {
2105 Some(c) => c.post_value,
2106 None => db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
2107 EvmError::Custom(format!(
2108 "BAL validation failed for withdrawal: db error reading \
2109 storage {addr:?}[{slot_u256}]: {e}"
2110 ))
2111 })?,
2112 };
2113 if value != seeded {
2114 return Err(EvmError::Custom(format!(
2115 "BAL validation failed for withdrawal: account {addr:?} \
2116 storage slot {slot_u256} changed during withdrawal/request \
2117 phase ({value}) but BAL has no change at index \
2118 {withdrawal_idx} (last_bal={seeded})"
2119 )));
2120 }
2121 }
2122 } else {
2123 let pre_value = db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
2126 EvmError::Custom(format!(
2127 "BAL validation failed for withdrawal: db error reading \
2128 storage {addr:?}[{slot_u256}]: {e}"
2129 ))
2130 })?;
2131 if value != pre_value {
2132 return Err(EvmError::Custom(format!(
2133 "BAL validation failed for withdrawal: account {addr:?} \
2134 storage slot {slot_u256} changed during withdrawal/request \
2135 phase ({value}) but slot is absent from BAL storage_changes \
2136 (pre={pre_value})"
2137 )));
2138 }
2139 }
2140 }
2141 }
2142
2143 Ok(())
2144 }
2145
2146 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2152 pub fn warm_block(
2153 block: &Block,
2154 store: Arc<dyn Database>,
2155 vm_type: VMType,
2156 crypto: &dyn Crypto,
2157 cancelled: &AtomicBool,
2158 ) -> Result<(), EvmError> {
2159 let txs_with_sender = block
2160 .body
2161 .get_transactions_with_sender(crypto)
2162 .map_err(|error| {
2163 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
2164 })?;
2165
2166 Self::warm_txs(
2167 &txs_with_sender,
2168 &block.header,
2169 store.clone(),
2170 vm_type,
2171 crypto,
2172 &|| cancelled.load(Ordering::Relaxed),
2173 )?;
2174
2175 let mut db = GeneralizedDatabase::new(store.clone());
2176
2177 if cancelled.load(Ordering::Relaxed) {
2178 return Ok(());
2179 }
2180
2181 for withdrawal in block
2182 .body
2183 .withdrawals
2184 .iter()
2185 .flatten()
2186 .filter(|withdrawal| withdrawal.amount > 0)
2187 {
2188 db.get_account_mut(withdrawal.address).map_err(|_| {
2189 EvmError::DB(format!(
2190 "Withdrawal account {} not found",
2191 withdrawal.address
2192 ))
2193 })?;
2194 }
2195 Ok(())
2196 }
2197
2198 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2206 pub fn warm_txs(
2207 txs_with_sender: &[(&Transaction, Address)],
2208 header: &BlockHeader,
2209 store: Arc<dyn Database>,
2210 vm_type: VMType,
2211 crypto: &dyn Crypto,
2212 should_stop: &(dyn Fn() -> bool + Sync),
2213 ) -> Result<(), EvmError> {
2214 let mut sender_groups: FxHashMap<Address, Vec<&Transaction>> = FxHashMap::default();
2217 for (tx, sender) in txs_with_sender {
2218 sender_groups.entry(*sender).or_default().push(tx);
2219 }
2220
2221 let chain_config = store.get_chain_config()?;
2224 let evm_config = EVMConfig::new_from_chain_config(&chain_config, header);
2225 let chain_id = chain_config.chain_id;
2226 let base_blob_fee_per_gas = get_base_fee_per_blob_gas(header.excess_blob_gas, &evm_config)?;
2228
2229 sender_groups.into_par_iter().for_each_with(
2232 Vec::with_capacity(STACK_LIMIT),
2233 |stack_pool, (sender, txs)| {
2234 if should_stop() {
2235 return;
2236 }
2237 let mut memory_pool = Vec::with_capacity(1);
2241 let mut group_db = GeneralizedDatabase::new(store.clone());
2243 for tx in txs {
2244 if should_stop() {
2245 return;
2246 }
2247 let _ = Self::execute_tx_in_block(
2248 tx,
2249 sender,
2250 header,
2251 &mut group_db,
2252 vm_type,
2253 base_blob_fee_per_gas,
2254 stack_pool,
2255 &mut memory_pool,
2256 true,
2257 crypto,
2258 evm_config,
2259 chain_id,
2260 );
2261 }
2262 },
2263 );
2264 Ok(())
2265 }
2266
2267 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2270 pub fn bal_storage_slots(bal: &BlockAccessList) -> Vec<(Address, H256)> {
2271 bal.accounts()
2272 .iter()
2273 .flat_map(|ac| {
2274 ac.all_storage_slots()
2275 .map(move |slot| (ac.address, H256::from_uint(&slot)))
2276 })
2277 .collect()
2278 }
2279
2280 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2289 pub fn warm_block_from_bal(
2290 bal: &BlockAccessList,
2291 store: Arc<dyn Database>,
2292 cancelled: &AtomicBool,
2293 ) -> Result<(), EvmError> {
2294 let accounts = bal.accounts();
2295 if accounts.is_empty() {
2296 return Ok(());
2297 }
2298
2299 let account_addresses: Vec<Address> = accounts.iter().map(|ac| ac.address).collect();
2305 store
2306 .prefetch_accounts(&account_addresses)
2307 .map_err(|e| EvmError::Custom(format!("prefetch_accounts: {e}")))?;
2308
2309 if cancelled.load(Ordering::Relaxed) {
2310 return Ok(());
2311 }
2312
2313 let code_hashes: Vec<ethrex_common::H256> = accounts
2317 .par_iter()
2318 .filter_map(|ac| {
2319 store
2320 .get_account_state(ac.address)
2321 .ok()
2322 .filter(|s| s.code_hash != *EMPTY_KECCAK_HASH)
2323 .map(|s| s.code_hash)
2324 })
2325 .collect();
2326 code_hashes.par_iter().for_each(|&h| {
2327 let _ = store.get_account_code(h);
2328 });
2329
2330 Ok(())
2331 }
2332
2333 fn send_state_transitions_tx(
2334 merkleizer: &Sender<Vec<AccountUpdate>>,
2335 db: &mut GeneralizedDatabase,
2336 queue_length: &AtomicUsize,
2337 ) -> Result<(), EvmError> {
2338 let transitions = LEVM::get_state_transitions_tx(db)?;
2339 merkleizer
2340 .send(transitions)
2341 .map_err(|e| EvmError::Custom(format!("send failed: {e}")))?;
2342 queue_length.fetch_add(1, Ordering::Relaxed);
2343 Ok(())
2344 }
2345
2346 fn setup_env(
2347 tx: &Transaction,
2348 tx_sender: Address,
2349 block_header: &BlockHeader,
2350 db: &GeneralizedDatabase,
2351 vm_type: VMType,
2352 ) -> Result<Environment, EvmError> {
2353 let chain_config = db.store.get_chain_config()?;
2357 let config = EVMConfig::new_from_chain_config(&chain_config, block_header);
2358 let base_blob_fee_per_gas =
2359 get_base_fee_per_blob_gas(block_header.excess_blob_gas, &config)?;
2360 Self::setup_env_with_config(
2361 tx,
2362 tx_sender,
2363 block_header,
2364 config,
2365 chain_config.chain_id,
2366 vm_type,
2367 base_blob_fee_per_gas,
2368 )
2369 }
2370
2371 fn setup_env_with_config(
2375 tx: &Transaction,
2376 tx_sender: Address,
2377 block_header: &BlockHeader,
2378 config: EVMConfig,
2379 chain_id: u64,
2380 vm_type: VMType,
2381 base_blob_fee_per_gas: U256,
2382 ) -> Result<Environment, EvmError> {
2383 let gas_price: U256 = calculate_gas_price_for_tx(
2384 tx,
2385 block_header.base_fee_per_gas.unwrap_or_default(),
2386 &vm_type,
2387 )?;
2388
2389 let block_excess_blob_gas = block_header.excess_blob_gas;
2390 let env = Environment {
2391 origin: tx_sender,
2392 gas_limit: tx.gas_limit(),
2393 config,
2394 block_number: block_header.number,
2395 coinbase: block_header.coinbase,
2396 timestamp: block_header.timestamp,
2397 prev_randao: Some(block_header.prev_randao),
2398 slot_number: block_header
2399 .slot_number
2400 .map(U256::from)
2401 .unwrap_or(U256::zero()),
2402 chain_id: chain_id.into(),
2403 base_fee_per_gas: block_header.base_fee_per_gas.unwrap_or_default().into(),
2404 base_blob_fee_per_gas,
2405 gas_price,
2406 block_excess_blob_gas,
2407 block_blob_gas_used: block_header.blob_gas_used,
2408 tx_blob_hashes: tx.blob_versioned_hashes(),
2409 tx_max_priority_fee_per_gas: tx.max_priority_fee().map(U256::from),
2410 tx_max_fee_per_gas: tx.max_fee_per_gas().map(U256::from),
2411 tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas(),
2412 tx_nonce: tx.nonce(),
2413 block_gas_limit: block_header.gas_limit,
2414 difficulty: block_header.difficulty,
2415 is_privileged: matches!(tx, Transaction::PrivilegedL2Transaction(_)),
2416 fee_token: tx.fee_token(),
2417 disable_balance_check: false,
2418 is_system_call: false,
2419 };
2420
2421 Ok(env)
2422 }
2423
2424 pub fn execute_tx(
2425 tx: &Transaction,
2427 tx_sender: Address,
2429 block_header: &BlockHeader,
2431 db: &mut GeneralizedDatabase,
2432 vm_type: VMType,
2433 crypto: &dyn Crypto,
2434 ) -> Result<ExecutionReport, EvmError> {
2435 let env = Self::setup_env(tx, tx_sender, block_header, db, vm_type)?;
2436 let mut vm = VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)?;
2437
2438 vm.execute().map_err(VMError::into)
2439 }
2440
2441 #[allow(clippy::too_many_arguments)]
2444 fn execute_tx_in_block(
2445 tx: &Transaction,
2447 tx_sender: Address,
2449 block_header: &BlockHeader,
2451 db: &mut GeneralizedDatabase,
2452 vm_type: VMType,
2453 base_blob_fee_per_gas: U256,
2454 stack_pool: &mut Vec<Stack>,
2455 memory_pool: &mut Vec<Memory>,
2456 disable_balance_check: bool,
2457 crypto: &dyn Crypto,
2458 config: EVMConfig,
2459 chain_id: u64,
2460 ) -> Result<ExecutionReport, EvmError> {
2461 let mut env = Self::setup_env_with_config(
2462 tx,
2463 tx_sender,
2464 block_header,
2465 config,
2466 chain_id,
2467 vm_type,
2468 base_blob_fee_per_gas,
2469 )?;
2470 env.disable_balance_check = disable_balance_check;
2471 let mut vm = VM::new_pooled(
2475 env,
2476 db,
2477 tx,
2478 LevmCallTracer::disabled(),
2479 vm_type,
2480 crypto,
2481 stack_pool,
2482 memory_pool,
2483 )?;
2484 let result = vm.execute().map_err(VMError::into);
2485 vm.reclaim_into(stack_pool, memory_pool);
2487 result
2488 }
2489
2490 pub fn undo_last_tx(db: &mut GeneralizedDatabase) -> Result<(), EvmError> {
2491 db.undo_last_transaction()?;
2492 Ok(())
2493 }
2494
2495 pub fn simulate_tx_from_generic(
2496 tx: &GenericTransaction,
2498 block_header: &BlockHeader,
2500 db: &mut GeneralizedDatabase,
2501 vm_type: VMType,
2502 crypto: &dyn Crypto,
2503 ) -> Result<ExecutionResult, EvmError> {
2504 let mut env = env_from_generic(tx, block_header, db, vm_type)?;
2505
2506 env.block_gas_limit = i64::MAX as u64; adjust_disabled_base_fee(&mut env);
2509
2510 let converted_tx = generic_tx_to_transaction(tx)?;
2511 let mut vm = vm_from_generic(&converted_tx, env, db, vm_type, crypto)?;
2512
2513 vm.execute()
2514 .map(|value| value.into())
2515 .map_err(VMError::into)
2516 }
2517
2518 #[allow(clippy::too_many_arguments)]
2533 pub fn simulate_frame_validation_prefix(
2534 tx: &Transaction,
2535 block_header: &BlockHeader,
2536 db: &mut GeneralizedDatabase,
2537 vm_type: VMType,
2538 crypto: &dyn Crypto,
2539 prefix: ðrex_common::types::ValidationPrefix,
2540 _canonical_paymaster_code_hash: Option<H256>,
2541 ) -> Result<FrameValidationOutcome, EvmError> {
2542 use ethrex_common::types::FRAME_TX_MAX_VERIFY_GAS;
2543
2544 let frame_tx = match tx {
2545 Transaction::FrameTransaction(ft) => ft,
2546 _ => {
2547 return Err(EvmError::Custom(
2548 "simulate_frame_validation_prefix requires a frame transaction".to_string(),
2549 ));
2550 }
2551 };
2552 let sender = frame_tx.sender;
2553
2554 let env = Self::setup_env(tx, sender, block_header, db, vm_type)?;
2555 let mut vm = VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)?;
2556
2557 let canonical_pay_frame: Option<usize> = None;
2560
2561 let sim = match vm.run_frame_validation_prefix(
2562 &prefix.frame_indices,
2563 prefix.deploy_index,
2564 canonical_pay_frame,
2565 ) {
2566 Ok(sim) => sim,
2567 Err(err) => {
2568 return Ok(FrameValidationOutcome {
2572 passed: false,
2573 violation: Some(EvmError::from(err).to_string()),
2574 max_cost: Self::frame_tx_max_cost(frame_tx),
2575 accessed_paymaster: None,
2576 touched_sender_slots: Vec::new(),
2577 });
2578 }
2579 };
2580
2581 let max_cost = Self::frame_tx_max_cost(frame_tx);
2582 let touched_sender_slots = vm.validation_observer.touched_sender_slots.clone();
2583 let accessed_paymaster = sim.payer_address.map(|payer| (payer, false));
2590
2591 if let Some(violation) = &vm.validation_observer.violation {
2593 return Ok(FrameValidationOutcome {
2594 passed: false,
2595 violation: Some(format!("{violation:?}")),
2596 max_cost,
2597 accessed_paymaster,
2598 touched_sender_slots,
2599 });
2600 }
2601
2602 if sim.any_revert {
2604 return Ok(FrameValidationOutcome {
2605 passed: false,
2606 violation: Some("validation prefix frame reverted".to_string()),
2607 max_cost,
2608 accessed_paymaster,
2609 touched_sender_slots,
2610 });
2611 }
2612
2613 if sim.payer_address.is_none() {
2616 return Ok(FrameValidationOutcome {
2617 passed: false,
2618 violation: Some("validation prefix did not establish a payer".to_string()),
2619 max_cost,
2620 accessed_paymaster,
2621 touched_sender_slots,
2622 });
2623 }
2624
2625 if prefix.deploy_index.is_some() {
2627 let code = vm.db.get_account_code(sender).map_err(VMError::from)?;
2628 if code.is_empty() {
2629 return Ok(FrameValidationOutcome {
2630 passed: false,
2631 violation: Some(format!("{:?}", FrameSimViolation::DeployInstalledNoCode)),
2632 max_cost,
2633 accessed_paymaster,
2634 touched_sender_slots,
2635 });
2636 }
2637 }
2638
2639 if sim.total_gas_used > FRAME_TX_MAX_VERIFY_GAS {
2641 return Ok(FrameValidationOutcome {
2642 passed: false,
2643 violation: Some(format!(
2644 "validation prefix gas {} exceeds MAX_VERIFY_GAS {}",
2645 sim.total_gas_used, FRAME_TX_MAX_VERIFY_GAS
2646 )),
2647 max_cost,
2648 accessed_paymaster,
2649 touched_sender_slots,
2650 });
2651 }
2652
2653 Ok(FrameValidationOutcome {
2654 passed: true,
2655 violation: None,
2656 max_cost,
2657 accessed_paymaster,
2658 touched_sender_slots,
2659 })
2660 }
2661
2662 fn frame_tx_max_cost(frame_tx: ðrex_common::types::FrameTransaction) -> U256 {
2666 let gas_cost = U256::from(frame_tx.max_fee_per_gas)
2671 .saturating_mul(U256::from(frame_tx.total_gas_limit()));
2672 let blob_cost = U256::from(frame_tx.blob_versioned_hashes.len())
2673 .saturating_mul(U256::from(131072u64))
2674 .saturating_mul(frame_tx.max_fee_per_blob_gas);
2675 gas_cost.saturating_add(blob_cost)
2676 }
2677
2678 pub fn get_state_transitions(
2679 db: &mut GeneralizedDatabase,
2680 ) -> Result<Vec<AccountUpdate>, EvmError> {
2681 Ok(db.get_state_transitions()?)
2682 }
2683
2684 pub fn get_state_transitions_tx(
2685 db: &mut GeneralizedDatabase,
2686 ) -> Result<Vec<AccountUpdate>, EvmError> {
2687 Ok(db.get_state_transitions_tx()?)
2688 }
2689
2690 pub fn process_withdrawals(
2691 db: &mut GeneralizedDatabase,
2692 withdrawals: &[Withdrawal],
2693 ) -> Result<(), EvmError> {
2694 for (address, increment) in withdrawals
2696 .iter()
2697 .filter(|withdrawal| withdrawal.amount > 0)
2698 .map(|w| (w.address, u128::from(w.amount) * u128::from(GWEI_TO_WEI)))
2699 {
2700 let account = db
2701 .get_account_mut(address)
2702 .map_err(|_| EvmError::DB(format!("Withdrawal account {address} not found")))?;
2703
2704 let initial_balance = account.info.balance;
2705 account.info.balance += increment.into();
2706 let new_balance = account.info.balance;
2707
2708 if let Some(recorder) = db.bal_recorder_mut() {
2710 recorder.set_initial_balance(address, initial_balance);
2711 recorder.record_balance_change(address, new_balance);
2712 }
2713 }
2714 Ok(())
2715 }
2716
2717 pub fn beacon_root_contract_call(
2719 block_header: &BlockHeader,
2720 db: &mut GeneralizedDatabase,
2721 vm_type: VMType,
2722 crypto: &dyn Crypto,
2723 ) -> Result<(), EvmError> {
2724 if let VMType::L2(_) = vm_type {
2725 return Err(EvmError::InvalidEVM(
2726 "beacon_root_contract_call should not be called for L2 VM".to_string(),
2727 ));
2728 }
2729
2730 let beacon_root = block_header.parent_beacon_block_root.ok_or_else(|| {
2731 EvmError::Header("parent_beacon_block_root field is missing".to_string())
2732 })?;
2733
2734 generic_system_contract_levm(
2735 block_header,
2736 Bytes::copy_from_slice(beacon_root.as_bytes()),
2737 db,
2738 BEACON_ROOTS_ADDRESS.address,
2739 SYSTEM_ADDRESS,
2740 vm_type,
2741 crypto,
2742 )?;
2743 Ok(())
2744 }
2745
2746 pub fn process_block_hash_history(
2747 block_header: &BlockHeader,
2748 db: &mut GeneralizedDatabase,
2749 vm_type: VMType,
2750 crypto: &dyn Crypto,
2751 ) -> Result<(), EvmError> {
2752 if let VMType::L2(_) = vm_type {
2753 return Err(EvmError::InvalidEVM(
2754 "process_block_hash_history should not be called for L2 VM".to_string(),
2755 ));
2756 }
2757
2758 generic_system_contract_levm(
2759 block_header,
2760 Bytes::copy_from_slice(block_header.parent_hash.as_bytes()),
2761 db,
2762 HISTORY_STORAGE_ADDRESS.address,
2763 SYSTEM_ADDRESS,
2764 vm_type,
2765 crypto,
2766 )?;
2767 Ok(())
2768 }
2769
2770 pub fn install_expiry_verifier_code(
2776 db: &mut GeneralizedDatabase,
2777 crypto: &dyn Crypto,
2778 ) -> Result<(), EvmError> {
2779 const PREDEPLOY_NONCE: u64 = 1;
2781
2782 let current = db.get_account_code(EXPIRY_VERIFIER_PREDEPLOY.address)?;
2783 if current.code() == EXPIRY_VERIFIER_RUNTIME_BYTECODE.as_slice() {
2784 return Ok(());
2785 }
2786 let code = Code::from_bytecode(
2787 Bytes::from_static(&EXPIRY_VERIFIER_RUNTIME_BYTECODE),
2788 crypto,
2789 );
2790 let code_hash = code.hash;
2791 if let Some(recorder) = db.bal_recorder_mut() {
2795 recorder.record_code_change(EXPIRY_VERIFIER_PREDEPLOY.address, code.code_bytes());
2796 recorder.record_nonce_change(EXPIRY_VERIFIER_PREDEPLOY.address, PREDEPLOY_NONCE);
2797 }
2798 let acc = db
2799 .get_account_mut(EXPIRY_VERIFIER_PREDEPLOY.address)
2800 .map_err(EvmError::from)?;
2801 acc.info.code_hash = code_hash;
2802 acc.info.nonce = PREDEPLOY_NONCE;
2803 db.codes.entry(code_hash).or_insert(code);
2804 Ok(())
2805 }
2806
2807 pub(crate) fn read_withdrawal_requests(
2808 block_header: &BlockHeader,
2809 db: &mut GeneralizedDatabase,
2810 vm_type: VMType,
2811 crypto: &dyn Crypto,
2812 ) -> Result<ExecutionReport, EvmError> {
2813 if let VMType::L2(_) = vm_type {
2814 return Err(EvmError::InvalidEVM(
2815 "read_withdrawal_requests should not be called for L2 VM".to_string(),
2816 ));
2817 }
2818
2819 let report = generic_system_contract_levm(
2820 block_header,
2821 Bytes::new(),
2822 db,
2823 WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS.address,
2824 SYSTEM_ADDRESS,
2825 vm_type,
2826 crypto,
2827 )?;
2828
2829 match report.result {
2830 TxResult::Success => Ok(report),
2831 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
2833 "REVERT when reading withdrawal requests with error: {vm_error:?}. According to EIP-7002, the revert of this system call invalidates the block.",
2834 ))),
2835 }
2836 }
2837
2838 pub(crate) fn dequeue_consolidation_requests(
2839 block_header: &BlockHeader,
2840 db: &mut GeneralizedDatabase,
2841 vm_type: VMType,
2842 crypto: &dyn Crypto,
2843 ) -> Result<ExecutionReport, EvmError> {
2844 if let VMType::L2(_) = vm_type {
2845 return Err(EvmError::InvalidEVM(
2846 "dequeue_consolidation_requests should not be called for L2 VM".to_string(),
2847 ));
2848 }
2849
2850 let report = generic_system_contract_levm(
2851 block_header,
2852 Bytes::new(),
2853 db,
2854 CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS.address,
2855 SYSTEM_ADDRESS,
2856 vm_type,
2857 crypto,
2858 )?;
2859
2860 match report.result {
2861 TxResult::Success => Ok(report),
2862 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
2864 "REVERT when dequeuing consolidation requests with error: {vm_error:?}. According to EIP-7251, the revert of this system call invalidates the block.",
2865 ))),
2866 }
2867 }
2868
2869 pub(crate) fn read_builder_deposit_requests(
2870 block_header: &BlockHeader,
2871 db: &mut GeneralizedDatabase,
2872 vm_type: VMType,
2873 crypto: &dyn Crypto,
2874 ) -> Result<ExecutionReport, EvmError> {
2875 if let VMType::L2(_) = vm_type {
2876 return Err(EvmError::InvalidEVM(
2877 "read_builder_deposit_requests should not be called for L2 VM".to_string(),
2878 ));
2879 }
2880
2881 let report = generic_system_contract_levm(
2882 block_header,
2883 Bytes::new(),
2884 db,
2885 BUILDER_DEPOSIT_CONTRACT_ADDRESS.address,
2886 SYSTEM_ADDRESS,
2887 vm_type,
2888 crypto,
2889 )?;
2890
2891 match report.result {
2892 TxResult::Success => Ok(report),
2893 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
2895 "REVERT when reading builder deposit requests with error: {vm_error:?}. According to EIP-8282, the revert of this system call invalidates the block.",
2896 ))),
2897 }
2898 }
2899
2900 pub(crate) fn dequeue_builder_exit_requests(
2901 block_header: &BlockHeader,
2902 db: &mut GeneralizedDatabase,
2903 vm_type: VMType,
2904 crypto: &dyn Crypto,
2905 ) -> Result<ExecutionReport, EvmError> {
2906 if let VMType::L2(_) = vm_type {
2907 return Err(EvmError::InvalidEVM(
2908 "dequeue_builder_exit_requests should not be called for L2 VM".to_string(),
2909 ));
2910 }
2911
2912 let report = generic_system_contract_levm(
2913 block_header,
2914 Bytes::new(),
2915 db,
2916 BUILDER_EXIT_CONTRACT_ADDRESS.address,
2917 SYSTEM_ADDRESS,
2918 vm_type,
2919 crypto,
2920 )?;
2921
2922 match report.result {
2923 TxResult::Success => Ok(report),
2924 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
2926 "REVERT when dequeuing builder exit requests with error: {vm_error:?}. According to EIP-8282, the revert of this system call invalidates the block.",
2927 ))),
2928 }
2929 }
2930
2931 pub fn create_access_list(
2932 mut tx: GenericTransaction,
2933 header: &BlockHeader,
2934 db: &mut GeneralizedDatabase,
2935 vm_type: VMType,
2936 crypto: &dyn Crypto,
2937 ) -> Result<(ExecutionResult, AccessList), VMError> {
2938 let mut env = env_from_generic(&tx, header, db, vm_type)?;
2939
2940 adjust_disabled_base_fee(&mut env);
2941
2942 let converted_tx = generic_tx_to_transaction(&tx)?;
2943 let mut vm = vm_from_generic(&converted_tx, env.clone(), db, vm_type, crypto)?;
2944
2945 vm.stateless_execute()?;
2946
2947 tx.access_list = vm.substate.make_access_list();
2949 let converted_tx = generic_tx_to_transaction(&tx)?;
2950 let mut vm = vm_from_generic(&converted_tx, env, db, vm_type, crypto)?;
2951
2952 let report = vm.stateless_execute()?;
2953
2954 Ok((
2955 report.into(),
2956 tx.access_list
2957 .into_iter()
2958 .map(|x| (x.address, x.storage_keys))
2959 .collect(),
2960 ))
2961 }
2962
2963 pub fn prepare_block(
2964 block: &Block,
2965 db: &mut GeneralizedDatabase,
2966 vm_type: VMType,
2967 crypto: &dyn Crypto,
2968 ) -> Result<(), EvmError> {
2969 let chain_config = db.store.get_chain_config()?;
2970 let block_header = &block.header;
2971 let fork = chain_config.fork(block_header.timestamp);
2972
2973 if let VMType::L2(_) = vm_type {
2975 return Ok(());
2976 }
2977
2978 if fork >= Fork::Hegota {
2982 Self::install_expiry_verifier_code(db, crypto)?;
2983 }
2984
2985 if block_header.parent_beacon_block_root.is_some() && fork >= Fork::Cancun {
2986 Self::beacon_root_contract_call(block_header, db, vm_type, crypto)?;
2987 }
2988
2989 if fork >= Fork::Prague {
2990 Self::process_block_hash_history(block_header, db, vm_type, crypto)?;
2992 }
2993 Ok(())
2994 }
2995}
2996
2997pub fn generic_system_contract_levm(
2998 block_header: &BlockHeader,
2999 calldata: Bytes,
3000 db: &mut GeneralizedDatabase,
3001 contract_address: Address,
3002 system_address: Address,
3003 vm_type: VMType,
3004 crypto: &dyn Crypto,
3005) -> Result<ExecutionReport, EvmError> {
3006 let chain_config = db.store.get_chain_config()?;
3007 let config = EVMConfig::new_from_chain_config(&chain_config, block_header);
3008 let env = Environment {
3009 origin: system_address,
3010 gas_limit: SYS_CALL_GAS_LIMIT,
3021 block_number: block_header.number,
3022 coinbase: block_header.coinbase,
3023 timestamp: block_header.timestamp,
3024 prev_randao: Some(block_header.prev_randao),
3025 base_fee_per_gas: U256::zero(),
3026 gas_price: U256::zero(),
3027 block_excess_blob_gas: block_header.excess_blob_gas,
3028 block_blob_gas_used: block_header.blob_gas_used,
3029 block_gas_limit: block_header.gas_limit,
3034 is_system_call: true,
3035 config,
3036 ..Default::default()
3037 };
3038
3039 debug_assert!(
3045 env.gas_price.is_zero() && env.base_fee_per_gas.is_zero(),
3046 "system calls must run with a zero gas price"
3047 );
3048
3049 if PRAGUE_SYSTEM_CONTRACTS
3059 .iter()
3060 .chain(AMSTERDAM_REQUEST_PREDEPLOYS.iter())
3061 .any(|contract| contract.address == contract_address)
3062 && db.get_account_code(contract_address)?.is_empty()
3063 {
3064 return Err(EvmError::SystemContractCallFailed(format!(
3065 "System contract: {contract_address} has no code after deployment"
3066 )));
3067 };
3068
3069 let tx = &Transaction::EIP1559Transaction(EIP1559Transaction {
3070 to: TxKind::Call(contract_address),
3071 value: U256::zero(),
3072 data: calldata,
3073 ..Default::default()
3074 });
3075 if let Some(recorder) = db.bal_recorder.as_mut() {
3077 recorder.enter_system_call();
3078 }
3079
3080 let result = VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)
3081 .and_then(|mut vm| vm.execute())
3082 .map_err(EvmError::from);
3083
3084 if let Some(recorder) = db.bal_recorder.as_mut() {
3086 recorder.exit_system_call();
3087 }
3088
3089 result
3090}
3091
3092#[allow(unreachable_code)]
3093#[allow(unused_variables)]
3094pub fn extract_all_requests_levm(
3095 receipts: &[Receipt],
3096 db: &mut GeneralizedDatabase,
3097 header: &BlockHeader,
3098 vm_type: VMType,
3099 crypto: &dyn Crypto,
3100) -> Result<Vec<Requests>, EvmError> {
3101 if let VMType::L2(_) = vm_type {
3102 return Err(EvmError::InvalidEVM(
3103 "extract_all_requests_levm should not be called for L2 VM".to_string(),
3104 ));
3105 }
3106
3107 let chain_config = db.store.get_chain_config()?;
3108 let fork = chain_config.fork(header.timestamp);
3109
3110 if fork < Fork::Prague {
3111 return Ok(Default::default());
3112 }
3113
3114 let withdrawals_data: Vec<u8> = LEVM::read_withdrawal_requests(header, db, vm_type, crypto)?
3115 .output
3116 .into();
3117 let consolidation_data: Vec<u8> =
3118 LEVM::dequeue_consolidation_requests(header, db, vm_type, crypto)?
3119 .output
3120 .into();
3121
3122 let deposits = Requests::from_deposit_receipts(chain_config.deposit_contract_address, receipts)
3123 .ok_or(EvmError::InvalidDepositRequest)?;
3124 let withdrawals = Requests::from_withdrawals_data(withdrawals_data);
3125 let consolidation = Requests::from_consolidation_data(consolidation_data);
3126
3127 let mut requests = vec![deposits, withdrawals, consolidation];
3128
3129 if fork >= Fork::Amsterdam {
3133 requests.reserve_exact(2);
3135 let builder_deposit_data: Vec<u8> =
3136 LEVM::read_builder_deposit_requests(header, db, vm_type, crypto)?
3137 .output
3138 .into();
3139 let builder_exit_data: Vec<u8> =
3140 LEVM::dequeue_builder_exit_requests(header, db, vm_type, crypto)?
3141 .output
3142 .into();
3143 requests.push(Requests::from_builder_deposit_data(builder_deposit_data));
3144 requests.push(Requests::from_builder_exit_data(builder_exit_data));
3145 }
3146
3147 Ok(requests)
3148}
3149
3150pub fn calculate_gas_price_for_generic(tx: &GenericTransaction, basefee: u64) -> U256 {
3153 if !tx.gas_price.is_zero() {
3154 tx.gas_price
3156 } else {
3157 min(
3159 tx.max_priority_fee_per_gas.unwrap_or(0) + basefee,
3160 tx.max_fee_per_gas.unwrap_or(0),
3161 )
3162 .into()
3163 }
3164}
3165
3166pub fn calculate_gas_price_for_tx(
3167 tx: &Transaction,
3168 mut fee_per_gas: u64,
3169 vm_type: &VMType,
3170) -> Result<U256, VMError> {
3171 let Some(max_priority_fee) = tx.max_priority_fee() else {
3172 return Ok(tx.gas_price());
3174 };
3175
3176 let max_fee_per_gas = tx.max_fee_per_gas().ok_or(VMError::TxValidation(
3177 TxValidationError::InsufficientMaxFeePerGas,
3178 ))?;
3179
3180 if let VMType::L2(fee_config) = vm_type
3181 && let Some(operator_fee_config) = &fee_config.operator_fee_config
3182 {
3183 fee_per_gas += operator_fee_config.operator_fee_per_gas;
3184 }
3185
3186 if fee_per_gas > max_fee_per_gas {
3187 return Err(VMError::TxValidation(
3188 TxValidationError::InsufficientMaxFeePerGas,
3189 ));
3190 }
3191
3192 Ok(min(max_priority_fee + fee_per_gas, max_fee_per_gas).into())
3193}
3194
3195fn adjust_disabled_base_fee(env: &mut Environment) {
3199 if env.gas_price == U256::zero() {
3200 env.base_fee_per_gas = U256::zero();
3201 }
3202 if env
3203 .tx_max_fee_per_blob_gas
3204 .is_some_and(|v| v == U256::zero())
3205 {
3206 env.block_excess_blob_gas = None;
3207 }
3208}
3209
3210fn adjust_disabled_l2_fees(env: &Environment, vm_type: VMType) -> VMType {
3212 if env.gas_price == U256::zero()
3213 && let VMType::L2(fee_config) = vm_type
3214 {
3215 return VMType::L2(FeeConfig {
3217 operator_fee_config: None,
3218 l1_fee_config: None,
3219 ..fee_config
3220 });
3221 }
3222 vm_type
3223}
3224
3225fn env_from_generic(
3226 tx: &GenericTransaction,
3227 header: &BlockHeader,
3228 db: &GeneralizedDatabase,
3229 vm_type: VMType,
3230) -> Result<Environment, VMError> {
3231 let chain_config = db.store.get_chain_config()?;
3232 let gas_price =
3233 calculate_gas_price_for_generic(tx, header.base_fee_per_gas.unwrap_or(INITIAL_BASE_FEE));
3234 let block_excess_blob_gas = header.excess_blob_gas;
3235 let config = EVMConfig::new_from_chain_config(&chain_config, header);
3236
3237 let slot_number = if let VMType::L2(_) = vm_type {
3240 U256::zero()
3241 } else if config.fork >= Fork::Amsterdam {
3242 header
3243 .slot_number
3244 .map(U256::from)
3245 .ok_or(VMError::Internal(InternalError::Custom(
3246 "slot_number must be present in Amsterdam+ blocks".to_string(),
3247 )))?
3248 } else {
3249 header.slot_number.map(U256::from).unwrap_or(U256::zero())
3252 };
3253
3254 Ok(Environment {
3255 origin: tx.from.0.into(),
3256 gas_limit: tx
3257 .gas
3258 .unwrap_or(get_max_allowed_gas_limit(header.gas_limit, config.fork)), config,
3260 block_number: header.number,
3261 coinbase: header.coinbase,
3262 timestamp: header.timestamp,
3263 prev_randao: Some(header.prev_randao),
3264 slot_number,
3265 chain_id: chain_config.chain_id.into(),
3266 base_fee_per_gas: header.base_fee_per_gas.unwrap_or_default().into(),
3267 base_blob_fee_per_gas: get_base_fee_per_blob_gas(block_excess_blob_gas, &config)?,
3268 gas_price,
3269 block_excess_blob_gas,
3270 block_blob_gas_used: header.blob_gas_used,
3271 tx_blob_hashes: tx.blob_versioned_hashes.clone(),
3272 tx_max_priority_fee_per_gas: tx.max_priority_fee_per_gas.map(U256::from),
3273 tx_max_fee_per_gas: tx.max_fee_per_gas.map(U256::from),
3274 tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
3275 tx_nonce: tx.nonce.unwrap_or_default(),
3276 block_gas_limit: header.gas_limit,
3277 difficulty: header.difficulty,
3278 is_privileged: false,
3279 fee_token: tx.fee_token,
3280 disable_balance_check: false,
3281 is_system_call: false,
3282 })
3283}
3284
3285fn generic_tx_to_transaction(tx: &GenericTransaction) -> Result<Transaction, VMError> {
3290 Ok(match &tx.authorization_list {
3291 Some(authorization_list) => Transaction::EIP7702Transaction(EIP7702Transaction {
3292 to: match tx.to {
3293 TxKind::Call(to) => to,
3294 TxKind::Create => {
3295 return Err(InternalError::msg("Generic Tx cannot be create type").into());
3296 }
3297 },
3298 value: tx.value,
3299 data: tx.input.clone(),
3300 access_list: tx
3301 .access_list
3302 .iter()
3303 .map(|list| (list.address, list.storage_keys.clone()))
3304 .collect(),
3305 authorization_list: authorization_list
3306 .iter()
3307 .map(|auth| Into::<AuthorizationTuple>::into(auth.clone()))
3308 .collect(),
3309 ..Default::default()
3310 }),
3311 None => Transaction::EIP1559Transaction(EIP1559Transaction {
3312 to: tx.to.clone(),
3313 value: tx.value,
3314 data: tx.input.clone(),
3315 access_list: tx
3316 .access_list
3317 .iter()
3318 .map(|list| (list.address, list.storage_keys.clone()))
3319 .collect(),
3320 ..Default::default()
3321 }),
3322 })
3323}
3324
3325fn vm_from_generic<'a>(
3326 tx: &'a Transaction,
3327 env: Environment,
3328 db: &'a mut GeneralizedDatabase,
3329 vm_type: VMType,
3330 crypto: &'a dyn Crypto,
3331) -> Result<VM<'a>, VMError> {
3332 let vm_type = adjust_disabled_l2_fees(&env, vm_type);
3333 VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)
3334}
3335
3336pub fn get_max_allowed_gas_limit(block_gas_limit: u64, fork: Fork) -> u64 {
3337 if fork >= Fork::Osaka && fork < Fork::Amsterdam {
3345 POST_OSAKA_GAS_LIMIT_CAP
3346 } else {
3347 block_gas_limit
3348 }
3349}
3350
3351#[allow(dead_code)]
3359fn describe_balance_diff(expected: U256, actual: U256) -> String {
3360 let (sign, mag) = if expected >= actual {
3361 ("+", expected - actual)
3362 } else {
3363 ("-", actual - expected)
3364 };
3365 let Ok(mag_u128) = u128::try_from(mag) else {
3366 return format!("{sign}{mag}");
3367 };
3368 if mag_u128 == 0 {
3369 return "0".to_string();
3370 }
3371 let cpsb: u128 = 1530;
3372 let consts = [
3374 ("NEW_ACCOUNT", 120u128),
3375 ("STORAGE_SET", 64),
3376 ("AUTH_BASE", 23),
3377 ("AUTH_TOTAL", 143),
3378 ];
3379 for &gp in &[10u128, 1, 7, 100, 1000, 1_000_000_000] {
3381 if !mag_u128.is_multiple_of(gp) {
3382 continue;
3383 }
3384 let gas = mag_u128 / gp;
3385 if !gas.is_multiple_of(cpsb) {
3386 continue;
3387 }
3388 let bytes = gas / cpsb;
3389 for (name, c) in consts {
3390 if bytes.is_multiple_of(c) {
3391 let n = bytes / c;
3392 return format!(
3393 "{sign}{mag_u128} wei (= {gas} gas at {gp} wei/gas = {n}× {name}_state_gas)"
3394 );
3395 }
3396 }
3397 }
3398 format!("{sign}{mag_u128} wei")
3399}
3400
3401#[cfg(test)]
3402mod bal_tests {
3403 use super::*;
3404 use ethrex_common::H256;
3405 use ethrex_common::types::AccountState;
3406 use ethrex_common::types::block_access_list::{
3407 AccountChanges, BalanceChange, NonceChange, SlotChange, StorageChange,
3408 };
3409 use ethrex_levm::errors::DatabaseError;
3410
3411 fn addr(byte: u8) -> Address {
3412 let mut a = Address::zero();
3413 a.0[19] = byte;
3414 a
3415 }
3416
3417 struct MockStore {
3419 accounts: FxHashMap<Address, AccountState>,
3420 }
3421
3422 impl MockStore {
3423 fn new() -> Self {
3424 Self {
3425 accounts: FxHashMap::default(),
3426 }
3427 }
3428
3429 fn with_account(mut self, address: Address, state: AccountState) -> Self {
3430 self.accounts.insert(address, state);
3431 self
3432 }
3433 }
3434
3435 impl Database for MockStore {
3436 fn get_account_state(&self, address: Address) -> Result<AccountState, DatabaseError> {
3437 Ok(self.accounts.get(&address).copied().unwrap_or_default())
3438 }
3439 fn get_storage_value(&self, _: Address, _: H256) -> Result<U256, DatabaseError> {
3440 Ok(U256::zero())
3441 }
3442 fn get_block_hash(&self, _: u64) -> Result<H256, DatabaseError> {
3443 Ok(H256::zero())
3444 }
3445 fn get_chain_config(&self) -> Result<ethrex_common::types::ChainConfig, DatabaseError> {
3446 Err(DatabaseError::Custom("not implemented".into()))
3447 }
3448 fn get_account_code(&self, _: H256) -> Result<ethrex_common::types::Code, DatabaseError> {
3449 Ok(ethrex_common::types::Code::from_bytecode(
3450 Bytes::new(),
3451 ðrex_crypto::NativeCrypto,
3452 ))
3453 }
3454 fn get_code_metadata(
3455 &self,
3456 _: H256,
3457 ) -> Result<ethrex_common::types::CodeMetadata, DatabaseError> {
3458 Ok(ethrex_common::types::CodeMetadata { length: 0 })
3459 }
3460 }
3461
3462 #[test]
3463 fn test_bal_to_account_updates_basic() {
3464 let address = addr(1);
3466 let store = MockStore::new().with_account(
3467 address,
3468 AccountState {
3469 balance: U256::from(100),
3470 nonce: 5,
3471 code_hash: *EMPTY_KECCAK_HASH,
3472 storage_root: H256::zero(),
3473 },
3474 );
3475
3476 let bal = BlockAccessList::from_accounts(vec![
3477 AccountChanges::new(address)
3478 .with_balance_changes(vec![
3479 BalanceChange::new(1, U256::from(90)),
3480 BalanceChange::new(2, U256::from(80)),
3481 ])
3482 .with_nonce_changes(vec![NonceChange::new(1, 6)])
3483 .with_storage_changes(vec![SlotChange::with_changes(
3484 U256::from(42),
3485 vec![StorageChange::new(1, U256::from(999))],
3486 )]),
3487 ]);
3488
3489 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3490 assert_eq!(updates.len(), 1);
3491 let u = &updates[0];
3492 assert_eq!(u.address, address);
3493 assert!(!u.removed);
3494 let info = u.info.as_ref().unwrap();
3495 assert_eq!(info.balance, U256::from(80));
3497 assert_eq!(info.nonce, 6);
3498 assert_eq!(info.code_hash, *EMPTY_KECCAK_HASH);
3499 let key = ethrex_common::utils::u256_to_h256(U256::from(42));
3501 assert_eq!(*u.added_storage.get(&key).unwrap(), U256::from(999));
3502 }
3503
3504 #[test]
3505 fn test_bal_to_account_updates_highest_index_wins() {
3506 let address = addr(2);
3508 let store = MockStore::new().with_account(
3509 address,
3510 AccountState {
3511 balance: U256::from(1000),
3512 nonce: 0,
3513 code_hash: *EMPTY_KECCAK_HASH,
3514 storage_root: H256::zero(),
3515 },
3516 );
3517
3518 let bal = BlockAccessList::from_accounts(vec![
3519 AccountChanges::new(address).with_balance_changes(vec![
3520 BalanceChange::new(1, U256::from(900)),
3521 BalanceChange::new(2, U256::from(800)),
3522 BalanceChange::new(3, U256::from(700)),
3523 ]),
3524 ]);
3525
3526 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3527 assert_eq!(updates.len(), 1);
3528 assert_eq!(updates[0].info.as_ref().unwrap().balance, U256::from(700));
3529 }
3530
3531 #[test]
3532 fn test_bal_to_account_updates_reads_only_skipped() {
3533 let address = addr(3);
3535 let store = MockStore::new();
3536
3537 let bal = BlockAccessList::from_accounts(vec![
3538 AccountChanges::new(address).with_storage_reads(vec![U256::from(1)]),
3539 ]);
3540
3541 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3542 assert!(updates.is_empty());
3543 }
3544
3545 #[test]
3546 fn test_bal_to_account_updates_removal() {
3547 let address = addr(4);
3549 let store = MockStore::new().with_account(
3550 address,
3551 AccountState {
3552 balance: U256::from(50),
3553 nonce: 1,
3554 code_hash: *EMPTY_KECCAK_HASH,
3555 storage_root: H256::zero(),
3556 },
3557 );
3558
3559 let bal = BlockAccessList::from_accounts(vec![
3560 AccountChanges::new(address)
3561 .with_balance_changes(vec![BalanceChange::new(1, U256::zero())])
3562 .with_nonce_changes(vec![NonceChange::new(1, 0)]),
3563 ]);
3564
3565 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3566 assert_eq!(updates.len(), 1);
3567 assert!(updates[0].removed);
3568 }
3569
3570 #[test]
3571 fn test_bal_to_account_updates_storage_zero() {
3572 let address = addr(5);
3574 let store = MockStore::new();
3575
3576 let bal = BlockAccessList::from_accounts(vec![
3577 AccountChanges::new(address).with_storage_changes(vec![SlotChange::with_changes(
3578 U256::from(7),
3579 vec![StorageChange::new(1, U256::zero())],
3580 )]),
3581 ]);
3582
3583 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3584 assert_eq!(updates.len(), 1);
3585 let key = ethrex_common::utils::u256_to_h256(U256::from(7));
3586 assert_eq!(*updates[0].added_storage.get(&key).unwrap(), U256::zero());
3587 }
3588
3589 #[test]
3590 fn test_bal_to_account_updates_code_deployment() {
3591 let address = addr(6);
3593 let store = MockStore::new();
3594 let code = Bytes::from(vec![0x60, 0x00, 0x60, 0x00, 0xf3]); let expected_hash =
3596 ethrex_common::types::Code::from_bytecode(code.clone(), ðrex_crypto::NativeCrypto)
3597 .hash;
3598
3599 let bal = BlockAccessList::from_accounts(vec![
3600 AccountChanges::new(address)
3601 .with_code_changes(vec![
3602 ethrex_common::types::block_access_list::CodeChange::new(1, code.clone()),
3603 ])
3604 .with_nonce_changes(vec![NonceChange::new(1, 1)]),
3605 ]);
3606
3607 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3608 assert_eq!(updates.len(), 1);
3609 let u = &updates[0];
3610 assert_eq!(u.info.as_ref().unwrap().code_hash, expected_hash);
3611 assert_eq!(u.code.as_ref().unwrap().code(), &code[..]);
3612 }
3613}
3614
3615#[cfg(test)]
3616mod system_call_coinbase_tests {
3617 use super::*;
3623 use ethrex_common::types::{AccountState, AccountUpdate, ChainConfig, Code, CodeMetadata};
3624 use ethrex_crypto::NativeCrypto;
3625 use ethrex_levm::db::Database;
3626 use ethrex_levm::errors::DatabaseError;
3627 use std::sync::Arc;
3628
3629 const HISTORY_RUNTIME_CODE: &str = concat!(
3631 "3373fffffffffffffffffffffffffffffffffffffffe1460465760203603604257",
3632 "5f35600143038111604257611fff81430311604257611fff900654",
3633 "5f5260205ff35b5f5ffd5b5f35611fff60014303065500",
3634 );
3635
3636 struct Store {
3637 chain_config: ChainConfig,
3638 history_code: Code,
3639 }
3640
3641 impl Database for Store {
3642 fn get_account_state(&self, address: Address) -> Result<AccountState, DatabaseError> {
3643 if address == HISTORY_STORAGE_ADDRESS.address {
3644 return Ok(AccountState {
3645 nonce: 1,
3646 code_hash: self.history_code.hash,
3647 ..Default::default()
3648 });
3649 }
3650 Ok(AccountState::default())
3651 }
3652 fn get_storage_value(&self, _: Address, _: H256) -> Result<U256, DatabaseError> {
3653 Ok(U256::zero())
3654 }
3655 fn get_block_hash(&self, _: u64) -> Result<H256, DatabaseError> {
3656 Ok(H256::zero())
3657 }
3658 fn get_chain_config(&self) -> Result<ChainConfig, DatabaseError> {
3659 Ok(self.chain_config)
3660 }
3661 fn get_account_code(&self, code_hash: H256) -> Result<Code, DatabaseError> {
3662 if code_hash == self.history_code.hash {
3663 return Ok(self.history_code.clone());
3664 }
3665 Ok(Code::default())
3666 }
3667 fn get_code_metadata(&self, code_hash: H256) -> Result<CodeMetadata, DatabaseError> {
3668 let length = if code_hash == self.history_code.hash {
3669 self.history_code.len() as u64
3670 } else {
3671 0
3672 };
3673 Ok(CodeMetadata { length })
3674 }
3675 }
3676
3677 fn history_code() -> Code {
3678 let bytes = hex::decode(HISTORY_RUNTIME_CODE).expect("history runtime code is valid hex");
3679 Code::from_bytecode(Bytes::from(bytes), &NativeCrypto)
3680 }
3681
3682 fn prague_db() -> GeneralizedDatabase {
3683 GeneralizedDatabase::new(Arc::new(Store {
3684 chain_config: ChainConfig {
3685 prague_time: Some(0),
3686 ..Default::default()
3687 },
3688 history_code: history_code(),
3689 }))
3690 }
3691
3692 fn parent_hash_value(parent_hash: H256) -> U256 {
3693 U256::from_big_endian(parent_hash.as_bytes())
3694 }
3695
3696 fn history_slot(block_number: u64) -> H256 {
3697 H256::from_low_u64_be((block_number - 1) % 8191)
3698 }
3699
3700 fn run_history_update(coinbase: Address) -> (Option<U256>, Vec<AccountUpdate>, H256) {
3704 let mut db = prague_db();
3705 let parent_hash = H256::from_low_u64_be(0x2935);
3706 let block_number = 42;
3707 let header = BlockHeader {
3708 parent_hash,
3709 coinbase,
3710 number: block_number,
3711 timestamp: 1,
3712 ..Default::default()
3713 };
3714
3715 LEVM::process_block_hash_history(&header, &mut db, VMType::L1, &NativeCrypto)
3716 .expect("history system call executes");
3717
3718 let slot = history_slot(block_number);
3719 let stored_value = db
3720 .current_accounts_state
3721 .get(&HISTORY_STORAGE_ADDRESS.address)
3722 .and_then(|account| account.storage.get(&slot).copied());
3723 let updates =
3724 LEVM::get_state_transitions(&mut db).expect("state transitions are generated");
3725
3726 (stored_value, updates, parent_hash)
3727 }
3728
3729 fn assert_history_write_emitted(coinbase: Address) {
3730 let (stored_value, updates, parent_hash) = run_history_update(coinbase);
3731 let slot = history_slot(42);
3732 assert_eq!(
3733 stored_value,
3734 Some(parent_hash_value(parent_hash)),
3735 "history storage must hold the parent hash after the system call"
3736 );
3737 assert!(
3738 updates.iter().any(|update| {
3739 update.address == HISTORY_STORAGE_ADDRESS.address
3740 && update.added_storage.get(&slot) == Some(&parent_hash_value(parent_hash))
3741 }),
3742 "the history-contract storage write must be emitted as a state update"
3743 );
3744 }
3745
3746 #[test]
3747 fn ordinary_coinbase_preserves_history_storage_write() {
3748 assert_history_write_emitted(Address::from_low_u64_be(0xbeef));
3749 }
3750
3751 #[test]
3754 fn history_address_coinbase_preserves_history_storage_write() {
3755 assert_history_write_emitted(HISTORY_STORAGE_ADDRESS.address);
3756 }
3757}