1pub mod db;
2mod tracing;
3
4use super::{BlockExecutionResult, TxGasBreakdown};
5use crate::system_contracts::{
6 BEACON_ROOTS_ADDRESS, CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, HISTORY_STORAGE_ADDRESS,
7 PRAGUE_SYSTEM_CONTRACTS, SYSTEM_ADDRESS, WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
8};
9use crate::{EvmError, ExecutionResult};
10use bytes::Bytes;
11#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
12use ethrex_common::H256;
13#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
14use ethrex_common::constants::EMPTY_KECCAK_HASH;
15#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
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, GWEI_TO_WEI,
34 GenericTransaction, INITIAL_BASE_FEE, Receipt, Transaction, TxKind, Withdrawal,
35 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_BASE_COST,
47 TX_MAX_GAS_LIMIT_AMSTERDAM,
48};
49use ethrex_levm::db::gen_db::GeneralizedDatabase;
50#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
51use ethrex_levm::db::gen_db::{
52 LazyBalCursor, code_from_bal, post_value_at_or_before, seed_one_address_info_from_bal,
53};
54#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
55use ethrex_levm::db::{Database, gen_db::CacheDB};
56use ethrex_levm::errors::{InternalError, TxValidationError};
57use ethrex_levm::memory::Memory;
58#[cfg(feature = "perf_opcode_timings")]
59use ethrex_levm::timings::{OPCODE_TIMINGS, PRECOMPILES_TIMINGS};
60use ethrex_levm::tracing::LevmCallTracer;
61use ethrex_levm::utils::get_base_fee_per_blob_gas;
62use ethrex_levm::utils::intrinsic_gas_dimensions;
63use ethrex_levm::vm::VMType;
64use ethrex_levm::{
65 Environment,
66 errors::{ExecutionReport, TxResult, VMError},
67 vm::VM,
68};
69#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
70use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
71#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
72use rustc_hash::{FxHashMap, FxHashSet};
73use std::cmp::min;
74#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
75use std::sync::Arc;
76#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
77use std::sync::atomic::AtomicBool;
78use std::sync::atomic::{AtomicUsize, Ordering};
79use std::sync::mpsc::Sender;
80
81#[derive(Debug)]
87pub struct LEVM;
88
89fn check_gas_limit(
91 block_gas_used: u64,
92 tx_gas_limit: u64,
93 block_gas_limit: u64,
94) -> Result<(), EvmError> {
95 if tx_gas_limit > block_gas_limit.saturating_sub(block_gas_used) {
96 return Err(EvmError::Transaction(format!(
97 "Gas allowance exceeded: \
98 used {block_gas_used} + tx limit {tx_gas_limit} > block limit {block_gas_limit}"
99 )));
100 }
101 Ok(())
102}
103
104pub fn check_2d_gas_allowance(
119 tx: &Transaction,
120 fork: Fork,
121 block_gas_used_regular: u64,
122 block_gas_used_state: u64,
123 block_gas_limit: u64,
124) -> Result<(), EvmError> {
125 let (intrinsic_regular, intrinsic_state) = intrinsic_gas_dimensions(tx, fork, block_gas_limit)
126 .map_err(|e| EvmError::Transaction(format!("intrinsic gas computation failed: {e}")))?;
127
128 let tx_gas = tx.gas_limit();
129 let regular_available = block_gas_limit.saturating_sub(block_gas_used_regular);
130 let state_available = block_gas_limit.saturating_sub(block_gas_used_state);
131
132 let regular_contrib = tx_gas
137 .saturating_sub(intrinsic_state)
138 .min(TX_MAX_GAS_LIMIT_AMSTERDAM);
139 if regular_contrib > regular_available {
140 return Err(EvmError::Transaction(format!(
141 "Gas allowance exceeded: regular dim worst-case {regular_contrib} > \
142 available {regular_available} (block_gas_used_regular={block_gas_used_regular}, \
143 block_gas_limit={block_gas_limit})"
144 )));
145 }
146
147 let state_contrib = tx_gas.saturating_sub(intrinsic_regular);
149 if state_contrib > state_available {
150 return Err(EvmError::Transaction(format!(
151 "Gas allowance exceeded: state dim worst-case {state_contrib} > \
152 available {state_available} (block_gas_used_state={block_gas_used_state}, \
153 block_gas_limit={block_gas_limit})"
154 )));
155 }
156
157 Ok(())
158}
159
160#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
163#[derive(Debug, thiserror::Error)]
164enum BalValidationError {
165 #[error("{0}")]
166 Mismatch(String),
167 #[error("{0}")]
168 Database(String),
169}
170
171impl LEVM {
172 pub fn execute_block(
177 block: &Block,
178 db: &mut GeneralizedDatabase,
179 vm_type: VMType,
180 crypto: &dyn Crypto,
181 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
182 let chain_config = db.store.get_chain_config()?;
183 let is_amsterdam = chain_config.is_amsterdam_activated(block.header.timestamp);
184
185 debug_assert!(
190 block.body.transactions.len() < u32::MAX as usize,
191 "tx count overflows u32 BlockAccessIndex"
192 );
193
194 if is_amsterdam {
196 db.enable_bal_recording();
197 db.set_bal_index(0);
199 }
200
201 Self::prepare_block(block, db, vm_type, crypto)?;
202
203 let evm_config = EVMConfig::new_from_chain_config(&chain_config, &block.header);
207 let chain_id = chain_config.chain_id;
208 let base_blob_fee_per_gas =
209 get_base_fee_per_blob_gas(block.header.excess_blob_gas, &evm_config)?;
210 let mut shared_stack_pool = Vec::with_capacity(STACK_LIMIT);
212 let mut shared_memory_pool = Vec::with_capacity(1);
213
214 let n_txs = block.body.transactions.len();
215 let mut receipts = Vec::with_capacity(n_txs);
216 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(n_txs);
217 let mut cumulative_gas_used = 0_u64;
219 let mut block_gas_used = 0_u64;
221 let mut block_regular_gas_used = 0_u64;
223 let mut block_state_gas_used = 0_u64;
224 let transactions_with_sender =
225 block
226 .body
227 .get_transactions_with_sender(crypto)
228 .map_err(|error| {
229 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
230 })?;
231
232 for (tx_idx, (tx, tx_sender)) in transactions_with_sender.into_iter().enumerate() {
233 if !is_amsterdam {
240 check_gas_limit(cumulative_gas_used, tx.gas_limit(), block.header.gas_limit)?;
241 }
242
243 if is_amsterdam {
245 check_2d_gas_allowance(
246 tx,
247 Fork::Amsterdam,
248 block_regular_gas_used,
249 block_state_gas_used,
250 block.header.gas_limit,
251 )?;
252 }
253
254 if is_amsterdam {
256 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
257 db.set_bal_index(bal_index);
258
259 if let Some(recorder) = db.bal_recorder_mut() {
261 recorder.record_touched_address(tx_sender);
262 if let TxKind::Call(to) = tx.to() {
263 recorder.record_touched_address(to);
264 }
265 }
266 }
267
268 let report = Self::execute_tx_in_block(
269 tx,
270 tx_sender,
271 &block.header,
272 db,
273 vm_type,
274 base_blob_fee_per_gas,
275 &mut shared_stack_pool,
276 &mut shared_memory_pool,
277 false,
278 crypto,
279 evm_config,
280 chain_id,
281 )?;
282
283 tx_gas_breakdowns.push(TxGasBreakdown::from_report(
284 tx_idx,
285 tx.hash(crypto),
286 &report,
287 ));
288
289 cumulative_gas_used += report.gas_spent;
291
292 let tx_state_gas = report.state_gas_used;
295 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
296 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
297 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
298
299 if is_amsterdam {
300 block_gas_used = block_regular_gas_used.max(block_state_gas_used);
302 ::tracing::debug!(
303 "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}",
304 report.gas_used,
305 report.gas_spent,
306 );
307
308 if block_regular_gas_used > block.header.gas_limit
313 || block_state_gas_used > block.header.gas_limit
314 {
315 return Err(EvmError::Transaction(format!(
316 "Gas allowance exceeded: Block gas used overflow: \
317 block_gas_used {block_gas_used} > block_gas_limit {}",
318 block.header.gas_limit
319 )));
320 }
321 } else {
322 block_gas_used = block_gas_used.saturating_add(report.gas_used);
323 }
324
325 let receipt = Receipt::new(
326 tx.tx_type(),
327 matches!(report.result, TxResult::Success),
328 cumulative_gas_used,
329 report.logs,
330 );
331
332 receipts.push(receipt);
333 }
334
335 if is_amsterdam && block_gas_used > block.header.gas_limit {
339 return Err(EvmError::Transaction(format!(
340 "Gas allowance exceeded: Block gas used overflow: \
341 block_gas_used {block_gas_used} > block_gas_limit {}",
342 block.header.gas_limit
343 )));
344 }
345
346 if is_amsterdam {
349 let post_tx_index =
350 u32::try_from(block.body.transactions.len() + 1).unwrap_or(u32::MAX);
351 db.set_bal_index(post_tx_index);
352
353 if let Some(withdrawals) = &block.body.withdrawals
357 && let Some(recorder) = db.bal_recorder_mut()
358 {
359 recorder.extend_touched_addresses(withdrawals.iter().map(|w| w.address));
360 }
361 }
362
363 let requests = match vm_type {
367 VMType::L1 => extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?,
368 VMType::L2(_) => Default::default(),
369 };
370
371 if let Some(withdrawals) = &block.body.withdrawals {
372 Self::process_withdrawals(db, withdrawals)?;
373 }
374
375 let bal = db.take_bal();
377
378 Ok((
379 BlockExecutionResult {
380 receipts,
381 requests,
382 block_gas_used,
383 tx_gas_breakdowns,
384 },
385 bal,
386 ))
387 }
388
389 #[allow(clippy::too_many_arguments)]
393 pub fn execute_block_pipeline(
394 block: &Block,
395 db: &mut GeneralizedDatabase,
396 vm_type: VMType,
397 merkleizer: Option<Sender<Vec<AccountUpdate>>>,
398 queue_length: &AtomicUsize,
399 crypto: &dyn Crypto,
400 header_bal: Option<&BlockAccessList>,
401 bal_parallel_exec_enabled: bool,
402 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
403 let chain_config = db.store.get_chain_config()?;
404 let is_amsterdam = chain_config.is_amsterdam_activated(block.header.timestamp);
405 let evm_config = EVMConfig::new_from_chain_config(&chain_config, &block.header);
408 let chain_id = chain_config.chain_id;
409
410 debug_assert!(
412 block.body.transactions.len() < u32::MAX as usize,
413 "tx count overflows u32 BlockAccessIndex"
414 );
415
416 let transactions_with_sender =
417 block
418 .body
419 .get_transactions_with_sender(crypto)
420 .map_err(|error| {
421 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
422 })?;
423
424 #[cfg(any(feature = "eip-8025", not(feature = "rayon")))]
425 let _ = (header_bal, bal_parallel_exec_enabled);
428 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
429 if let Some(bal) = header_bal
435 && is_amsterdam
436 && bal_parallel_exec_enabled
437 {
438 validate_header_bal_indices(bal, block.body.transactions.len())
444 .map_err(|e| EvmError::Custom(e.to_string()))?;
445
446 Self::prepare_block(block, db, vm_type, crypto)?;
449
450 let validation_index = bal.build_validation_index();
452
453 LEVM::get_state_transitions_tx(db)?;
455 let system_seed = Arc::new(std::mem::take(&mut db.initial_accounts_state));
456
457 let parallel_result = Self::execute_block_parallel(
458 block,
459 &transactions_with_sender,
460 db,
461 vm_type,
462 bal,
463 merkleizer.as_ref(),
464 queue_length,
465 system_seed,
466 crypto,
467 &validation_index,
468 );
469
470 let (
475 receipts,
476 block_gas_used,
477 mut unread_storage_reads,
478 mut unaccessed_pure_accounts,
479 tx_gas_breakdowns,
480 ) = match parallel_result {
481 Ok(result) => result,
482 Err(parallel_err) => {
483 let last_tx_idx =
484 u32::try_from(block.body.transactions.len()).unwrap_or(u32::MAX);
485 if Self::seed_db_from_bal(
486 db,
487 bal,
488 last_tx_idx,
489 &validation_index.accounts_by_min_index,
490 )
491 .is_ok()
492 && let VMType::L1 = vm_type
493 && let Err(e @ EvmError::SystemContractCallFailed(_)) =
494 extract_all_requests_levm(&[], db, &block.header, vm_type, crypto)
495 {
496 return Err(e);
497 }
498 return Err(parallel_err);
499 }
500 };
501
502 let last_tx_idx = u32::try_from(block.body.transactions.len()).unwrap_or(u32::MAX);
507 Self::seed_db_from_bal(
509 db,
510 bal,
511 last_tx_idx,
512 &validation_index.accounts_by_min_index,
513 )?;
514
515 let requests = match vm_type {
517 VMType::L1 => {
518 extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?
519 }
520 VMType::L2(_) => Default::default(),
521 };
522
523 if let Some(withdrawals) = &block.body.withdrawals {
524 Self::process_withdrawals(db, withdrawals)?;
525 }
526 let withdrawal_idx = u32::try_from(block.body.transactions.len())
534 .map(|n| n.saturating_add(1))
535 .unwrap_or(u32::MAX);
536 Self::validate_bal_withdrawal_index(db, bal, withdrawal_idx, &validation_index)?;
537
538 if !unread_storage_reads.is_empty() {
540 for (addr, acct) in &db.current_accounts_state {
541 for key in acct.storage.keys() {
542 unread_storage_reads.remove(&(*addr, *key));
543 }
544 }
545 }
546
547 if !unaccessed_pure_accounts.is_empty() {
552 if let Some(withdrawals) = &block.body.withdrawals {
553 for w in withdrawals {
554 unaccessed_pure_accounts.remove(&w.address);
555 }
556 }
557 for addr in db.current_accounts_state.keys() {
558 if *addr == SYSTEM_ADDRESS {
562 continue;
563 }
564 unaccessed_pure_accounts.remove(addr);
565 }
566 }
567
568 if let Some((addr, key)) = unread_storage_reads.iter().next() {
570 let slot = ethrex_common::BigEndianHash::into_uint(key);
571 return Err(EvmError::Custom(format!(
572 "BAL validation failed: storage_read for account {addr:?} slot \
573 {slot} was never actually read during block execution"
574 )));
575 }
576
577 if let Some(addr) = unaccessed_pure_accounts.iter().next() {
579 return Err(EvmError::Custom(format!(
580 "BAL validation failed: account {addr:?} has no mutations \
581 and no storage reads but was never accessed during block execution"
582 )));
583 }
584
585 validate_block_access_list_size(&block.header, &chain_config, bal)
588 .map_err(|e| EvmError::Custom(e.to_string()))?;
589
590 return Ok((
591 BlockExecutionResult {
592 receipts,
593 requests,
594 block_gas_used,
595 tx_gas_breakdowns,
596 },
597 None,
598 ));
599 }
600
601 let Some(merkleizer) = merkleizer else {
607 return Err(EvmError::Custom(
608 "sequential execution path called without a merkleizer Sender".to_string(),
609 ));
610 };
611 if is_amsterdam {
612 db.enable_bal_recording();
613 db.set_bal_index(0);
615 }
616
617 Self::prepare_block(block, db, vm_type, crypto)?;
618
619 let base_blob_fee_per_gas =
621 get_base_fee_per_blob_gas(block.header.excess_blob_gas, &evm_config)?;
622
623 let mut shared_stack_pool = Vec::with_capacity(STACK_LIMIT);
624 let mut shared_memory_pool = Vec::with_capacity(1);
626
627 let n_txs = block.body.transactions.len();
628 let mut receipts = Vec::with_capacity(n_txs);
629 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(n_txs);
630 let mut cumulative_gas_used = 0_u64;
632 let mut block_gas_used = 0_u64;
634 let mut block_regular_gas_used = 0_u64;
636 let mut block_state_gas_used = 0_u64;
637 let mut tx_since_last_flush = 2;
640
641 for (tx_idx, (tx, tx_sender)) in transactions_with_sender.into_iter().enumerate() {
642 if !is_amsterdam {
649 check_gas_limit(cumulative_gas_used, tx.gas_limit(), block.header.gas_limit)?;
650 }
651
652 if is_amsterdam {
654 check_2d_gas_allowance(
655 tx,
656 Fork::Amsterdam,
657 block_regular_gas_used,
658 block_state_gas_used,
659 block.header.gas_limit,
660 )?;
661 }
662
663 if is_amsterdam {
665 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
666 db.set_bal_index(bal_index);
667
668 if let Some(recorder) = db.bal_recorder_mut() {
670 recorder.record_touched_address(tx_sender);
671 if let TxKind::Call(to) = tx.to() {
672 recorder.record_touched_address(to);
673 }
674 }
675 }
676
677 let report = Self::execute_tx_in_block(
678 tx,
679 tx_sender,
680 &block.header,
681 db,
682 vm_type,
683 base_blob_fee_per_gas,
684 &mut shared_stack_pool,
685 &mut shared_memory_pool,
686 false,
687 crypto,
688 evm_config,
689 chain_id,
690 )?;
691
692 tx_gas_breakdowns.push(TxGasBreakdown::from_report(
693 tx_idx,
694 tx.hash(crypto),
695 &report,
696 ));
697
698 if queue_length.load(Ordering::Relaxed) == 0 && tx_since_last_flush > 5 {
699 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
700 tx_since_last_flush = 0;
701 } else {
702 tx_since_last_flush += 1;
703 }
704
705 cumulative_gas_used += report.gas_spent;
707
708 let tx_state_gas = report.state_gas_used;
711 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
712 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
713 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
714
715 if is_amsterdam {
716 block_gas_used = block_regular_gas_used.max(block_state_gas_used);
718
719 if block_regular_gas_used > block.header.gas_limit
724 || block_state_gas_used > block.header.gas_limit
725 {
726 return Err(EvmError::Transaction(format!(
727 "Gas allowance exceeded: Block gas used overflow: \
728 block_gas_used {block_gas_used} > block_gas_limit {}",
729 block.header.gas_limit
730 )));
731 }
732 } else {
733 block_gas_used = block_gas_used.saturating_add(report.gas_used);
734 }
735
736 let receipt = Receipt::new(
737 tx.tx_type(),
738 matches!(report.result, TxResult::Success),
739 cumulative_gas_used,
740 report.logs,
741 );
742
743 receipts.push(receipt);
744 }
745
746 if is_amsterdam && block_gas_used > block.header.gas_limit {
750 return Err(EvmError::Transaction(format!(
751 "Gas allowance exceeded: Block gas used overflow: \
752 block_gas_used {block_gas_used} > block_gas_limit {}",
753 block.header.gas_limit
754 )));
755 }
756
757 #[cfg(feature = "perf_opcode_timings")]
758 {
759 let mut timings = OPCODE_TIMINGS.lock().expect("poison");
760 timings.inc_tx_count(receipts.len());
761 timings.inc_block_count();
762 ::tracing::info!("{}", timings.info_pretty());
763 let precompiles_timings = PRECOMPILES_TIMINGS.lock().expect("poison");
764 ::tracing::info!("{}", precompiles_timings.info_pretty());
765 }
766
767 if queue_length.load(Ordering::Relaxed) == 0 {
768 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
769 }
770
771 if is_amsterdam {
774 let post_tx_index =
775 u32::try_from(block.body.transactions.len() + 1).unwrap_or(u32::MAX);
776 db.set_bal_index(post_tx_index);
777
778 if let Some(withdrawals) = &block.body.withdrawals
780 && let Some(recorder) = db.bal_recorder_mut()
781 {
782 recorder.extend_touched_addresses(withdrawals.iter().map(|w| w.address));
783 }
784 }
785
786 let requests = match vm_type {
790 VMType::L1 => extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?,
791 VMType::L2(_) => Default::default(),
792 };
793
794 if let Some(withdrawals) = &block.body.withdrawals {
795 Self::process_withdrawals(db, withdrawals)?;
796 }
797 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
798
799 let bal = db.take_bal();
801
802 Ok((
803 BlockExecutionResult {
804 receipts,
805 requests,
806 block_gas_used,
807 tx_gas_breakdowns,
808 },
809 bal,
810 ))
811 }
812
813 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
818 fn bal_to_account_updates(
819 bal: &BlockAccessList,
820 store: &dyn Database,
821 ) -> Result<Vec<AccountUpdate>, EvmError> {
822 use ethrex_common::types::AccountInfo;
823
824 let mut updates = Vec::new();
825
826 let write_addrs: Vec<Address> = bal
828 .accounts()
829 .iter()
830 .filter(|ac| {
831 !ac.balance_changes.is_empty()
832 || !ac.nonce_changes.is_empty()
833 || !ac.code_changes.is_empty()
834 || !ac.storage_changes.is_empty()
835 })
836 .map(|ac| ac.address)
837 .collect();
838 store
839 .prefetch_accounts(&write_addrs)
840 .map_err(|e| EvmError::Custom(format!("bal_to_account_updates prefetch: {e}")))?;
841
842 for acct_changes in bal.accounts() {
843 let addr = acct_changes.address;
844
845 let has_writes = !acct_changes.balance_changes.is_empty()
847 || !acct_changes.nonce_changes.is_empty()
848 || !acct_changes.code_changes.is_empty()
849 || !acct_changes.storage_changes.is_empty();
850 if !has_writes {
851 continue;
852 }
853
854 let prestate = store
856 .get_account_state(addr)
857 .map_err(|e| EvmError::Custom(format!("bal_to_account_updates: {e}")))?;
858
859 let balance = acct_changes
861 .balance_changes
862 .last()
863 .map(|c| c.post_balance)
864 .unwrap_or(prestate.balance);
865
866 let nonce = acct_changes
868 .nonce_changes
869 .last()
870 .map(|c| c.post_nonce)
871 .unwrap_or(prestate.nonce);
872
873 let (code_hash, code) = if let Some(c) = acct_changes.code_changes.last() {
875 code_from_bal(&c.new_code)
876 } else {
877 (prestate.code_hash, None)
878 };
879
880 let mut added_storage = FxHashMap::with_capacity_and_hasher(
882 acct_changes.storage_changes.len(),
883 Default::default(),
884 );
885 for slot_change in &acct_changes.storage_changes {
886 if let Some(last) = slot_change.slot_changes.last() {
887 let key = ethrex_common::utils::u256_to_h256(slot_change.slot);
888 added_storage.insert(key, last.post_value);
889 }
890 }
891
892 let post_empty = balance.is_zero() && nonce == 0 && code_hash == *EMPTY_KECCAK_HASH;
894 let pre_empty = prestate.balance.is_zero()
895 && prestate.nonce == 0
896 && prestate.code_hash == *EMPTY_KECCAK_HASH;
897 let removed = post_empty && !pre_empty;
898
899 let balance_changed = acct_changes
900 .balance_changes
901 .last()
902 .is_some_and(|c| c.post_balance != prestate.balance);
903 let nonce_changed = acct_changes
904 .nonce_changes
905 .last()
906 .is_some_and(|c| c.post_nonce != prestate.nonce);
907 let code_changed = acct_changes.code_changes.last().is_some();
908 let acc_info_updated = balance_changed || nonce_changed || code_changed;
909
910 if !removed && !acc_info_updated && added_storage.is_empty() {
911 continue;
912 }
913
914 let info = if acc_info_updated {
915 Some(AccountInfo {
916 code_hash,
917 balance,
918 nonce,
919 })
920 } else {
921 None
922 };
923
924 let update = AccountUpdate {
925 address: addr,
926 removed,
927 info,
928 code,
929 added_storage,
930 removed_storage: false,
937 };
938 updates.push(update);
939 }
940
941 Ok(updates)
942 }
943
944 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
961 fn seed_db_from_bal(
962 db: &mut GeneralizedDatabase,
963 bal: &BlockAccessList,
964 max_idx: u32,
965 accounts_by_min_index: &[(u32, usize)],
966 ) -> Result<(), EvmError> {
967 let end = accounts_by_min_index.partition_point(|(min_idx, _)| *min_idx <= max_idx);
968 let bal_accounts = bal.accounts();
969 for &(_, acct_idx) in &accounts_by_min_index[..end] {
970 seed_one_address_info_from_bal(db, bal, acct_idx, max_idx)
971 .map_err(|e| EvmError::Custom(format!("seed_db_from_bal: {e}")))?;
972
973 let acct_changes = &bal_accounts[acct_idx];
974 if acct_changes.storage_changes.is_empty() {
975 continue;
976 }
977 let any_storage = acct_changes.storage_changes.iter().any(|sc| {
978 sc.slot_changes
979 .first()
980 .is_some_and(|c| c.block_access_index <= max_idx)
981 });
982 if !any_storage {
983 continue;
984 }
985 let addr = acct_changes.address;
986 if !db.current_accounts_state.contains_key(&addr) {
987 db.get_account(addr)
988 .map_err(|e| EvmError::Custom(format!("seed storage: {e}")))?;
989 }
990 let acc = db
991 .get_account_mut(addr)
992 .map_err(|e| EvmError::Custom(format!("seed storage mut: {e}")))?;
993 for sc in &acct_changes.storage_changes {
994 if let Some(value) = post_value_at_or_before(sc, max_idx) {
995 acc.storage
996 .insert(ethrex_common::utils::u256_to_h256(sc.slot), value);
997 }
998 }
999 }
1000 Ok(())
1001 }
1002
1003 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1010 #[allow(clippy::too_many_arguments, clippy::type_complexity)]
1011 fn execute_block_parallel(
1012 block: &Block,
1013 txs_with_sender: &[(&Transaction, Address)],
1014 db: &mut GeneralizedDatabase,
1015 vm_type: VMType,
1016 bal: &BlockAccessList,
1017 merkleizer: Option<&Sender<Vec<AccountUpdate>>>,
1018 queue_length: &AtomicUsize,
1019 system_seed: Arc<CacheDB>,
1020 crypto: &dyn Crypto,
1021 validation_index: &BalAddressIndex,
1022 ) -> Result<
1023 (
1024 Vec<Receipt>,
1025 u64,
1026 FxHashSet<(Address, H256)>,
1027 FxHashSet<Address>,
1028 Vec<TxGasBreakdown>,
1029 ),
1030 EvmError,
1031 > {
1032 let store = db.store.clone();
1033 let header = &block.header;
1034 let n_txs = txs_with_sender.len();
1035 let chain_config = store.get_chain_config()?;
1040 let is_amsterdam = chain_config.is_amsterdam_activated(header.timestamp);
1041 let evm_config = EVMConfig::new_from_chain_config(&chain_config, header);
1044 let chain_id = chain_config.chain_id;
1045 let base_blob_fee_per_gas = get_base_fee_per_blob_gas(header.excess_blob_gas, &evm_config)?;
1047 debug_assert!(
1048 is_amsterdam,
1049 "execute_block_parallel invoked on non-Amsterdam block"
1050 );
1051
1052 if let Some(merkleizer) = merkleizer {
1056 let account_updates = Self::bal_to_account_updates(bal, store.as_ref())?;
1057 merkleizer
1058 .send(account_updates)
1059 .map_err(|e| EvmError::Custom(format!("merkleizer send failed: {e}")))?;
1060 queue_length.fetch_add(1, Ordering::Relaxed);
1061 }
1062
1063 let mut unread_storage_reads: FxHashSet<(Address, H256)> = FxHashSet::default();
1066 let mut unaccessed_pure_accounts: FxHashSet<Address> = FxHashSet::default();
1069 for acct in bal.accounts() {
1070 for &slot in &acct.storage_reads {
1071 let key = ethrex_common::utils::u256_to_h256(slot);
1072 unread_storage_reads.insert((acct.address, key));
1073 }
1074 let is_pure = acct.storage_changes.is_empty()
1075 && acct.storage_reads.is_empty()
1076 && acct.balance_changes.is_empty()
1077 && acct.nonce_changes.is_empty()
1078 && acct.code_changes.is_empty();
1079 if is_pure {
1080 unaccessed_pure_accounts.insert(acct.address);
1081 }
1082 }
1083
1084 for addr in system_seed.keys() {
1090 if *addr == SYSTEM_ADDRESS {
1091 continue;
1092 }
1093 unaccessed_pure_accounts.remove(addr);
1094 }
1095
1096 unread_storage_reads.retain(|(addr, key)| {
1098 !system_seed
1099 .get(addr)
1100 .is_some_and(|a| a.storage.contains_key(key))
1101 });
1102
1103 let arc_bal = Arc::new(bal.clone());
1105 let arc_idx = Arc::new(validation_index.clone());
1106
1107 type TxExecResult = (
1121 usize,
1122 TxType,
1123 ExecutionReport,
1124 FxHashSet<Address>, Vec<(Address, H256)>, Vec<Address>, Option<EvmError>, );
1129
1130 let exec_results: Result<Vec<TxExecResult>, EvmError> = (0..n_txs)
1131 .into_par_iter()
1132 .map(|tx_idx| -> Result<_, EvmError> {
1133 let (tx, sender) = &txs_with_sender[tx_idx];
1134 let mut tx_db = GeneralizedDatabase::new_with_shared_base_and_capacity(
1135 store.clone(),
1136 system_seed.clone(),
1137 32,
1138 );
1139 tx_db.lazy_bal = Some(LazyBalCursor {
1140 bal: arc_bal.clone(),
1141 bal_index: u32::try_from(tx_idx + 1).unwrap_or(u32::MAX),
1142 index: arc_idx.clone(),
1143 });
1144 let mut stack_pool = Vec::with_capacity(8);
1147 let mut memory_pool = Vec::with_capacity(1);
1149
1150 tx_db.accessed_accounts =
1154 Some(FxHashSet::with_capacity_and_hasher(16, Default::default()));
1155
1156 tx_db.enable_bal_recording();
1161 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
1162 tx_db.set_bal_index(bal_index);
1163 if let Some(recorder) = tx_db.bal_recorder_mut() {
1164 recorder.record_touched_address(*sender);
1165 if let TxKind::Call(to) = tx.to() {
1166 recorder.record_touched_address(to);
1167 }
1168 }
1169
1170 let report = LEVM::execute_tx_in_block(
1171 tx,
1172 *sender,
1173 header,
1174 &mut tx_db,
1175 vm_type,
1176 base_blob_fee_per_gas,
1177 &mut stack_pool,
1178 &mut memory_pool,
1179 false,
1180 crypto,
1181 evm_config,
1182 chain_id,
1183 )?;
1184
1185 let current_state = std::mem::take(&mut tx_db.current_accounts_state);
1186 let codes = std::mem::take(&mut tx_db.codes);
1187 let tracked = tx_db.accessed_accounts.take().unwrap_or_default();
1188 let (shadow_touched, shadow_reads) = tx_db
1189 .bal_recorder
1190 .take()
1191 .map(|mut r| (r.take_touched_addresses(), r.take_storage_reads()))
1192 .unwrap_or_default();
1193
1194 let mut reads_satisfied: Vec<(Address, H256)> =
1202 Vec::with_capacity(current_state.len() * 4);
1203 let mut destroyed: Vec<Address> = Vec::new();
1207 for (addr, acct) in ¤t_state {
1208 if matches!(
1209 acct.status,
1210 AccountStatus::Destroyed | AccountStatus::DestroyedModified
1211 ) {
1212 destroyed.push(*addr);
1213 } else {
1214 for key in acct.storage.keys() {
1215 reads_satisfied.push((*addr, *key));
1216 }
1217 }
1218 }
1219
1220 let deferred_bal_err: Option<EvmError> = (|| -> Result<(), EvmError> {
1226 let bal_idx = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
1227 let seed_idx = u32::try_from(tx_idx).unwrap_or(u32::MAX);
1228 Self::validate_tx_execution(
1229 bal_idx,
1230 seed_idx,
1231 ¤t_state,
1232 &codes,
1233 bal,
1234 validation_index,
1235 &system_seed,
1236 &store,
1237 )
1238 .map_err(|e| {
1239 EvmError::Custom(format!("BAL validation failed for tx {tx_idx}: {e}"))
1240 })?;
1241
1242 for addr in &shadow_touched {
1244 if !validation_index.addr_to_idx.contains_key(addr) {
1245 return Err(EvmError::Custom(format!(
1246 "BAL validation failed for tx {tx_idx}: account {addr:?} was \
1247 accessed during execution but is missing from BAL"
1248 )));
1249 }
1250 }
1251 for (addr, slot) in &shadow_reads {
1252 let Some(&bal_acct_idx) = validation_index.addr_to_idx.get(addr) else {
1253 continue;
1255 };
1256 let acct = &bal.accounts()[bal_acct_idx];
1257 let in_changes = acct
1258 .storage_changes
1259 .binary_search_by(|sc| sc.slot.cmp(slot))
1260 .is_ok();
1261 let in_reads = acct.storage_reads.contains(slot);
1262 if !in_changes && !in_reads {
1263 return Err(EvmError::Custom(format!(
1264 "BAL validation failed for tx {tx_idx}: storage slot {slot} of \
1265 account {addr:?} was read during execution but is missing from \
1266 BAL (no storage_changes or storage_reads entry)"
1267 )));
1268 }
1269 }
1270 Ok(())
1271 })()
1272 .err();
1273
1274 drop(current_state);
1275 drop(codes);
1276
1277 Ok((
1278 tx_idx,
1279 tx.tx_type(),
1280 report,
1281 tracked,
1282 reads_satisfied,
1283 destroyed,
1284 deferred_bal_err,
1285 ))
1286 })
1287 .collect();
1288
1289 let mut exec_results = exec_results?;
1290
1291 exec_results.sort_unstable_by_key(|(idx, _, _, _, _, _, _)| *idx);
1298
1299 let mut block_regular_gas_used = 0_u64;
1304 let mut block_state_gas_used = 0_u64;
1305 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(exec_results.len());
1306 for (tx_idx, _, report, _, _, _, _) in &exec_results {
1307 let (tx, _) = txs_with_sender
1308 .get(*tx_idx)
1309 .ok_or_else(|| EvmError::Custom(format!("tx index {tx_idx} out of bounds")))?;
1310 if is_amsterdam {
1311 check_2d_gas_allowance(
1312 tx,
1313 Fork::Amsterdam,
1314 block_regular_gas_used,
1315 block_state_gas_used,
1316 header.gas_limit,
1317 )?;
1318 }
1319
1320 tx_gas_breakdowns.push(TxGasBreakdown::from_report(
1321 *tx_idx,
1322 tx.hash(crypto),
1323 report,
1324 ));
1325
1326 let tx_state_gas = report.state_gas_used;
1327 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
1328 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
1329 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
1330 }
1331 let block_gas_used = block_regular_gas_used.max(block_state_gas_used);
1332 if block_gas_used > header.gas_limit {
1334 return Err(EvmError::Transaction(format!(
1335 "Gas allowance exceeded: Block gas used overflow: \
1336 block_gas_used {block_gas_used} > block_gas_limit {}",
1337 header.gas_limit
1338 )));
1339 }
1340
1341 for (_, _, _, _, _, _, deferred) in &mut exec_results {
1344 if let Some(err) = deferred.take() {
1345 return Err(err);
1346 }
1347 }
1348
1349 for (_, _, _, tracked_accounts, reads_satisfied, destroyed, _) in &exec_results {
1352 if !unread_storage_reads.is_empty() {
1353 for addr in destroyed {
1354 unread_storage_reads.retain(|&(a, _)| a != *addr);
1355 }
1356 for pair in reads_satisfied {
1357 unread_storage_reads.remove(pair);
1358 }
1359 }
1360 if !unaccessed_pure_accounts.is_empty() {
1364 unaccessed_pure_accounts.remove(&header.coinbase);
1365 for addr in tracked_accounts {
1366 unaccessed_pure_accounts.remove(addr);
1367 }
1368 }
1369 }
1370
1371 let mut receipts = Vec::with_capacity(n_txs);
1373 let mut cumulative_gas_used = 0_u64;
1374 for (_, tx_type, report, _, _, _, _) in exec_results {
1375 cumulative_gas_used += report.gas_spent;
1376 let receipt = Receipt::new(
1377 tx_type,
1378 matches!(report.result, TxResult::Success),
1379 cumulative_gas_used,
1380 report.logs,
1381 );
1382 receipts.push(receipt);
1383 }
1384
1385 Ok((
1386 receipts,
1387 block_gas_used,
1388 unread_storage_reads,
1389 unaccessed_pure_accounts,
1390 tx_gas_breakdowns,
1391 ))
1392 }
1393
1394 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1397 fn seeded_balance(
1398 seed_idx: u32,
1399 acct: ðrex_common::types::block_access_list::AccountChanges,
1400 system_seed: &CacheDB,
1401 store: &Arc<dyn Database>,
1402 ) -> Result<U256, BalValidationError> {
1403 let pos = acct
1404 .balance_changes
1405 .partition_point(|c| c.block_access_index <= seed_idx);
1406 if pos > 0 {
1407 Ok(acct.balance_changes[pos - 1].post_balance)
1408 } else if let Some(a) = system_seed.get(&acct.address) {
1409 Ok(a.info.balance)
1410 } else {
1411 store
1412 .get_account_state(acct.address)
1413 .map(|a| a.balance)
1414 .map_err(|e| {
1415 BalValidationError::Database(format!(
1416 "DB error reading balance for {:?}: {e}",
1417 acct.address
1418 ))
1419 })
1420 }
1421 }
1422
1423 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1426 fn seeded_nonce(
1427 seed_idx: u32,
1428 acct: ðrex_common::types::block_access_list::AccountChanges,
1429 system_seed: &CacheDB,
1430 store: &Arc<dyn Database>,
1431 ) -> Result<u64, BalValidationError> {
1432 let pos = acct
1433 .nonce_changes
1434 .partition_point(|c| c.block_access_index <= seed_idx);
1435 if pos > 0 {
1436 Ok(acct.nonce_changes[pos - 1].post_nonce)
1437 } else if let Some(a) = system_seed.get(&acct.address) {
1438 Ok(a.info.nonce)
1439 } else {
1440 store
1441 .get_account_state(acct.address)
1442 .map(|a| a.nonce)
1443 .map_err(|e| {
1444 BalValidationError::Database(format!(
1445 "DB error reading nonce for {:?}: {e}",
1446 acct.address
1447 ))
1448 })
1449 }
1450 }
1451
1452 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1468 #[allow(clippy::too_many_arguments)]
1469 fn validate_tx_execution(
1470 bal_idx: u32,
1471 seed_idx: u32,
1472 current_state: &FxHashMap<Address, LevmAccount>,
1473 codes: &FxHashMap<H256, Code>,
1474 bal: &BlockAccessList,
1475 index: &BalAddressIndex,
1476 system_seed: &CacheDB,
1477 store: &Arc<dyn Database>,
1478 ) -> Result<(), BalValidationError> {
1479 if let Some(active_accounts) = index.tx_to_accounts.get(&bal_idx) {
1482 for &acct_inner_idx in active_accounts {
1483 let acct = &bal.accounts()[acct_inner_idx];
1484 let addr = acct.address;
1485 let actual = current_state.get(&addr);
1486
1487 if let Some(expected) = find_exact_change_balance(&acct.balance_changes, bal_idx) {
1489 match actual {
1490 Some(a) if a.info.balance == expected => {}
1491 Some(a) => {
1492 return Err(BalValidationError::Mismatch(format!(
1493 "account {addr:?} balance mismatch at index {bal_idx}: BAL={expected}, exec={} (diff={})",
1494 a.info.balance,
1495 describe_balance_diff(expected, a.info.balance),
1496 )));
1497 }
1498 None => {
1499 let seeded = Self::seeded_balance(seed_idx, acct, system_seed, store)?;
1504 if expected != seeded {
1505 let all_bal_indices: Vec<u32> = acct
1507 .balance_changes
1508 .iter()
1509 .map(|c| c.block_access_index)
1510 .collect();
1511 let all_nonce_indices: Vec<u32> = acct
1512 .nonce_changes
1513 .iter()
1514 .map(|c| c.block_access_index)
1515 .collect();
1516 let all_storage_indices: Vec<(u32, u64)> = acct
1517 .storage_changes
1518 .iter()
1519 .flat_map(|sc| {
1520 sc.slot_changes
1521 .iter()
1522 .map(|c| (c.block_access_index, sc.slot.low_u64()))
1523 })
1524 .collect();
1525 let code_indices: Vec<u32> = acct
1526 .code_changes
1527 .iter()
1528 .map(|c| c.block_access_index)
1529 .collect();
1530 return Err(BalValidationError::Mismatch(format!(
1531 "account {addr:?} has BAL balance change at {bal_idx} \
1532 but not in execution state (expected={expected}, pre={seeded}, \
1533 all_bal_idx={all_bal_indices:?}, nonce_idx={all_nonce_indices:?}, \
1534 storage_idx={all_storage_indices:?}, code_idx={code_indices:?})"
1535 )));
1536 }
1537 }
1538 }
1539 }
1540
1541 if let Some(expected) = find_exact_change_nonce(&acct.nonce_changes, bal_idx) {
1543 match actual {
1544 Some(a) if a.info.nonce == expected => {}
1545 Some(a) => {
1546 return Err(BalValidationError::Mismatch(format!(
1547 "account {addr:?} nonce mismatch at index {bal_idx}: BAL={expected}, exec={}",
1548 a.info.nonce
1549 )));
1550 }
1551 None => {
1552 let seeded = Self::seeded_nonce(seed_idx, acct, system_seed, store)?;
1553 if expected != seeded {
1554 return Err(BalValidationError::Mismatch(format!(
1555 "account {addr:?} has BAL nonce change at {bal_idx} \
1556 but not in execution state (expected={expected}, pre={seeded})"
1557 )));
1558 }
1559 }
1560 }
1561 }
1562
1563 if let Some(expected_code) = find_exact_change_code(&acct.code_changes, bal_idx) {
1565 match actual {
1566 Some(a) => {
1567 let actual_code = if let Some(c) = codes.get(&a.info.code_hash) {
1568 c.code_bytes()
1569 } else {
1570 let c = store.get_account_code(a.info.code_hash).map_err(|e| {
1571 BalValidationError::Database(format!(
1572 "DB error reading account code for {addr:?}: {e}"
1573 ))
1574 })?;
1575 c.code_bytes()
1576 };
1577 if actual_code != *expected_code {
1578 return Err(BalValidationError::Mismatch(format!(
1579 "account {addr:?} code mismatch at index {bal_idx}"
1580 )));
1581 }
1582 }
1583 None => {
1584 let code_hash = if let Some(a) = system_seed.get(&addr) {
1588 a.info.code_hash
1589 } else {
1590 store
1591 .get_account_state(addr)
1592 .map(|a| a.code_hash)
1593 .map_err(|e| {
1594 BalValidationError::Database(format!(
1595 "DB error reading account state for {addr:?}: {e}"
1596 ))
1597 })?
1598 };
1599 let pre_code = if let Some(c) = codes.get(&code_hash) {
1600 c.code_bytes()
1601 } else {
1602 let c = store.get_account_code(code_hash).map_err(|e| {
1603 BalValidationError::Database(format!(
1604 "DB error reading account code for hash \
1605 {code_hash:?}: {e}"
1606 ))
1607 })?;
1608 c.code_bytes()
1609 };
1610 if *expected_code != pre_code {
1611 return Err(BalValidationError::Mismatch(format!(
1612 "account {addr:?} has BAL code change at {bal_idx} \
1613 but not in execution state"
1614 )));
1615 }
1616 }
1617 }
1618 }
1619
1620 for sc in &acct.storage_changes {
1622 if let Some(expected_value) =
1623 find_exact_change_storage(&sc.slot_changes, bal_idx)
1624 {
1625 let key = ethrex_common::utils::u256_to_h256(sc.slot);
1626 let actual_value = actual.and_then(|a| a.storage.get(&key)).copied();
1627 if actual_value != Some(expected_value) {
1628 if actual.is_none() || actual_value.is_none() {
1630 let pre_value =
1631 store.get_storage_value(addr, key).map_err(|e| {
1632 BalValidationError::Database(format!(
1633 "DB error reading storage for {addr:?} slot {}: {e}",
1634 sc.slot
1635 ))
1636 })?;
1637 if expected_value == pre_value {
1638 continue; }
1640 }
1641 return Err(BalValidationError::Mismatch(format!(
1642 "account {addr:?} storage slot {} mismatch at index {bal_idx}: \
1643 BAL={expected_value}, exec={actual_value:?}",
1644 sc.slot
1645 )));
1646 }
1647 }
1648 }
1649 }
1650 }
1651
1652 for (addr, account) in current_state {
1655 if account.is_unmodified() {
1656 continue;
1657 }
1658
1659 let Some(&bal_acct_idx) = index.addr_to_idx.get(addr) else {
1660 let pre = system_seed
1664 .get(addr)
1665 .map(|a| (a.info.balance, a.info.nonce, a.info.code_hash))
1666 .or_else(|| {
1667 store
1668 .get_account_state(*addr)
1669 .ok()
1670 .map(|a| (a.balance, a.nonce, a.code_hash))
1671 })
1672 .unwrap_or_default();
1673 let post = (
1674 account.info.balance,
1675 account.info.nonce,
1676 account.info.code_hash,
1677 );
1678 if pre != post {
1679 return Err(BalValidationError::Mismatch(format!(
1680 "account {addr:?} was modified by execution but is absent from BAL"
1681 )));
1682 }
1683 continue;
1684 };
1685
1686 let acct = &bal.accounts()[bal_acct_idx];
1687
1688 if !has_exact_change_balance(&acct.balance_changes, bal_idx) {
1690 let seeded_pos = acct
1691 .balance_changes
1692 .partition_point(|c| c.block_access_index <= seed_idx);
1693 let seeded = if seeded_pos > 0 {
1694 acct.balance_changes[seeded_pos - 1].post_balance
1695 } else {
1696 system_seed
1698 .get(addr)
1699 .map(|a| a.info.balance)
1700 .unwrap_or_else(|| {
1701 store
1702 .get_account_state(*addr)
1703 .map(|a| a.balance)
1704 .unwrap_or_default()
1705 })
1706 };
1707 if account.info.balance != seeded {
1708 return Err(BalValidationError::Mismatch(format!(
1709 "account {addr:?} balance changed by execution ({}) but BAL has no \
1710 balance change at index {bal_idx} (seeded={seeded})",
1711 account.info.balance
1712 )));
1713 }
1714 }
1715
1716 if !has_exact_change_nonce(&acct.nonce_changes, bal_idx) {
1718 let seeded_pos = acct
1719 .nonce_changes
1720 .partition_point(|c| c.block_access_index <= seed_idx);
1721 let seeded = if seeded_pos > 0 {
1722 acct.nonce_changes[seeded_pos - 1].post_nonce
1723 } else {
1724 system_seed
1725 .get(addr)
1726 .map(|a| a.info.nonce)
1727 .unwrap_or_else(|| {
1728 store
1729 .get_account_state(*addr)
1730 .map(|a| a.nonce)
1731 .unwrap_or_default()
1732 })
1733 };
1734 if account.info.nonce != seeded {
1735 return Err(BalValidationError::Mismatch(format!(
1736 "account {addr:?} nonce changed by execution ({}) but BAL has no \
1737 nonce change at index {bal_idx} (seeded={seeded})",
1738 account.info.nonce
1739 )));
1740 }
1741 }
1742
1743 if !has_exact_change_code(&acct.code_changes, bal_idx) {
1747 let seeded_pos = acct
1748 .code_changes
1749 .partition_point(|c| c.block_access_index <= seed_idx);
1750 let seeded_hash = if seeded_pos > 0 {
1751 let seeded_code = &acct.code_changes[seeded_pos - 1].new_code;
1752 if seeded_code.is_empty() {
1753 *EMPTY_KECCAK_HASH
1754 } else {
1755 ethrex_common::utils::keccak(seeded_code)
1756 }
1757 } else {
1758 system_seed
1760 .get(addr)
1761 .map(|a| a.info.code_hash)
1762 .unwrap_or_else(|| {
1763 store
1764 .get_account_state(*addr)
1765 .map(|a| a.code_hash)
1766 .unwrap_or(*EMPTY_KECCAK_HASH)
1767 })
1768 };
1769 if account.info.code_hash != seeded_hash {
1770 return Err(BalValidationError::Mismatch(format!(
1771 "account {addr:?} code changed by execution but BAL has no \
1772 code change at index {bal_idx} (seeded_hash={seeded_hash:?})"
1773 )));
1774 }
1775 }
1776
1777 for (key_h256, &value) in &account.storage {
1779 let slot_u256 = u256_from_big_endian_const(key_h256.0);
1780 let pos = acct
1782 .storage_changes
1783 .partition_point(|sc| sc.slot < slot_u256);
1784 if pos < acct.storage_changes.len() && acct.storage_changes[pos].slot == slot_u256 {
1785 let sc = &acct.storage_changes[pos];
1786 if !has_exact_change_storage(&sc.slot_changes, bal_idx) {
1787 let seeded_pos = sc
1788 .slot_changes
1789 .partition_point(|c| c.block_access_index <= seed_idx);
1790 if seeded_pos > 0 {
1791 let seeded = sc.slot_changes[seeded_pos - 1].post_value;
1792 if value != seeded {
1793 return Err(BalValidationError::Mismatch(format!(
1794 "account {addr:?} storage slot {slot_u256} changed by \
1795 execution ({value}) but BAL has no change at index \
1796 {bal_idx} (seeded={seeded})"
1797 )));
1798 }
1799 }
1800 }
1801 }
1802 }
1805 }
1806
1807 Ok(())
1808 }
1809
1810 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1822 fn validate_bal_withdrawal_index(
1823 db: &GeneralizedDatabase,
1824 bal: &BlockAccessList,
1825 withdrawal_idx: u32,
1826 index: &BalAddressIndex,
1827 ) -> Result<(), EvmError> {
1828 for acct in bal.accounts() {
1831 let addr = acct.address;
1832 let actual = db.current_accounts_state.get(&addr);
1833
1834 if let Some(expected) = find_exact_change_balance(&acct.balance_changes, withdrawal_idx)
1836 {
1837 match actual {
1838 Some(a) if a.info.balance == expected => {}
1839 Some(a) => {
1840 return Err(EvmError::Custom(format!(
1841 "BAL validation failed for withdrawal: account {addr:?} balance \
1842 mismatch at index {withdrawal_idx}: BAL={expected}, actual={}",
1843 a.info.balance
1844 )));
1845 }
1846 None => {
1847 return Err(EvmError::Custom(format!(
1848 "BAL validation failed for withdrawal: account {addr:?} has \
1849 balance change at index {withdrawal_idx} but was not touched \
1850 by withdrawal/request phase"
1851 )));
1852 }
1853 }
1854 }
1855
1856 if let Some(expected) = find_exact_change_nonce(&acct.nonce_changes, withdrawal_idx) {
1858 match actual {
1859 Some(a) if a.info.nonce == expected => {}
1860 Some(a) => {
1861 return Err(EvmError::Custom(format!(
1862 "BAL validation failed for withdrawal: account {addr:?} nonce \
1863 mismatch at index {withdrawal_idx}: BAL={expected}, actual={}",
1864 a.info.nonce
1865 )));
1866 }
1867 None => {
1868 return Err(EvmError::Custom(format!(
1869 "BAL validation failed for withdrawal: account {addr:?} has \
1870 nonce change at index {withdrawal_idx} but was not touched \
1871 by withdrawal/request phase"
1872 )));
1873 }
1874 }
1875 }
1876
1877 if let Some(expected_code) = find_exact_change_code(&acct.code_changes, withdrawal_idx)
1879 {
1880 let code_hash = if expected_code.is_empty() {
1881 *EMPTY_KECCAK_HASH
1882 } else {
1883 ethrex_common::utils::keccak(expected_code)
1884 };
1885 match actual {
1886 Some(a) if a.info.code_hash == code_hash => {}
1887 Some(_) => {
1888 return Err(EvmError::Custom(format!(
1889 "BAL validation failed for withdrawal: account {addr:?} code \
1890 mismatch at index {withdrawal_idx}"
1891 )));
1892 }
1893 None => {
1894 return Err(EvmError::Custom(format!(
1895 "BAL validation failed for withdrawal: account {addr:?} has \
1896 code change at index {withdrawal_idx} but was not touched \
1897 by withdrawal/request phase"
1898 )));
1899 }
1900 }
1901 }
1902
1903 for sc in &acct.storage_changes {
1905 if let Some(expected_value) =
1906 find_exact_change_storage(&sc.slot_changes, withdrawal_idx)
1907 {
1908 let key = ethrex_common::utils::u256_to_h256(sc.slot);
1909 let actual_value = actual.and_then(|a| a.storage.get(&key)).copied();
1910 if actual_value != Some(expected_value) {
1911 return Err(EvmError::Custom(format!(
1912 "BAL validation failed for withdrawal: account {addr:?} storage \
1913 slot {} mismatch at index {withdrawal_idx}: BAL={expected_value}, \
1914 actual={actual_value:?}",
1915 sc.slot
1916 )));
1917 }
1918 }
1919 }
1920 }
1921
1922 for (addr, account) in &db.current_accounts_state {
1925 if account.is_unmodified() {
1926 continue;
1927 }
1928
1929 let Some(&bal_acct_idx) = index.addr_to_idx.get(addr) else {
1930 let pre_state = db.store.get_account_state(*addr).map_err(|e| {
1934 EvmError::Custom(format!(
1935 "BAL validation failed for withdrawal: db error reading \
1936 account {addr:?}: {e}"
1937 ))
1938 })?;
1939 let pre = (pre_state.balance, pre_state.nonce, pre_state.code_hash);
1940 let post = (
1941 account.info.balance,
1942 account.info.nonce,
1943 account.info.code_hash,
1944 );
1945 if pre != post {
1946 return Err(EvmError::Custom(format!(
1947 "BAL validation failed for withdrawal: account {addr:?} was modified \
1948 during withdrawal/request phase but is absent from BAL"
1949 )));
1950 }
1951 for (key_h256, &value) in &account.storage {
1954 let pre_value = db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
1955 EvmError::Custom(format!(
1956 "BAL validation failed for withdrawal: db error reading \
1957 storage {addr:?}[{}]: {e}",
1958 u256_from_big_endian_const(key_h256.0)
1959 ))
1960 })?;
1961 if value != pre_value {
1962 return Err(EvmError::Custom(format!(
1963 "BAL validation failed for withdrawal: account {addr:?} storage \
1964 slot {} changed during withdrawal/request phase but is absent \
1965 from BAL",
1966 u256_from_big_endian_const(key_h256.0)
1967 )));
1968 }
1969 }
1970 continue;
1971 };
1972
1973 let acct = &bal.accounts()[bal_acct_idx];
1974
1975 if !has_exact_change_balance(&acct.balance_changes, withdrawal_idx) {
1978 let seeded = match acct.balance_changes.last() {
1979 Some(c) => c.post_balance,
1980 None => {
1981 db.store
1982 .get_account_state(*addr)
1983 .map_err(|e| {
1984 EvmError::Custom(format!(
1985 "BAL validation failed for withdrawal: db error reading \
1986 account {addr:?}: {e}"
1987 ))
1988 })?
1989 .balance
1990 }
1991 };
1992 if account.info.balance != seeded {
1993 return Err(EvmError::Custom(format!(
1994 "BAL validation failed for withdrawal: account {addr:?} balance \
1995 changed during withdrawal/request phase ({}) but BAL has no \
1996 balance change at index {withdrawal_idx} (last_bal={seeded})",
1997 account.info.balance
1998 )));
1999 }
2000 }
2001
2002 if !has_exact_change_nonce(&acct.nonce_changes, withdrawal_idx) {
2004 let seeded = match acct.nonce_changes.last() {
2005 Some(c) => c.post_nonce,
2006 None => {
2007 db.store
2008 .get_account_state(*addr)
2009 .map_err(|e| {
2010 EvmError::Custom(format!(
2011 "BAL validation failed for withdrawal: db error reading \
2012 account {addr:?}: {e}"
2013 ))
2014 })?
2015 .nonce
2016 }
2017 };
2018 if account.info.nonce != seeded {
2019 return Err(EvmError::Custom(format!(
2020 "BAL validation failed for withdrawal: account {addr:?} nonce \
2021 changed during withdrawal/request phase ({}) but BAL has no \
2022 nonce change at index {withdrawal_idx} (last_bal={seeded})",
2023 account.info.nonce
2024 )));
2025 }
2026 }
2027
2028 if !has_exact_change_code(&acct.code_changes, withdrawal_idx) {
2030 let seeded_hash = match acct.code_changes.last() {
2031 Some(c) if c.new_code.is_empty() => *EMPTY_KECCAK_HASH,
2032 Some(c) => ethrex_common::utils::keccak(&c.new_code),
2033 None => {
2034 db.store
2035 .get_account_state(*addr)
2036 .map_err(|e| {
2037 EvmError::Custom(format!(
2038 "BAL validation failed for withdrawal: db error reading \
2039 account {addr:?}: {e}"
2040 ))
2041 })?
2042 .code_hash
2043 }
2044 };
2045 if account.info.code_hash != seeded_hash {
2046 return Err(EvmError::Custom(format!(
2047 "BAL validation failed for withdrawal: account {addr:?} code \
2048 changed during withdrawal/request phase but BAL has no \
2049 code change at index {withdrawal_idx} \
2050 (actual={:?}, last_bal={seeded_hash:?})",
2051 account.info.code_hash
2052 )));
2053 }
2054 }
2055
2056 for (key_h256, &value) in &account.storage {
2059 let slot_u256 = u256_from_big_endian_const(key_h256.0);
2060 let pos = acct
2061 .storage_changes
2062 .partition_point(|sc| sc.slot < slot_u256);
2063 if pos < acct.storage_changes.len() && acct.storage_changes[pos].slot == slot_u256 {
2064 let sc = &acct.storage_changes[pos];
2065 if !has_exact_change_storage(&sc.slot_changes, withdrawal_idx) {
2066 let seeded = match sc.slot_changes.last() {
2069 Some(c) => c.post_value,
2070 None => db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
2071 EvmError::Custom(format!(
2072 "BAL validation failed for withdrawal: db error reading \
2073 storage {addr:?}[{slot_u256}]: {e}"
2074 ))
2075 })?,
2076 };
2077 if value != seeded {
2078 return Err(EvmError::Custom(format!(
2079 "BAL validation failed for withdrawal: account {addr:?} \
2080 storage slot {slot_u256} changed during withdrawal/request \
2081 phase ({value}) but BAL has no change at index \
2082 {withdrawal_idx} (last_bal={seeded})"
2083 )));
2084 }
2085 }
2086 } else {
2087 let pre_value = db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
2090 EvmError::Custom(format!(
2091 "BAL validation failed for withdrawal: db error reading \
2092 storage {addr:?}[{slot_u256}]: {e}"
2093 ))
2094 })?;
2095 if value != pre_value {
2096 return Err(EvmError::Custom(format!(
2097 "BAL validation failed for withdrawal: account {addr:?} \
2098 storage slot {slot_u256} changed during withdrawal/request \
2099 phase ({value}) but slot is absent from BAL storage_changes \
2100 (pre={pre_value})"
2101 )));
2102 }
2103 }
2104 }
2105 }
2106
2107 Ok(())
2108 }
2109
2110 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2121 pub fn warm_block(
2122 block: &Block,
2123 store: Arc<dyn Database>,
2124 vm_type: VMType,
2125 crypto: &dyn Crypto,
2126 cancelled: &AtomicBool,
2127 ) -> Result<(), EvmError> {
2128 let mut db = GeneralizedDatabase::new(store.clone());
2129
2130 let txs_with_sender = block
2131 .body
2132 .get_transactions_with_sender(crypto)
2133 .map_err(|error| {
2134 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
2135 })?;
2136
2137 let mut sender_groups: FxHashMap<Address, Vec<&Transaction>> = FxHashMap::default();
2139 for (tx, sender) in &txs_with_sender {
2140 sender_groups.entry(*sender).or_default().push(tx);
2141 }
2142
2143 let chain_config = store.get_chain_config()?;
2146 let evm_config = EVMConfig::new_from_chain_config(&chain_config, &block.header);
2147 let chain_id = chain_config.chain_id;
2148 let base_blob_fee_per_gas =
2150 get_base_fee_per_blob_gas(block.header.excess_blob_gas, &evm_config)?;
2151
2152 sender_groups.into_par_iter().for_each_with(
2155 Vec::with_capacity(STACK_LIMIT),
2156 |stack_pool, (sender, txs)| {
2157 if cancelled.load(Ordering::Relaxed) {
2158 return;
2159 }
2160 let mut memory_pool = Vec::with_capacity(1);
2164 let mut group_db = GeneralizedDatabase::new(store.clone());
2166 for tx in txs {
2169 let _ = Self::execute_tx_in_block(
2170 tx,
2171 sender,
2172 &block.header,
2173 &mut group_db,
2174 vm_type,
2175 base_blob_fee_per_gas,
2176 stack_pool,
2177 &mut memory_pool,
2178 true,
2179 crypto,
2180 evm_config,
2181 chain_id,
2182 );
2183 }
2184 },
2185 );
2186
2187 if cancelled.load(Ordering::Relaxed) {
2188 return Ok(());
2189 }
2190
2191 for withdrawal in block
2192 .body
2193 .withdrawals
2194 .iter()
2195 .flatten()
2196 .filter(|withdrawal| withdrawal.amount > 0)
2197 {
2198 db.get_account_mut(withdrawal.address).map_err(|_| {
2199 EvmError::DB(format!(
2200 "Withdrawal account {} not found",
2201 withdrawal.address
2202 ))
2203 })?;
2204 }
2205 Ok(())
2206 }
2207
2208 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2211 pub fn bal_storage_slots(bal: &BlockAccessList) -> Vec<(Address, H256)> {
2212 bal.accounts()
2213 .iter()
2214 .flat_map(|ac| {
2215 ac.all_storage_slots()
2216 .map(move |slot| (ac.address, H256::from_uint(&slot)))
2217 })
2218 .collect()
2219 }
2220
2221 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2230 pub fn warm_block_from_bal(
2231 bal: &BlockAccessList,
2232 store: Arc<dyn Database>,
2233 cancelled: &AtomicBool,
2234 ) -> Result<(), EvmError> {
2235 let accounts = bal.accounts();
2236 if accounts.is_empty() {
2237 return Ok(());
2238 }
2239
2240 let account_addresses: Vec<Address> = accounts.iter().map(|ac| ac.address).collect();
2246 store
2247 .prefetch_accounts(&account_addresses)
2248 .map_err(|e| EvmError::Custom(format!("prefetch_accounts: {e}")))?;
2249
2250 if cancelled.load(Ordering::Relaxed) {
2251 return Ok(());
2252 }
2253
2254 let code_hashes: Vec<ethrex_common::H256> = accounts
2258 .par_iter()
2259 .filter_map(|ac| {
2260 store
2261 .get_account_state(ac.address)
2262 .ok()
2263 .filter(|s| s.code_hash != *EMPTY_KECCAK_HASH)
2264 .map(|s| s.code_hash)
2265 })
2266 .collect();
2267 code_hashes.par_iter().for_each(|&h| {
2268 let _ = store.get_account_code(h);
2269 });
2270
2271 Ok(())
2272 }
2273
2274 fn send_state_transitions_tx(
2275 merkleizer: &Sender<Vec<AccountUpdate>>,
2276 db: &mut GeneralizedDatabase,
2277 queue_length: &AtomicUsize,
2278 ) -> Result<(), EvmError> {
2279 let transitions = LEVM::get_state_transitions_tx(db)?;
2280 merkleizer
2281 .send(transitions)
2282 .map_err(|e| EvmError::Custom(format!("send failed: {e}")))?;
2283 queue_length.fetch_add(1, Ordering::Relaxed);
2284 Ok(())
2285 }
2286
2287 fn setup_env(
2288 tx: &Transaction,
2289 tx_sender: Address,
2290 block_header: &BlockHeader,
2291 db: &GeneralizedDatabase,
2292 vm_type: VMType,
2293 ) -> Result<Environment, EvmError> {
2294 let chain_config = db.store.get_chain_config()?;
2298 let config = EVMConfig::new_from_chain_config(&chain_config, block_header);
2299 let base_blob_fee_per_gas =
2300 get_base_fee_per_blob_gas(block_header.excess_blob_gas, &config)?;
2301 Self::setup_env_with_config(
2302 tx,
2303 tx_sender,
2304 block_header,
2305 config,
2306 chain_config.chain_id,
2307 vm_type,
2308 base_blob_fee_per_gas,
2309 )
2310 }
2311
2312 fn setup_env_with_config(
2316 tx: &Transaction,
2317 tx_sender: Address,
2318 block_header: &BlockHeader,
2319 config: EVMConfig,
2320 chain_id: u64,
2321 vm_type: VMType,
2322 base_blob_fee_per_gas: U256,
2323 ) -> Result<Environment, EvmError> {
2324 let gas_price: U256 = calculate_gas_price_for_tx(
2325 tx,
2326 block_header.base_fee_per_gas.unwrap_or_default(),
2327 &vm_type,
2328 )?;
2329
2330 let block_excess_blob_gas = block_header.excess_blob_gas;
2331 let env = Environment {
2332 origin: tx_sender,
2333 gas_limit: tx.gas_limit(),
2334 config,
2335 block_number: block_header.number,
2336 coinbase: block_header.coinbase,
2337 timestamp: block_header.timestamp,
2338 prev_randao: Some(block_header.prev_randao),
2339 slot_number: block_header
2340 .slot_number
2341 .map(U256::from)
2342 .unwrap_or(U256::zero()),
2343 chain_id: chain_id.into(),
2344 base_fee_per_gas: block_header.base_fee_per_gas.unwrap_or_default().into(),
2345 base_blob_fee_per_gas,
2346 gas_price,
2347 block_excess_blob_gas,
2348 block_blob_gas_used: block_header.blob_gas_used,
2349 tx_blob_hashes: tx.blob_versioned_hashes(),
2350 tx_max_priority_fee_per_gas: tx.max_priority_fee().map(U256::from),
2351 tx_max_fee_per_gas: tx.max_fee_per_gas().map(U256::from),
2352 tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas(),
2353 tx_nonce: tx.nonce(),
2354 block_gas_limit: block_header.gas_limit,
2355 difficulty: block_header.difficulty,
2356 is_privileged: matches!(tx, Transaction::PrivilegedL2Transaction(_)),
2357 fee_token: tx.fee_token(),
2358 disable_balance_check: false,
2359 is_system_call: false,
2360 };
2361
2362 Ok(env)
2363 }
2364
2365 pub fn execute_tx(
2366 tx: &Transaction,
2368 tx_sender: Address,
2370 block_header: &BlockHeader,
2372 db: &mut GeneralizedDatabase,
2373 vm_type: VMType,
2374 crypto: &dyn Crypto,
2375 ) -> Result<ExecutionReport, EvmError> {
2376 let env = Self::setup_env(tx, tx_sender, block_header, db, vm_type)?;
2377 let mut vm = VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)?;
2378
2379 vm.execute().map_err(VMError::into)
2380 }
2381
2382 #[allow(clippy::too_many_arguments)]
2385 fn execute_tx_in_block(
2386 tx: &Transaction,
2388 tx_sender: Address,
2390 block_header: &BlockHeader,
2392 db: &mut GeneralizedDatabase,
2393 vm_type: VMType,
2394 base_blob_fee_per_gas: U256,
2395 stack_pool: &mut Vec<Stack>,
2396 memory_pool: &mut Vec<Memory>,
2397 disable_balance_check: bool,
2398 crypto: &dyn Crypto,
2399 config: EVMConfig,
2400 chain_id: u64,
2401 ) -> Result<ExecutionReport, EvmError> {
2402 let mut env = Self::setup_env_with_config(
2403 tx,
2404 tx_sender,
2405 block_header,
2406 config,
2407 chain_id,
2408 vm_type,
2409 base_blob_fee_per_gas,
2410 )?;
2411 env.disable_balance_check = disable_balance_check;
2412 let mut vm = VM::new_pooled(
2416 env,
2417 db,
2418 tx,
2419 LevmCallTracer::disabled(),
2420 vm_type,
2421 crypto,
2422 stack_pool,
2423 memory_pool,
2424 )?;
2425 let result = vm.execute().map_err(VMError::into);
2426 vm.reclaim_into(stack_pool, memory_pool);
2428 result
2429 }
2430
2431 pub fn undo_last_tx(db: &mut GeneralizedDatabase) -> Result<(), EvmError> {
2432 db.undo_last_transaction()?;
2433 Ok(())
2434 }
2435
2436 pub fn simulate_tx_from_generic(
2437 tx: &GenericTransaction,
2439 block_header: &BlockHeader,
2441 db: &mut GeneralizedDatabase,
2442 vm_type: VMType,
2443 crypto: &dyn Crypto,
2444 ) -> Result<ExecutionResult, EvmError> {
2445 let mut env = env_from_generic(tx, block_header, db, vm_type)?;
2446
2447 env.block_gas_limit = i64::MAX as u64; adjust_disabled_base_fee(&mut env);
2450
2451 let converted_tx = generic_tx_to_transaction(tx)?;
2452 let mut vm = vm_from_generic(&converted_tx, env, db, vm_type, crypto)?;
2453
2454 vm.execute()
2455 .map(|value| value.into())
2456 .map_err(VMError::into)
2457 }
2458
2459 pub fn get_state_transitions(
2460 db: &mut GeneralizedDatabase,
2461 ) -> Result<Vec<AccountUpdate>, EvmError> {
2462 Ok(db.get_state_transitions()?)
2463 }
2464
2465 pub fn get_state_transitions_tx(
2466 db: &mut GeneralizedDatabase,
2467 ) -> Result<Vec<AccountUpdate>, EvmError> {
2468 Ok(db.get_state_transitions_tx()?)
2469 }
2470
2471 pub fn process_withdrawals(
2472 db: &mut GeneralizedDatabase,
2473 withdrawals: &[Withdrawal],
2474 ) -> Result<(), EvmError> {
2475 for (address, increment) in withdrawals
2477 .iter()
2478 .filter(|withdrawal| withdrawal.amount > 0)
2479 .map(|w| (w.address, u128::from(w.amount) * u128::from(GWEI_TO_WEI)))
2480 {
2481 let account = db
2482 .get_account_mut(address)
2483 .map_err(|_| EvmError::DB(format!("Withdrawal account {address} not found")))?;
2484
2485 let initial_balance = account.info.balance;
2486 account.info.balance += increment.into();
2487 let new_balance = account.info.balance;
2488
2489 if let Some(recorder) = db.bal_recorder_mut() {
2491 recorder.set_initial_balance(address, initial_balance);
2492 recorder.record_balance_change(address, new_balance);
2493 }
2494 }
2495 Ok(())
2496 }
2497
2498 pub fn beacon_root_contract_call(
2500 block_header: &BlockHeader,
2501 db: &mut GeneralizedDatabase,
2502 vm_type: VMType,
2503 crypto: &dyn Crypto,
2504 ) -> Result<(), EvmError> {
2505 if let VMType::L2(_) = vm_type {
2506 return Err(EvmError::InvalidEVM(
2507 "beacon_root_contract_call should not be called for L2 VM".to_string(),
2508 ));
2509 }
2510
2511 let beacon_root = block_header.parent_beacon_block_root.ok_or_else(|| {
2512 EvmError::Header("parent_beacon_block_root field is missing".to_string())
2513 })?;
2514
2515 generic_system_contract_levm(
2516 block_header,
2517 Bytes::copy_from_slice(beacon_root.as_bytes()),
2518 db,
2519 BEACON_ROOTS_ADDRESS.address,
2520 SYSTEM_ADDRESS,
2521 vm_type,
2522 crypto,
2523 )?;
2524 Ok(())
2525 }
2526
2527 pub fn process_block_hash_history(
2528 block_header: &BlockHeader,
2529 db: &mut GeneralizedDatabase,
2530 vm_type: VMType,
2531 crypto: &dyn Crypto,
2532 ) -> Result<(), EvmError> {
2533 if let VMType::L2(_) = vm_type {
2534 return Err(EvmError::InvalidEVM(
2535 "process_block_hash_history should not be called for L2 VM".to_string(),
2536 ));
2537 }
2538
2539 generic_system_contract_levm(
2540 block_header,
2541 Bytes::copy_from_slice(block_header.parent_hash.as_bytes()),
2542 db,
2543 HISTORY_STORAGE_ADDRESS.address,
2544 SYSTEM_ADDRESS,
2545 vm_type,
2546 crypto,
2547 )?;
2548 Ok(())
2549 }
2550 pub(crate) fn read_withdrawal_requests(
2551 block_header: &BlockHeader,
2552 db: &mut GeneralizedDatabase,
2553 vm_type: VMType,
2554 crypto: &dyn Crypto,
2555 ) -> Result<ExecutionReport, EvmError> {
2556 if let VMType::L2(_) = vm_type {
2557 return Err(EvmError::InvalidEVM(
2558 "read_withdrawal_requests should not be called for L2 VM".to_string(),
2559 ));
2560 }
2561
2562 let report = generic_system_contract_levm(
2563 block_header,
2564 Bytes::new(),
2565 db,
2566 WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS.address,
2567 SYSTEM_ADDRESS,
2568 vm_type,
2569 crypto,
2570 )?;
2571
2572 match report.result {
2573 TxResult::Success => Ok(report),
2574 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
2576 "REVERT when reading withdrawal requests with error: {vm_error:?}. According to EIP-7002, the revert of this system call invalidates the block.",
2577 ))),
2578 }
2579 }
2580
2581 pub(crate) fn dequeue_consolidation_requests(
2582 block_header: &BlockHeader,
2583 db: &mut GeneralizedDatabase,
2584 vm_type: VMType,
2585 crypto: &dyn Crypto,
2586 ) -> Result<ExecutionReport, EvmError> {
2587 if let VMType::L2(_) = vm_type {
2588 return Err(EvmError::InvalidEVM(
2589 "dequeue_consolidation_requests should not be called for L2 VM".to_string(),
2590 ));
2591 }
2592
2593 let report = generic_system_contract_levm(
2594 block_header,
2595 Bytes::new(),
2596 db,
2597 CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS.address,
2598 SYSTEM_ADDRESS,
2599 vm_type,
2600 crypto,
2601 )?;
2602
2603 match report.result {
2604 TxResult::Success => Ok(report),
2605 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
2607 "REVERT when dequeuing consolidation requests with error: {vm_error:?}. According to EIP-7251, the revert of this system call invalidates the block.",
2608 ))),
2609 }
2610 }
2611
2612 pub fn create_access_list(
2613 mut tx: GenericTransaction,
2614 header: &BlockHeader,
2615 db: &mut GeneralizedDatabase,
2616 vm_type: VMType,
2617 crypto: &dyn Crypto,
2618 ) -> Result<(ExecutionResult, AccessList), VMError> {
2619 let mut env = env_from_generic(&tx, header, db, vm_type)?;
2620
2621 adjust_disabled_base_fee(&mut env);
2622
2623 let converted_tx = generic_tx_to_transaction(&tx)?;
2624 let mut vm = vm_from_generic(&converted_tx, env.clone(), db, vm_type, crypto)?;
2625
2626 vm.stateless_execute()?;
2627
2628 tx.access_list = vm.substate.make_access_list();
2630 let converted_tx = generic_tx_to_transaction(&tx)?;
2631 let mut vm = vm_from_generic(&converted_tx, env, db, vm_type, crypto)?;
2632
2633 let report = vm.stateless_execute()?;
2634
2635 Ok((
2636 report.into(),
2637 tx.access_list
2638 .into_iter()
2639 .map(|x| (x.address, x.storage_keys))
2640 .collect(),
2641 ))
2642 }
2643
2644 pub fn prepare_block(
2645 block: &Block,
2646 db: &mut GeneralizedDatabase,
2647 vm_type: VMType,
2648 crypto: &dyn Crypto,
2649 ) -> Result<(), EvmError> {
2650 let chain_config = db.store.get_chain_config()?;
2651 let block_header = &block.header;
2652 let fork = chain_config.fork(block_header.timestamp);
2653
2654 if let VMType::L2(_) = vm_type {
2656 return Ok(());
2657 }
2658
2659 if block_header.parent_beacon_block_root.is_some() && fork >= Fork::Cancun {
2660 Self::beacon_root_contract_call(block_header, db, vm_type, crypto)?;
2661 }
2662
2663 if fork >= Fork::Prague {
2664 Self::process_block_hash_history(block_header, db, vm_type, crypto)?;
2666 }
2667 Ok(())
2668 }
2669}
2670
2671pub fn generic_system_contract_levm(
2672 block_header: &BlockHeader,
2673 calldata: Bytes,
2674 db: &mut GeneralizedDatabase,
2675 contract_address: Address,
2676 system_address: Address,
2677 vm_type: VMType,
2678 crypto: &dyn Crypto,
2679) -> Result<ExecutionReport, EvmError> {
2680 let chain_config = db.store.get_chain_config()?;
2681 let config = EVMConfig::new_from_chain_config(&chain_config, block_header);
2682 let env = Environment {
2683 origin: system_address,
2684 gas_limit: SYS_CALL_GAS_LIMIT + TX_BASE_COST,
2687 block_number: block_header.number,
2688 coinbase: block_header.coinbase,
2689 timestamp: block_header.timestamp,
2690 prev_randao: Some(block_header.prev_randao),
2691 base_fee_per_gas: U256::zero(),
2692 gas_price: U256::zero(),
2693 block_excess_blob_gas: block_header.excess_blob_gas,
2694 block_blob_gas_used: block_header.blob_gas_used,
2695 block_gas_limit: block_header.gas_limit,
2700 is_system_call: true,
2701 config,
2702 ..Default::default()
2703 };
2704
2705 debug_assert!(
2711 env.gas_price.is_zero() && env.base_fee_per_gas.is_zero(),
2712 "system calls must run with a zero gas price"
2713 );
2714
2715 if PRAGUE_SYSTEM_CONTRACTS
2721 .iter()
2722 .any(|contract| contract.address == contract_address)
2723 && db.get_account_code(contract_address)?.is_empty()
2724 {
2725 return Err(EvmError::SystemContractCallFailed(format!(
2726 "System contract: {contract_address} has no code after deployment"
2727 )));
2728 };
2729
2730 let tx = &Transaction::EIP1559Transaction(EIP1559Transaction {
2731 to: TxKind::Call(contract_address),
2732 value: U256::zero(),
2733 data: calldata,
2734 ..Default::default()
2735 });
2736 if let Some(recorder) = db.bal_recorder.as_mut() {
2738 recorder.enter_system_call();
2739 }
2740
2741 let result = VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)
2742 .and_then(|mut vm| vm.execute())
2743 .map_err(EvmError::from);
2744
2745 if let Some(recorder) = db.bal_recorder.as_mut() {
2747 recorder.exit_system_call();
2748 }
2749
2750 result
2751}
2752
2753#[allow(unreachable_code)]
2754#[allow(unused_variables)]
2755pub fn extract_all_requests_levm(
2756 receipts: &[Receipt],
2757 db: &mut GeneralizedDatabase,
2758 header: &BlockHeader,
2759 vm_type: VMType,
2760 crypto: &dyn Crypto,
2761) -> Result<Vec<Requests>, EvmError> {
2762 if let VMType::L2(_) = vm_type {
2763 return Err(EvmError::InvalidEVM(
2764 "extract_all_requests_levm should not be called for L2 VM".to_string(),
2765 ));
2766 }
2767
2768 let chain_config = db.store.get_chain_config()?;
2769 let fork = chain_config.fork(header.timestamp);
2770
2771 if fork < Fork::Prague {
2772 return Ok(Default::default());
2773 }
2774
2775 let withdrawals_data: Vec<u8> = LEVM::read_withdrawal_requests(header, db, vm_type, crypto)?
2776 .output
2777 .into();
2778 let consolidation_data: Vec<u8> =
2779 LEVM::dequeue_consolidation_requests(header, db, vm_type, crypto)?
2780 .output
2781 .into();
2782
2783 let deposits = Requests::from_deposit_receipts(chain_config.deposit_contract_address, receipts)
2784 .ok_or(EvmError::InvalidDepositRequest)?;
2785 let withdrawals = Requests::from_withdrawals_data(withdrawals_data);
2786 let consolidation = Requests::from_consolidation_data(consolidation_data);
2787
2788 Ok(vec![deposits, withdrawals, consolidation])
2789}
2790
2791pub fn calculate_gas_price_for_generic(tx: &GenericTransaction, basefee: u64) -> U256 {
2794 if !tx.gas_price.is_zero() {
2795 tx.gas_price
2797 } else {
2798 min(
2800 tx.max_priority_fee_per_gas.unwrap_or(0) + basefee,
2801 tx.max_fee_per_gas.unwrap_or(0),
2802 )
2803 .into()
2804 }
2805}
2806
2807pub fn calculate_gas_price_for_tx(
2808 tx: &Transaction,
2809 mut fee_per_gas: u64,
2810 vm_type: &VMType,
2811) -> Result<U256, VMError> {
2812 let Some(max_priority_fee) = tx.max_priority_fee() else {
2813 return Ok(tx.gas_price());
2815 };
2816
2817 let max_fee_per_gas = tx.max_fee_per_gas().ok_or(VMError::TxValidation(
2818 TxValidationError::InsufficientMaxFeePerGas,
2819 ))?;
2820
2821 if let VMType::L2(fee_config) = vm_type
2822 && let Some(operator_fee_config) = &fee_config.operator_fee_config
2823 {
2824 fee_per_gas += operator_fee_config.operator_fee_per_gas;
2825 }
2826
2827 if fee_per_gas > max_fee_per_gas {
2828 return Err(VMError::TxValidation(
2829 TxValidationError::InsufficientMaxFeePerGas,
2830 ));
2831 }
2832
2833 Ok(min(max_priority_fee + fee_per_gas, max_fee_per_gas).into())
2834}
2835
2836fn adjust_disabled_base_fee(env: &mut Environment) {
2840 if env.gas_price == U256::zero() {
2841 env.base_fee_per_gas = U256::zero();
2842 }
2843 if env
2844 .tx_max_fee_per_blob_gas
2845 .is_some_and(|v| v == U256::zero())
2846 {
2847 env.block_excess_blob_gas = None;
2848 }
2849}
2850
2851fn adjust_disabled_l2_fees(env: &Environment, vm_type: VMType) -> VMType {
2853 if env.gas_price == U256::zero()
2854 && let VMType::L2(fee_config) = vm_type
2855 {
2856 return VMType::L2(FeeConfig {
2858 operator_fee_config: None,
2859 l1_fee_config: None,
2860 ..fee_config
2861 });
2862 }
2863 vm_type
2864}
2865
2866fn env_from_generic(
2867 tx: &GenericTransaction,
2868 header: &BlockHeader,
2869 db: &GeneralizedDatabase,
2870 vm_type: VMType,
2871) -> Result<Environment, VMError> {
2872 let chain_config = db.store.get_chain_config()?;
2873 let gas_price =
2874 calculate_gas_price_for_generic(tx, header.base_fee_per_gas.unwrap_or(INITIAL_BASE_FEE));
2875 let block_excess_blob_gas = header.excess_blob_gas;
2876 let config = EVMConfig::new_from_chain_config(&chain_config, header);
2877
2878 let slot_number = if let VMType::L2(_) = vm_type {
2881 U256::zero()
2882 } else if config.fork >= Fork::Amsterdam {
2883 header
2884 .slot_number
2885 .map(U256::from)
2886 .ok_or(VMError::Internal(InternalError::Custom(
2887 "slot_number must be present in Amsterdam+ blocks".to_string(),
2888 )))?
2889 } else {
2890 header.slot_number.map(U256::from).unwrap_or(U256::zero())
2893 };
2894
2895 Ok(Environment {
2896 origin: tx.from.0.into(),
2897 gas_limit: tx
2898 .gas
2899 .unwrap_or(get_max_allowed_gas_limit(header.gas_limit, config.fork)), config,
2901 block_number: header.number,
2902 coinbase: header.coinbase,
2903 timestamp: header.timestamp,
2904 prev_randao: Some(header.prev_randao),
2905 slot_number,
2906 chain_id: chain_config.chain_id.into(),
2907 base_fee_per_gas: header.base_fee_per_gas.unwrap_or_default().into(),
2908 base_blob_fee_per_gas: get_base_fee_per_blob_gas(block_excess_blob_gas, &config)?,
2909 gas_price,
2910 block_excess_blob_gas,
2911 block_blob_gas_used: header.blob_gas_used,
2912 tx_blob_hashes: tx.blob_versioned_hashes.clone(),
2913 tx_max_priority_fee_per_gas: tx.max_priority_fee_per_gas.map(U256::from),
2914 tx_max_fee_per_gas: tx.max_fee_per_gas.map(U256::from),
2915 tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
2916 tx_nonce: tx.nonce.unwrap_or_default(),
2917 block_gas_limit: header.gas_limit,
2918 difficulty: header.difficulty,
2919 is_privileged: false,
2920 fee_token: tx.fee_token,
2921 disable_balance_check: false,
2922 is_system_call: false,
2923 })
2924}
2925
2926fn generic_tx_to_transaction(tx: &GenericTransaction) -> Result<Transaction, VMError> {
2931 Ok(match &tx.authorization_list {
2932 Some(authorization_list) => Transaction::EIP7702Transaction(EIP7702Transaction {
2933 to: match tx.to {
2934 TxKind::Call(to) => to,
2935 TxKind::Create => {
2936 return Err(InternalError::msg("Generic Tx cannot be create type").into());
2937 }
2938 },
2939 value: tx.value,
2940 data: tx.input.clone(),
2941 access_list: tx
2942 .access_list
2943 .iter()
2944 .map(|list| (list.address, list.storage_keys.clone()))
2945 .collect(),
2946 authorization_list: authorization_list
2947 .iter()
2948 .map(|auth| Into::<AuthorizationTuple>::into(auth.clone()))
2949 .collect(),
2950 ..Default::default()
2951 }),
2952 None => Transaction::EIP1559Transaction(EIP1559Transaction {
2953 to: tx.to.clone(),
2954 value: tx.value,
2955 data: tx.input.clone(),
2956 access_list: tx
2957 .access_list
2958 .iter()
2959 .map(|list| (list.address, list.storage_keys.clone()))
2960 .collect(),
2961 ..Default::default()
2962 }),
2963 })
2964}
2965
2966fn vm_from_generic<'a>(
2967 tx: &'a Transaction,
2968 env: Environment,
2969 db: &'a mut GeneralizedDatabase,
2970 vm_type: VMType,
2971 crypto: &'a dyn Crypto,
2972) -> Result<VM<'a>, VMError> {
2973 let vm_type = adjust_disabled_l2_fees(&env, vm_type);
2974 VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)
2975}
2976
2977pub fn get_max_allowed_gas_limit(block_gas_limit: u64, fork: Fork) -> u64 {
2978 if fork >= Fork::Osaka {
2979 POST_OSAKA_GAS_LIMIT_CAP
2980 } else {
2981 block_gas_limit
2982 }
2983}
2984
2985#[allow(dead_code)]
2993fn describe_balance_diff(expected: U256, actual: U256) -> String {
2994 let (sign, mag) = if expected >= actual {
2995 ("+", expected - actual)
2996 } else {
2997 ("-", actual - expected)
2998 };
2999 let Ok(mag_u128) = u128::try_from(mag) else {
3000 return format!("{sign}{mag}");
3001 };
3002 if mag_u128 == 0 {
3003 return "0".to_string();
3004 }
3005 let cpsb: u128 = 1530;
3006 let consts = [
3008 ("NEW_ACCOUNT", 120u128),
3009 ("STORAGE_SET", 64),
3010 ("AUTH_BASE", 23),
3011 ("AUTH_TOTAL", 143),
3012 ];
3013 for &gp in &[10u128, 1, 7, 100, 1000, 1_000_000_000] {
3015 if !mag_u128.is_multiple_of(gp) {
3016 continue;
3017 }
3018 let gas = mag_u128 / gp;
3019 if !gas.is_multiple_of(cpsb) {
3020 continue;
3021 }
3022 let bytes = gas / cpsb;
3023 for (name, c) in consts {
3024 if bytes.is_multiple_of(c) {
3025 let n = bytes / c;
3026 return format!(
3027 "{sign}{mag_u128} wei (= {gas} gas at {gp} wei/gas = {n}× {name}_state_gas)"
3028 );
3029 }
3030 }
3031 }
3032 format!("{sign}{mag_u128} wei")
3033}
3034
3035#[cfg(test)]
3036mod bal_tests {
3037 use super::*;
3038 use ethrex_common::H256;
3039 use ethrex_common::types::AccountState;
3040 use ethrex_common::types::block_access_list::{
3041 AccountChanges, BalanceChange, NonceChange, SlotChange, StorageChange,
3042 };
3043 use ethrex_levm::errors::DatabaseError;
3044
3045 fn addr(byte: u8) -> Address {
3046 let mut a = Address::zero();
3047 a.0[19] = byte;
3048 a
3049 }
3050
3051 struct MockStore {
3053 accounts: FxHashMap<Address, AccountState>,
3054 }
3055
3056 impl MockStore {
3057 fn new() -> Self {
3058 Self {
3059 accounts: FxHashMap::default(),
3060 }
3061 }
3062
3063 fn with_account(mut self, address: Address, state: AccountState) -> Self {
3064 self.accounts.insert(address, state);
3065 self
3066 }
3067 }
3068
3069 impl Database for MockStore {
3070 fn get_account_state(&self, address: Address) -> Result<AccountState, DatabaseError> {
3071 Ok(self.accounts.get(&address).copied().unwrap_or_default())
3072 }
3073 fn get_storage_value(&self, _: Address, _: H256) -> Result<U256, DatabaseError> {
3074 Ok(U256::zero())
3075 }
3076 fn get_block_hash(&self, _: u64) -> Result<H256, DatabaseError> {
3077 Ok(H256::zero())
3078 }
3079 fn get_chain_config(&self) -> Result<ethrex_common::types::ChainConfig, DatabaseError> {
3080 Err(DatabaseError::Custom("not implemented".into()))
3081 }
3082 fn get_account_code(&self, _: H256) -> Result<ethrex_common::types::Code, DatabaseError> {
3083 Ok(ethrex_common::types::Code::from_bytecode(
3084 Bytes::new(),
3085 ðrex_crypto::NativeCrypto,
3086 ))
3087 }
3088 fn get_code_metadata(
3089 &self,
3090 _: H256,
3091 ) -> Result<ethrex_common::types::CodeMetadata, DatabaseError> {
3092 Ok(ethrex_common::types::CodeMetadata { length: 0 })
3093 }
3094 }
3095
3096 #[test]
3097 fn test_bal_to_account_updates_basic() {
3098 let address = addr(1);
3100 let store = MockStore::new().with_account(
3101 address,
3102 AccountState {
3103 balance: U256::from(100),
3104 nonce: 5,
3105 code_hash: *EMPTY_KECCAK_HASH,
3106 storage_root: H256::zero(),
3107 },
3108 );
3109
3110 let bal = BlockAccessList::from_accounts(vec![
3111 AccountChanges::new(address)
3112 .with_balance_changes(vec![
3113 BalanceChange::new(1, U256::from(90)),
3114 BalanceChange::new(2, U256::from(80)),
3115 ])
3116 .with_nonce_changes(vec![NonceChange::new(1, 6)])
3117 .with_storage_changes(vec![SlotChange::with_changes(
3118 U256::from(42),
3119 vec![StorageChange::new(1, U256::from(999))],
3120 )]),
3121 ]);
3122
3123 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3124 assert_eq!(updates.len(), 1);
3125 let u = &updates[0];
3126 assert_eq!(u.address, address);
3127 assert!(!u.removed);
3128 let info = u.info.as_ref().unwrap();
3129 assert_eq!(info.balance, U256::from(80));
3131 assert_eq!(info.nonce, 6);
3132 assert_eq!(info.code_hash, *EMPTY_KECCAK_HASH);
3133 let key = ethrex_common::utils::u256_to_h256(U256::from(42));
3135 assert_eq!(*u.added_storage.get(&key).unwrap(), U256::from(999));
3136 }
3137
3138 #[test]
3139 fn test_bal_to_account_updates_highest_index_wins() {
3140 let address = addr(2);
3142 let store = MockStore::new().with_account(
3143 address,
3144 AccountState {
3145 balance: U256::from(1000),
3146 nonce: 0,
3147 code_hash: *EMPTY_KECCAK_HASH,
3148 storage_root: H256::zero(),
3149 },
3150 );
3151
3152 let bal = BlockAccessList::from_accounts(vec![
3153 AccountChanges::new(address).with_balance_changes(vec![
3154 BalanceChange::new(1, U256::from(900)),
3155 BalanceChange::new(2, U256::from(800)),
3156 BalanceChange::new(3, U256::from(700)),
3157 ]),
3158 ]);
3159
3160 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3161 assert_eq!(updates.len(), 1);
3162 assert_eq!(updates[0].info.as_ref().unwrap().balance, U256::from(700));
3163 }
3164
3165 #[test]
3166 fn test_bal_to_account_updates_reads_only_skipped() {
3167 let address = addr(3);
3169 let store = MockStore::new();
3170
3171 let bal = BlockAccessList::from_accounts(vec![
3172 AccountChanges::new(address).with_storage_reads(vec![U256::from(1)]),
3173 ]);
3174
3175 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3176 assert!(updates.is_empty());
3177 }
3178
3179 #[test]
3180 fn test_bal_to_account_updates_removal() {
3181 let address = addr(4);
3183 let store = MockStore::new().with_account(
3184 address,
3185 AccountState {
3186 balance: U256::from(50),
3187 nonce: 1,
3188 code_hash: *EMPTY_KECCAK_HASH,
3189 storage_root: H256::zero(),
3190 },
3191 );
3192
3193 let bal = BlockAccessList::from_accounts(vec![
3194 AccountChanges::new(address)
3195 .with_balance_changes(vec![BalanceChange::new(1, U256::zero())])
3196 .with_nonce_changes(vec![NonceChange::new(1, 0)]),
3197 ]);
3198
3199 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3200 assert_eq!(updates.len(), 1);
3201 assert!(updates[0].removed);
3202 }
3203
3204 #[test]
3205 fn test_bal_to_account_updates_storage_zero() {
3206 let address = addr(5);
3208 let store = MockStore::new();
3209
3210 let bal = BlockAccessList::from_accounts(vec![
3211 AccountChanges::new(address).with_storage_changes(vec![SlotChange::with_changes(
3212 U256::from(7),
3213 vec![StorageChange::new(1, U256::zero())],
3214 )]),
3215 ]);
3216
3217 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3218 assert_eq!(updates.len(), 1);
3219 let key = ethrex_common::utils::u256_to_h256(U256::from(7));
3220 assert_eq!(*updates[0].added_storage.get(&key).unwrap(), U256::zero());
3221 }
3222
3223 #[test]
3224 fn test_bal_to_account_updates_code_deployment() {
3225 let address = addr(6);
3227 let store = MockStore::new();
3228 let code = Bytes::from(vec![0x60, 0x00, 0x60, 0x00, 0xf3]); let expected_hash =
3230 ethrex_common::types::Code::from_bytecode(code.clone(), ðrex_crypto::NativeCrypto)
3231 .hash;
3232
3233 let bal = BlockAccessList::from_accounts(vec![
3234 AccountChanges::new(address)
3235 .with_code_changes(vec![
3236 ethrex_common::types::block_access_list::CodeChange::new(1, code.clone()),
3237 ])
3238 .with_nonce_changes(vec![NonceChange::new(1, 1)]),
3239 ]);
3240
3241 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3242 assert_eq!(updates.len(), 1);
3243 let u = &updates[0];
3244 assert_eq!(u.info.as_ref().unwrap().code_hash, expected_hash);
3245 assert_eq!(u.code.as_ref().unwrap().code(), &code[..]);
3246 }
3247}
3248
3249#[cfg(test)]
3250mod system_call_coinbase_tests {
3251 use super::*;
3257 use ethrex_common::types::{AccountState, AccountUpdate, ChainConfig, Code, CodeMetadata};
3258 use ethrex_crypto::NativeCrypto;
3259 use ethrex_levm::db::Database;
3260 use ethrex_levm::errors::DatabaseError;
3261 use std::sync::Arc;
3262
3263 const HISTORY_RUNTIME_CODE: &str = concat!(
3265 "3373fffffffffffffffffffffffffffffffffffffffe1460465760203603604257",
3266 "5f35600143038111604257611fff81430311604257611fff900654",
3267 "5f5260205ff35b5f5ffd5b5f35611fff60014303065500",
3268 );
3269
3270 struct Store {
3271 chain_config: ChainConfig,
3272 history_code: Code,
3273 }
3274
3275 impl Database for Store {
3276 fn get_account_state(&self, address: Address) -> Result<AccountState, DatabaseError> {
3277 if address == HISTORY_STORAGE_ADDRESS.address {
3278 return Ok(AccountState {
3279 nonce: 1,
3280 code_hash: self.history_code.hash,
3281 ..Default::default()
3282 });
3283 }
3284 Ok(AccountState::default())
3285 }
3286 fn get_storage_value(&self, _: Address, _: H256) -> Result<U256, DatabaseError> {
3287 Ok(U256::zero())
3288 }
3289 fn get_block_hash(&self, _: u64) -> Result<H256, DatabaseError> {
3290 Ok(H256::zero())
3291 }
3292 fn get_chain_config(&self) -> Result<ChainConfig, DatabaseError> {
3293 Ok(self.chain_config)
3294 }
3295 fn get_account_code(&self, code_hash: H256) -> Result<Code, DatabaseError> {
3296 if code_hash == self.history_code.hash {
3297 return Ok(self.history_code.clone());
3298 }
3299 Ok(Code::default())
3300 }
3301 fn get_code_metadata(&self, code_hash: H256) -> Result<CodeMetadata, DatabaseError> {
3302 let length = if code_hash == self.history_code.hash {
3303 self.history_code.len() as u64
3304 } else {
3305 0
3306 };
3307 Ok(CodeMetadata { length })
3308 }
3309 }
3310
3311 fn history_code() -> Code {
3312 let bytes = hex::decode(HISTORY_RUNTIME_CODE).expect("history runtime code is valid hex");
3313 Code::from_bytecode(Bytes::from(bytes), &NativeCrypto)
3314 }
3315
3316 fn prague_db() -> GeneralizedDatabase {
3317 GeneralizedDatabase::new(Arc::new(Store {
3318 chain_config: ChainConfig {
3319 prague_time: Some(0),
3320 ..Default::default()
3321 },
3322 history_code: history_code(),
3323 }))
3324 }
3325
3326 fn parent_hash_value(parent_hash: H256) -> U256 {
3327 U256::from_big_endian(parent_hash.as_bytes())
3328 }
3329
3330 fn history_slot(block_number: u64) -> H256 {
3331 H256::from_low_u64_be((block_number - 1) % 8191)
3332 }
3333
3334 fn run_history_update(coinbase: Address) -> (Option<U256>, Vec<AccountUpdate>, H256) {
3338 let mut db = prague_db();
3339 let parent_hash = H256::from_low_u64_be(0x2935);
3340 let block_number = 42;
3341 let header = BlockHeader {
3342 parent_hash,
3343 coinbase,
3344 number: block_number,
3345 timestamp: 1,
3346 ..Default::default()
3347 };
3348
3349 LEVM::process_block_hash_history(&header, &mut db, VMType::L1, &NativeCrypto)
3350 .expect("history system call executes");
3351
3352 let slot = history_slot(block_number);
3353 let stored_value = db
3354 .current_accounts_state
3355 .get(&HISTORY_STORAGE_ADDRESS.address)
3356 .and_then(|account| account.storage.get(&slot).copied());
3357 let updates =
3358 LEVM::get_state_transitions(&mut db).expect("state transitions are generated");
3359
3360 (stored_value, updates, parent_hash)
3361 }
3362
3363 fn assert_history_write_emitted(coinbase: Address) {
3364 let (stored_value, updates, parent_hash) = run_history_update(coinbase);
3365 let slot = history_slot(42);
3366 assert_eq!(
3367 stored_value,
3368 Some(parent_hash_value(parent_hash)),
3369 "history storage must hold the parent hash after the system call"
3370 );
3371 assert!(
3372 updates.iter().any(|update| {
3373 update.address == HISTORY_STORAGE_ADDRESS.address
3374 && update.added_storage.get(&slot) == Some(&parent_hash_value(parent_hash))
3375 }),
3376 "the history-contract storage write must be emitted as a state update"
3377 );
3378 }
3379
3380 #[test]
3381 fn ordinary_coinbase_preserves_history_storage_write() {
3382 assert_history_write_emitted(Address::from_low_u64_be(0xbeef));
3383 }
3384
3385 #[test]
3388 fn history_address_coinbase_preserves_history_storage_write() {
3389 assert_history_write_emitted(HISTORY_STORAGE_ADDRESS.address);
3390 }
3391}