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")))]
180#[derive(Debug, thiserror::Error)]
181pub enum BalValidationError {
182 #[error("{0}")]
183 Mismatch(String),
184 #[error("{0}")]
185 Database(String),
186}
187
188impl LEVM {
189 pub fn execute_block(
194 block: &Block,
195 db: &mut GeneralizedDatabase,
196 vm_type: VMType,
197 crypto: &dyn Crypto,
198 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
199 let chain_config = db.store.get_chain_config()?;
200 let is_amsterdam = chain_config.is_amsterdam_activated(block.header.timestamp);
201
202 debug_assert!(
207 block.body.transactions.len() < u32::MAX as usize,
208 "tx count overflows u32 BlockAccessIndex"
209 );
210
211 if is_amsterdam {
213 db.enable_bal_recording();
214 db.set_bal_index(0);
216 }
217
218 Self::prepare_block(block, db, vm_type, crypto)?;
219
220 let evm_config = EVMConfig::new_from_chain_config(&chain_config, &block.header);
224 let chain_id = chain_config.chain_id;
225 let base_blob_fee_per_gas =
226 get_base_fee_per_blob_gas(block.header.excess_blob_gas, &evm_config)?;
227 let mut shared_stack_pool = Vec::with_capacity(STACK_LIMIT);
229 let mut shared_memory_pool = Vec::with_capacity(1);
230
231 let n_txs = block.body.transactions.len();
232 let mut receipts = Vec::with_capacity(n_txs);
233 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(n_txs);
234 let mut cumulative_gas_used = 0_u64;
236 let mut block_gas_used = 0_u64;
238 let mut block_regular_gas_used = 0_u64;
240 let mut block_state_gas_used = 0_u64;
241 let transactions_with_sender =
242 block
243 .body
244 .get_transactions_with_sender(crypto)
245 .map_err(|error| {
246 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
247 })?;
248
249 for (tx_idx, (tx, tx_sender)) in transactions_with_sender.into_iter().enumerate() {
250 if !is_amsterdam {
257 check_gas_limit(cumulative_gas_used, tx.gas_limit(), block.header.gas_limit)?;
258 }
259
260 if is_amsterdam {
262 check_2d_gas_allowance(
263 tx,
264 block_regular_gas_used,
265 block_state_gas_used,
266 block.header.gas_limit,
267 )?;
268 }
269
270 if is_amsterdam {
272 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
273 db.set_bal_index(bal_index);
274
275 if let Some(recorder) = db.bal_recorder_mut() {
279 recorder.record_touched_address(tx_sender);
280 }
281 }
282
283 let report = Self::execute_tx_in_block(
284 tx,
285 tx_sender,
286 &block.header,
287 db,
288 vm_type,
289 base_blob_fee_per_gas,
290 &mut shared_stack_pool,
291 &mut shared_memory_pool,
292 false,
293 crypto,
294 evm_config,
295 chain_id,
296 )?;
297
298 tx_gas_breakdowns.push(TxGasBreakdown::from_report(
299 tx_idx,
300 tx.hash(crypto),
301 &report,
302 ));
303
304 cumulative_gas_used += report.gas_spent;
306
307 let tx_state_gas = report.state_gas_used;
310 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
311 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
312 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
313
314 if is_amsterdam {
315 block_gas_used = block_regular_gas_used.max(block_state_gas_used);
317 ::tracing::debug!(
318 "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}",
319 report.gas_used,
320 report.gas_spent,
321 );
322
323 if block_regular_gas_used > block.header.gas_limit
328 || block_state_gas_used > block.header.gas_limit
329 {
330 return Err(EvmError::Transaction(format!(
331 "Gas allowance exceeded: Block gas used overflow: \
332 block_gas_used {block_gas_used} > block_gas_limit {}",
333 block.header.gas_limit
334 )));
335 }
336 } else {
337 block_gas_used = block_gas_used.saturating_add(report.gas_used);
338 }
339
340 let mut receipt = Receipt::new(
341 tx.tx_type(),
342 matches!(report.result, TxResult::Success),
343 cumulative_gas_used,
344 report.logs,
345 );
346
347 if matches!(tx, Transaction::FrameTransaction(_)) {
349 receipt.payer = report.payer_address;
350 receipt.frame_receipts = frame_receipts_from(report.frame_results);
351 }
352
353 receipts.push(receipt);
354 }
355
356 if is_amsterdam && block_gas_used > block.header.gas_limit {
360 return Err(EvmError::Transaction(format!(
361 "Gas allowance exceeded: Block gas used overflow: \
362 block_gas_used {block_gas_used} > block_gas_limit {}",
363 block.header.gas_limit
364 )));
365 }
366
367 if is_amsterdam {
370 let post_tx_index =
371 u32::try_from(block.body.transactions.len() + 1).unwrap_or(u32::MAX);
372 db.set_bal_index(post_tx_index);
373
374 if let Some(withdrawals) = &block.body.withdrawals
378 && let Some(recorder) = db.bal_recorder_mut()
379 {
380 recorder.extend_touched_addresses(withdrawals.iter().map(|w| w.address));
381 }
382 }
383
384 let requests = match vm_type {
388 VMType::L1 => extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?,
389 VMType::L2(_) => Default::default(),
390 };
391
392 if let Some(withdrawals) = &block.body.withdrawals {
393 Self::process_withdrawals(db, withdrawals)?;
394 }
395
396 let bal = db.take_bal();
398
399 Ok((
400 BlockExecutionResult {
401 receipts,
402 requests,
403 block_gas_used,
404 tx_gas_breakdowns,
405 },
406 bal,
407 ))
408 }
409
410 #[allow(clippy::too_many_arguments)]
414 pub fn execute_block_pipeline(
415 block: &Block,
416 db: &mut GeneralizedDatabase,
417 vm_type: VMType,
418 merkleizer: Option<Sender<Vec<AccountUpdate>>>,
419 queue_length: &AtomicUsize,
420 crypto: &dyn Crypto,
421 header_bal: Option<Arc<BlockAccessList>>,
422 bal_parallel_exec_enabled: bool,
423 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
424 let chain_config = db.store.get_chain_config()?;
425 let is_amsterdam = chain_config.is_amsterdam_activated(block.header.timestamp);
426 let evm_config = EVMConfig::new_from_chain_config(&chain_config, &block.header);
429 let chain_id = chain_config.chain_id;
430
431 debug_assert!(
433 block.body.transactions.len() < u32::MAX as usize,
434 "tx count overflows u32 BlockAccessIndex"
435 );
436
437 let transactions_with_sender =
438 block
439 .body
440 .get_transactions_with_sender(crypto)
441 .map_err(|error| {
442 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
443 })?;
444
445 #[cfg(any(feature = "eip-8025", not(feature = "rayon")))]
446 let _ = (header_bal, bal_parallel_exec_enabled);
449 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
450 if let Some(bal) = header_bal
456 && is_amsterdam
457 && bal_parallel_exec_enabled
458 {
459 validate_header_bal_indices(&bal, block.body.transactions.len())
465 .map_err(|e| EvmError::Custom(e.to_string()))?;
466
467 Self::prepare_block(block, db, vm_type, crypto)?;
470
471 let validation_index = Arc::new(bal.build_validation_index());
473
474 Self::validate_bal_pre_exec_index(db, &bal, &validation_index)?;
481
482 LEVM::get_state_transitions_tx(db)?;
484 let system_seed = Arc::new(std::mem::take(&mut db.initial_accounts_state));
485
486 let parallel_result = Self::execute_block_parallel(
487 block,
488 &transactions_with_sender,
489 db,
490 vm_type,
491 Arc::clone(&bal),
492 merkleizer.as_ref(),
493 queue_length,
494 system_seed,
495 crypto,
496 Arc::clone(&validation_index),
497 );
498
499 let (
504 receipts,
505 block_gas_used,
506 mut unread_storage_reads,
507 mut unaccessed_pure_accounts,
508 tx_gas_breakdowns,
509 ) = match parallel_result {
510 Ok(result) => result,
511 Err(parallel_err) => {
512 let last_tx_idx =
513 u32::try_from(block.body.transactions.len()).unwrap_or(u32::MAX);
514 if Self::seed_db_from_bal(
515 db,
516 &bal,
517 last_tx_idx,
518 &validation_index.accounts_by_min_index,
519 )
520 .is_ok()
521 && let VMType::L1 = vm_type
522 && let Err(e @ EvmError::SystemContractCallFailed(_)) =
523 extract_all_requests_levm(&[], db, &block.header, vm_type, crypto)
524 {
525 return Err(e);
526 }
527 return Err(parallel_err);
528 }
529 };
530
531 let last_tx_idx = u32::try_from(block.body.transactions.len()).unwrap_or(u32::MAX);
536 Self::seed_db_from_bal(
538 db,
539 &bal,
540 last_tx_idx,
541 &validation_index.accounts_by_min_index,
542 )?;
543
544 let requests = match vm_type {
546 VMType::L1 => {
547 extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?
548 }
549 VMType::L2(_) => Default::default(),
550 };
551
552 if let Some(withdrawals) = &block.body.withdrawals {
553 Self::process_withdrawals(db, withdrawals)?;
554 }
555 let withdrawal_idx = u32::try_from(block.body.transactions.len())
563 .map(|n| n.saturating_add(1))
564 .unwrap_or(u32::MAX);
565 Self::validate_bal_withdrawal_index(db, &bal, withdrawal_idx, &validation_index)?;
566
567 if !unread_storage_reads.is_empty() {
569 for (addr, acct) in &db.current_accounts_state {
570 for key in acct.storage.keys() {
571 unread_storage_reads.remove(&(*addr, *key));
572 }
573 }
574 }
575
576 if !unaccessed_pure_accounts.is_empty() {
581 if let Some(withdrawals) = &block.body.withdrawals {
582 for w in withdrawals {
583 unaccessed_pure_accounts.remove(&w.address);
584 }
585 }
586 for addr in db.current_accounts_state.keys() {
587 if *addr == SYSTEM_ADDRESS {
591 continue;
592 }
593 unaccessed_pure_accounts.remove(addr);
594 }
595 }
596
597 if let Some((addr, key)) = unread_storage_reads.iter().next() {
599 let slot = ethrex_common::BigEndianHash::into_uint(key);
600 return Err(EvmError::Custom(format!(
601 "BAL validation failed: storage_read for account {addr:?} slot \
602 {slot} was never actually read during block execution"
603 )));
604 }
605
606 if let Some(addr) = unaccessed_pure_accounts.iter().next() {
608 return Err(EvmError::Custom(format!(
609 "BAL validation failed: account {addr:?} has no mutations \
610 and no storage reads but was never accessed during block execution"
611 )));
612 }
613
614 validate_block_access_list_size(&block.header, &chain_config, &bal)
617 .map_err(|e| EvmError::Custom(e.to_string()))?;
618
619 return Ok((
620 BlockExecutionResult {
621 receipts,
622 requests,
623 block_gas_used,
624 tx_gas_breakdowns,
625 },
626 None,
627 ));
628 }
629
630 let Some(merkleizer) = merkleizer else {
636 return Err(EvmError::Custom(
637 "sequential execution path called without a merkleizer Sender".to_string(),
638 ));
639 };
640 if is_amsterdam {
641 db.enable_bal_recording();
642 db.set_bal_index(0);
644 }
645
646 Self::prepare_block(block, db, vm_type, crypto)?;
647
648 let base_blob_fee_per_gas =
650 get_base_fee_per_blob_gas(block.header.excess_blob_gas, &evm_config)?;
651
652 let mut shared_stack_pool = Vec::with_capacity(STACK_LIMIT);
653 let mut shared_memory_pool = Vec::with_capacity(1);
655
656 let n_txs = block.body.transactions.len();
657 let mut receipts = Vec::with_capacity(n_txs);
658 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(n_txs);
659 let mut cumulative_gas_used = 0_u64;
661 let mut block_gas_used = 0_u64;
663 let mut block_regular_gas_used = 0_u64;
665 let mut block_state_gas_used = 0_u64;
666 let mut tx_since_last_flush = 2;
669
670 for (tx_idx, (tx, tx_sender)) in transactions_with_sender.into_iter().enumerate() {
671 if !is_amsterdam {
678 check_gas_limit(cumulative_gas_used, tx.gas_limit(), block.header.gas_limit)?;
679 }
680
681 if is_amsterdam {
683 check_2d_gas_allowance(
684 tx,
685 block_regular_gas_used,
686 block_state_gas_used,
687 block.header.gas_limit,
688 )?;
689 }
690
691 if is_amsterdam {
693 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
694 db.set_bal_index(bal_index);
695
696 if let Some(recorder) = db.bal_recorder_mut() {
700 recorder.record_touched_address(tx_sender);
701 }
702 }
703
704 let report = Self::execute_tx_in_block(
705 tx,
706 tx_sender,
707 &block.header,
708 db,
709 vm_type,
710 base_blob_fee_per_gas,
711 &mut shared_stack_pool,
712 &mut shared_memory_pool,
713 false,
714 crypto,
715 evm_config,
716 chain_id,
717 )?;
718
719 tx_gas_breakdowns.push(TxGasBreakdown::from_report(
720 tx_idx,
721 tx.hash(crypto),
722 &report,
723 ));
724
725 if queue_length.load(Ordering::Relaxed) == 0 && tx_since_last_flush > 5 {
726 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
727 tx_since_last_flush = 0;
728 } else {
729 tx_since_last_flush += 1;
730 }
731
732 cumulative_gas_used += report.gas_spent;
734
735 let tx_state_gas = report.state_gas_used;
738 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
739 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
740 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
741
742 if is_amsterdam {
743 block_gas_used = block_regular_gas_used.max(block_state_gas_used);
745
746 if block_regular_gas_used > block.header.gas_limit
751 || block_state_gas_used > block.header.gas_limit
752 {
753 return Err(EvmError::Transaction(format!(
754 "Gas allowance exceeded: Block gas used overflow: \
755 block_gas_used {block_gas_used} > block_gas_limit {}",
756 block.header.gas_limit
757 )));
758 }
759 } else {
760 block_gas_used = block_gas_used.saturating_add(report.gas_used);
761 }
762
763 let mut receipt = Receipt::new(
764 tx.tx_type(),
765 matches!(report.result, TxResult::Success),
766 cumulative_gas_used,
767 report.logs,
768 );
769
770 if matches!(tx, Transaction::FrameTransaction(_)) {
772 receipt.payer = report.payer_address;
773 receipt.frame_receipts = frame_receipts_from(report.frame_results);
774 }
775
776 receipts.push(receipt);
777 }
778
779 if is_amsterdam && block_gas_used > block.header.gas_limit {
783 return Err(EvmError::Transaction(format!(
784 "Gas allowance exceeded: Block gas used overflow: \
785 block_gas_used {block_gas_used} > block_gas_limit {}",
786 block.header.gas_limit
787 )));
788 }
789
790 #[cfg(feature = "perf_opcode_timings")]
791 {
792 let mut timings = OPCODE_TIMINGS.lock().expect("poison");
793 timings.inc_tx_count(receipts.len());
794 timings.inc_block_count();
795 ::tracing::info!("{}", timings.info_pretty());
796 let precompiles_timings = PRECOMPILES_TIMINGS.lock().expect("poison");
797 ::tracing::info!("{}", precompiles_timings.info_pretty());
798 }
799
800 if queue_length.load(Ordering::Relaxed) == 0 {
801 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
802 }
803
804 if is_amsterdam {
807 let post_tx_index =
808 u32::try_from(block.body.transactions.len() + 1).unwrap_or(u32::MAX);
809 db.set_bal_index(post_tx_index);
810
811 if let Some(withdrawals) = &block.body.withdrawals
813 && let Some(recorder) = db.bal_recorder_mut()
814 {
815 recorder.extend_touched_addresses(withdrawals.iter().map(|w| w.address));
816 }
817 }
818
819 let requests = match vm_type {
823 VMType::L1 => extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?,
824 VMType::L2(_) => Default::default(),
825 };
826
827 if let Some(withdrawals) = &block.body.withdrawals {
828 Self::process_withdrawals(db, withdrawals)?;
829 }
830 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
831
832 let bal = db.take_bal();
834
835 Ok((
836 BlockExecutionResult {
837 receipts,
838 requests,
839 block_gas_used,
840 tx_gas_breakdowns,
841 },
842 bal,
843 ))
844 }
845
846 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
851 fn bal_to_account_updates(
852 bal: &BlockAccessList,
853 store: &dyn Database,
854 ) -> Result<Vec<AccountUpdate>, EvmError> {
855 use ethrex_common::types::AccountInfo;
856
857 let mut updates = Vec::new();
858
859 let write_addrs: Vec<Address> = bal
861 .accounts()
862 .iter()
863 .filter(|ac| {
864 !ac.balance_changes.is_empty()
865 || !ac.nonce_changes.is_empty()
866 || !ac.code_changes.is_empty()
867 || !ac.storage_changes.is_empty()
868 })
869 .map(|ac| ac.address)
870 .collect();
871 store
872 .prefetch_accounts(&write_addrs)
873 .map_err(|e| EvmError::Custom(format!("bal_to_account_updates prefetch: {e}")))?;
874
875 for acct_changes in bal.accounts() {
876 let addr = acct_changes.address;
877
878 let has_writes = !acct_changes.balance_changes.is_empty()
880 || !acct_changes.nonce_changes.is_empty()
881 || !acct_changes.code_changes.is_empty()
882 || !acct_changes.storage_changes.is_empty();
883 if !has_writes {
884 continue;
885 }
886
887 let prestate = store
889 .get_account_state(addr)
890 .map_err(|e| EvmError::Custom(format!("bal_to_account_updates: {e}")))?;
891
892 let balance = acct_changes
894 .balance_changes
895 .last()
896 .map(|c| c.post_balance)
897 .unwrap_or(prestate.balance);
898
899 let nonce = acct_changes
901 .nonce_changes
902 .last()
903 .map(|c| c.post_nonce)
904 .unwrap_or(prestate.nonce);
905
906 let (code_hash, code) = if let Some(c) = acct_changes.code_changes.last() {
908 code_from_bal(&c.new_code)
909 } else {
910 (prestate.code_hash, None)
911 };
912
913 let mut added_storage = FxHashMap::with_capacity_and_hasher(
915 acct_changes.storage_changes.len(),
916 Default::default(),
917 );
918 for slot_change in &acct_changes.storage_changes {
919 if let Some(last) = slot_change.slot_changes.last() {
920 let key = ethrex_common::utils::u256_to_h256(slot_change.slot);
921 added_storage.insert(key, last.post_value);
922 }
923 }
924
925 let post_empty = balance.is_zero() && nonce == 0 && code_hash == *EMPTY_KECCAK_HASH;
927 let pre_empty = prestate.balance.is_zero()
928 && prestate.nonce == 0
929 && prestate.code_hash == *EMPTY_KECCAK_HASH;
930 let removed = post_empty && !pre_empty;
931
932 let balance_changed = acct_changes
933 .balance_changes
934 .last()
935 .is_some_and(|c| c.post_balance != prestate.balance);
936 let nonce_changed = acct_changes
937 .nonce_changes
938 .last()
939 .is_some_and(|c| c.post_nonce != prestate.nonce);
940 let code_changed = acct_changes.code_changes.last().is_some();
941 let acc_info_updated = balance_changed || nonce_changed || code_changed;
942
943 if !removed && !acc_info_updated && added_storage.is_empty() {
944 continue;
945 }
946
947 let info = if acc_info_updated {
948 Some(AccountInfo {
949 code_hash,
950 balance,
951 nonce,
952 })
953 } else {
954 None
955 };
956
957 let update = AccountUpdate {
958 address: addr,
959 removed,
960 info,
961 code,
962 added_storage,
963 removed_storage: false,
970 };
971 updates.push(update);
972 }
973
974 Ok(updates)
975 }
976
977 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
994 fn seed_db_from_bal(
995 db: &mut GeneralizedDatabase,
996 bal: &BlockAccessList,
997 max_idx: u32,
998 accounts_by_min_index: &[(u32, usize)],
999 ) -> Result<(), EvmError> {
1000 let end = accounts_by_min_index.partition_point(|(min_idx, _)| *min_idx <= max_idx);
1001 let bal_accounts = bal.accounts();
1002 for &(_, acct_idx) in &accounts_by_min_index[..end] {
1003 seed_one_address_info_from_bal(db, bal, acct_idx, max_idx)
1004 .map_err(|e| EvmError::Custom(format!("seed_db_from_bal: {e}")))?;
1005
1006 let acct_changes = &bal_accounts[acct_idx];
1007 if acct_changes.storage_changes.is_empty() {
1008 continue;
1009 }
1010 let any_storage = acct_changes.storage_changes.iter().any(|sc| {
1011 sc.slot_changes
1012 .first()
1013 .is_some_and(|c| c.block_access_index <= max_idx)
1014 });
1015 if !any_storage {
1016 continue;
1017 }
1018 let addr = acct_changes.address;
1019 if !db.current_accounts_state.contains_key(&addr) {
1020 db.get_account(addr)
1021 .map_err(|e| EvmError::Custom(format!("seed storage: {e}")))?;
1022 }
1023 let acc = db
1024 .get_account_mut(addr)
1025 .map_err(|e| EvmError::Custom(format!("seed storage mut: {e}")))?;
1026 for sc in &acct_changes.storage_changes {
1027 if let Some(value) = post_value_at_or_before(sc, max_idx) {
1028 acc.storage
1029 .insert(ethrex_common::utils::u256_to_h256(sc.slot), value);
1030 }
1031 }
1032 }
1033 Ok(())
1034 }
1035
1036 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1043 #[allow(clippy::too_many_arguments, clippy::type_complexity)]
1044 fn execute_block_parallel(
1045 block: &Block,
1046 txs_with_sender: &[(&Transaction, Address)],
1047 db: &mut GeneralizedDatabase,
1048 vm_type: VMType,
1049 bal: Arc<BlockAccessList>,
1050 merkleizer: Option<&Sender<Vec<AccountUpdate>>>,
1051 queue_length: &AtomicUsize,
1052 system_seed: Arc<CacheDB>,
1053 crypto: &dyn Crypto,
1054 validation_index: Arc<BalAddressIndex>,
1055 ) -> Result<
1056 (
1057 Vec<Receipt>,
1058 u64,
1059 FxHashSet<(Address, H256)>,
1060 FxHashSet<Address>,
1061 Vec<TxGasBreakdown>,
1062 ),
1063 EvmError,
1064 > {
1065 let store = db.store.clone();
1066 let header = &block.header;
1067 let n_txs = txs_with_sender.len();
1068 let chain_config = store.get_chain_config()?;
1073 let is_amsterdam = chain_config.is_amsterdam_activated(header.timestamp);
1074 let evm_config = EVMConfig::new_from_chain_config(&chain_config, header);
1077 let chain_id = chain_config.chain_id;
1078 let base_blob_fee_per_gas = get_base_fee_per_blob_gas(header.excess_blob_gas, &evm_config)?;
1080 debug_assert!(
1081 is_amsterdam,
1082 "execute_block_parallel invoked on non-Amsterdam block"
1083 );
1084
1085 if let Some(merkleizer) = merkleizer {
1089 let account_updates = Self::bal_to_account_updates(&bal, store.as_ref())?;
1090 merkleizer
1091 .send(account_updates)
1092 .map_err(|e| EvmError::Custom(format!("merkleizer send failed: {e}")))?;
1093 queue_length.fetch_add(1, Ordering::Relaxed);
1094 }
1095
1096 let mut unread_storage_reads: FxHashSet<(Address, H256)> = FxHashSet::default();
1099 let mut unaccessed_pure_accounts: FxHashSet<Address> = FxHashSet::default();
1102 for acct in bal.accounts() {
1103 for &slot in &acct.storage_reads {
1104 let key = ethrex_common::utils::u256_to_h256(slot);
1105 unread_storage_reads.insert((acct.address, key));
1106 }
1107 let is_pure = acct.storage_changes.is_empty()
1108 && acct.storage_reads.is_empty()
1109 && acct.balance_changes.is_empty()
1110 && acct.nonce_changes.is_empty()
1111 && acct.code_changes.is_empty();
1112 if is_pure {
1113 unaccessed_pure_accounts.insert(acct.address);
1114 }
1115 }
1116
1117 for addr in system_seed.keys() {
1123 if *addr == SYSTEM_ADDRESS {
1124 continue;
1125 }
1126 unaccessed_pure_accounts.remove(addr);
1127 }
1128
1129 unread_storage_reads.retain(|(addr, key)| {
1131 !system_seed
1132 .get(addr)
1133 .is_some_and(|a| a.storage.contains_key(key))
1134 });
1135
1136 let arc_bal = bal;
1138 let arc_idx = validation_index;
1139
1140 type TxExecResult = (
1154 usize,
1155 TxType,
1156 ExecutionReport,
1157 FxHashSet<Address>, Vec<(Address, H256)>, Vec<Address>, Option<EvmError>, );
1162
1163 let exec_results: Result<Vec<TxExecResult>, EvmError> = (0..n_txs)
1164 .into_par_iter()
1165 .map(|tx_idx| -> Result<_, EvmError> {
1166 let (tx, sender) = &txs_with_sender[tx_idx];
1167 let mut tx_db = GeneralizedDatabase::new_with_shared_base_and_capacity(
1169 store.clone(),
1170 system_seed.clone(),
1171 32,
1172 );
1173 tx_db.lazy_bal = Some(LazyBalCursor {
1174 bal: arc_bal.clone(),
1175 bal_index: u32::try_from(tx_idx + 1).unwrap_or(u32::MAX),
1176 index: arc_idx.clone(),
1177 });
1178 let mut stack_pool = Vec::with_capacity(8);
1181 let mut memory_pool = Vec::with_capacity(1);
1183
1184 tx_db.accessed_accounts =
1188 Some(FxHashSet::with_capacity_and_hasher(16, Default::default()));
1189
1190 tx_db.enable_bal_recording();
1195 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
1196 tx_db.set_bal_index(bal_index);
1197 if let Some(recorder) = tx_db.bal_recorder_mut() {
1201 recorder.record_touched_address(*sender);
1202 }
1203
1204 let report = LEVM::execute_tx_in_block(
1205 tx,
1206 *sender,
1207 header,
1208 &mut tx_db,
1209 vm_type,
1210 base_blob_fee_per_gas,
1211 &mut stack_pool,
1212 &mut memory_pool,
1213 false,
1214 crypto,
1215 evm_config,
1216 chain_id,
1217 )?;
1218
1219 let current_state = std::mem::take(&mut tx_db.current_accounts_state);
1220 let codes = std::mem::take(&mut tx_db.codes);
1221 let mut tracked = tx_db.accessed_accounts.take().unwrap_or_default();
1222 let (shadow_touched, shadow_reads, tx_initial) = tx_db
1229 .bal_recorder
1230 .take()
1231 .map(|mut r| {
1232 (
1233 r.take_touched_addresses(),
1234 r.take_storage_reads(),
1235 r.take_tx_initial_storage(),
1236 )
1237 })
1238 .unwrap_or_default();
1239 let pure_reads: FxHashSet<(Address, U256)> = shadow_reads
1248 .iter()
1249 .copied()
1250 .filter(|k| !tx_initial.contains_key(k))
1251 .collect();
1252
1253 let mut reads_satisfied: Vec<(Address, H256)> =
1266 Vec::with_capacity(current_state.len() * 4);
1267 let mut destroyed: Vec<Address> = Vec::new();
1271 for (addr, acct) in ¤t_state {
1272 if matches!(
1273 acct.status,
1274 AccountStatus::Destroyed | AccountStatus::DestroyedModified
1275 ) {
1276 destroyed.push(*addr);
1277 } else {
1278 for key in acct.storage.keys() {
1279 reads_satisfied.push((*addr, *key));
1280 }
1281 }
1282 }
1283 for (addr, slot) in &shadow_reads {
1291 if destroyed.contains(addr) {
1292 reads_satisfied.push((*addr, ethrex_common::utils::u256_to_h256(*slot)));
1293 }
1294 }
1295
1296 let deferred_bal_err: Option<EvmError> = (|| -> Result<(), EvmError> {
1302 let bal_idx = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
1303 let seed_idx = u32::try_from(tx_idx).unwrap_or(u32::MAX);
1304 Self::validate_tx_execution(
1305 bal_idx,
1306 seed_idx,
1307 ¤t_state,
1308 &codes,
1309 &arc_bal,
1310 &arc_idx,
1311 &system_seed,
1312 &store,
1313 &pure_reads,
1314 &tx_initial,
1315 )
1316 .map_err(|e| match e {
1317 BalValidationError::Database(msg) => {
1321 EvmError::DB(format!("BAL validation for tx {tx_idx}: {msg}"))
1322 }
1323 BalValidationError::Mismatch(msg) => EvmError::Custom(format!(
1324 "BAL validation failed for tx {tx_idx}: {msg}"
1325 )),
1326 })?;
1327
1328 for addr in &shadow_touched {
1330 if !arc_idx.addr_to_idx.contains_key(addr) {
1331 return Err(EvmError::Custom(format!(
1332 "BAL validation failed for tx {tx_idx}: account {addr:?} was \
1333 accessed during execution but is missing from BAL"
1334 )));
1335 }
1336 }
1337 for (addr, slot) in &shadow_reads {
1338 let Some(&bal_acct_idx) = arc_idx.addr_to_idx.get(addr) else {
1339 continue;
1341 };
1342 let acct = &arc_bal.accounts()[bal_acct_idx];
1343 let in_changes = acct
1344 .storage_changes
1345 .binary_search_by(|sc| sc.slot.cmp(slot))
1346 .is_ok();
1347 debug_assert!(
1354 acct.storage_reads.windows(2).all(|w| w[0] < w[1]),
1355 "storage_reads must be strictly ascending for binary_search"
1356 );
1357 let in_reads = acct.storage_reads.binary_search(slot).is_ok();
1358 if !in_changes && !in_reads {
1359 return Err(EvmError::Custom(format!(
1360 "BAL validation failed for tx {tx_idx}: storage slot {slot} of \
1361 account {addr:?} was read during execution but is missing from \
1362 BAL (no storage_changes or storage_reads entry)"
1363 )));
1364 }
1365 }
1366 Ok(())
1367 })()
1368 .err();
1369
1370 drop(current_state);
1371 drop(codes);
1372
1373 tracked.extend(shadow_touched.iter().copied());
1382
1383 Ok((
1384 tx_idx,
1385 tx.tx_type(),
1386 report,
1387 tracked,
1388 reads_satisfied,
1389 destroyed,
1390 deferred_bal_err,
1391 ))
1392 })
1393 .collect();
1394
1395 let mut exec_results = exec_results?;
1396
1397 exec_results.sort_unstable_by_key(|(idx, _, _, _, _, _, _)| *idx);
1404
1405 let mut block_regular_gas_used = 0_u64;
1410 let mut block_state_gas_used = 0_u64;
1411 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(exec_results.len());
1412 for (tx_idx, _, report, _, _, _, _) in &exec_results {
1413 let (tx, _tx_sender) = txs_with_sender
1414 .get(*tx_idx)
1415 .ok_or_else(|| EvmError::Custom(format!("tx index {tx_idx} out of bounds")))?;
1416 if is_amsterdam {
1417 check_2d_gas_allowance(
1418 tx,
1419 block_regular_gas_used,
1420 block_state_gas_used,
1421 header.gas_limit,
1422 )?;
1423 }
1424
1425 tx_gas_breakdowns.push(TxGasBreakdown::from_report(
1426 *tx_idx,
1427 tx.hash(crypto),
1428 report,
1429 ));
1430
1431 let tx_state_gas = report.state_gas_used;
1432 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
1433 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
1434 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
1435 }
1436 let block_gas_used = block_regular_gas_used.max(block_state_gas_used);
1437 if block_gas_used > header.gas_limit {
1439 return Err(EvmError::Transaction(format!(
1440 "Gas allowance exceeded: Block gas used overflow: \
1441 block_gas_used {block_gas_used} > block_gas_limit {}",
1442 header.gas_limit
1443 )));
1444 }
1445
1446 for (_, _, _, _, _, _, deferred) in &mut exec_results {
1449 if let Some(err) = deferred.take() {
1450 return Err(err);
1451 }
1452 }
1453
1454 for (_, _, _, tracked_accounts, reads_satisfied, _, _) in &exec_results {
1463 if !unread_storage_reads.is_empty() {
1464 for pair in reads_satisfied {
1465 unread_storage_reads.remove(pair);
1466 }
1467 }
1468 if !unaccessed_pure_accounts.is_empty() {
1472 unaccessed_pure_accounts.remove(&header.coinbase);
1473 for addr in tracked_accounts {
1474 unaccessed_pure_accounts.remove(addr);
1475 }
1476 }
1477 }
1478
1479 let mut receipts = Vec::with_capacity(n_txs);
1481 let mut cumulative_gas_used = 0_u64;
1482 for (_, tx_type, report, _, _, _, _) in exec_results {
1483 cumulative_gas_used += report.gas_spent;
1484 let mut receipt = Receipt::new(
1485 tx_type,
1486 matches!(report.result, TxResult::Success),
1487 cumulative_gas_used,
1488 report.logs,
1489 );
1490 if tx_type == TxType::Frame {
1491 receipt.payer = report.payer_address;
1492 receipt.frame_receipts = frame_receipts_from(report.frame_results);
1493 }
1494 receipts.push(receipt);
1495 }
1496
1497 Ok((
1498 receipts,
1499 block_gas_used,
1500 unread_storage_reads,
1501 unaccessed_pure_accounts,
1502 tx_gas_breakdowns,
1503 ))
1504 }
1505
1506 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1509 fn seeded_balance(
1510 seed_idx: u32,
1511 acct: ðrex_common::types::block_access_list::AccountChanges,
1512 system_seed: &CacheDB,
1513 store: &Arc<dyn Database>,
1514 ) -> Result<U256, BalValidationError> {
1515 let pos = acct
1516 .balance_changes
1517 .partition_point(|c| c.block_access_index <= seed_idx);
1518 if pos > 0 {
1519 Ok(acct.balance_changes[pos - 1].post_balance)
1520 } else if let Some(a) = system_seed.get(&acct.address) {
1521 Ok(a.info.balance)
1522 } else {
1523 store
1524 .get_account_state(acct.address)
1525 .map(|a| a.balance)
1526 .map_err(|e| {
1527 BalValidationError::Database(format!(
1528 "DB error reading balance for {:?}: {e}",
1529 acct.address
1530 ))
1531 })
1532 }
1533 }
1534
1535 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1538 fn seeded_code_hash(
1539 seed_idx: u32,
1540 acct: ðrex_common::types::block_access_list::AccountChanges,
1541 system_seed: &CacheDB,
1542 store: &Arc<dyn Database>,
1543 ) -> Result<H256, BalValidationError> {
1544 let pos = acct
1545 .code_changes
1546 .partition_point(|c| c.block_access_index <= seed_idx);
1547 if pos > 0 {
1548 let seeded_code = &acct.code_changes[pos - 1].new_code;
1549 Ok(if seeded_code.is_empty() {
1550 *EMPTY_KECCAK_HASH
1551 } else {
1552 ethrex_common::utils::keccak(seeded_code)
1553 })
1554 } else if let Some(a) = system_seed.get(&acct.address) {
1555 Ok(a.info.code_hash)
1556 } else {
1557 store
1558 .get_account_state(acct.address)
1559 .map(|a| a.code_hash)
1560 .map_err(|e| {
1561 BalValidationError::Database(format!(
1562 "DB error reading account state for {:?}: {e}",
1563 acct.address
1564 ))
1565 })
1566 }
1567 }
1568
1569 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1572 fn seeded_nonce(
1573 seed_idx: u32,
1574 acct: ðrex_common::types::block_access_list::AccountChanges,
1575 system_seed: &CacheDB,
1576 store: &Arc<dyn Database>,
1577 ) -> Result<u64, BalValidationError> {
1578 let pos = acct
1579 .nonce_changes
1580 .partition_point(|c| c.block_access_index <= seed_idx);
1581 if pos > 0 {
1582 Ok(acct.nonce_changes[pos - 1].post_nonce)
1583 } else if let Some(a) = system_seed.get(&acct.address) {
1584 Ok(a.info.nonce)
1585 } else {
1586 store
1587 .get_account_state(acct.address)
1588 .map(|a| a.nonce)
1589 .map_err(|e| {
1590 BalValidationError::Database(format!(
1591 "DB error reading nonce for {:?}: {e}",
1592 acct.address
1593 ))
1594 })
1595 }
1596 }
1597
1598 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1606 fn seeded_account_triple(
1607 seed_idx: u32,
1608 acct: ðrex_common::types::block_access_list::AccountChanges,
1609 system_seed: &CacheDB,
1610 store: &Arc<dyn Database>,
1611 ) -> Result<(U256, u64, H256), BalValidationError> {
1612 let bpos = acct
1613 .balance_changes
1614 .partition_point(|c| c.block_access_index <= seed_idx);
1615 let npos = acct
1616 .nonce_changes
1617 .partition_point(|c| c.block_access_index <= seed_idx);
1618 let cpos = acct
1619 .code_changes
1620 .partition_point(|c| c.block_access_index <= seed_idx);
1621
1622 let mut balance = (bpos > 0).then(|| acct.balance_changes[bpos - 1].post_balance);
1623 let mut nonce = (npos > 0).then(|| acct.nonce_changes[npos - 1].post_nonce);
1624 let mut code_hash = (cpos > 0).then(|| {
1625 let c = &acct.code_changes[cpos - 1].new_code;
1626 if c.is_empty() {
1627 *EMPTY_KECCAK_HASH
1628 } else {
1629 ethrex_common::utils::keccak(c)
1630 }
1631 });
1632
1633 if let Some(a) = system_seed.get(&acct.address) {
1635 balance.get_or_insert(a.info.balance);
1636 nonce.get_or_insert(a.info.nonce);
1637 code_hash.get_or_insert(a.info.code_hash);
1638 }
1639
1640 if balance.is_none() || nonce.is_none() || code_hash.is_none() {
1642 let s = store.get_account_state(acct.address).map_err(|e| {
1643 BalValidationError::Database(format!(
1644 "DB error reading account state for {:?}: {e}",
1645 acct.address
1646 ))
1647 })?;
1648 balance.get_or_insert(s.balance);
1649 nonce.get_or_insert(s.nonce);
1650 code_hash.get_or_insert(s.code_hash);
1651 }
1652
1653 Ok((
1654 balance.unwrap_or_default(),
1655 nonce.unwrap_or_default(),
1656 code_hash.unwrap_or(*EMPTY_KECCAK_HASH),
1657 ))
1658 }
1659
1660 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1665 fn seeded_storage_pre_value(
1666 addr: Address,
1667 key: H256,
1668 slot: U256,
1669 system_seed: &CacheDB,
1670 store: &Arc<dyn Database>,
1671 tx_initial: &FxHashMap<(Address, U256), U256>,
1672 ) -> Result<U256, BalValidationError> {
1673 if let Some(v) = tx_initial.get(&(addr, slot)) {
1674 return Ok(*v);
1675 }
1676 Self::storage_from_seed_or_store(addr, key, slot, system_seed, store)
1677 }
1678
1679 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1682 fn storage_from_seed_or_store(
1683 addr: Address,
1684 key: H256,
1685 slot: U256,
1686 system_seed: &CacheDB,
1687 store: &Arc<dyn Database>,
1688 ) -> Result<U256, BalValidationError> {
1689 if let Some(v) = system_seed
1690 .get(&addr)
1691 .and_then(|a| a.storage.get(&key))
1692 .copied()
1693 {
1694 Ok(v)
1695 } else {
1696 store.get_storage_value(addr, key).map_err(|e| {
1697 BalValidationError::Database(format!(
1698 "DB error reading storage for {addr:?} slot {slot}: {e}"
1699 ))
1700 })
1701 }
1702 }
1703
1704 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1711 fn seeded_storage(
1712 seed_idx: u32,
1713 sc: ðrex_common::types::block_access_list::SlotChange,
1714 addr: Address,
1715 key: H256,
1716 system_seed: &CacheDB,
1717 store: &Arc<dyn Database>,
1718 tx_initial: &FxHashMap<(Address, U256), U256>,
1719 ) -> Result<U256, BalValidationError> {
1720 if let Some(v) = tx_initial.get(&(addr, sc.slot)) {
1721 return Ok(*v);
1722 }
1723 let pos = sc
1724 .slot_changes
1725 .partition_point(|c| c.block_access_index <= seed_idx);
1726 if pos > 0 {
1727 Ok(sc.slot_changes[pos - 1].post_value)
1728 } else {
1729 Self::storage_from_seed_or_store(addr, key, sc.slot, system_seed, store)
1730 }
1731 }
1732
1733 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1761 #[allow(clippy::too_many_arguments)]
1762 pub fn validate_tx_execution(
1763 bal_idx: u32,
1764 seed_idx: u32,
1765 current_state: &FxHashMap<Address, LevmAccount>,
1766 codes: &FxHashMap<H256, Code>,
1767 bal: &BlockAccessList,
1768 index: &BalAddressIndex,
1769 system_seed: &CacheDB,
1770 store: &Arc<dyn Database>,
1771 pure_reads: &FxHashSet<(Address, U256)>,
1772 tx_initial: &FxHashMap<(Address, U256), U256>,
1773 ) -> Result<(), BalValidationError> {
1774 if let Some(active_accounts) = index.tx_to_accounts.get(&bal_idx) {
1778 for &acct_inner_idx in active_accounts {
1779 let acct = &bal.accounts()[acct_inner_idx];
1780 let addr = acct.address;
1781 let actual = current_state.get(&addr);
1782 let mut seeded_info: Option<(U256, u64, H256)> = None;
1786
1787 if let Some(expected) = find_exact_change_balance(&acct.balance_changes, bal_idx) {
1789 match actual {
1790 Some(a) if a.info.balance == expected => {
1791 if seeded_info.is_none() {
1795 seeded_info = Some(Self::seeded_account_triple(
1796 seed_idx,
1797 acct,
1798 system_seed,
1799 store,
1800 )?);
1801 }
1802 let seeded = seeded_info.unwrap_or_default().0;
1803 if expected == seeded {
1804 return Err(BalValidationError::Mismatch(format!(
1805 "account {addr:?} has spurious no-op BAL balance change \
1806 at index {bal_idx}: post==pre=={expected}"
1807 )));
1808 }
1809 }
1810 Some(a) => {
1811 return Err(BalValidationError::Mismatch(format!(
1812 "account {addr:?} balance mismatch at index {bal_idx}: BAL={expected}, exec={} (diff={})",
1813 a.info.balance,
1814 describe_balance_diff(expected, a.info.balance),
1815 )));
1816 }
1817 None => {
1818 let seeded = Self::seeded_balance(seed_idx, acct, system_seed, store)?;
1825 if expected == seeded {
1826 return Err(BalValidationError::Mismatch(format!(
1827 "account {addr:?} has spurious no-op BAL balance change \
1828 at index {bal_idx} for an account absent from execution \
1829 state: post==pre=={expected}"
1830 )));
1831 }
1832 let all_bal_indices: Vec<u32> = acct
1834 .balance_changes
1835 .iter()
1836 .map(|c| c.block_access_index)
1837 .collect();
1838 let all_nonce_indices: Vec<u32> = acct
1839 .nonce_changes
1840 .iter()
1841 .map(|c| c.block_access_index)
1842 .collect();
1843 let all_storage_indices: Vec<(u32, u64)> = acct
1844 .storage_changes
1845 .iter()
1846 .flat_map(|sc| {
1847 sc.slot_changes
1848 .iter()
1849 .map(|c| (c.block_access_index, sc.slot.low_u64()))
1850 })
1851 .collect();
1852 let code_indices: Vec<u32> = acct
1853 .code_changes
1854 .iter()
1855 .map(|c| c.block_access_index)
1856 .collect();
1857 return Err(BalValidationError::Mismatch(format!(
1858 "account {addr:?} has BAL balance change at {bal_idx} \
1859 but not in execution state (expected={expected}, pre={seeded}, \
1860 all_bal_idx={all_bal_indices:?}, nonce_idx={all_nonce_indices:?}, \
1861 storage_idx={all_storage_indices:?}, code_idx={code_indices:?})"
1862 )));
1863 }
1864 }
1865 }
1866
1867 if let Some(expected) = find_exact_change_nonce(&acct.nonce_changes, bal_idx) {
1869 match actual {
1870 Some(a) if a.info.nonce == expected => {
1871 if seeded_info.is_none() {
1876 seeded_info = Some(Self::seeded_account_triple(
1877 seed_idx,
1878 acct,
1879 system_seed,
1880 store,
1881 )?);
1882 }
1883 let seeded = seeded_info.unwrap_or_default().1;
1884 if expected == seeded {
1885 return Err(BalValidationError::Mismatch(format!(
1886 "account {addr:?} has spurious no-op BAL nonce change \
1887 at index {bal_idx}: post==pre=={expected}"
1888 )));
1889 }
1890 }
1891 Some(a) => {
1892 return Err(BalValidationError::Mismatch(format!(
1893 "account {addr:?} nonce mismatch at index {bal_idx}: BAL={expected}, exec={}",
1894 a.info.nonce
1895 )));
1896 }
1897 None => {
1898 let seeded = Self::seeded_nonce(seed_idx, acct, system_seed, store)?;
1902 if expected == seeded {
1903 return Err(BalValidationError::Mismatch(format!(
1904 "account {addr:?} has spurious no-op BAL nonce change \
1905 at index {bal_idx} for an account absent from execution \
1906 state: post==pre=={expected}"
1907 )));
1908 }
1909 return Err(BalValidationError::Mismatch(format!(
1910 "account {addr:?} has BAL nonce change at {bal_idx} \
1911 but not in execution state (expected={expected}, pre={seeded})"
1912 )));
1913 }
1914 }
1915 }
1916
1917 if let Some(expected_code) = find_exact_change_code(&acct.code_changes, bal_idx) {
1919 match actual {
1920 Some(a) => {
1921 let actual_code = if let Some(c) = codes.get(&a.info.code_hash) {
1922 c.code_bytes()
1923 } else {
1924 let c = store.get_account_code(a.info.code_hash).map_err(|e| {
1925 BalValidationError::Database(format!(
1926 "DB error reading account code for {addr:?}: {e}"
1927 ))
1928 })?;
1929 c.code_bytes()
1930 };
1931 if actual_code != *expected_code {
1932 return Err(BalValidationError::Mismatch(format!(
1933 "account {addr:?} code mismatch at index {bal_idx}"
1934 )));
1935 }
1936 if seeded_info.is_none() {
1939 seeded_info = Some(Self::seeded_account_triple(
1940 seed_idx,
1941 acct,
1942 system_seed,
1943 store,
1944 )?);
1945 }
1946 let seeded_hash = seeded_info.unwrap_or_default().2;
1947 if a.info.code_hash == seeded_hash {
1948 return Err(BalValidationError::Mismatch(format!(
1949 "account {addr:?} has spurious no-op BAL code change \
1950 at index {bal_idx}"
1951 )));
1952 }
1953 }
1954 None => {
1955 let code_hash = if let Some(a) = system_seed.get(&addr) {
1959 a.info.code_hash
1960 } else {
1961 store
1962 .get_account_state(addr)
1963 .map(|a| a.code_hash)
1964 .map_err(|e| {
1965 BalValidationError::Database(format!(
1966 "DB error reading account state for {addr:?}: {e}"
1967 ))
1968 })?
1969 };
1970 let pre_code = if let Some(c) = codes.get(&code_hash) {
1971 c.code_bytes()
1972 } else {
1973 let c = store.get_account_code(code_hash).map_err(|e| {
1974 BalValidationError::Database(format!(
1975 "DB error reading account code for hash \
1976 {code_hash:?}: {e}"
1977 ))
1978 })?;
1979 c.code_bytes()
1980 };
1981 if *expected_code == pre_code {
1982 return Err(BalValidationError::Mismatch(format!(
1986 "account {addr:?} has spurious no-op BAL code change \
1987 at index {bal_idx} for an account absent from execution state"
1988 )));
1989 }
1990 return Err(BalValidationError::Mismatch(format!(
1991 "account {addr:?} has BAL code change at {bal_idx} \
1992 but not in execution state"
1993 )));
1994 }
1995 }
1996 }
1997
1998 for sc in &acct.storage_changes {
2000 if let Some(expected_value) =
2001 find_exact_change_storage(&sc.slot_changes, bal_idx)
2002 {
2003 let key = ethrex_common::utils::u256_to_h256(sc.slot);
2004 let actual_value = actual.and_then(|a| a.storage.get(&key)).copied();
2005 if actual_value != Some(expected_value) {
2006 if actual.is_none() || actual_value.is_none() {
2012 let seeded = Self::seeded_storage(
2013 seed_idx,
2014 sc,
2015 addr,
2016 key,
2017 system_seed,
2018 store,
2019 tx_initial,
2020 )?;
2021 if expected_value == seeded {
2022 return Err(BalValidationError::Mismatch(format!(
2023 "account {addr:?} has spurious no-op BAL storage \
2024 change for slot {} at index {bal_idx} for a slot \
2025 absent from execution state: post==pre=={expected_value}",
2026 sc.slot
2027 )));
2028 }
2029 }
2030 return Err(BalValidationError::Mismatch(format!(
2031 "account {addr:?} storage slot {} mismatch at index {bal_idx}: \
2032 BAL={expected_value}, exec={actual_value:?}",
2033 sc.slot
2034 )));
2035 }
2036 let seeded = Self::seeded_storage(
2040 seed_idx,
2041 sc,
2042 addr,
2043 key,
2044 system_seed,
2045 store,
2046 tx_initial,
2047 )?;
2048 if expected_value == seeded {
2049 return Err(BalValidationError::Mismatch(format!(
2050 "account {addr:?} has spurious no-op BAL storage change for \
2051 slot {} at index {bal_idx}",
2052 sc.slot
2053 )));
2054 }
2055 }
2056 }
2057 }
2058 }
2059
2060 for (addr, account) in current_state {
2063 if account.is_unmodified() {
2064 continue;
2065 }
2066
2067 let Some(&bal_acct_idx) = index.addr_to_idx.get(addr) else {
2068 let pre = system_seed
2072 .get(addr)
2073 .map(|a| (a.info.balance, a.info.nonce, a.info.code_hash))
2074 .or_else(|| {
2075 store
2076 .get_account_state(*addr)
2077 .ok()
2078 .map(|a| (a.balance, a.nonce, a.code_hash))
2079 })
2080 .unwrap_or_default();
2081 let post = (
2082 account.info.balance,
2083 account.info.nonce,
2084 account.info.code_hash,
2085 );
2086 if pre != post {
2087 return Err(BalValidationError::Mismatch(format!(
2088 "account {addr:?} was modified by execution but is absent from BAL"
2089 )));
2090 }
2091 continue;
2092 };
2093
2094 let acct = &bal.accounts()[bal_acct_idx];
2095
2096 if !has_exact_change_balance(&acct.balance_changes, bal_idx) {
2098 let seeded = Self::seeded_balance(seed_idx, acct, system_seed, store)?;
2099 if account.info.balance != seeded {
2100 return Err(BalValidationError::Mismatch(format!(
2101 "account {addr:?} balance changed by execution ({}) but BAL has no \
2102 balance change at index {bal_idx} (seeded={seeded})",
2103 account.info.balance
2104 )));
2105 }
2106 }
2107
2108 if !has_exact_change_nonce(&acct.nonce_changes, bal_idx) {
2110 let seeded = Self::seeded_nonce(seed_idx, acct, system_seed, store)?;
2111 if account.info.nonce != seeded {
2112 return Err(BalValidationError::Mismatch(format!(
2113 "account {addr:?} nonce changed by execution ({}) but BAL has no \
2114 nonce change at index {bal_idx} (seeded={seeded})",
2115 account.info.nonce
2116 )));
2117 }
2118 }
2119
2120 if !has_exact_change_code(&acct.code_changes, bal_idx) {
2124 let seeded_hash = Self::seeded_code_hash(seed_idx, acct, system_seed, store)?;
2125 if account.info.code_hash != seeded_hash {
2126 return Err(BalValidationError::Mismatch(format!(
2127 "account {addr:?} code changed by execution but BAL has no \
2128 code change at index {bal_idx} (seeded_hash={seeded_hash:?})"
2129 )));
2130 }
2131 }
2132
2133 for (key_h256, &value) in &account.storage {
2135 let slot_u256 = u256_from_big_endian_const(key_h256.0);
2136 let pos = acct
2138 .storage_changes
2139 .partition_point(|sc| sc.slot < slot_u256);
2140 if pos < acct.storage_changes.len() && acct.storage_changes[pos].slot == slot_u256 {
2141 let sc = &acct.storage_changes[pos];
2142 if !has_exact_change_storage(&sc.slot_changes, bal_idx) {
2143 let seeded = Self::seeded_storage(
2148 seed_idx,
2149 sc,
2150 *addr,
2151 *key_h256,
2152 system_seed,
2153 store,
2154 tx_initial,
2155 )?;
2156 if value != seeded {
2157 return Err(BalValidationError::Mismatch(format!(
2158 "account {addr:?} storage slot {slot_u256} changed by \
2159 execution ({value}) but BAL has no change at index \
2160 {bal_idx} (seeded={seeded})"
2161 )));
2162 }
2163 }
2164 }
2165 else if !pure_reads.contains(&(*addr, slot_u256)) {
2179 let pre_value = Self::seeded_storage_pre_value(
2184 *addr,
2185 *key_h256,
2186 slot_u256,
2187 system_seed,
2188 store,
2189 tx_initial,
2190 )?;
2191 if value != pre_value {
2192 let declared_as_read = acct.storage_reads.contains(&slot_u256);
2193 return Err(BalValidationError::Mismatch(format!(
2194 "account {addr:?} storage slot {slot_u256} was written by \
2195 execution ({value}) but has no storage_changes entry at index \
2196 {bal_idx} (pre_value={pre_value}, declared_as_read={declared_as_read})"
2197 )));
2198 }
2199 }
2200 }
2201 }
2202
2203 Ok(())
2204 }
2205
2206 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2218 fn validate_bal_withdrawal_index(
2219 db: &GeneralizedDatabase,
2220 bal: &BlockAccessList,
2221 withdrawal_idx: u32,
2222 index: &BalAddressIndex,
2223 ) -> Result<(), EvmError> {
2224 for acct in bal.accounts() {
2227 let addr = acct.address;
2228 let actual = db.current_accounts_state.get(&addr);
2229
2230 if let Some(expected) = find_exact_change_balance(&acct.balance_changes, withdrawal_idx)
2232 {
2233 match actual {
2234 Some(a) if a.info.balance == expected => {}
2235 Some(a) => {
2236 return Err(EvmError::Custom(format!(
2237 "BAL validation failed for withdrawal: account {addr:?} balance \
2238 mismatch at index {withdrawal_idx}: BAL={expected}, actual={}",
2239 a.info.balance
2240 )));
2241 }
2242 None => {
2243 return Err(EvmError::Custom(format!(
2244 "BAL validation failed for withdrawal: account {addr:?} has \
2245 balance change at index {withdrawal_idx} but was not touched \
2246 by withdrawal/request phase"
2247 )));
2248 }
2249 }
2250 }
2251
2252 if let Some(expected) = find_exact_change_nonce(&acct.nonce_changes, withdrawal_idx) {
2254 match actual {
2255 Some(a) if a.info.nonce == expected => {}
2256 Some(a) => {
2257 return Err(EvmError::Custom(format!(
2258 "BAL validation failed for withdrawal: account {addr:?} nonce \
2259 mismatch at index {withdrawal_idx}: BAL={expected}, actual={}",
2260 a.info.nonce
2261 )));
2262 }
2263 None => {
2264 return Err(EvmError::Custom(format!(
2265 "BAL validation failed for withdrawal: account {addr:?} has \
2266 nonce change at index {withdrawal_idx} but was not touched \
2267 by withdrawal/request phase"
2268 )));
2269 }
2270 }
2271 }
2272
2273 if let Some(expected_code) = find_exact_change_code(&acct.code_changes, withdrawal_idx)
2275 {
2276 let code_hash = if expected_code.is_empty() {
2277 *EMPTY_KECCAK_HASH
2278 } else {
2279 ethrex_common::utils::keccak(expected_code)
2280 };
2281 match actual {
2282 Some(a) if a.info.code_hash == code_hash => {}
2283 Some(_) => {
2284 return Err(EvmError::Custom(format!(
2285 "BAL validation failed for withdrawal: account {addr:?} code \
2286 mismatch at index {withdrawal_idx}"
2287 )));
2288 }
2289 None => {
2290 return Err(EvmError::Custom(format!(
2291 "BAL validation failed for withdrawal: account {addr:?} has \
2292 code change at index {withdrawal_idx} but was not touched \
2293 by withdrawal/request phase"
2294 )));
2295 }
2296 }
2297 }
2298
2299 for sc in &acct.storage_changes {
2301 if let Some(expected_value) =
2302 find_exact_change_storage(&sc.slot_changes, withdrawal_idx)
2303 {
2304 let key = ethrex_common::utils::u256_to_h256(sc.slot);
2305 let actual_value = actual.and_then(|a| a.storage.get(&key)).copied();
2306 if actual_value != Some(expected_value) {
2307 return Err(EvmError::Custom(format!(
2308 "BAL validation failed for withdrawal: account {addr:?} storage \
2309 slot {} mismatch at index {withdrawal_idx}: BAL={expected_value}, \
2310 actual={actual_value:?}",
2311 sc.slot
2312 )));
2313 }
2314 }
2315 }
2316 }
2317
2318 for (addr, account) in &db.current_accounts_state {
2321 if account.is_unmodified() {
2322 continue;
2323 }
2324
2325 let Some(&bal_acct_idx) = index.addr_to_idx.get(addr) else {
2326 let pre_state = db.store.get_account_state(*addr).map_err(|e| {
2330 EvmError::Custom(format!(
2331 "BAL validation failed for withdrawal: db error reading \
2332 account {addr:?}: {e}"
2333 ))
2334 })?;
2335 let pre = (pre_state.balance, pre_state.nonce, pre_state.code_hash);
2336 let post = (
2337 account.info.balance,
2338 account.info.nonce,
2339 account.info.code_hash,
2340 );
2341 if pre != post {
2342 return Err(EvmError::Custom(format!(
2343 "BAL validation failed for withdrawal: account {addr:?} was modified \
2344 during withdrawal/request phase but is absent from BAL"
2345 )));
2346 }
2347 for (key_h256, &value) in &account.storage {
2350 let pre_value = db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
2351 EvmError::Custom(format!(
2352 "BAL validation failed for withdrawal: db error reading \
2353 storage {addr:?}[{}]: {e}",
2354 u256_from_big_endian_const(key_h256.0)
2355 ))
2356 })?;
2357 if value != pre_value {
2358 return Err(EvmError::Custom(format!(
2359 "BAL validation failed for withdrawal: account {addr:?} storage \
2360 slot {} changed during withdrawal/request phase but is absent \
2361 from BAL",
2362 u256_from_big_endian_const(key_h256.0)
2363 )));
2364 }
2365 }
2366 continue;
2367 };
2368
2369 let acct = &bal.accounts()[bal_acct_idx];
2370
2371 if !has_exact_change_balance(&acct.balance_changes, withdrawal_idx) {
2374 let seeded = match acct.balance_changes.last() {
2375 Some(c) => c.post_balance,
2376 None => {
2377 db.store
2378 .get_account_state(*addr)
2379 .map_err(|e| {
2380 EvmError::Custom(format!(
2381 "BAL validation failed for withdrawal: db error reading \
2382 account {addr:?}: {e}"
2383 ))
2384 })?
2385 .balance
2386 }
2387 };
2388 if account.info.balance != seeded {
2389 return Err(EvmError::Custom(format!(
2390 "BAL validation failed for withdrawal: account {addr:?} balance \
2391 changed during withdrawal/request phase ({}) but BAL has no \
2392 balance change at index {withdrawal_idx} (last_bal={seeded})",
2393 account.info.balance
2394 )));
2395 }
2396 }
2397
2398 if !has_exact_change_nonce(&acct.nonce_changes, withdrawal_idx) {
2400 let seeded = match acct.nonce_changes.last() {
2401 Some(c) => c.post_nonce,
2402 None => {
2403 db.store
2404 .get_account_state(*addr)
2405 .map_err(|e| {
2406 EvmError::Custom(format!(
2407 "BAL validation failed for withdrawal: db error reading \
2408 account {addr:?}: {e}"
2409 ))
2410 })?
2411 .nonce
2412 }
2413 };
2414 if account.info.nonce != seeded {
2415 return Err(EvmError::Custom(format!(
2416 "BAL validation failed for withdrawal: account {addr:?} nonce \
2417 changed during withdrawal/request phase ({}) but BAL has no \
2418 nonce change at index {withdrawal_idx} (last_bal={seeded})",
2419 account.info.nonce
2420 )));
2421 }
2422 }
2423
2424 if !has_exact_change_code(&acct.code_changes, withdrawal_idx) {
2426 let seeded_hash = match acct.code_changes.last() {
2427 Some(c) if c.new_code.is_empty() => *EMPTY_KECCAK_HASH,
2428 Some(c) => ethrex_common::utils::keccak(&c.new_code),
2429 None => {
2430 db.store
2431 .get_account_state(*addr)
2432 .map_err(|e| {
2433 EvmError::Custom(format!(
2434 "BAL validation failed for withdrawal: db error reading \
2435 account {addr:?}: {e}"
2436 ))
2437 })?
2438 .code_hash
2439 }
2440 };
2441 if account.info.code_hash != seeded_hash {
2442 return Err(EvmError::Custom(format!(
2443 "BAL validation failed for withdrawal: account {addr:?} code \
2444 changed during withdrawal/request phase but BAL has no \
2445 code change at index {withdrawal_idx} \
2446 (actual={:?}, last_bal={seeded_hash:?})",
2447 account.info.code_hash
2448 )));
2449 }
2450 }
2451
2452 for (key_h256, &value) in &account.storage {
2455 let slot_u256 = u256_from_big_endian_const(key_h256.0);
2456 let pos = acct
2457 .storage_changes
2458 .partition_point(|sc| sc.slot < slot_u256);
2459 if pos < acct.storage_changes.len() && acct.storage_changes[pos].slot == slot_u256 {
2460 let sc = &acct.storage_changes[pos];
2461 if !has_exact_change_storage(&sc.slot_changes, withdrawal_idx) {
2462 let seeded = match sc.slot_changes.last() {
2465 Some(c) => c.post_value,
2466 None => db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
2467 EvmError::Custom(format!(
2468 "BAL validation failed for withdrawal: db error reading \
2469 storage {addr:?}[{slot_u256}]: {e}"
2470 ))
2471 })?,
2472 };
2473 if value != seeded {
2474 return Err(EvmError::Custom(format!(
2475 "BAL validation failed for withdrawal: account {addr:?} \
2476 storage slot {slot_u256} changed during withdrawal/request \
2477 phase ({value}) but BAL has no change at index \
2478 {withdrawal_idx} (last_bal={seeded})"
2479 )));
2480 }
2481 }
2482 } else {
2483 let pre_value = db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
2486 EvmError::Custom(format!(
2487 "BAL validation failed for withdrawal: db error reading \
2488 storage {addr:?}[{slot_u256}]: {e}"
2489 ))
2490 })?;
2491 if value != pre_value {
2492 return Err(EvmError::Custom(format!(
2493 "BAL validation failed for withdrawal: account {addr:?} \
2494 storage slot {slot_u256} changed during withdrawal/request \
2495 phase ({value}) but slot is absent from BAL storage_changes \
2496 (pre={pre_value})"
2497 )));
2498 }
2499 }
2500 }
2501 }
2502
2503 Ok(())
2504 }
2505
2506 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2524 fn validate_bal_pre_exec_index(
2525 db: &GeneralizedDatabase,
2526 bal: &BlockAccessList,
2527 index: &BalAddressIndex,
2528 ) -> Result<(), EvmError> {
2529 const PRE_EXEC_IDX: u32 = 0;
2530 let Some(active_accounts) = index.tx_to_accounts.get(&PRE_EXEC_IDX) else {
2531 return Ok(());
2532 };
2533
2534 let pre_account = |addr: Address| -> Result<(U256, u64, H256), EvmError> {
2539 let s = db.store.get_account_state(addr).map_err(|e| {
2540 EvmError::Custom(format!(
2541 "BAL validation failed for pre-exec: db error reading account {addr:?}: {e}"
2542 ))
2543 })?;
2544 Ok((s.balance, s.nonce, s.code_hash))
2545 };
2546
2547 for &acct_inner_idx in active_accounts {
2548 let acct = &bal.accounts()[acct_inner_idx];
2549 let addr = acct.address;
2550 let actual = db.current_accounts_state.get(&addr);
2551
2552 if let Some(expected) = find_exact_change_balance(&acct.balance_changes, PRE_EXEC_IDX) {
2554 match actual {
2555 Some(a) if a.info.balance == expected => {
2556 if expected == pre_account(addr)?.0 {
2557 return Err(EvmError::Custom(format!(
2558 "BAL validation failed for pre-exec: account {addr:?} has spurious \
2559 no-op balance change at index 0: post==pre=={expected}"
2560 )));
2561 }
2562 }
2563 Some(a) => {
2564 return Err(EvmError::Custom(format!(
2565 "BAL validation failed for pre-exec: account {addr:?} balance mismatch \
2566 at index 0: BAL={expected}, actual={}",
2567 a.info.balance
2568 )));
2569 }
2570 None => {
2571 return Err(EvmError::Custom(format!(
2572 "BAL validation failed for pre-exec: account {addr:?} has balance \
2573 change at index 0 but was not touched by the pre-exec phase"
2574 )));
2575 }
2576 }
2577 }
2578
2579 if let Some(expected) = find_exact_change_nonce(&acct.nonce_changes, PRE_EXEC_IDX) {
2581 match actual {
2582 Some(a) if a.info.nonce == expected => {
2583 if expected == pre_account(addr)?.1 {
2584 return Err(EvmError::Custom(format!(
2585 "BAL validation failed for pre-exec: account {addr:?} has spurious \
2586 no-op nonce change at index 0: post==pre=={expected}"
2587 )));
2588 }
2589 }
2590 Some(a) => {
2591 return Err(EvmError::Custom(format!(
2592 "BAL validation failed for pre-exec: account {addr:?} nonce mismatch \
2593 at index 0: BAL={expected}, actual={}",
2594 a.info.nonce
2595 )));
2596 }
2597 None => {
2598 return Err(EvmError::Custom(format!(
2599 "BAL validation failed for pre-exec: account {addr:?} has nonce \
2600 change at index 0 but was not touched by the pre-exec phase"
2601 )));
2602 }
2603 }
2604 }
2605
2606 if let Some(expected_code) = find_exact_change_code(&acct.code_changes, PRE_EXEC_IDX) {
2608 let expected_hash = if expected_code.is_empty() {
2609 *EMPTY_KECCAK_HASH
2610 } else {
2611 ethrex_common::utils::keccak(expected_code)
2612 };
2613 match actual {
2614 Some(a) if a.info.code_hash == expected_hash => {
2615 if expected_hash == pre_account(addr)?.2 {
2616 return Err(EvmError::Custom(format!(
2617 "BAL validation failed for pre-exec: account {addr:?} has spurious \
2618 no-op code change at index 0"
2619 )));
2620 }
2621 }
2622 Some(_) => {
2623 return Err(EvmError::Custom(format!(
2624 "BAL validation failed for pre-exec: account {addr:?} code mismatch \
2625 at index 0"
2626 )));
2627 }
2628 None => {
2629 return Err(EvmError::Custom(format!(
2630 "BAL validation failed for pre-exec: account {addr:?} has code \
2631 change at index 0 but was not touched by the pre-exec phase"
2632 )));
2633 }
2634 }
2635 }
2636
2637 for sc in &acct.storage_changes {
2639 let Some(expected_value) =
2640 find_exact_change_storage(&sc.slot_changes, PRE_EXEC_IDX)
2641 else {
2642 continue;
2643 };
2644 let key = ethrex_common::utils::u256_to_h256(sc.slot);
2645 let actual_value = actual.and_then(|a| a.storage.get(&key)).copied();
2646 if actual_value != Some(expected_value) {
2647 return Err(EvmError::Custom(format!(
2648 "BAL validation failed for pre-exec: account {addr:?} storage slot {} \
2649 mismatch at index 0: BAL={expected_value}, actual={actual_value:?}",
2650 sc.slot
2651 )));
2652 }
2653 let pre_value = db.store.get_storage_value(addr, key).map_err(|e| {
2654 EvmError::Custom(format!(
2655 "BAL validation failed for pre-exec: db error reading storage {addr:?} \
2656 slot {}: {e}",
2657 sc.slot
2658 ))
2659 })?;
2660 if expected_value == pre_value {
2661 return Err(EvmError::Custom(format!(
2662 "BAL validation failed for pre-exec: account {addr:?} has spurious no-op \
2663 storage change for slot {} at index 0: post==pre=={expected_value}",
2664 sc.slot
2665 )));
2666 }
2667 }
2668 }
2669
2670 Ok(())
2671 }
2672
2673 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2679 pub fn warm_block(
2680 block: &Block,
2681 store: Arc<dyn Database>,
2682 vm_type: VMType,
2683 crypto: &dyn Crypto,
2684 cancelled: &AtomicBool,
2685 ) -> Result<(), EvmError> {
2686 let txs_with_sender = block
2687 .body
2688 .get_transactions_with_sender(crypto)
2689 .map_err(|error| {
2690 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
2691 })?;
2692
2693 Self::warm_txs(
2694 &txs_with_sender,
2695 &block.header,
2696 store.clone(),
2697 vm_type,
2698 crypto,
2699 &|| cancelled.load(Ordering::Relaxed),
2700 )?;
2701
2702 let mut db = GeneralizedDatabase::new(store.clone());
2703
2704 if cancelled.load(Ordering::Relaxed) {
2705 return Ok(());
2706 }
2707
2708 for withdrawal in block
2709 .body
2710 .withdrawals
2711 .iter()
2712 .flatten()
2713 .filter(|withdrawal| withdrawal.amount > 0)
2714 {
2715 db.get_account_mut(withdrawal.address).map_err(|_| {
2716 EvmError::DB(format!(
2717 "Withdrawal account {} not found",
2718 withdrawal.address
2719 ))
2720 })?;
2721 }
2722 Ok(())
2723 }
2724
2725 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2733 pub fn warm_txs(
2734 txs_with_sender: &[(&Transaction, Address)],
2735 header: &BlockHeader,
2736 store: Arc<dyn Database>,
2737 vm_type: VMType,
2738 crypto: &dyn Crypto,
2739 should_stop: &(dyn Fn() -> bool + Sync),
2740 ) -> Result<(), EvmError> {
2741 let mut sender_groups: FxHashMap<Address, Vec<&Transaction>> = FxHashMap::default();
2744 for (tx, sender) in txs_with_sender {
2745 sender_groups.entry(*sender).or_default().push(tx);
2746 }
2747
2748 let chain_config = store.get_chain_config()?;
2751 let evm_config = EVMConfig::new_from_chain_config(&chain_config, header);
2752 let chain_id = chain_config.chain_id;
2753 let base_blob_fee_per_gas = get_base_fee_per_blob_gas(header.excess_blob_gas, &evm_config)?;
2755
2756 sender_groups.into_par_iter().for_each_with(
2759 Vec::with_capacity(STACK_LIMIT),
2760 |stack_pool, (sender, txs)| {
2761 if should_stop() {
2762 return;
2763 }
2764 let mut memory_pool = Vec::with_capacity(1);
2768 let mut group_db = GeneralizedDatabase::new(store.clone());
2770 for tx in txs {
2771 if should_stop() {
2772 return;
2773 }
2774 let _ = Self::execute_tx_in_block(
2775 tx,
2776 sender,
2777 header,
2778 &mut group_db,
2779 vm_type,
2780 base_blob_fee_per_gas,
2781 stack_pool,
2782 &mut memory_pool,
2783 true,
2784 crypto,
2785 evm_config,
2786 chain_id,
2787 );
2788 }
2789 },
2790 );
2791 Ok(())
2792 }
2793
2794 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2797 pub fn bal_storage_slots(bal: &BlockAccessList) -> Vec<(Address, H256)> {
2798 bal.accounts()
2799 .iter()
2800 .flat_map(|ac| {
2801 ac.all_storage_slots()
2802 .map(move |slot| (ac.address, H256::from_uint(&slot)))
2803 })
2804 .collect()
2805 }
2806
2807 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2816 pub fn warm_block_from_bal(
2817 bal: &BlockAccessList,
2818 store: Arc<dyn Database>,
2819 cancelled: &AtomicBool,
2820 ) -> Result<(), EvmError> {
2821 let accounts = bal.accounts();
2822 if accounts.is_empty() {
2823 return Ok(());
2824 }
2825
2826 let account_addresses: Vec<Address> = accounts.iter().map(|ac| ac.address).collect();
2832 store
2833 .prefetch_accounts(&account_addresses)
2834 .map_err(|e| EvmError::Custom(format!("prefetch_accounts: {e}")))?;
2835
2836 if cancelled.load(Ordering::Relaxed) {
2837 return Ok(());
2838 }
2839
2840 let code_hashes: Vec<ethrex_common::H256> = accounts
2844 .par_iter()
2845 .filter_map(|ac| {
2846 store
2847 .get_account_state(ac.address)
2848 .ok()
2849 .filter(|s| s.code_hash != *EMPTY_KECCAK_HASH)
2850 .map(|s| s.code_hash)
2851 })
2852 .collect();
2853 code_hashes.par_iter().for_each(|&h| {
2854 let _ = store.get_account_code(h);
2855 });
2856
2857 Ok(())
2858 }
2859
2860 fn send_state_transitions_tx(
2861 merkleizer: &Sender<Vec<AccountUpdate>>,
2862 db: &mut GeneralizedDatabase,
2863 queue_length: &AtomicUsize,
2864 ) -> Result<(), EvmError> {
2865 let transitions = LEVM::get_state_transitions_tx(db)?;
2866 merkleizer
2867 .send(transitions)
2868 .map_err(|e| EvmError::Custom(format!("send failed: {e}")))?;
2869 queue_length.fetch_add(1, Ordering::Relaxed);
2870 Ok(())
2871 }
2872
2873 fn setup_env(
2874 tx: &Transaction,
2875 tx_sender: Address,
2876 block_header: &BlockHeader,
2877 db: &GeneralizedDatabase,
2878 vm_type: VMType,
2879 ) -> Result<Environment, EvmError> {
2880 let chain_config = db.store.get_chain_config()?;
2884 let config = EVMConfig::new_from_chain_config(&chain_config, block_header);
2885 let base_blob_fee_per_gas =
2886 get_base_fee_per_blob_gas(block_header.excess_blob_gas, &config)?;
2887 Self::setup_env_with_config(
2888 tx,
2889 tx_sender,
2890 block_header,
2891 config,
2892 chain_config.chain_id,
2893 vm_type,
2894 base_blob_fee_per_gas,
2895 )
2896 }
2897
2898 fn setup_env_with_config(
2902 tx: &Transaction,
2903 tx_sender: Address,
2904 block_header: &BlockHeader,
2905 config: EVMConfig,
2906 chain_id: u64,
2907 vm_type: VMType,
2908 base_blob_fee_per_gas: U256,
2909 ) -> Result<Environment, EvmError> {
2910 let gas_price: U256 = calculate_gas_price_for_tx(
2911 tx,
2912 block_header.base_fee_per_gas.unwrap_or_default(),
2913 &vm_type,
2914 )?;
2915
2916 let block_excess_blob_gas = block_header.excess_blob_gas;
2917 let env = Environment {
2918 origin: tx_sender,
2919 gas_limit: tx.gas_limit(),
2920 config,
2921 block_number: block_header.number,
2922 coinbase: block_header.coinbase,
2923 timestamp: block_header.timestamp,
2924 prev_randao: Some(block_header.prev_randao),
2925 slot_number: block_header
2926 .slot_number
2927 .map(U256::from)
2928 .unwrap_or(U256::zero()),
2929 chain_id: chain_id.into(),
2930 base_fee_per_gas: block_header.base_fee_per_gas.unwrap_or_default().into(),
2931 base_blob_fee_per_gas,
2932 gas_price,
2933 block_excess_blob_gas,
2934 block_blob_gas_used: block_header.blob_gas_used,
2935 tx_blob_hashes: tx.blob_versioned_hashes(),
2936 tx_max_priority_fee_per_gas: tx.max_priority_fee().map(U256::from),
2937 tx_max_fee_per_gas: tx.max_fee_per_gas().map(U256::from),
2938 tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas(),
2939 tx_nonce: tx.nonce(),
2940 block_gas_limit: block_header.gas_limit,
2941 difficulty: block_header.difficulty,
2942 is_privileged: matches!(tx, Transaction::PrivilegedL2Transaction(_)),
2943 fee_token: tx.fee_token(),
2944 disable_balance_check: false,
2945 is_system_call: false,
2946 };
2947
2948 Ok(env)
2949 }
2950
2951 pub fn execute_tx(
2952 tx: &Transaction,
2954 tx_sender: Address,
2956 block_header: &BlockHeader,
2958 db: &mut GeneralizedDatabase,
2959 vm_type: VMType,
2960 crypto: &dyn Crypto,
2961 ) -> Result<ExecutionReport, EvmError> {
2962 let env = Self::setup_env(tx, tx_sender, block_header, db, vm_type)?;
2963 let mut vm = VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)?;
2964
2965 vm.execute().map_err(VMError::into)
2966 }
2967
2968 #[allow(clippy::too_many_arguments)]
2971 fn execute_tx_in_block(
2972 tx: &Transaction,
2974 tx_sender: Address,
2976 block_header: &BlockHeader,
2978 db: &mut GeneralizedDatabase,
2979 vm_type: VMType,
2980 base_blob_fee_per_gas: U256,
2981 stack_pool: &mut Vec<Stack>,
2982 memory_pool: &mut Vec<Memory>,
2983 disable_balance_check: bool,
2984 crypto: &dyn Crypto,
2985 config: EVMConfig,
2986 chain_id: u64,
2987 ) -> Result<ExecutionReport, EvmError> {
2988 let mut env = Self::setup_env_with_config(
2989 tx,
2990 tx_sender,
2991 block_header,
2992 config,
2993 chain_id,
2994 vm_type,
2995 base_blob_fee_per_gas,
2996 )?;
2997 env.disable_balance_check = disable_balance_check;
2998 let mut vm = VM::new_pooled(
3002 env,
3003 db,
3004 tx,
3005 LevmCallTracer::disabled(),
3006 vm_type,
3007 crypto,
3008 stack_pool,
3009 memory_pool,
3010 )?;
3011 let result = vm.execute().map_err(VMError::into);
3012 vm.reclaim_into(stack_pool, memory_pool);
3014 result
3015 }
3016
3017 pub fn undo_last_tx(db: &mut GeneralizedDatabase) -> Result<(), EvmError> {
3018 db.undo_last_transaction()?;
3019 Ok(())
3020 }
3021
3022 pub fn simulate_tx_from_generic(
3023 tx: &GenericTransaction,
3025 block_header: &BlockHeader,
3027 db: &mut GeneralizedDatabase,
3028 vm_type: VMType,
3029 crypto: &dyn Crypto,
3030 ) -> Result<ExecutionResult, EvmError> {
3031 let mut env = env_from_generic(tx, block_header, db, vm_type)?;
3032
3033 env.block_gas_limit = i64::MAX as u64; adjust_disabled_base_fee(&mut env);
3036
3037 let converted_tx = generic_tx_to_transaction(tx)?;
3038 let mut vm = vm_from_generic(&converted_tx, env, db, vm_type, crypto)?;
3039
3040 vm.execute()
3041 .map(|value| value.into())
3042 .map_err(VMError::into)
3043 }
3044
3045 #[allow(clippy::too_many_arguments)]
3060 pub fn simulate_frame_validation_prefix(
3061 tx: &Transaction,
3062 block_header: &BlockHeader,
3063 db: &mut GeneralizedDatabase,
3064 vm_type: VMType,
3065 crypto: &dyn Crypto,
3066 prefix: ðrex_common::types::ValidationPrefix,
3067 _canonical_paymaster_code_hash: Option<H256>,
3068 ) -> Result<FrameValidationOutcome, EvmError> {
3069 use ethrex_common::types::FRAME_TX_MAX_VERIFY_GAS;
3070
3071 let frame_tx = match tx {
3072 Transaction::FrameTransaction(ft) => ft,
3073 _ => {
3074 return Err(EvmError::Custom(
3075 "simulate_frame_validation_prefix requires a frame transaction".to_string(),
3076 ));
3077 }
3078 };
3079 let sender = frame_tx.sender;
3080
3081 let env = Self::setup_env(tx, sender, block_header, db, vm_type)?;
3082 let mut vm = VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)?;
3083
3084 let canonical_pay_frame: Option<usize> = None;
3087
3088 let sim = match vm.run_frame_validation_prefix(
3089 &prefix.frame_indices,
3090 prefix.deploy_index,
3091 canonical_pay_frame,
3092 ) {
3093 Ok(sim) => sim,
3094 Err(err) => {
3095 return Ok(FrameValidationOutcome {
3099 passed: false,
3100 violation: Some(EvmError::from(err).to_string()),
3101 max_cost: Self::frame_tx_max_cost(frame_tx),
3102 accessed_paymaster: None,
3103 touched_sender_slots: Vec::new(),
3104 });
3105 }
3106 };
3107
3108 let max_cost = Self::frame_tx_max_cost(frame_tx);
3109 let touched_sender_slots = vm.validation_observer.touched_sender_slots.clone();
3110 let accessed_paymaster = sim.payer_address.map(|payer| (payer, false));
3117
3118 if let Some(violation) = &vm.validation_observer.violation {
3120 return Ok(FrameValidationOutcome {
3121 passed: false,
3122 violation: Some(format!("{violation:?}")),
3123 max_cost,
3124 accessed_paymaster,
3125 touched_sender_slots,
3126 });
3127 }
3128
3129 if sim.any_revert {
3131 return Ok(FrameValidationOutcome {
3132 passed: false,
3133 violation: Some("validation prefix frame reverted".to_string()),
3134 max_cost,
3135 accessed_paymaster,
3136 touched_sender_slots,
3137 });
3138 }
3139
3140 if sim.payer_address.is_none() {
3143 return Ok(FrameValidationOutcome {
3144 passed: false,
3145 violation: Some("validation prefix did not establish a payer".to_string()),
3146 max_cost,
3147 accessed_paymaster,
3148 touched_sender_slots,
3149 });
3150 }
3151
3152 if prefix.deploy_index.is_some() {
3154 let code = vm.db.get_account_code(sender).map_err(VMError::from)?;
3155 if code.is_empty() {
3156 return Ok(FrameValidationOutcome {
3157 passed: false,
3158 violation: Some(format!("{:?}", FrameSimViolation::DeployInstalledNoCode)),
3159 max_cost,
3160 accessed_paymaster,
3161 touched_sender_slots,
3162 });
3163 }
3164 }
3165
3166 if sim.total_gas_used > FRAME_TX_MAX_VERIFY_GAS {
3168 return Ok(FrameValidationOutcome {
3169 passed: false,
3170 violation: Some(format!(
3171 "validation prefix gas {} exceeds MAX_VERIFY_GAS {}",
3172 sim.total_gas_used, FRAME_TX_MAX_VERIFY_GAS
3173 )),
3174 max_cost,
3175 accessed_paymaster,
3176 touched_sender_slots,
3177 });
3178 }
3179
3180 Ok(FrameValidationOutcome {
3181 passed: true,
3182 violation: None,
3183 max_cost,
3184 accessed_paymaster,
3185 touched_sender_slots,
3186 })
3187 }
3188
3189 fn frame_tx_max_cost(frame_tx: ðrex_common::types::FrameTransaction) -> U256 {
3193 let gas_cost = U256::from(frame_tx.max_fee_per_gas)
3198 .saturating_mul(U256::from(frame_tx.total_gas_limit()));
3199 let blob_cost = U256::from(frame_tx.blob_versioned_hashes.len())
3200 .saturating_mul(U256::from(131072u64))
3201 .saturating_mul(frame_tx.max_fee_per_blob_gas);
3202 gas_cost.saturating_add(blob_cost)
3203 }
3204
3205 pub fn get_state_transitions(
3206 db: &mut GeneralizedDatabase,
3207 ) -> Result<Vec<AccountUpdate>, EvmError> {
3208 Ok(db.get_state_transitions()?)
3209 }
3210
3211 pub fn get_state_transitions_tx(
3212 db: &mut GeneralizedDatabase,
3213 ) -> Result<Vec<AccountUpdate>, EvmError> {
3214 Ok(db.get_state_transitions_tx()?)
3215 }
3216
3217 pub fn process_withdrawals(
3218 db: &mut GeneralizedDatabase,
3219 withdrawals: &[Withdrawal],
3220 ) -> Result<(), EvmError> {
3221 for (address, increment) in withdrawals
3223 .iter()
3224 .filter(|withdrawal| withdrawal.amount > 0)
3225 .map(|w| (w.address, u128::from(w.amount) * u128::from(GWEI_TO_WEI)))
3226 {
3227 let account = db
3228 .get_account_mut(address)
3229 .map_err(|_| EvmError::DB(format!("Withdrawal account {address} not found")))?;
3230
3231 let initial_balance = account.info.balance;
3232 account.info.balance += increment.into();
3233 let new_balance = account.info.balance;
3234
3235 if let Some(recorder) = db.bal_recorder_mut() {
3237 recorder.set_initial_balance(address, initial_balance);
3238 recorder.record_balance_change(address, new_balance);
3239 }
3240 }
3241 Ok(())
3242 }
3243
3244 pub fn beacon_root_contract_call(
3246 block_header: &BlockHeader,
3247 db: &mut GeneralizedDatabase,
3248 vm_type: VMType,
3249 crypto: &dyn Crypto,
3250 ) -> Result<(), EvmError> {
3251 if let VMType::L2(_) = vm_type {
3252 return Err(EvmError::InvalidEVM(
3253 "beacon_root_contract_call should not be called for L2 VM".to_string(),
3254 ));
3255 }
3256
3257 let beacon_root = block_header.parent_beacon_block_root.ok_or_else(|| {
3258 EvmError::Header("parent_beacon_block_root field is missing".to_string())
3259 })?;
3260
3261 generic_system_contract_levm(
3262 block_header,
3263 Bytes::copy_from_slice(beacon_root.as_bytes()),
3264 db,
3265 BEACON_ROOTS_ADDRESS.address,
3266 SYSTEM_ADDRESS,
3267 vm_type,
3268 crypto,
3269 )?;
3270 Ok(())
3271 }
3272
3273 pub fn process_block_hash_history(
3274 block_header: &BlockHeader,
3275 db: &mut GeneralizedDatabase,
3276 vm_type: VMType,
3277 crypto: &dyn Crypto,
3278 ) -> Result<(), EvmError> {
3279 if let VMType::L2(_) = vm_type {
3280 return Err(EvmError::InvalidEVM(
3281 "process_block_hash_history should not be called for L2 VM".to_string(),
3282 ));
3283 }
3284
3285 generic_system_contract_levm(
3286 block_header,
3287 Bytes::copy_from_slice(block_header.parent_hash.as_bytes()),
3288 db,
3289 HISTORY_STORAGE_ADDRESS.address,
3290 SYSTEM_ADDRESS,
3291 vm_type,
3292 crypto,
3293 )?;
3294 Ok(())
3295 }
3296
3297 pub fn install_expiry_verifier_code(
3303 db: &mut GeneralizedDatabase,
3304 crypto: &dyn Crypto,
3305 ) -> Result<(), EvmError> {
3306 const PREDEPLOY_NONCE: u64 = 1;
3308
3309 let current = db.get_account_code(EXPIRY_VERIFIER_PREDEPLOY.address)?;
3310 if current.code() == EXPIRY_VERIFIER_RUNTIME_BYTECODE.as_slice() {
3311 return Ok(());
3312 }
3313 let code = Code::from_bytecode(
3314 Bytes::from_static(&EXPIRY_VERIFIER_RUNTIME_BYTECODE),
3315 crypto,
3316 );
3317 let code_hash = code.hash;
3318 if let Some(recorder) = db.bal_recorder_mut() {
3322 recorder.record_code_change(EXPIRY_VERIFIER_PREDEPLOY.address, code.code_bytes());
3323 recorder.record_nonce_change(EXPIRY_VERIFIER_PREDEPLOY.address, PREDEPLOY_NONCE);
3324 }
3325 let acc = db
3326 .get_account_mut(EXPIRY_VERIFIER_PREDEPLOY.address)
3327 .map_err(EvmError::from)?;
3328 acc.info.code_hash = code_hash;
3329 acc.info.nonce = PREDEPLOY_NONCE;
3330 db.codes.entry(code_hash).or_insert(code);
3331 Ok(())
3332 }
3333
3334 pub(crate) fn read_withdrawal_requests(
3335 block_header: &BlockHeader,
3336 db: &mut GeneralizedDatabase,
3337 vm_type: VMType,
3338 crypto: &dyn Crypto,
3339 ) -> Result<ExecutionReport, EvmError> {
3340 if let VMType::L2(_) = vm_type {
3341 return Err(EvmError::InvalidEVM(
3342 "read_withdrawal_requests should not be called for L2 VM".to_string(),
3343 ));
3344 }
3345
3346 let report = generic_system_contract_levm(
3347 block_header,
3348 Bytes::new(),
3349 db,
3350 WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS.address,
3351 SYSTEM_ADDRESS,
3352 vm_type,
3353 crypto,
3354 )?;
3355
3356 match report.result {
3357 TxResult::Success => Ok(report),
3358 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
3360 "REVERT when reading withdrawal requests with error: {vm_error:?}. According to EIP-7002, the revert of this system call invalidates the block.",
3361 ))),
3362 }
3363 }
3364
3365 pub(crate) fn dequeue_consolidation_requests(
3366 block_header: &BlockHeader,
3367 db: &mut GeneralizedDatabase,
3368 vm_type: VMType,
3369 crypto: &dyn Crypto,
3370 ) -> Result<ExecutionReport, EvmError> {
3371 if let VMType::L2(_) = vm_type {
3372 return Err(EvmError::InvalidEVM(
3373 "dequeue_consolidation_requests should not be called for L2 VM".to_string(),
3374 ));
3375 }
3376
3377 let report = generic_system_contract_levm(
3378 block_header,
3379 Bytes::new(),
3380 db,
3381 CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS.address,
3382 SYSTEM_ADDRESS,
3383 vm_type,
3384 crypto,
3385 )?;
3386
3387 match report.result {
3388 TxResult::Success => Ok(report),
3389 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
3391 "REVERT when dequeuing consolidation requests with error: {vm_error:?}. According to EIP-7251, the revert of this system call invalidates the block.",
3392 ))),
3393 }
3394 }
3395
3396 pub(crate) fn read_builder_deposit_requests(
3397 block_header: &BlockHeader,
3398 db: &mut GeneralizedDatabase,
3399 vm_type: VMType,
3400 crypto: &dyn Crypto,
3401 ) -> Result<ExecutionReport, EvmError> {
3402 if let VMType::L2(_) = vm_type {
3403 return Err(EvmError::InvalidEVM(
3404 "read_builder_deposit_requests should not be called for L2 VM".to_string(),
3405 ));
3406 }
3407
3408 let report = generic_system_contract_levm(
3409 block_header,
3410 Bytes::new(),
3411 db,
3412 BUILDER_DEPOSIT_CONTRACT_ADDRESS.address,
3413 SYSTEM_ADDRESS,
3414 vm_type,
3415 crypto,
3416 )?;
3417
3418 match report.result {
3419 TxResult::Success => Ok(report),
3420 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
3422 "REVERT when reading builder deposit requests with error: {vm_error:?}. According to EIP-8282, the revert of this system call invalidates the block.",
3423 ))),
3424 }
3425 }
3426
3427 pub(crate) fn dequeue_builder_exit_requests(
3428 block_header: &BlockHeader,
3429 db: &mut GeneralizedDatabase,
3430 vm_type: VMType,
3431 crypto: &dyn Crypto,
3432 ) -> Result<ExecutionReport, EvmError> {
3433 if let VMType::L2(_) = vm_type {
3434 return Err(EvmError::InvalidEVM(
3435 "dequeue_builder_exit_requests should not be called for L2 VM".to_string(),
3436 ));
3437 }
3438
3439 let report = generic_system_contract_levm(
3440 block_header,
3441 Bytes::new(),
3442 db,
3443 BUILDER_EXIT_CONTRACT_ADDRESS.address,
3444 SYSTEM_ADDRESS,
3445 vm_type,
3446 crypto,
3447 )?;
3448
3449 match report.result {
3450 TxResult::Success => Ok(report),
3451 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
3453 "REVERT when dequeuing builder exit requests with error: {vm_error:?}. According to EIP-8282, the revert of this system call invalidates the block.",
3454 ))),
3455 }
3456 }
3457
3458 pub fn create_access_list(
3459 mut tx: GenericTransaction,
3460 header: &BlockHeader,
3461 db: &mut GeneralizedDatabase,
3462 vm_type: VMType,
3463 crypto: &dyn Crypto,
3464 ) -> Result<(ExecutionResult, AccessList), VMError> {
3465 let mut env = env_from_generic(&tx, header, db, vm_type)?;
3466
3467 adjust_disabled_base_fee(&mut env);
3468
3469 let converted_tx = generic_tx_to_transaction(&tx)?;
3470 let mut vm = vm_from_generic(&converted_tx, env.clone(), db, vm_type, crypto)?;
3471
3472 vm.stateless_execute()?;
3473
3474 tx.access_list = vm.substate.make_access_list();
3476 let converted_tx = generic_tx_to_transaction(&tx)?;
3477 let mut vm = vm_from_generic(&converted_tx, env, db, vm_type, crypto)?;
3478
3479 let report = vm.stateless_execute()?;
3480
3481 Ok((
3482 report.into(),
3483 tx.access_list
3484 .into_iter()
3485 .map(|x| (x.address, x.storage_keys))
3486 .collect(),
3487 ))
3488 }
3489
3490 pub fn prepare_block(
3491 block: &Block,
3492 db: &mut GeneralizedDatabase,
3493 vm_type: VMType,
3494 crypto: &dyn Crypto,
3495 ) -> Result<(), EvmError> {
3496 let chain_config = db.store.get_chain_config()?;
3497 let block_header = &block.header;
3498 let fork = chain_config.fork(block_header.timestamp);
3499
3500 if let VMType::L2(_) = vm_type {
3502 return Ok(());
3503 }
3504
3505 if fork >= Fork::Hegota {
3509 Self::install_expiry_verifier_code(db, crypto)?;
3510 }
3511
3512 if block_header.parent_beacon_block_root.is_some() && fork >= Fork::Cancun {
3513 Self::beacon_root_contract_call(block_header, db, vm_type, crypto)?;
3514 }
3515
3516 if fork >= Fork::Prague {
3517 Self::process_block_hash_history(block_header, db, vm_type, crypto)?;
3519 }
3520 Ok(())
3521 }
3522}
3523
3524pub fn generic_system_contract_levm(
3525 block_header: &BlockHeader,
3526 calldata: Bytes,
3527 db: &mut GeneralizedDatabase,
3528 contract_address: Address,
3529 system_address: Address,
3530 vm_type: VMType,
3531 crypto: &dyn Crypto,
3532) -> Result<ExecutionReport, EvmError> {
3533 let chain_config = db.store.get_chain_config()?;
3534 let config = EVMConfig::new_from_chain_config(&chain_config, block_header);
3535 let env = Environment {
3536 origin: system_address,
3537 gas_limit: SYS_CALL_GAS_LIMIT,
3548 block_number: block_header.number,
3549 coinbase: block_header.coinbase,
3550 timestamp: block_header.timestamp,
3551 prev_randao: Some(block_header.prev_randao),
3552 base_fee_per_gas: U256::zero(),
3553 gas_price: U256::zero(),
3554 block_excess_blob_gas: block_header.excess_blob_gas,
3555 block_blob_gas_used: block_header.blob_gas_used,
3556 block_gas_limit: block_header.gas_limit,
3561 is_system_call: true,
3562 config,
3563 ..Default::default()
3564 };
3565
3566 debug_assert!(
3572 env.gas_price.is_zero() && env.base_fee_per_gas.is_zero(),
3573 "system calls must run with a zero gas price"
3574 );
3575
3576 if PRAGUE_SYSTEM_CONTRACTS
3586 .iter()
3587 .chain(AMSTERDAM_REQUEST_PREDEPLOYS.iter())
3588 .any(|contract| contract.address == contract_address)
3589 && db.get_account_code(contract_address)?.is_empty()
3590 {
3591 return Err(EvmError::SystemContractCallFailed(format!(
3592 "System contract: {contract_address} has no code after deployment"
3593 )));
3594 };
3595
3596 let tx = &Transaction::EIP1559Transaction(EIP1559Transaction {
3597 to: TxKind::Call(contract_address),
3598 value: U256::zero(),
3599 data: calldata,
3600 ..Default::default()
3601 });
3602 if let Some(recorder) = db.bal_recorder.as_mut() {
3604 recorder.enter_system_call();
3605 }
3606
3607 let result = VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)
3608 .and_then(|mut vm| vm.execute())
3609 .map_err(EvmError::from);
3610
3611 if let Some(recorder) = db.bal_recorder.as_mut() {
3613 recorder.exit_system_call();
3614 }
3615
3616 result
3617}
3618
3619#[allow(unreachable_code)]
3620#[allow(unused_variables)]
3621pub fn extract_all_requests_levm(
3622 receipts: &[Receipt],
3623 db: &mut GeneralizedDatabase,
3624 header: &BlockHeader,
3625 vm_type: VMType,
3626 crypto: &dyn Crypto,
3627) -> Result<Vec<Requests>, EvmError> {
3628 if let VMType::L2(_) = vm_type {
3629 return Err(EvmError::InvalidEVM(
3630 "extract_all_requests_levm should not be called for L2 VM".to_string(),
3631 ));
3632 }
3633
3634 let chain_config = db.store.get_chain_config()?;
3635 let fork = chain_config.fork(header.timestamp);
3636
3637 if fork < Fork::Prague {
3638 return Ok(Default::default());
3639 }
3640
3641 let withdrawals_data: Vec<u8> = LEVM::read_withdrawal_requests(header, db, vm_type, crypto)?
3642 .output
3643 .into();
3644 let consolidation_data: Vec<u8> =
3645 LEVM::dequeue_consolidation_requests(header, db, vm_type, crypto)?
3646 .output
3647 .into();
3648
3649 let deposits = Requests::from_deposit_receipts(chain_config.deposit_contract_address, receipts)
3650 .ok_or(EvmError::InvalidDepositRequest)?;
3651 let withdrawals = Requests::from_withdrawals_data(withdrawals_data);
3652 let consolidation = Requests::from_consolidation_data(consolidation_data);
3653
3654 let mut requests = vec![deposits, withdrawals, consolidation];
3655
3656 if fork >= Fork::Amsterdam {
3660 requests.reserve_exact(2);
3662 let builder_deposit_data: Vec<u8> =
3663 LEVM::read_builder_deposit_requests(header, db, vm_type, crypto)?
3664 .output
3665 .into();
3666 let builder_exit_data: Vec<u8> =
3667 LEVM::dequeue_builder_exit_requests(header, db, vm_type, crypto)?
3668 .output
3669 .into();
3670 requests.push(Requests::from_builder_deposit_data(builder_deposit_data));
3671 requests.push(Requests::from_builder_exit_data(builder_exit_data));
3672 }
3673
3674 Ok(requests)
3675}
3676
3677pub fn calculate_gas_price_for_generic(tx: &GenericTransaction, basefee: u64) -> U256 {
3680 if !tx.gas_price.is_zero() {
3681 tx.gas_price
3683 } else {
3684 min(
3686 tx.max_priority_fee_per_gas.unwrap_or(0) + basefee,
3687 tx.max_fee_per_gas.unwrap_or(0),
3688 )
3689 .into()
3690 }
3691}
3692
3693pub fn calculate_gas_price_for_tx(
3694 tx: &Transaction,
3695 mut fee_per_gas: u64,
3696 vm_type: &VMType,
3697) -> Result<U256, VMError> {
3698 let Some(max_priority_fee) = tx.max_priority_fee() else {
3699 return Ok(tx.gas_price());
3701 };
3702
3703 let max_fee_per_gas = tx.max_fee_per_gas().ok_or(VMError::TxValidation(
3704 TxValidationError::InsufficientMaxFeePerGas,
3705 ))?;
3706
3707 if let VMType::L2(fee_config) = vm_type
3708 && let Some(operator_fee_config) = &fee_config.operator_fee_config
3709 {
3710 fee_per_gas += operator_fee_config.operator_fee_per_gas;
3711 }
3712
3713 if fee_per_gas > max_fee_per_gas {
3714 return Err(VMError::TxValidation(
3715 TxValidationError::InsufficientMaxFeePerGas,
3716 ));
3717 }
3718
3719 Ok(min(max_priority_fee + fee_per_gas, max_fee_per_gas).into())
3720}
3721
3722fn adjust_disabled_base_fee(env: &mut Environment) {
3726 if env.gas_price == U256::zero() {
3727 env.base_fee_per_gas = U256::zero();
3728 }
3729 if env
3730 .tx_max_fee_per_blob_gas
3731 .is_some_and(|v| v == U256::zero())
3732 {
3733 env.block_excess_blob_gas = None;
3734 }
3735}
3736
3737fn adjust_disabled_l2_fees(env: &Environment, vm_type: VMType) -> VMType {
3739 if env.gas_price == U256::zero()
3740 && let VMType::L2(fee_config) = vm_type
3741 {
3742 return VMType::L2(FeeConfig {
3744 operator_fee_config: None,
3745 l1_fee_config: None,
3746 ..fee_config
3747 });
3748 }
3749 vm_type
3750}
3751
3752fn env_from_generic(
3753 tx: &GenericTransaction,
3754 header: &BlockHeader,
3755 db: &GeneralizedDatabase,
3756 vm_type: VMType,
3757) -> Result<Environment, VMError> {
3758 let chain_config = db.store.get_chain_config()?;
3759 let gas_price =
3760 calculate_gas_price_for_generic(tx, header.base_fee_per_gas.unwrap_or(INITIAL_BASE_FEE));
3761 let block_excess_blob_gas = header.excess_blob_gas;
3762 let config = EVMConfig::new_from_chain_config(&chain_config, header);
3763
3764 let slot_number = if let VMType::L2(_) = vm_type {
3767 U256::zero()
3768 } else if config.fork >= Fork::Amsterdam {
3769 header
3770 .slot_number
3771 .map(U256::from)
3772 .ok_or(VMError::Internal(InternalError::Custom(
3773 "slot_number must be present in Amsterdam+ blocks".to_string(),
3774 )))?
3775 } else {
3776 header.slot_number.map(U256::from).unwrap_or(U256::zero())
3779 };
3780
3781 Ok(Environment {
3782 origin: tx.from.0.into(),
3783 gas_limit: tx
3784 .gas
3785 .unwrap_or(get_max_allowed_gas_limit(header.gas_limit, config.fork)), config,
3787 block_number: header.number,
3788 coinbase: header.coinbase,
3789 timestamp: header.timestamp,
3790 prev_randao: Some(header.prev_randao),
3791 slot_number,
3792 chain_id: chain_config.chain_id.into(),
3793 base_fee_per_gas: header.base_fee_per_gas.unwrap_or_default().into(),
3794 base_blob_fee_per_gas: get_base_fee_per_blob_gas(block_excess_blob_gas, &config)?,
3795 gas_price,
3796 block_excess_blob_gas,
3797 block_blob_gas_used: header.blob_gas_used,
3798 tx_blob_hashes: tx.blob_versioned_hashes.clone(),
3799 tx_max_priority_fee_per_gas: tx.max_priority_fee_per_gas.map(U256::from),
3800 tx_max_fee_per_gas: tx.max_fee_per_gas.map(U256::from),
3801 tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
3802 tx_nonce: tx.nonce.unwrap_or_default(),
3803 block_gas_limit: header.gas_limit,
3804 difficulty: header.difficulty,
3805 is_privileged: false,
3806 fee_token: tx.fee_token,
3807 disable_balance_check: false,
3808 is_system_call: false,
3809 })
3810}
3811
3812fn generic_tx_to_transaction(tx: &GenericTransaction) -> Result<Transaction, VMError> {
3817 Ok(match &tx.authorization_list {
3818 Some(authorization_list) => Transaction::EIP7702Transaction(EIP7702Transaction {
3819 to: match tx.to {
3820 TxKind::Call(to) => to,
3821 TxKind::Create => {
3822 return Err(InternalError::msg("Generic Tx cannot be create type").into());
3823 }
3824 },
3825 value: tx.value,
3826 data: tx.input.clone(),
3827 access_list: tx
3828 .access_list
3829 .iter()
3830 .map(|list| (list.address, list.storage_keys.clone()))
3831 .collect(),
3832 authorization_list: authorization_list
3833 .iter()
3834 .map(|auth| Into::<AuthorizationTuple>::into(auth.clone()))
3835 .collect(),
3836 ..Default::default()
3837 }),
3838 None => Transaction::EIP1559Transaction(EIP1559Transaction {
3839 to: tx.to.clone(),
3840 value: tx.value,
3841 data: tx.input.clone(),
3842 access_list: tx
3843 .access_list
3844 .iter()
3845 .map(|list| (list.address, list.storage_keys.clone()))
3846 .collect(),
3847 ..Default::default()
3848 }),
3849 })
3850}
3851
3852fn vm_from_generic<'a>(
3853 tx: &'a Transaction,
3854 env: Environment,
3855 db: &'a mut GeneralizedDatabase,
3856 vm_type: VMType,
3857 crypto: &'a dyn Crypto,
3858) -> Result<VM<'a>, VMError> {
3859 let vm_type = adjust_disabled_l2_fees(&env, vm_type);
3860 VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)
3861}
3862
3863pub fn get_max_allowed_gas_limit(block_gas_limit: u64, fork: Fork) -> u64 {
3864 if fork >= Fork::Osaka && fork < Fork::Amsterdam {
3872 POST_OSAKA_GAS_LIMIT_CAP
3873 } else {
3874 block_gas_limit
3875 }
3876}
3877
3878#[allow(dead_code)]
3886fn describe_balance_diff(expected: U256, actual: U256) -> String {
3887 let (sign, mag) = if expected >= actual {
3888 ("+", expected - actual)
3889 } else {
3890 ("-", actual - expected)
3891 };
3892 let Ok(mag_u128) = u128::try_from(mag) else {
3893 return format!("{sign}{mag}");
3894 };
3895 if mag_u128 == 0 {
3896 return "0".to_string();
3897 }
3898 let cpsb: u128 = 1530;
3899 let consts = [
3901 ("NEW_ACCOUNT", 120u128),
3902 ("STORAGE_SET", 64),
3903 ("AUTH_BASE", 23),
3904 ("AUTH_TOTAL", 143),
3905 ];
3906 for &gp in &[10u128, 1, 7, 100, 1000, 1_000_000_000] {
3908 if !mag_u128.is_multiple_of(gp) {
3909 continue;
3910 }
3911 let gas = mag_u128 / gp;
3912 if !gas.is_multiple_of(cpsb) {
3913 continue;
3914 }
3915 let bytes = gas / cpsb;
3916 for (name, c) in consts {
3917 if bytes.is_multiple_of(c) {
3918 let n = bytes / c;
3919 return format!(
3920 "{sign}{mag_u128} wei (= {gas} gas at {gp} wei/gas = {n}× {name}_state_gas)"
3921 );
3922 }
3923 }
3924 }
3925 format!("{sign}{mag_u128} wei")
3926}
3927
3928#[cfg(test)]
3929mod bal_tests {
3930 use super::*;
3931 use ethrex_common::H256;
3932 use ethrex_common::types::AccountState;
3933 use ethrex_common::types::block_access_list::{
3934 AccountChanges, BalanceChange, NonceChange, SlotChange, StorageChange,
3935 };
3936 use ethrex_levm::errors::DatabaseError;
3937
3938 fn addr(byte: u8) -> Address {
3939 let mut a = Address::zero();
3940 a.0[19] = byte;
3941 a
3942 }
3943
3944 struct MockStore {
3946 accounts: FxHashMap<Address, AccountState>,
3947 }
3948
3949 impl MockStore {
3950 fn new() -> Self {
3951 Self {
3952 accounts: FxHashMap::default(),
3953 }
3954 }
3955
3956 fn with_account(mut self, address: Address, state: AccountState) -> Self {
3957 self.accounts.insert(address, state);
3958 self
3959 }
3960 }
3961
3962 impl Database for MockStore {
3963 fn get_account_state(&self, address: Address) -> Result<AccountState, DatabaseError> {
3964 Ok(self.accounts.get(&address).copied().unwrap_or_default())
3965 }
3966 fn get_storage_value(&self, _: Address, _: H256) -> Result<U256, DatabaseError> {
3967 Ok(U256::zero())
3968 }
3969 fn get_block_hash(&self, _: u64) -> Result<H256, DatabaseError> {
3970 Ok(H256::zero())
3971 }
3972 fn get_chain_config(&self) -> Result<ethrex_common::types::ChainConfig, DatabaseError> {
3973 Err(DatabaseError::Custom("not implemented".into()))
3974 }
3975 fn get_account_code(&self, _: H256) -> Result<ethrex_common::types::Code, DatabaseError> {
3976 Ok(ethrex_common::types::Code::from_bytecode(
3977 Bytes::new(),
3978 ðrex_crypto::NativeCrypto,
3979 ))
3980 }
3981 fn get_code_metadata(
3982 &self,
3983 _: H256,
3984 ) -> Result<ethrex_common::types::CodeMetadata, DatabaseError> {
3985 Ok(ethrex_common::types::CodeMetadata { length: 0 })
3986 }
3987 }
3988
3989 #[test]
3990 fn test_bal_to_account_updates_basic() {
3991 let address = addr(1);
3993 let store = MockStore::new().with_account(
3994 address,
3995 AccountState {
3996 balance: U256::from(100),
3997 nonce: 5,
3998 code_hash: *EMPTY_KECCAK_HASH,
3999 storage_root: H256::zero(),
4000 },
4001 );
4002
4003 let bal = BlockAccessList::from_accounts(vec![
4004 AccountChanges::new(address)
4005 .with_balance_changes(vec![
4006 BalanceChange::new(1, U256::from(90)),
4007 BalanceChange::new(2, U256::from(80)),
4008 ])
4009 .with_nonce_changes(vec![NonceChange::new(1, 6)])
4010 .with_storage_changes(vec![SlotChange::with_changes(
4011 U256::from(42),
4012 vec![StorageChange::new(1, U256::from(999))],
4013 )]),
4014 ]);
4015
4016 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
4017 assert_eq!(updates.len(), 1);
4018 let u = &updates[0];
4019 assert_eq!(u.address, address);
4020 assert!(!u.removed);
4021 let info = u.info.as_ref().unwrap();
4022 assert_eq!(info.balance, U256::from(80));
4024 assert_eq!(info.nonce, 6);
4025 assert_eq!(info.code_hash, *EMPTY_KECCAK_HASH);
4026 let key = ethrex_common::utils::u256_to_h256(U256::from(42));
4028 assert_eq!(*u.added_storage.get(&key).unwrap(), U256::from(999));
4029 }
4030
4031 #[test]
4032 fn test_bal_to_account_updates_highest_index_wins() {
4033 let address = addr(2);
4035 let store = MockStore::new().with_account(
4036 address,
4037 AccountState {
4038 balance: U256::from(1000),
4039 nonce: 0,
4040 code_hash: *EMPTY_KECCAK_HASH,
4041 storage_root: H256::zero(),
4042 },
4043 );
4044
4045 let bal = BlockAccessList::from_accounts(vec![
4046 AccountChanges::new(address).with_balance_changes(vec![
4047 BalanceChange::new(1, U256::from(900)),
4048 BalanceChange::new(2, U256::from(800)),
4049 BalanceChange::new(3, U256::from(700)),
4050 ]),
4051 ]);
4052
4053 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
4054 assert_eq!(updates.len(), 1);
4055 assert_eq!(updates[0].info.as_ref().unwrap().balance, U256::from(700));
4056 }
4057
4058 #[test]
4059 fn test_bal_to_account_updates_reads_only_skipped() {
4060 let address = addr(3);
4062 let store = MockStore::new();
4063
4064 let bal = BlockAccessList::from_accounts(vec![
4065 AccountChanges::new(address).with_storage_reads(vec![U256::from(1)]),
4066 ]);
4067
4068 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
4069 assert!(updates.is_empty());
4070 }
4071
4072 #[test]
4073 fn test_bal_to_account_updates_removal() {
4074 let address = addr(4);
4076 let store = MockStore::new().with_account(
4077 address,
4078 AccountState {
4079 balance: U256::from(50),
4080 nonce: 1,
4081 code_hash: *EMPTY_KECCAK_HASH,
4082 storage_root: H256::zero(),
4083 },
4084 );
4085
4086 let bal = BlockAccessList::from_accounts(vec![
4087 AccountChanges::new(address)
4088 .with_balance_changes(vec![BalanceChange::new(1, U256::zero())])
4089 .with_nonce_changes(vec![NonceChange::new(1, 0)]),
4090 ]);
4091
4092 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
4093 assert_eq!(updates.len(), 1);
4094 assert!(updates[0].removed);
4095 }
4096
4097 #[test]
4098 fn test_bal_to_account_updates_storage_zero() {
4099 let address = addr(5);
4101 let store = MockStore::new();
4102
4103 let bal = BlockAccessList::from_accounts(vec![
4104 AccountChanges::new(address).with_storage_changes(vec![SlotChange::with_changes(
4105 U256::from(7),
4106 vec![StorageChange::new(1, U256::zero())],
4107 )]),
4108 ]);
4109
4110 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
4111 assert_eq!(updates.len(), 1);
4112 let key = ethrex_common::utils::u256_to_h256(U256::from(7));
4113 assert_eq!(*updates[0].added_storage.get(&key).unwrap(), U256::zero());
4114 }
4115
4116 #[test]
4117 fn test_bal_to_account_updates_code_deployment() {
4118 let address = addr(6);
4120 let store = MockStore::new();
4121 let code = Bytes::from(vec![0x60, 0x00, 0x60, 0x00, 0xf3]); let expected_hash =
4123 ethrex_common::types::Code::from_bytecode(code.clone(), ðrex_crypto::NativeCrypto)
4124 .hash;
4125
4126 let bal = BlockAccessList::from_accounts(vec![
4127 AccountChanges::new(address)
4128 .with_code_changes(vec![
4129 ethrex_common::types::block_access_list::CodeChange::new(1, code.clone()),
4130 ])
4131 .with_nonce_changes(vec![NonceChange::new(1, 1)]),
4132 ]);
4133
4134 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
4135 assert_eq!(updates.len(), 1);
4136 let u = &updates[0];
4137 assert_eq!(u.info.as_ref().unwrap().code_hash, expected_hash);
4138 assert_eq!(u.code.as_ref().unwrap().code(), &code[..]);
4139 }
4140}
4141
4142#[cfg(test)]
4143mod system_call_coinbase_tests {
4144 use super::*;
4150 use ethrex_common::types::{AccountState, AccountUpdate, ChainConfig, Code, CodeMetadata};
4151 use ethrex_crypto::NativeCrypto;
4152 use ethrex_levm::db::Database;
4153 use ethrex_levm::errors::DatabaseError;
4154 use std::sync::Arc;
4155
4156 const HISTORY_RUNTIME_CODE: &str = concat!(
4158 "3373fffffffffffffffffffffffffffffffffffffffe1460465760203603604257",
4159 "5f35600143038111604257611fff81430311604257611fff900654",
4160 "5f5260205ff35b5f5ffd5b5f35611fff60014303065500",
4161 );
4162
4163 struct Store {
4164 chain_config: ChainConfig,
4165 history_code: Code,
4166 }
4167
4168 impl Database for Store {
4169 fn get_account_state(&self, address: Address) -> Result<AccountState, DatabaseError> {
4170 if address == HISTORY_STORAGE_ADDRESS.address {
4171 return Ok(AccountState {
4172 nonce: 1,
4173 code_hash: self.history_code.hash,
4174 ..Default::default()
4175 });
4176 }
4177 Ok(AccountState::default())
4178 }
4179 fn get_storage_value(&self, _: Address, _: H256) -> Result<U256, DatabaseError> {
4180 Ok(U256::zero())
4181 }
4182 fn get_block_hash(&self, _: u64) -> Result<H256, DatabaseError> {
4183 Ok(H256::zero())
4184 }
4185 fn get_chain_config(&self) -> Result<ChainConfig, DatabaseError> {
4186 Ok(self.chain_config)
4187 }
4188 fn get_account_code(&self, code_hash: H256) -> Result<Code, DatabaseError> {
4189 if code_hash == self.history_code.hash {
4190 return Ok(self.history_code.clone());
4191 }
4192 Ok(Code::default())
4193 }
4194 fn get_code_metadata(&self, code_hash: H256) -> Result<CodeMetadata, DatabaseError> {
4195 let length = if code_hash == self.history_code.hash {
4196 self.history_code.len() as u64
4197 } else {
4198 0
4199 };
4200 Ok(CodeMetadata { length })
4201 }
4202 }
4203
4204 fn history_code() -> Code {
4205 let bytes = hex::decode(HISTORY_RUNTIME_CODE).expect("history runtime code is valid hex");
4206 Code::from_bytecode(Bytes::from(bytes), &NativeCrypto)
4207 }
4208
4209 fn prague_db() -> GeneralizedDatabase {
4210 GeneralizedDatabase::new(Arc::new(Store {
4211 chain_config: ChainConfig {
4212 prague_time: Some(0),
4213 ..Default::default()
4214 },
4215 history_code: history_code(),
4216 }))
4217 }
4218
4219 fn parent_hash_value(parent_hash: H256) -> U256 {
4220 U256::from_big_endian(parent_hash.as_bytes())
4221 }
4222
4223 fn history_slot(block_number: u64) -> H256 {
4224 H256::from_low_u64_be((block_number - 1) % 8191)
4225 }
4226
4227 fn run_history_update(coinbase: Address) -> (Option<U256>, Vec<AccountUpdate>, H256) {
4231 let mut db = prague_db();
4232 let parent_hash = H256::from_low_u64_be(0x2935);
4233 let block_number = 42;
4234 let header = BlockHeader {
4235 parent_hash,
4236 coinbase,
4237 number: block_number,
4238 timestamp: 1,
4239 ..Default::default()
4240 };
4241
4242 LEVM::process_block_hash_history(&header, &mut db, VMType::L1, &NativeCrypto)
4243 .expect("history system call executes");
4244
4245 let slot = history_slot(block_number);
4246 let stored_value = db
4247 .current_accounts_state
4248 .get(&HISTORY_STORAGE_ADDRESS.address)
4249 .and_then(|account| account.storage.get(&slot).copied());
4250 let updates =
4251 LEVM::get_state_transitions(&mut db).expect("state transitions are generated");
4252
4253 (stored_value, updates, parent_hash)
4254 }
4255
4256 fn assert_history_write_emitted(coinbase: Address) {
4257 let (stored_value, updates, parent_hash) = run_history_update(coinbase);
4258 let slot = history_slot(42);
4259 assert_eq!(
4260 stored_value,
4261 Some(parent_hash_value(parent_hash)),
4262 "history storage must hold the parent hash after the system call"
4263 );
4264 assert!(
4265 updates.iter().any(|update| {
4266 update.address == HISTORY_STORAGE_ADDRESS.address
4267 && update.added_storage.get(&slot) == Some(&parent_hash_value(parent_hash))
4268 }),
4269 "the history-contract storage write must be emitted as a state update"
4270 );
4271 }
4272
4273 #[test]
4274 fn ordinary_coinbase_preserves_history_storage_write() {
4275 assert_history_write_emitted(Address::from_low_u64_be(0xbeef));
4276 }
4277
4278 #[test]
4281 fn history_address_coinbase_preserves_history_storage_write() {
4282 assert_history_write_emitted(HISTORY_STORAGE_ADDRESS.address);
4283 }
4284}