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