1pub mod db;
2mod tracing;
3
4use super::{BlockExecutionResult, 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 HISTORY_STORAGE_ADDRESS, PRAGUE_SYSTEM_CONTRACTS, SYSTEM_ADDRESS,
9 WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
10};
11use crate::{EvmError, ExecutionResult};
12use bytes::Bytes;
13#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
14use ethrex_common::H256;
15#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
16use ethrex_common::constants::EMPTY_KECCAK_HASH;
17#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
18use ethrex_common::types::Code;
19#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
20use ethrex_common::types::TxType;
21use ethrex_common::types::block_access_list::BlockAccessList;
22#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
23use ethrex_common::types::block_access_list::{
24 BalAddressIndex, find_exact_change_balance, find_exact_change_code, find_exact_change_nonce,
25 find_exact_change_storage, has_exact_change_balance, has_exact_change_code,
26 has_exact_change_nonce, has_exact_change_storage,
27};
28use ethrex_common::types::fee_config::FeeConfig;
29use ethrex_common::types::{AuthorizationTuple, EIP7702Transaction};
30#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
31use ethrex_common::utils::u256_from_big_endian_const;
32use ethrex_common::{
33 Address, U256,
34 types::{
35 AccessList, AccountUpdate, Block, BlockHeader, EIP1559Transaction, Fork, GWEI_TO_WEI,
36 GenericTransaction, INITIAL_BASE_FEE, Receipt, Transaction, TxKind, Withdrawal,
37 requests::Requests,
38 },
39};
40#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
41use ethrex_common::{BigEndianHash, validate_block_access_list_size, validate_header_bal_indices};
42use ethrex_crypto::Crypto;
43use ethrex_levm::EVMConfig;
44#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
45use ethrex_levm::account::{AccountStatus, LevmAccount};
46use ethrex_levm::call_frame::Stack;
47use ethrex_levm::constants::{
48 POST_OSAKA_GAS_LIMIT_CAP, STACK_LIMIT, SYS_CALL_GAS_LIMIT, TX_MAX_GAS_LIMIT_AMSTERDAM,
49};
50use ethrex_levm::db::gen_db::GeneralizedDatabase;
51#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
52use ethrex_levm::db::gen_db::{
53 LazyBalCursor, code_from_bal, post_value_at_or_before, seed_one_address_info_from_bal,
54};
55#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
56use ethrex_levm::db::{Database, gen_db::CacheDB};
57use ethrex_levm::errors::{InternalError, TxValidationError};
58use ethrex_levm::memory::Memory;
59#[cfg(feature = "perf_opcode_timings")]
60use ethrex_levm::timings::{OPCODE_TIMINGS, PRECOMPILES_TIMINGS};
61use ethrex_levm::tracing::LevmCallTracer;
62use ethrex_levm::utils::get_base_fee_per_blob_gas;
63use ethrex_levm::vm::VMType;
64use ethrex_levm::{
65 Environment,
66 errors::{ExecutionReport, TxResult, VMError},
67 vm::VM,
68};
69#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
70use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
71#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
72use rustc_hash::{FxHashMap, FxHashSet};
73use std::cmp::min;
74use std::sync::Arc;
75#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
76use std::sync::atomic::AtomicBool;
77use std::sync::atomic::{AtomicUsize, Ordering};
78use std::sync::mpsc::Sender;
79
80#[derive(Debug)]
86pub struct LEVM;
87
88fn check_gas_limit(
90 block_gas_used: u64,
91 tx_gas_limit: u64,
92 block_gas_limit: u64,
93) -> Result<(), EvmError> {
94 if tx_gas_limit > block_gas_limit.saturating_sub(block_gas_used) {
95 return Err(EvmError::Transaction(format!(
96 "Gas allowance exceeded: \
97 used {block_gas_used} + tx limit {tx_gas_limit} > block limit {block_gas_limit}"
98 )));
99 }
100 Ok(())
101}
102
103pub fn check_2d_gas_allowance(
122 tx: &Transaction,
123 block_gas_used_regular: u64,
124 block_gas_used_state: u64,
125 block_gas_limit: u64,
126) -> Result<(), EvmError> {
127 let tx_gas = tx.gas_limit();
128 let regular_available = block_gas_limit.saturating_sub(block_gas_used_regular);
129 let state_available = block_gas_limit.saturating_sub(block_gas_used_state);
130
131 let regular_contrib = tx_gas.min(TX_MAX_GAS_LIMIT_AMSTERDAM);
136 if regular_contrib > regular_available {
137 return Err(EvmError::Transaction(format!(
138 "Gas allowance exceeded: regular dim worst-case {regular_contrib} > \
139 available {regular_available} (block_gas_used_regular={block_gas_used_regular}, \
140 block_gas_limit={block_gas_limit})"
141 )));
142 }
143
144 let state_contrib = tx_gas;
146 if state_contrib > state_available {
147 return Err(EvmError::Transaction(format!(
148 "Gas allowance exceeded: state dim worst-case {state_contrib} > \
149 available {state_available} (block_gas_used_state={block_gas_used_state}, \
150 block_gas_limit={block_gas_limit})"
151 )));
152 }
153
154 Ok(())
155}
156
157#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
160#[derive(Debug, thiserror::Error)]
161enum BalValidationError {
162 #[error("{0}")]
163 Mismatch(String),
164 #[error("{0}")]
165 Database(String),
166}
167
168impl LEVM {
169 pub fn execute_block(
174 block: &Block,
175 db: &mut GeneralizedDatabase,
176 vm_type: VMType,
177 crypto: &dyn Crypto,
178 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
179 let chain_config = db.store.get_chain_config()?;
180 let is_amsterdam = chain_config.is_amsterdam_activated(block.header.timestamp);
181
182 debug_assert!(
187 block.body.transactions.len() < u32::MAX as usize,
188 "tx count overflows u32 BlockAccessIndex"
189 );
190
191 if is_amsterdam {
193 db.enable_bal_recording();
194 db.set_bal_index(0);
196 }
197
198 Self::prepare_block(block, db, vm_type, crypto)?;
199
200 let evm_config = EVMConfig::new_from_chain_config(&chain_config, &block.header);
204 let chain_id = chain_config.chain_id;
205 let base_blob_fee_per_gas =
206 get_base_fee_per_blob_gas(block.header.excess_blob_gas, &evm_config)?;
207 let mut shared_stack_pool = Vec::with_capacity(STACK_LIMIT);
209 let mut shared_memory_pool = Vec::with_capacity(1);
210
211 let n_txs = block.body.transactions.len();
212 let mut receipts = Vec::with_capacity(n_txs);
213 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(n_txs);
214 let mut cumulative_gas_used = 0_u64;
216 let mut block_gas_used = 0_u64;
218 let mut block_regular_gas_used = 0_u64;
220 let mut block_state_gas_used = 0_u64;
221 let transactions_with_sender =
222 block
223 .body
224 .get_transactions_with_sender(crypto)
225 .map_err(|error| {
226 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
227 })?;
228
229 for (tx_idx, (tx, tx_sender)) in transactions_with_sender.into_iter().enumerate() {
230 if !is_amsterdam {
237 check_gas_limit(cumulative_gas_used, tx.gas_limit(), block.header.gas_limit)?;
238 }
239
240 if is_amsterdam {
242 check_2d_gas_allowance(
243 tx,
244 block_regular_gas_used,
245 block_state_gas_used,
246 block.header.gas_limit,
247 )?;
248 }
249
250 if is_amsterdam {
252 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
253 db.set_bal_index(bal_index);
254
255 if let Some(recorder) = db.bal_recorder_mut() {
257 recorder.record_touched_address(tx_sender);
258 if let TxKind::Call(to) = tx.to() {
259 recorder.record_touched_address(to);
260 }
261 }
262 }
263
264 let report = Self::execute_tx_in_block(
265 tx,
266 tx_sender,
267 &block.header,
268 db,
269 vm_type,
270 base_blob_fee_per_gas,
271 &mut shared_stack_pool,
272 &mut shared_memory_pool,
273 false,
274 crypto,
275 evm_config,
276 chain_id,
277 )?;
278
279 tx_gas_breakdowns.push(TxGasBreakdown::from_report(
280 tx_idx,
281 tx.hash(crypto),
282 &report,
283 ));
284
285 cumulative_gas_used += report.gas_spent;
287
288 let tx_state_gas = report.state_gas_used;
291 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
292 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
293 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
294
295 if is_amsterdam {
296 block_gas_used = block_regular_gas_used.max(block_state_gas_used);
298 ::tracing::debug!(
299 "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}",
300 report.gas_used,
301 report.gas_spent,
302 );
303
304 if block_regular_gas_used > block.header.gas_limit
309 || block_state_gas_used > block.header.gas_limit
310 {
311 return Err(EvmError::Transaction(format!(
312 "Gas allowance exceeded: Block gas used overflow: \
313 block_gas_used {block_gas_used} > block_gas_limit {}",
314 block.header.gas_limit
315 )));
316 }
317 } else {
318 block_gas_used = block_gas_used.saturating_add(report.gas_used);
319 }
320
321 let receipt = Receipt::new(
322 tx.tx_type(),
323 matches!(report.result, TxResult::Success),
324 cumulative_gas_used,
325 report.logs,
326 );
327
328 receipts.push(receipt);
329 }
330
331 if is_amsterdam && block_gas_used > block.header.gas_limit {
335 return Err(EvmError::Transaction(format!(
336 "Gas allowance exceeded: Block gas used overflow: \
337 block_gas_used {block_gas_used} > block_gas_limit {}",
338 block.header.gas_limit
339 )));
340 }
341
342 if is_amsterdam {
345 let post_tx_index =
346 u32::try_from(block.body.transactions.len() + 1).unwrap_or(u32::MAX);
347 db.set_bal_index(post_tx_index);
348
349 if let Some(withdrawals) = &block.body.withdrawals
353 && let Some(recorder) = db.bal_recorder_mut()
354 {
355 recorder.extend_touched_addresses(withdrawals.iter().map(|w| w.address));
356 }
357 }
358
359 let requests = match vm_type {
363 VMType::L1 => extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?,
364 VMType::L2(_) => Default::default(),
365 };
366
367 if let Some(withdrawals) = &block.body.withdrawals {
368 Self::process_withdrawals(db, withdrawals)?;
369 }
370
371 let bal = db.take_bal();
373
374 Ok((
375 BlockExecutionResult {
376 receipts,
377 requests,
378 block_gas_used,
379 tx_gas_breakdowns,
380 },
381 bal,
382 ))
383 }
384
385 #[allow(clippy::too_many_arguments)]
389 pub fn execute_block_pipeline(
390 block: &Block,
391 db: &mut GeneralizedDatabase,
392 vm_type: VMType,
393 merkleizer: Option<Sender<Vec<AccountUpdate>>>,
394 queue_length: &AtomicUsize,
395 crypto: &dyn Crypto,
396 header_bal: Option<Arc<BlockAccessList>>,
397 bal_parallel_exec_enabled: bool,
398 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
399 let chain_config = db.store.get_chain_config()?;
400 let is_amsterdam = chain_config.is_amsterdam_activated(block.header.timestamp);
401 let evm_config = EVMConfig::new_from_chain_config(&chain_config, &block.header);
404 let chain_id = chain_config.chain_id;
405
406 debug_assert!(
408 block.body.transactions.len() < u32::MAX as usize,
409 "tx count overflows u32 BlockAccessIndex"
410 );
411
412 let transactions_with_sender =
413 block
414 .body
415 .get_transactions_with_sender(crypto)
416 .map_err(|error| {
417 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
418 })?;
419
420 #[cfg(any(feature = "eip-8025", not(feature = "rayon")))]
421 let _ = (header_bal, bal_parallel_exec_enabled);
424 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
425 if let Some(bal) = header_bal
431 && is_amsterdam
432 && bal_parallel_exec_enabled
433 {
434 validate_header_bal_indices(&bal, block.body.transactions.len())
440 .map_err(|e| EvmError::Custom(e.to_string()))?;
441
442 Self::prepare_block(block, db, vm_type, crypto)?;
445
446 let validation_index = Arc::new(bal.build_validation_index());
448
449 LEVM::get_state_transitions_tx(db)?;
451 let system_seed = Arc::new(std::mem::take(&mut db.initial_accounts_state));
452
453 let parallel_result = Self::execute_block_parallel(
454 block,
455 &transactions_with_sender,
456 db,
457 vm_type,
458 Arc::clone(&bal),
459 merkleizer.as_ref(),
460 queue_length,
461 system_seed,
462 crypto,
463 Arc::clone(&validation_index),
464 );
465
466 let (
471 receipts,
472 block_gas_used,
473 mut unread_storage_reads,
474 mut unaccessed_pure_accounts,
475 tx_gas_breakdowns,
476 ) = match parallel_result {
477 Ok(result) => result,
478 Err(parallel_err) => {
479 let last_tx_idx =
480 u32::try_from(block.body.transactions.len()).unwrap_or(u32::MAX);
481 if Self::seed_db_from_bal(
482 db,
483 &bal,
484 last_tx_idx,
485 &validation_index.accounts_by_min_index,
486 )
487 .is_ok()
488 && let VMType::L1 = vm_type
489 && let Err(e @ EvmError::SystemContractCallFailed(_)) =
490 extract_all_requests_levm(&[], db, &block.header, vm_type, crypto)
491 {
492 return Err(e);
493 }
494 return Err(parallel_err);
495 }
496 };
497
498 let last_tx_idx = u32::try_from(block.body.transactions.len()).unwrap_or(u32::MAX);
503 Self::seed_db_from_bal(
505 db,
506 &bal,
507 last_tx_idx,
508 &validation_index.accounts_by_min_index,
509 )?;
510
511 let requests = match vm_type {
513 VMType::L1 => {
514 extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?
515 }
516 VMType::L2(_) => Default::default(),
517 };
518
519 if let Some(withdrawals) = &block.body.withdrawals {
520 Self::process_withdrawals(db, withdrawals)?;
521 }
522 let withdrawal_idx = u32::try_from(block.body.transactions.len())
530 .map(|n| n.saturating_add(1))
531 .unwrap_or(u32::MAX);
532 Self::validate_bal_withdrawal_index(db, &bal, withdrawal_idx, &validation_index)?;
533
534 if !unread_storage_reads.is_empty() {
536 for (addr, acct) in &db.current_accounts_state {
537 for key in acct.storage.keys() {
538 unread_storage_reads.remove(&(*addr, *key));
539 }
540 }
541 }
542
543 if !unaccessed_pure_accounts.is_empty() {
548 if let Some(withdrawals) = &block.body.withdrawals {
549 for w in withdrawals {
550 unaccessed_pure_accounts.remove(&w.address);
551 }
552 }
553 for addr in db.current_accounts_state.keys() {
554 if *addr == SYSTEM_ADDRESS {
558 continue;
559 }
560 unaccessed_pure_accounts.remove(addr);
561 }
562 }
563
564 if let Some((addr, key)) = unread_storage_reads.iter().next() {
566 let slot = ethrex_common::BigEndianHash::into_uint(key);
567 return Err(EvmError::Custom(format!(
568 "BAL validation failed: storage_read for account {addr:?} slot \
569 {slot} was never actually read during block execution"
570 )));
571 }
572
573 if let Some(addr) = unaccessed_pure_accounts.iter().next() {
575 return Err(EvmError::Custom(format!(
576 "BAL validation failed: account {addr:?} has no mutations \
577 and no storage reads but was never accessed during block execution"
578 )));
579 }
580
581 validate_block_access_list_size(&block.header, &chain_config, &bal)
584 .map_err(|e| EvmError::Custom(e.to_string()))?;
585
586 return Ok((
587 BlockExecutionResult {
588 receipts,
589 requests,
590 block_gas_used,
591 tx_gas_breakdowns,
592 },
593 None,
594 ));
595 }
596
597 let Some(merkleizer) = merkleizer else {
603 return Err(EvmError::Custom(
604 "sequential execution path called without a merkleizer Sender".to_string(),
605 ));
606 };
607 if is_amsterdam {
608 db.enable_bal_recording();
609 db.set_bal_index(0);
611 }
612
613 Self::prepare_block(block, db, vm_type, crypto)?;
614
615 let base_blob_fee_per_gas =
617 get_base_fee_per_blob_gas(block.header.excess_blob_gas, &evm_config)?;
618
619 let mut shared_stack_pool = Vec::with_capacity(STACK_LIMIT);
620 let mut shared_memory_pool = Vec::with_capacity(1);
622
623 let n_txs = block.body.transactions.len();
624 let mut receipts = Vec::with_capacity(n_txs);
625 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(n_txs);
626 let mut cumulative_gas_used = 0_u64;
628 let mut block_gas_used = 0_u64;
630 let mut block_regular_gas_used = 0_u64;
632 let mut block_state_gas_used = 0_u64;
633 let mut tx_since_last_flush = 2;
636
637 for (tx_idx, (tx, tx_sender)) in transactions_with_sender.into_iter().enumerate() {
638 if !is_amsterdam {
645 check_gas_limit(cumulative_gas_used, tx.gas_limit(), block.header.gas_limit)?;
646 }
647
648 if is_amsterdam {
650 check_2d_gas_allowance(
651 tx,
652 block_regular_gas_used,
653 block_state_gas_used,
654 block.header.gas_limit,
655 )?;
656 }
657
658 if is_amsterdam {
660 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
661 db.set_bal_index(bal_index);
662
663 if let Some(recorder) = db.bal_recorder_mut() {
665 recorder.record_touched_address(tx_sender);
666 if let TxKind::Call(to) = tx.to() {
667 recorder.record_touched_address(to);
668 }
669 }
670 }
671
672 let report = Self::execute_tx_in_block(
673 tx,
674 tx_sender,
675 &block.header,
676 db,
677 vm_type,
678 base_blob_fee_per_gas,
679 &mut shared_stack_pool,
680 &mut shared_memory_pool,
681 false,
682 crypto,
683 evm_config,
684 chain_id,
685 )?;
686
687 tx_gas_breakdowns.push(TxGasBreakdown::from_report(
688 tx_idx,
689 tx.hash(crypto),
690 &report,
691 ));
692
693 if queue_length.load(Ordering::Relaxed) == 0 && tx_since_last_flush > 5 {
694 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
695 tx_since_last_flush = 0;
696 } else {
697 tx_since_last_flush += 1;
698 }
699
700 cumulative_gas_used += report.gas_spent;
702
703 let tx_state_gas = report.state_gas_used;
706 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
707 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
708 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
709
710 if is_amsterdam {
711 block_gas_used = block_regular_gas_used.max(block_state_gas_used);
713
714 if block_regular_gas_used > block.header.gas_limit
719 || block_state_gas_used > block.header.gas_limit
720 {
721 return Err(EvmError::Transaction(format!(
722 "Gas allowance exceeded: Block gas used overflow: \
723 block_gas_used {block_gas_used} > block_gas_limit {}",
724 block.header.gas_limit
725 )));
726 }
727 } else {
728 block_gas_used = block_gas_used.saturating_add(report.gas_used);
729 }
730
731 let receipt = Receipt::new(
732 tx.tx_type(),
733 matches!(report.result, TxResult::Success),
734 cumulative_gas_used,
735 report.logs,
736 );
737
738 receipts.push(receipt);
739 }
740
741 if is_amsterdam && block_gas_used > block.header.gas_limit {
745 return Err(EvmError::Transaction(format!(
746 "Gas allowance exceeded: Block gas used overflow: \
747 block_gas_used {block_gas_used} > block_gas_limit {}",
748 block.header.gas_limit
749 )));
750 }
751
752 #[cfg(feature = "perf_opcode_timings")]
753 {
754 let mut timings = OPCODE_TIMINGS.lock().expect("poison");
755 timings.inc_tx_count(receipts.len());
756 timings.inc_block_count();
757 ::tracing::info!("{}", timings.info_pretty());
758 let precompiles_timings = PRECOMPILES_TIMINGS.lock().expect("poison");
759 ::tracing::info!("{}", precompiles_timings.info_pretty());
760 }
761
762 if queue_length.load(Ordering::Relaxed) == 0 {
763 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
764 }
765
766 if is_amsterdam {
769 let post_tx_index =
770 u32::try_from(block.body.transactions.len() + 1).unwrap_or(u32::MAX);
771 db.set_bal_index(post_tx_index);
772
773 if let Some(withdrawals) = &block.body.withdrawals
775 && let Some(recorder) = db.bal_recorder_mut()
776 {
777 recorder.extend_touched_addresses(withdrawals.iter().map(|w| w.address));
778 }
779 }
780
781 let requests = match vm_type {
785 VMType::L1 => extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?,
786 VMType::L2(_) => Default::default(),
787 };
788
789 if let Some(withdrawals) = &block.body.withdrawals {
790 Self::process_withdrawals(db, withdrawals)?;
791 }
792 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
793
794 let bal = db.take_bal();
796
797 Ok((
798 BlockExecutionResult {
799 receipts,
800 requests,
801 block_gas_used,
802 tx_gas_breakdowns,
803 },
804 bal,
805 ))
806 }
807
808 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
813 fn bal_to_account_updates(
814 bal: &BlockAccessList,
815 store: &dyn Database,
816 ) -> Result<Vec<AccountUpdate>, EvmError> {
817 use ethrex_common::types::AccountInfo;
818
819 let mut updates = Vec::new();
820
821 let write_addrs: Vec<Address> = bal
823 .accounts()
824 .iter()
825 .filter(|ac| {
826 !ac.balance_changes.is_empty()
827 || !ac.nonce_changes.is_empty()
828 || !ac.code_changes.is_empty()
829 || !ac.storage_changes.is_empty()
830 })
831 .map(|ac| ac.address)
832 .collect();
833 store
834 .prefetch_accounts(&write_addrs)
835 .map_err(|e| EvmError::Custom(format!("bal_to_account_updates prefetch: {e}")))?;
836
837 for acct_changes in bal.accounts() {
838 let addr = acct_changes.address;
839
840 let has_writes = !acct_changes.balance_changes.is_empty()
842 || !acct_changes.nonce_changes.is_empty()
843 || !acct_changes.code_changes.is_empty()
844 || !acct_changes.storage_changes.is_empty();
845 if !has_writes {
846 continue;
847 }
848
849 let prestate = store
851 .get_account_state(addr)
852 .map_err(|e| EvmError::Custom(format!("bal_to_account_updates: {e}")))?;
853
854 let balance = acct_changes
856 .balance_changes
857 .last()
858 .map(|c| c.post_balance)
859 .unwrap_or(prestate.balance);
860
861 let nonce = acct_changes
863 .nonce_changes
864 .last()
865 .map(|c| c.post_nonce)
866 .unwrap_or(prestate.nonce);
867
868 let (code_hash, code) = if let Some(c) = acct_changes.code_changes.last() {
870 code_from_bal(&c.new_code)
871 } else {
872 (prestate.code_hash, None)
873 };
874
875 let mut added_storage = FxHashMap::with_capacity_and_hasher(
877 acct_changes.storage_changes.len(),
878 Default::default(),
879 );
880 for slot_change in &acct_changes.storage_changes {
881 if let Some(last) = slot_change.slot_changes.last() {
882 let key = ethrex_common::utils::u256_to_h256(slot_change.slot);
883 added_storage.insert(key, last.post_value);
884 }
885 }
886
887 let post_empty = balance.is_zero() && nonce == 0 && code_hash == *EMPTY_KECCAK_HASH;
889 let pre_empty = prestate.balance.is_zero()
890 && prestate.nonce == 0
891 && prestate.code_hash == *EMPTY_KECCAK_HASH;
892 let removed = post_empty && !pre_empty;
893
894 let balance_changed = acct_changes
895 .balance_changes
896 .last()
897 .is_some_and(|c| c.post_balance != prestate.balance);
898 let nonce_changed = acct_changes
899 .nonce_changes
900 .last()
901 .is_some_and(|c| c.post_nonce != prestate.nonce);
902 let code_changed = acct_changes.code_changes.last().is_some();
903 let acc_info_updated = balance_changed || nonce_changed || code_changed;
904
905 if !removed && !acc_info_updated && added_storage.is_empty() {
906 continue;
907 }
908
909 let info = if acc_info_updated {
910 Some(AccountInfo {
911 code_hash,
912 balance,
913 nonce,
914 })
915 } else {
916 None
917 };
918
919 let update = AccountUpdate {
920 address: addr,
921 removed,
922 info,
923 code,
924 added_storage,
925 removed_storage: false,
932 };
933 updates.push(update);
934 }
935
936 Ok(updates)
937 }
938
939 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
956 fn seed_db_from_bal(
957 db: &mut GeneralizedDatabase,
958 bal: &BlockAccessList,
959 max_idx: u32,
960 accounts_by_min_index: &[(u32, usize)],
961 ) -> Result<(), EvmError> {
962 let end = accounts_by_min_index.partition_point(|(min_idx, _)| *min_idx <= max_idx);
963 let bal_accounts = bal.accounts();
964 for &(_, acct_idx) in &accounts_by_min_index[..end] {
965 seed_one_address_info_from_bal(db, bal, acct_idx, max_idx)
966 .map_err(|e| EvmError::Custom(format!("seed_db_from_bal: {e}")))?;
967
968 let acct_changes = &bal_accounts[acct_idx];
969 if acct_changes.storage_changes.is_empty() {
970 continue;
971 }
972 let any_storage = acct_changes.storage_changes.iter().any(|sc| {
973 sc.slot_changes
974 .first()
975 .is_some_and(|c| c.block_access_index <= max_idx)
976 });
977 if !any_storage {
978 continue;
979 }
980 let addr = acct_changes.address;
981 if !db.current_accounts_state.contains_key(&addr) {
982 db.get_account(addr)
983 .map_err(|e| EvmError::Custom(format!("seed storage: {e}")))?;
984 }
985 let acc = db
986 .get_account_mut(addr)
987 .map_err(|e| EvmError::Custom(format!("seed storage mut: {e}")))?;
988 for sc in &acct_changes.storage_changes {
989 if let Some(value) = post_value_at_or_before(sc, max_idx) {
990 acc.storage
991 .insert(ethrex_common::utils::u256_to_h256(sc.slot), value);
992 }
993 }
994 }
995 Ok(())
996 }
997
998 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1005 #[allow(clippy::too_many_arguments, clippy::type_complexity)]
1006 fn execute_block_parallel(
1007 block: &Block,
1008 txs_with_sender: &[(&Transaction, Address)],
1009 db: &mut GeneralizedDatabase,
1010 vm_type: VMType,
1011 bal: Arc<BlockAccessList>,
1012 merkleizer: Option<&Sender<Vec<AccountUpdate>>>,
1013 queue_length: &AtomicUsize,
1014 system_seed: Arc<CacheDB>,
1015 crypto: &dyn Crypto,
1016 validation_index: Arc<BalAddressIndex>,
1017 ) -> Result<
1018 (
1019 Vec<Receipt>,
1020 u64,
1021 FxHashSet<(Address, H256)>,
1022 FxHashSet<Address>,
1023 Vec<TxGasBreakdown>,
1024 ),
1025 EvmError,
1026 > {
1027 let store = db.store.clone();
1028 let header = &block.header;
1029 let n_txs = txs_with_sender.len();
1030 let chain_config = store.get_chain_config()?;
1035 let is_amsterdam = chain_config.is_amsterdam_activated(header.timestamp);
1036 let evm_config = EVMConfig::new_from_chain_config(&chain_config, header);
1039 let chain_id = chain_config.chain_id;
1040 let base_blob_fee_per_gas = get_base_fee_per_blob_gas(header.excess_blob_gas, &evm_config)?;
1042 debug_assert!(
1043 is_amsterdam,
1044 "execute_block_parallel invoked on non-Amsterdam block"
1045 );
1046
1047 if let Some(merkleizer) = merkleizer {
1051 let account_updates = Self::bal_to_account_updates(&bal, store.as_ref())?;
1052 merkleizer
1053 .send(account_updates)
1054 .map_err(|e| EvmError::Custom(format!("merkleizer send failed: {e}")))?;
1055 queue_length.fetch_add(1, Ordering::Relaxed);
1056 }
1057
1058 let mut unread_storage_reads: FxHashSet<(Address, H256)> = FxHashSet::default();
1061 let mut unaccessed_pure_accounts: FxHashSet<Address> = FxHashSet::default();
1064 for acct in bal.accounts() {
1065 for &slot in &acct.storage_reads {
1066 let key = ethrex_common::utils::u256_to_h256(slot);
1067 unread_storage_reads.insert((acct.address, key));
1068 }
1069 let is_pure = acct.storage_changes.is_empty()
1070 && acct.storage_reads.is_empty()
1071 && acct.balance_changes.is_empty()
1072 && acct.nonce_changes.is_empty()
1073 && acct.code_changes.is_empty();
1074 if is_pure {
1075 unaccessed_pure_accounts.insert(acct.address);
1076 }
1077 }
1078
1079 for addr in system_seed.keys() {
1085 if *addr == SYSTEM_ADDRESS {
1086 continue;
1087 }
1088 unaccessed_pure_accounts.remove(addr);
1089 }
1090
1091 unread_storage_reads.retain(|(addr, key)| {
1093 !system_seed
1094 .get(addr)
1095 .is_some_and(|a| a.storage.contains_key(key))
1096 });
1097
1098 let arc_bal = bal;
1100 let arc_idx = validation_index;
1101
1102 type TxExecResult = (
1116 usize,
1117 TxType,
1118 ExecutionReport,
1119 FxHashSet<Address>, Vec<(Address, H256)>, Vec<Address>, Option<EvmError>, );
1124
1125 let exec_results: Result<Vec<TxExecResult>, EvmError> = (0..n_txs)
1126 .into_par_iter()
1127 .map(|tx_idx| -> Result<_, EvmError> {
1128 let (tx, sender) = &txs_with_sender[tx_idx];
1129 let mut tx_db = GeneralizedDatabase::new_with_shared_base_and_capacity(
1131 store.clone(),
1132 system_seed.clone(),
1133 32,
1134 );
1135 tx_db.lazy_bal = Some(LazyBalCursor {
1136 bal: arc_bal.clone(),
1137 bal_index: u32::try_from(tx_idx + 1).unwrap_or(u32::MAX),
1138 index: arc_idx.clone(),
1139 });
1140 let mut stack_pool = Vec::with_capacity(8);
1143 let mut memory_pool = Vec::with_capacity(1);
1145
1146 tx_db.accessed_accounts =
1150 Some(FxHashSet::with_capacity_and_hasher(16, Default::default()));
1151
1152 tx_db.enable_bal_recording();
1157 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
1158 tx_db.set_bal_index(bal_index);
1159 if let Some(recorder) = tx_db.bal_recorder_mut() {
1160 recorder.record_touched_address(*sender);
1161 if let TxKind::Call(to) = tx.to() {
1162 recorder.record_touched_address(to);
1163 }
1164 }
1165
1166 let report = LEVM::execute_tx_in_block(
1167 tx,
1168 *sender,
1169 header,
1170 &mut tx_db,
1171 vm_type,
1172 base_blob_fee_per_gas,
1173 &mut stack_pool,
1174 &mut memory_pool,
1175 false,
1176 crypto,
1177 evm_config,
1178 chain_id,
1179 )?;
1180
1181 let current_state = std::mem::take(&mut tx_db.current_accounts_state);
1182 let codes = std::mem::take(&mut tx_db.codes);
1183 let tracked = tx_db.accessed_accounts.take().unwrap_or_default();
1184 let (shadow_touched, shadow_reads) = tx_db
1185 .bal_recorder
1186 .take()
1187 .map(|mut r| (r.take_touched_addresses(), r.take_storage_reads()))
1188 .unwrap_or_default();
1189
1190 let mut reads_satisfied: Vec<(Address, H256)> =
1198 Vec::with_capacity(current_state.len() * 4);
1199 let mut destroyed: Vec<Address> = Vec::new();
1203 for (addr, acct) in ¤t_state {
1204 if matches!(
1205 acct.status,
1206 AccountStatus::Destroyed | AccountStatus::DestroyedModified
1207 ) {
1208 destroyed.push(*addr);
1209 } else {
1210 for key in acct.storage.keys() {
1211 reads_satisfied.push((*addr, *key));
1212 }
1213 }
1214 }
1215
1216 let deferred_bal_err: Option<EvmError> = (|| -> Result<(), EvmError> {
1222 let bal_idx = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
1223 let seed_idx = u32::try_from(tx_idx).unwrap_or(u32::MAX);
1224 Self::validate_tx_execution(
1225 bal_idx,
1226 seed_idx,
1227 ¤t_state,
1228 &codes,
1229 &arc_bal,
1230 &arc_idx,
1231 &system_seed,
1232 &store,
1233 )
1234 .map_err(|e| {
1235 EvmError::Custom(format!("BAL validation failed for tx {tx_idx}: {e}"))
1236 })?;
1237
1238 for addr in &shadow_touched {
1240 if !arc_idx.addr_to_idx.contains_key(addr) {
1241 return Err(EvmError::Custom(format!(
1242 "BAL validation failed for tx {tx_idx}: account {addr:?} was \
1243 accessed during execution but is missing from BAL"
1244 )));
1245 }
1246 }
1247 for (addr, slot) in &shadow_reads {
1248 let Some(&bal_acct_idx) = arc_idx.addr_to_idx.get(addr) else {
1249 continue;
1251 };
1252 let acct = &arc_bal.accounts()[bal_acct_idx];
1253 let in_changes = acct
1254 .storage_changes
1255 .binary_search_by(|sc| sc.slot.cmp(slot))
1256 .is_ok();
1257 let in_reads = acct.storage_reads.contains(slot);
1258 if !in_changes && !in_reads {
1259 return Err(EvmError::Custom(format!(
1260 "BAL validation failed for tx {tx_idx}: storage slot {slot} of \
1261 account {addr:?} was read during execution but is missing from \
1262 BAL (no storage_changes or storage_reads entry)"
1263 )));
1264 }
1265 }
1266 Ok(())
1267 })()
1268 .err();
1269
1270 drop(current_state);
1271 drop(codes);
1272
1273 Ok((
1274 tx_idx,
1275 tx.tx_type(),
1276 report,
1277 tracked,
1278 reads_satisfied,
1279 destroyed,
1280 deferred_bal_err,
1281 ))
1282 })
1283 .collect();
1284
1285 let mut exec_results = exec_results?;
1286
1287 exec_results.sort_unstable_by_key(|(idx, _, _, _, _, _, _)| *idx);
1294
1295 let mut block_regular_gas_used = 0_u64;
1300 let mut block_state_gas_used = 0_u64;
1301 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(exec_results.len());
1302 for (tx_idx, _, report, _, _, _, _) in &exec_results {
1303 let (tx, _tx_sender) = txs_with_sender
1304 .get(*tx_idx)
1305 .ok_or_else(|| EvmError::Custom(format!("tx index {tx_idx} out of bounds")))?;
1306 if is_amsterdam {
1307 check_2d_gas_allowance(
1308 tx,
1309 block_regular_gas_used,
1310 block_state_gas_used,
1311 header.gas_limit,
1312 )?;
1313 }
1314
1315 tx_gas_breakdowns.push(TxGasBreakdown::from_report(
1316 *tx_idx,
1317 tx.hash(crypto),
1318 report,
1319 ));
1320
1321 let tx_state_gas = report.state_gas_used;
1322 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
1323 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
1324 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
1325 }
1326 let block_gas_used = block_regular_gas_used.max(block_state_gas_used);
1327 if block_gas_used > header.gas_limit {
1329 return Err(EvmError::Transaction(format!(
1330 "Gas allowance exceeded: Block gas used overflow: \
1331 block_gas_used {block_gas_used} > block_gas_limit {}",
1332 header.gas_limit
1333 )));
1334 }
1335
1336 for (_, _, _, _, _, _, deferred) in &mut exec_results {
1339 if let Some(err) = deferred.take() {
1340 return Err(err);
1341 }
1342 }
1343
1344 for (_, _, _, tracked_accounts, reads_satisfied, destroyed, _) in &exec_results {
1347 if !unread_storage_reads.is_empty() {
1348 for addr in destroyed {
1349 unread_storage_reads.retain(|&(a, _)| a != *addr);
1350 }
1351 for pair in reads_satisfied {
1352 unread_storage_reads.remove(pair);
1353 }
1354 }
1355 if !unaccessed_pure_accounts.is_empty() {
1359 unaccessed_pure_accounts.remove(&header.coinbase);
1360 for addr in tracked_accounts {
1361 unaccessed_pure_accounts.remove(addr);
1362 }
1363 }
1364 }
1365
1366 let mut receipts = Vec::with_capacity(n_txs);
1368 let mut cumulative_gas_used = 0_u64;
1369 for (_, tx_type, report, _, _, _, _) in exec_results {
1370 cumulative_gas_used += report.gas_spent;
1371 let receipt = Receipt::new(
1372 tx_type,
1373 matches!(report.result, TxResult::Success),
1374 cumulative_gas_used,
1375 report.logs,
1376 );
1377 receipts.push(receipt);
1378 }
1379
1380 Ok((
1381 receipts,
1382 block_gas_used,
1383 unread_storage_reads,
1384 unaccessed_pure_accounts,
1385 tx_gas_breakdowns,
1386 ))
1387 }
1388
1389 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1392 fn seeded_balance(
1393 seed_idx: u32,
1394 acct: ðrex_common::types::block_access_list::AccountChanges,
1395 system_seed: &CacheDB,
1396 store: &Arc<dyn Database>,
1397 ) -> Result<U256, BalValidationError> {
1398 let pos = acct
1399 .balance_changes
1400 .partition_point(|c| c.block_access_index <= seed_idx);
1401 if pos > 0 {
1402 Ok(acct.balance_changes[pos - 1].post_balance)
1403 } else if let Some(a) = system_seed.get(&acct.address) {
1404 Ok(a.info.balance)
1405 } else {
1406 store
1407 .get_account_state(acct.address)
1408 .map(|a| a.balance)
1409 .map_err(|e| {
1410 BalValidationError::Database(format!(
1411 "DB error reading balance for {:?}: {e}",
1412 acct.address
1413 ))
1414 })
1415 }
1416 }
1417
1418 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1421 fn seeded_nonce(
1422 seed_idx: u32,
1423 acct: ðrex_common::types::block_access_list::AccountChanges,
1424 system_seed: &CacheDB,
1425 store: &Arc<dyn Database>,
1426 ) -> Result<u64, BalValidationError> {
1427 let pos = acct
1428 .nonce_changes
1429 .partition_point(|c| c.block_access_index <= seed_idx);
1430 if pos > 0 {
1431 Ok(acct.nonce_changes[pos - 1].post_nonce)
1432 } else if let Some(a) = system_seed.get(&acct.address) {
1433 Ok(a.info.nonce)
1434 } else {
1435 store
1436 .get_account_state(acct.address)
1437 .map(|a| a.nonce)
1438 .map_err(|e| {
1439 BalValidationError::Database(format!(
1440 "DB error reading nonce for {:?}: {e}",
1441 acct.address
1442 ))
1443 })
1444 }
1445 }
1446
1447 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1463 #[allow(clippy::too_many_arguments)]
1464 fn validate_tx_execution(
1465 bal_idx: u32,
1466 seed_idx: u32,
1467 current_state: &FxHashMap<Address, LevmAccount>,
1468 codes: &FxHashMap<H256, Code>,
1469 bal: &BlockAccessList,
1470 index: &BalAddressIndex,
1471 system_seed: &CacheDB,
1472 store: &Arc<dyn Database>,
1473 ) -> Result<(), BalValidationError> {
1474 if let Some(active_accounts) = index.tx_to_accounts.get(&bal_idx) {
1477 for &acct_inner_idx in active_accounts {
1478 let acct = &bal.accounts()[acct_inner_idx];
1479 let addr = acct.address;
1480 let actual = current_state.get(&addr);
1481
1482 if let Some(expected) = find_exact_change_balance(&acct.balance_changes, bal_idx) {
1484 match actual {
1485 Some(a) if a.info.balance == expected => {}
1486 Some(a) => {
1487 return Err(BalValidationError::Mismatch(format!(
1488 "account {addr:?} balance mismatch at index {bal_idx}: BAL={expected}, exec={} (diff={})",
1489 a.info.balance,
1490 describe_balance_diff(expected, a.info.balance),
1491 )));
1492 }
1493 None => {
1494 let seeded = Self::seeded_balance(seed_idx, acct, system_seed, store)?;
1499 if expected != seeded {
1500 let all_bal_indices: Vec<u32> = acct
1502 .balance_changes
1503 .iter()
1504 .map(|c| c.block_access_index)
1505 .collect();
1506 let all_nonce_indices: Vec<u32> = acct
1507 .nonce_changes
1508 .iter()
1509 .map(|c| c.block_access_index)
1510 .collect();
1511 let all_storage_indices: Vec<(u32, u64)> = acct
1512 .storage_changes
1513 .iter()
1514 .flat_map(|sc| {
1515 sc.slot_changes
1516 .iter()
1517 .map(|c| (c.block_access_index, sc.slot.low_u64()))
1518 })
1519 .collect();
1520 let code_indices: Vec<u32> = acct
1521 .code_changes
1522 .iter()
1523 .map(|c| c.block_access_index)
1524 .collect();
1525 return Err(BalValidationError::Mismatch(format!(
1526 "account {addr:?} has BAL balance change at {bal_idx} \
1527 but not in execution state (expected={expected}, pre={seeded}, \
1528 all_bal_idx={all_bal_indices:?}, nonce_idx={all_nonce_indices:?}, \
1529 storage_idx={all_storage_indices:?}, code_idx={code_indices:?})"
1530 )));
1531 }
1532 }
1533 }
1534 }
1535
1536 if let Some(expected) = find_exact_change_nonce(&acct.nonce_changes, bal_idx) {
1538 match actual {
1539 Some(a) if a.info.nonce == expected => {}
1540 Some(a) => {
1541 return Err(BalValidationError::Mismatch(format!(
1542 "account {addr:?} nonce mismatch at index {bal_idx}: BAL={expected}, exec={}",
1543 a.info.nonce
1544 )));
1545 }
1546 None => {
1547 let seeded = Self::seeded_nonce(seed_idx, acct, system_seed, store)?;
1548 if expected != seeded {
1549 return Err(BalValidationError::Mismatch(format!(
1550 "account {addr:?} has BAL nonce change at {bal_idx} \
1551 but not in execution state (expected={expected}, pre={seeded})"
1552 )));
1553 }
1554 }
1555 }
1556 }
1557
1558 if let Some(expected_code) = find_exact_change_code(&acct.code_changes, bal_idx) {
1560 match actual {
1561 Some(a) => {
1562 let actual_code = if let Some(c) = codes.get(&a.info.code_hash) {
1563 c.code_bytes()
1564 } else {
1565 let c = store.get_account_code(a.info.code_hash).map_err(|e| {
1566 BalValidationError::Database(format!(
1567 "DB error reading account code for {addr:?}: {e}"
1568 ))
1569 })?;
1570 c.code_bytes()
1571 };
1572 if actual_code != *expected_code {
1573 return Err(BalValidationError::Mismatch(format!(
1574 "account {addr:?} code mismatch at index {bal_idx}"
1575 )));
1576 }
1577 }
1578 None => {
1579 let code_hash = if let Some(a) = system_seed.get(&addr) {
1583 a.info.code_hash
1584 } else {
1585 store
1586 .get_account_state(addr)
1587 .map(|a| a.code_hash)
1588 .map_err(|e| {
1589 BalValidationError::Database(format!(
1590 "DB error reading account state for {addr:?}: {e}"
1591 ))
1592 })?
1593 };
1594 let pre_code = if let Some(c) = codes.get(&code_hash) {
1595 c.code_bytes()
1596 } else {
1597 let c = store.get_account_code(code_hash).map_err(|e| {
1598 BalValidationError::Database(format!(
1599 "DB error reading account code for hash \
1600 {code_hash:?}: {e}"
1601 ))
1602 })?;
1603 c.code_bytes()
1604 };
1605 if *expected_code != pre_code {
1606 return Err(BalValidationError::Mismatch(format!(
1607 "account {addr:?} has BAL code change at {bal_idx} \
1608 but not in execution state"
1609 )));
1610 }
1611 }
1612 }
1613 }
1614
1615 for sc in &acct.storage_changes {
1617 if let Some(expected_value) =
1618 find_exact_change_storage(&sc.slot_changes, bal_idx)
1619 {
1620 let key = ethrex_common::utils::u256_to_h256(sc.slot);
1621 let actual_value = actual.and_then(|a| a.storage.get(&key)).copied();
1622 if actual_value != Some(expected_value) {
1623 if actual.is_none() || actual_value.is_none() {
1625 let pre_value =
1626 store.get_storage_value(addr, key).map_err(|e| {
1627 BalValidationError::Database(format!(
1628 "DB error reading storage for {addr:?} slot {}: {e}",
1629 sc.slot
1630 ))
1631 })?;
1632 if expected_value == pre_value {
1633 continue; }
1635 }
1636 return Err(BalValidationError::Mismatch(format!(
1637 "account {addr:?} storage slot {} mismatch at index {bal_idx}: \
1638 BAL={expected_value}, exec={actual_value:?}",
1639 sc.slot
1640 )));
1641 }
1642 }
1643 }
1644 }
1645 }
1646
1647 for (addr, account) in current_state {
1650 if account.is_unmodified() {
1651 continue;
1652 }
1653
1654 let Some(&bal_acct_idx) = index.addr_to_idx.get(addr) else {
1655 let pre = system_seed
1659 .get(addr)
1660 .map(|a| (a.info.balance, a.info.nonce, a.info.code_hash))
1661 .or_else(|| {
1662 store
1663 .get_account_state(*addr)
1664 .ok()
1665 .map(|a| (a.balance, a.nonce, a.code_hash))
1666 })
1667 .unwrap_or_default();
1668 let post = (
1669 account.info.balance,
1670 account.info.nonce,
1671 account.info.code_hash,
1672 );
1673 if pre != post {
1674 return Err(BalValidationError::Mismatch(format!(
1675 "account {addr:?} was modified by execution but is absent from BAL"
1676 )));
1677 }
1678 continue;
1679 };
1680
1681 let acct = &bal.accounts()[bal_acct_idx];
1682
1683 if !has_exact_change_balance(&acct.balance_changes, bal_idx) {
1685 let seeded_pos = acct
1686 .balance_changes
1687 .partition_point(|c| c.block_access_index <= seed_idx);
1688 let seeded = if seeded_pos > 0 {
1689 acct.balance_changes[seeded_pos - 1].post_balance
1690 } else {
1691 system_seed
1693 .get(addr)
1694 .map(|a| a.info.balance)
1695 .unwrap_or_else(|| {
1696 store
1697 .get_account_state(*addr)
1698 .map(|a| a.balance)
1699 .unwrap_or_default()
1700 })
1701 };
1702 if account.info.balance != seeded {
1703 return Err(BalValidationError::Mismatch(format!(
1704 "account {addr:?} balance changed by execution ({}) but BAL has no \
1705 balance change at index {bal_idx} (seeded={seeded})",
1706 account.info.balance
1707 )));
1708 }
1709 }
1710
1711 if !has_exact_change_nonce(&acct.nonce_changes, bal_idx) {
1713 let seeded_pos = acct
1714 .nonce_changes
1715 .partition_point(|c| c.block_access_index <= seed_idx);
1716 let seeded = if seeded_pos > 0 {
1717 acct.nonce_changes[seeded_pos - 1].post_nonce
1718 } else {
1719 system_seed
1720 .get(addr)
1721 .map(|a| a.info.nonce)
1722 .unwrap_or_else(|| {
1723 store
1724 .get_account_state(*addr)
1725 .map(|a| a.nonce)
1726 .unwrap_or_default()
1727 })
1728 };
1729 if account.info.nonce != seeded {
1730 return Err(BalValidationError::Mismatch(format!(
1731 "account {addr:?} nonce changed by execution ({}) but BAL has no \
1732 nonce change at index {bal_idx} (seeded={seeded})",
1733 account.info.nonce
1734 )));
1735 }
1736 }
1737
1738 if !has_exact_change_code(&acct.code_changes, bal_idx) {
1742 let seeded_pos = acct
1743 .code_changes
1744 .partition_point(|c| c.block_access_index <= seed_idx);
1745 let seeded_hash = if seeded_pos > 0 {
1746 let seeded_code = &acct.code_changes[seeded_pos - 1].new_code;
1747 if seeded_code.is_empty() {
1748 *EMPTY_KECCAK_HASH
1749 } else {
1750 ethrex_common::utils::keccak(seeded_code)
1751 }
1752 } else {
1753 system_seed
1755 .get(addr)
1756 .map(|a| a.info.code_hash)
1757 .unwrap_or_else(|| {
1758 store
1759 .get_account_state(*addr)
1760 .map(|a| a.code_hash)
1761 .unwrap_or(*EMPTY_KECCAK_HASH)
1762 })
1763 };
1764 if account.info.code_hash != seeded_hash {
1765 return Err(BalValidationError::Mismatch(format!(
1766 "account {addr:?} code changed by execution but BAL has no \
1767 code change at index {bal_idx} (seeded_hash={seeded_hash:?})"
1768 )));
1769 }
1770 }
1771
1772 for (key_h256, &value) in &account.storage {
1774 let slot_u256 = u256_from_big_endian_const(key_h256.0);
1775 let pos = acct
1777 .storage_changes
1778 .partition_point(|sc| sc.slot < slot_u256);
1779 if pos < acct.storage_changes.len() && acct.storage_changes[pos].slot == slot_u256 {
1780 let sc = &acct.storage_changes[pos];
1781 if !has_exact_change_storage(&sc.slot_changes, bal_idx) {
1782 let seeded_pos = sc
1783 .slot_changes
1784 .partition_point(|c| c.block_access_index <= seed_idx);
1785 if seeded_pos > 0 {
1786 let seeded = sc.slot_changes[seeded_pos - 1].post_value;
1787 if value != seeded {
1788 return Err(BalValidationError::Mismatch(format!(
1789 "account {addr:?} storage slot {slot_u256} changed by \
1790 execution ({value}) but BAL has no change at index \
1791 {bal_idx} (seeded={seeded})"
1792 )));
1793 }
1794 }
1795 }
1796 }
1797 }
1800 }
1801
1802 Ok(())
1803 }
1804
1805 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1817 fn validate_bal_withdrawal_index(
1818 db: &GeneralizedDatabase,
1819 bal: &BlockAccessList,
1820 withdrawal_idx: u32,
1821 index: &BalAddressIndex,
1822 ) -> Result<(), EvmError> {
1823 for acct in bal.accounts() {
1826 let addr = acct.address;
1827 let actual = db.current_accounts_state.get(&addr);
1828
1829 if let Some(expected) = find_exact_change_balance(&acct.balance_changes, withdrawal_idx)
1831 {
1832 match actual {
1833 Some(a) if a.info.balance == expected => {}
1834 Some(a) => {
1835 return Err(EvmError::Custom(format!(
1836 "BAL validation failed for withdrawal: account {addr:?} balance \
1837 mismatch at index {withdrawal_idx}: BAL={expected}, actual={}",
1838 a.info.balance
1839 )));
1840 }
1841 None => {
1842 return Err(EvmError::Custom(format!(
1843 "BAL validation failed for withdrawal: account {addr:?} has \
1844 balance change at index {withdrawal_idx} but was not touched \
1845 by withdrawal/request phase"
1846 )));
1847 }
1848 }
1849 }
1850
1851 if let Some(expected) = find_exact_change_nonce(&acct.nonce_changes, withdrawal_idx) {
1853 match actual {
1854 Some(a) if a.info.nonce == expected => {}
1855 Some(a) => {
1856 return Err(EvmError::Custom(format!(
1857 "BAL validation failed for withdrawal: account {addr:?} nonce \
1858 mismatch at index {withdrawal_idx}: BAL={expected}, actual={}",
1859 a.info.nonce
1860 )));
1861 }
1862 None => {
1863 return Err(EvmError::Custom(format!(
1864 "BAL validation failed for withdrawal: account {addr:?} has \
1865 nonce change at index {withdrawal_idx} but was not touched \
1866 by withdrawal/request phase"
1867 )));
1868 }
1869 }
1870 }
1871
1872 if let Some(expected_code) = find_exact_change_code(&acct.code_changes, withdrawal_idx)
1874 {
1875 let code_hash = if expected_code.is_empty() {
1876 *EMPTY_KECCAK_HASH
1877 } else {
1878 ethrex_common::utils::keccak(expected_code)
1879 };
1880 match actual {
1881 Some(a) if a.info.code_hash == code_hash => {}
1882 Some(_) => {
1883 return Err(EvmError::Custom(format!(
1884 "BAL validation failed for withdrawal: account {addr:?} code \
1885 mismatch at index {withdrawal_idx}"
1886 )));
1887 }
1888 None => {
1889 return Err(EvmError::Custom(format!(
1890 "BAL validation failed for withdrawal: account {addr:?} has \
1891 code change at index {withdrawal_idx} but was not touched \
1892 by withdrawal/request phase"
1893 )));
1894 }
1895 }
1896 }
1897
1898 for sc in &acct.storage_changes {
1900 if let Some(expected_value) =
1901 find_exact_change_storage(&sc.slot_changes, withdrawal_idx)
1902 {
1903 let key = ethrex_common::utils::u256_to_h256(sc.slot);
1904 let actual_value = actual.and_then(|a| a.storage.get(&key)).copied();
1905 if actual_value != Some(expected_value) {
1906 return Err(EvmError::Custom(format!(
1907 "BAL validation failed for withdrawal: account {addr:?} storage \
1908 slot {} mismatch at index {withdrawal_idx}: BAL={expected_value}, \
1909 actual={actual_value:?}",
1910 sc.slot
1911 )));
1912 }
1913 }
1914 }
1915 }
1916
1917 for (addr, account) in &db.current_accounts_state {
1920 if account.is_unmodified() {
1921 continue;
1922 }
1923
1924 let Some(&bal_acct_idx) = index.addr_to_idx.get(addr) else {
1925 let pre_state = db.store.get_account_state(*addr).map_err(|e| {
1929 EvmError::Custom(format!(
1930 "BAL validation failed for withdrawal: db error reading \
1931 account {addr:?}: {e}"
1932 ))
1933 })?;
1934 let pre = (pre_state.balance, pre_state.nonce, pre_state.code_hash);
1935 let post = (
1936 account.info.balance,
1937 account.info.nonce,
1938 account.info.code_hash,
1939 );
1940 if pre != post {
1941 return Err(EvmError::Custom(format!(
1942 "BAL validation failed for withdrawal: account {addr:?} was modified \
1943 during withdrawal/request phase but is absent from BAL"
1944 )));
1945 }
1946 for (key_h256, &value) in &account.storage {
1949 let pre_value = db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
1950 EvmError::Custom(format!(
1951 "BAL validation failed for withdrawal: db error reading \
1952 storage {addr:?}[{}]: {e}",
1953 u256_from_big_endian_const(key_h256.0)
1954 ))
1955 })?;
1956 if value != pre_value {
1957 return Err(EvmError::Custom(format!(
1958 "BAL validation failed for withdrawal: account {addr:?} storage \
1959 slot {} changed during withdrawal/request phase but is absent \
1960 from BAL",
1961 u256_from_big_endian_const(key_h256.0)
1962 )));
1963 }
1964 }
1965 continue;
1966 };
1967
1968 let acct = &bal.accounts()[bal_acct_idx];
1969
1970 if !has_exact_change_balance(&acct.balance_changes, withdrawal_idx) {
1973 let seeded = match acct.balance_changes.last() {
1974 Some(c) => c.post_balance,
1975 None => {
1976 db.store
1977 .get_account_state(*addr)
1978 .map_err(|e| {
1979 EvmError::Custom(format!(
1980 "BAL validation failed for withdrawal: db error reading \
1981 account {addr:?}: {e}"
1982 ))
1983 })?
1984 .balance
1985 }
1986 };
1987 if account.info.balance != seeded {
1988 return Err(EvmError::Custom(format!(
1989 "BAL validation failed for withdrawal: account {addr:?} balance \
1990 changed during withdrawal/request phase ({}) but BAL has no \
1991 balance change at index {withdrawal_idx} (last_bal={seeded})",
1992 account.info.balance
1993 )));
1994 }
1995 }
1996
1997 if !has_exact_change_nonce(&acct.nonce_changes, withdrawal_idx) {
1999 let seeded = match acct.nonce_changes.last() {
2000 Some(c) => c.post_nonce,
2001 None => {
2002 db.store
2003 .get_account_state(*addr)
2004 .map_err(|e| {
2005 EvmError::Custom(format!(
2006 "BAL validation failed for withdrawal: db error reading \
2007 account {addr:?}: {e}"
2008 ))
2009 })?
2010 .nonce
2011 }
2012 };
2013 if account.info.nonce != seeded {
2014 return Err(EvmError::Custom(format!(
2015 "BAL validation failed for withdrawal: account {addr:?} nonce \
2016 changed during withdrawal/request phase ({}) but BAL has no \
2017 nonce change at index {withdrawal_idx} (last_bal={seeded})",
2018 account.info.nonce
2019 )));
2020 }
2021 }
2022
2023 if !has_exact_change_code(&acct.code_changes, withdrawal_idx) {
2025 let seeded_hash = match acct.code_changes.last() {
2026 Some(c) if c.new_code.is_empty() => *EMPTY_KECCAK_HASH,
2027 Some(c) => ethrex_common::utils::keccak(&c.new_code),
2028 None => {
2029 db.store
2030 .get_account_state(*addr)
2031 .map_err(|e| {
2032 EvmError::Custom(format!(
2033 "BAL validation failed for withdrawal: db error reading \
2034 account {addr:?}: {e}"
2035 ))
2036 })?
2037 .code_hash
2038 }
2039 };
2040 if account.info.code_hash != seeded_hash {
2041 return Err(EvmError::Custom(format!(
2042 "BAL validation failed for withdrawal: account {addr:?} code \
2043 changed during withdrawal/request phase but BAL has no \
2044 code change at index {withdrawal_idx} \
2045 (actual={:?}, last_bal={seeded_hash:?})",
2046 account.info.code_hash
2047 )));
2048 }
2049 }
2050
2051 for (key_h256, &value) in &account.storage {
2054 let slot_u256 = u256_from_big_endian_const(key_h256.0);
2055 let pos = acct
2056 .storage_changes
2057 .partition_point(|sc| sc.slot < slot_u256);
2058 if pos < acct.storage_changes.len() && acct.storage_changes[pos].slot == slot_u256 {
2059 let sc = &acct.storage_changes[pos];
2060 if !has_exact_change_storage(&sc.slot_changes, withdrawal_idx) {
2061 let seeded = match sc.slot_changes.last() {
2064 Some(c) => c.post_value,
2065 None => db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
2066 EvmError::Custom(format!(
2067 "BAL validation failed for withdrawal: db error reading \
2068 storage {addr:?}[{slot_u256}]: {e}"
2069 ))
2070 })?,
2071 };
2072 if value != seeded {
2073 return Err(EvmError::Custom(format!(
2074 "BAL validation failed for withdrawal: account {addr:?} \
2075 storage slot {slot_u256} changed during withdrawal/request \
2076 phase ({value}) but BAL has no change at index \
2077 {withdrawal_idx} (last_bal={seeded})"
2078 )));
2079 }
2080 }
2081 } else {
2082 let pre_value = db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
2085 EvmError::Custom(format!(
2086 "BAL validation failed for withdrawal: db error reading \
2087 storage {addr:?}[{slot_u256}]: {e}"
2088 ))
2089 })?;
2090 if value != pre_value {
2091 return Err(EvmError::Custom(format!(
2092 "BAL validation failed for withdrawal: account {addr:?} \
2093 storage slot {slot_u256} changed during withdrawal/request \
2094 phase ({value}) but slot is absent from BAL storage_changes \
2095 (pre={pre_value})"
2096 )));
2097 }
2098 }
2099 }
2100 }
2101
2102 Ok(())
2103 }
2104
2105 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2116 pub fn warm_block(
2117 block: &Block,
2118 store: Arc<dyn Database>,
2119 vm_type: VMType,
2120 crypto: &dyn Crypto,
2121 cancelled: &AtomicBool,
2122 ) -> Result<(), EvmError> {
2123 let mut db = GeneralizedDatabase::new(store.clone());
2124
2125 let txs_with_sender = block
2126 .body
2127 .get_transactions_with_sender(crypto)
2128 .map_err(|error| {
2129 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
2130 })?;
2131
2132 let mut sender_groups: FxHashMap<Address, Vec<&Transaction>> = FxHashMap::default();
2134 for (tx, sender) in &txs_with_sender {
2135 sender_groups.entry(*sender).or_default().push(tx);
2136 }
2137
2138 let chain_config = store.get_chain_config()?;
2141 let evm_config = EVMConfig::new_from_chain_config(&chain_config, &block.header);
2142 let chain_id = chain_config.chain_id;
2143 let base_blob_fee_per_gas =
2145 get_base_fee_per_blob_gas(block.header.excess_blob_gas, &evm_config)?;
2146
2147 sender_groups.into_par_iter().for_each_with(
2150 Vec::with_capacity(STACK_LIMIT),
2151 |stack_pool, (sender, txs)| {
2152 if cancelled.load(Ordering::Relaxed) {
2153 return;
2154 }
2155 let mut memory_pool = Vec::with_capacity(1);
2159 let mut group_db = GeneralizedDatabase::new(store.clone());
2161 for tx in txs {
2164 let _ = Self::execute_tx_in_block(
2165 tx,
2166 sender,
2167 &block.header,
2168 &mut group_db,
2169 vm_type,
2170 base_blob_fee_per_gas,
2171 stack_pool,
2172 &mut memory_pool,
2173 true,
2174 crypto,
2175 evm_config,
2176 chain_id,
2177 );
2178 }
2179 },
2180 );
2181
2182 if cancelled.load(Ordering::Relaxed) {
2183 return Ok(());
2184 }
2185
2186 for withdrawal in block
2187 .body
2188 .withdrawals
2189 .iter()
2190 .flatten()
2191 .filter(|withdrawal| withdrawal.amount > 0)
2192 {
2193 db.get_account_mut(withdrawal.address).map_err(|_| {
2194 EvmError::DB(format!(
2195 "Withdrawal account {} not found",
2196 withdrawal.address
2197 ))
2198 })?;
2199 }
2200 Ok(())
2201 }
2202
2203 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2206 pub fn bal_storage_slots(bal: &BlockAccessList) -> Vec<(Address, H256)> {
2207 bal.accounts()
2208 .iter()
2209 .flat_map(|ac| {
2210 ac.all_storage_slots()
2211 .map(move |slot| (ac.address, H256::from_uint(&slot)))
2212 })
2213 .collect()
2214 }
2215
2216 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2225 pub fn warm_block_from_bal(
2226 bal: &BlockAccessList,
2227 store: Arc<dyn Database>,
2228 cancelled: &AtomicBool,
2229 ) -> Result<(), EvmError> {
2230 let accounts = bal.accounts();
2231 if accounts.is_empty() {
2232 return Ok(());
2233 }
2234
2235 let account_addresses: Vec<Address> = accounts.iter().map(|ac| ac.address).collect();
2241 store
2242 .prefetch_accounts(&account_addresses)
2243 .map_err(|e| EvmError::Custom(format!("prefetch_accounts: {e}")))?;
2244
2245 if cancelled.load(Ordering::Relaxed) {
2246 return Ok(());
2247 }
2248
2249 let code_hashes: Vec<ethrex_common::H256> = accounts
2253 .par_iter()
2254 .filter_map(|ac| {
2255 store
2256 .get_account_state(ac.address)
2257 .ok()
2258 .filter(|s| s.code_hash != *EMPTY_KECCAK_HASH)
2259 .map(|s| s.code_hash)
2260 })
2261 .collect();
2262 code_hashes.par_iter().for_each(|&h| {
2263 let _ = store.get_account_code(h);
2264 });
2265
2266 Ok(())
2267 }
2268
2269 fn send_state_transitions_tx(
2270 merkleizer: &Sender<Vec<AccountUpdate>>,
2271 db: &mut GeneralizedDatabase,
2272 queue_length: &AtomicUsize,
2273 ) -> Result<(), EvmError> {
2274 let transitions = LEVM::get_state_transitions_tx(db)?;
2275 merkleizer
2276 .send(transitions)
2277 .map_err(|e| EvmError::Custom(format!("send failed: {e}")))?;
2278 queue_length.fetch_add(1, Ordering::Relaxed);
2279 Ok(())
2280 }
2281
2282 fn setup_env(
2283 tx: &Transaction,
2284 tx_sender: Address,
2285 block_header: &BlockHeader,
2286 db: &GeneralizedDatabase,
2287 vm_type: VMType,
2288 ) -> Result<Environment, EvmError> {
2289 let chain_config = db.store.get_chain_config()?;
2293 let config = EVMConfig::new_from_chain_config(&chain_config, block_header);
2294 let base_blob_fee_per_gas =
2295 get_base_fee_per_blob_gas(block_header.excess_blob_gas, &config)?;
2296 Self::setup_env_with_config(
2297 tx,
2298 tx_sender,
2299 block_header,
2300 config,
2301 chain_config.chain_id,
2302 vm_type,
2303 base_blob_fee_per_gas,
2304 )
2305 }
2306
2307 fn setup_env_with_config(
2311 tx: &Transaction,
2312 tx_sender: Address,
2313 block_header: &BlockHeader,
2314 config: EVMConfig,
2315 chain_id: u64,
2316 vm_type: VMType,
2317 base_blob_fee_per_gas: U256,
2318 ) -> Result<Environment, EvmError> {
2319 let gas_price: U256 = calculate_gas_price_for_tx(
2320 tx,
2321 block_header.base_fee_per_gas.unwrap_or_default(),
2322 &vm_type,
2323 )?;
2324
2325 let block_excess_blob_gas = block_header.excess_blob_gas;
2326 let env = Environment {
2327 origin: tx_sender,
2328 gas_limit: tx.gas_limit(),
2329 config,
2330 block_number: block_header.number,
2331 coinbase: block_header.coinbase,
2332 timestamp: block_header.timestamp,
2333 prev_randao: Some(block_header.prev_randao),
2334 slot_number: block_header
2335 .slot_number
2336 .map(U256::from)
2337 .unwrap_or(U256::zero()),
2338 chain_id: chain_id.into(),
2339 base_fee_per_gas: block_header.base_fee_per_gas.unwrap_or_default().into(),
2340 base_blob_fee_per_gas,
2341 gas_price,
2342 block_excess_blob_gas,
2343 block_blob_gas_used: block_header.blob_gas_used,
2344 tx_blob_hashes: tx.blob_versioned_hashes(),
2345 tx_max_priority_fee_per_gas: tx.max_priority_fee().map(U256::from),
2346 tx_max_fee_per_gas: tx.max_fee_per_gas().map(U256::from),
2347 tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas(),
2348 tx_nonce: tx.nonce(),
2349 block_gas_limit: block_header.gas_limit,
2350 difficulty: block_header.difficulty,
2351 is_privileged: matches!(tx, Transaction::PrivilegedL2Transaction(_)),
2352 fee_token: tx.fee_token(),
2353 disable_balance_check: false,
2354 is_system_call: false,
2355 };
2356
2357 Ok(env)
2358 }
2359
2360 pub fn execute_tx(
2361 tx: &Transaction,
2363 tx_sender: Address,
2365 block_header: &BlockHeader,
2367 db: &mut GeneralizedDatabase,
2368 vm_type: VMType,
2369 crypto: &dyn Crypto,
2370 ) -> Result<ExecutionReport, EvmError> {
2371 let env = Self::setup_env(tx, tx_sender, block_header, db, vm_type)?;
2372 let mut vm = VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)?;
2373
2374 vm.execute().map_err(VMError::into)
2375 }
2376
2377 #[allow(clippy::too_many_arguments)]
2380 fn execute_tx_in_block(
2381 tx: &Transaction,
2383 tx_sender: Address,
2385 block_header: &BlockHeader,
2387 db: &mut GeneralizedDatabase,
2388 vm_type: VMType,
2389 base_blob_fee_per_gas: U256,
2390 stack_pool: &mut Vec<Stack>,
2391 memory_pool: &mut Vec<Memory>,
2392 disable_balance_check: bool,
2393 crypto: &dyn Crypto,
2394 config: EVMConfig,
2395 chain_id: u64,
2396 ) -> Result<ExecutionReport, EvmError> {
2397 let mut env = Self::setup_env_with_config(
2398 tx,
2399 tx_sender,
2400 block_header,
2401 config,
2402 chain_id,
2403 vm_type,
2404 base_blob_fee_per_gas,
2405 )?;
2406 env.disable_balance_check = disable_balance_check;
2407 let mut vm = VM::new_pooled(
2411 env,
2412 db,
2413 tx,
2414 LevmCallTracer::disabled(),
2415 vm_type,
2416 crypto,
2417 stack_pool,
2418 memory_pool,
2419 )?;
2420 let result = vm.execute().map_err(VMError::into);
2421 vm.reclaim_into(stack_pool, memory_pool);
2423 result
2424 }
2425
2426 pub fn undo_last_tx(db: &mut GeneralizedDatabase) -> Result<(), EvmError> {
2427 db.undo_last_transaction()?;
2428 Ok(())
2429 }
2430
2431 pub fn simulate_tx_from_generic(
2432 tx: &GenericTransaction,
2434 block_header: &BlockHeader,
2436 db: &mut GeneralizedDatabase,
2437 vm_type: VMType,
2438 crypto: &dyn Crypto,
2439 ) -> Result<ExecutionResult, EvmError> {
2440 let mut env = env_from_generic(tx, block_header, db, vm_type)?;
2441
2442 env.block_gas_limit = i64::MAX as u64; adjust_disabled_base_fee(&mut env);
2445
2446 let converted_tx = generic_tx_to_transaction(tx)?;
2447 let mut vm = vm_from_generic(&converted_tx, env, db, vm_type, crypto)?;
2448
2449 vm.execute()
2450 .map(|value| value.into())
2451 .map_err(VMError::into)
2452 }
2453
2454 pub fn get_state_transitions(
2455 db: &mut GeneralizedDatabase,
2456 ) -> Result<Vec<AccountUpdate>, EvmError> {
2457 Ok(db.get_state_transitions()?)
2458 }
2459
2460 pub fn get_state_transitions_tx(
2461 db: &mut GeneralizedDatabase,
2462 ) -> Result<Vec<AccountUpdate>, EvmError> {
2463 Ok(db.get_state_transitions_tx()?)
2464 }
2465
2466 pub fn process_withdrawals(
2467 db: &mut GeneralizedDatabase,
2468 withdrawals: &[Withdrawal],
2469 ) -> Result<(), EvmError> {
2470 for (address, increment) in withdrawals
2472 .iter()
2473 .filter(|withdrawal| withdrawal.amount > 0)
2474 .map(|w| (w.address, u128::from(w.amount) * u128::from(GWEI_TO_WEI)))
2475 {
2476 let account = db
2477 .get_account_mut(address)
2478 .map_err(|_| EvmError::DB(format!("Withdrawal account {address} not found")))?;
2479
2480 let initial_balance = account.info.balance;
2481 account.info.balance += increment.into();
2482 let new_balance = account.info.balance;
2483
2484 if let Some(recorder) = db.bal_recorder_mut() {
2486 recorder.set_initial_balance(address, initial_balance);
2487 recorder.record_balance_change(address, new_balance);
2488 }
2489 }
2490 Ok(())
2491 }
2492
2493 pub fn beacon_root_contract_call(
2495 block_header: &BlockHeader,
2496 db: &mut GeneralizedDatabase,
2497 vm_type: VMType,
2498 crypto: &dyn Crypto,
2499 ) -> Result<(), EvmError> {
2500 if let VMType::L2(_) = vm_type {
2501 return Err(EvmError::InvalidEVM(
2502 "beacon_root_contract_call should not be called for L2 VM".to_string(),
2503 ));
2504 }
2505
2506 let beacon_root = block_header.parent_beacon_block_root.ok_or_else(|| {
2507 EvmError::Header("parent_beacon_block_root field is missing".to_string())
2508 })?;
2509
2510 generic_system_contract_levm(
2511 block_header,
2512 Bytes::copy_from_slice(beacon_root.as_bytes()),
2513 db,
2514 BEACON_ROOTS_ADDRESS.address,
2515 SYSTEM_ADDRESS,
2516 vm_type,
2517 crypto,
2518 )?;
2519 Ok(())
2520 }
2521
2522 pub fn process_block_hash_history(
2523 block_header: &BlockHeader,
2524 db: &mut GeneralizedDatabase,
2525 vm_type: VMType,
2526 crypto: &dyn Crypto,
2527 ) -> Result<(), EvmError> {
2528 if let VMType::L2(_) = vm_type {
2529 return Err(EvmError::InvalidEVM(
2530 "process_block_hash_history should not be called for L2 VM".to_string(),
2531 ));
2532 }
2533
2534 generic_system_contract_levm(
2535 block_header,
2536 Bytes::copy_from_slice(block_header.parent_hash.as_bytes()),
2537 db,
2538 HISTORY_STORAGE_ADDRESS.address,
2539 SYSTEM_ADDRESS,
2540 vm_type,
2541 crypto,
2542 )?;
2543 Ok(())
2544 }
2545 pub(crate) fn read_withdrawal_requests(
2546 block_header: &BlockHeader,
2547 db: &mut GeneralizedDatabase,
2548 vm_type: VMType,
2549 crypto: &dyn Crypto,
2550 ) -> Result<ExecutionReport, EvmError> {
2551 if let VMType::L2(_) = vm_type {
2552 return Err(EvmError::InvalidEVM(
2553 "read_withdrawal_requests should not be called for L2 VM".to_string(),
2554 ));
2555 }
2556
2557 let report = generic_system_contract_levm(
2558 block_header,
2559 Bytes::new(),
2560 db,
2561 WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS.address,
2562 SYSTEM_ADDRESS,
2563 vm_type,
2564 crypto,
2565 )?;
2566
2567 match report.result {
2568 TxResult::Success => Ok(report),
2569 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
2571 "REVERT when reading withdrawal requests with error: {vm_error:?}. According to EIP-7002, the revert of this system call invalidates the block.",
2572 ))),
2573 }
2574 }
2575
2576 pub(crate) fn dequeue_consolidation_requests(
2577 block_header: &BlockHeader,
2578 db: &mut GeneralizedDatabase,
2579 vm_type: VMType,
2580 crypto: &dyn Crypto,
2581 ) -> Result<ExecutionReport, EvmError> {
2582 if let VMType::L2(_) = vm_type {
2583 return Err(EvmError::InvalidEVM(
2584 "dequeue_consolidation_requests should not be called for L2 VM".to_string(),
2585 ));
2586 }
2587
2588 let report = generic_system_contract_levm(
2589 block_header,
2590 Bytes::new(),
2591 db,
2592 CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS.address,
2593 SYSTEM_ADDRESS,
2594 vm_type,
2595 crypto,
2596 )?;
2597
2598 match report.result {
2599 TxResult::Success => Ok(report),
2600 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
2602 "REVERT when dequeuing consolidation requests with error: {vm_error:?}. According to EIP-7251, the revert of this system call invalidates the block.",
2603 ))),
2604 }
2605 }
2606
2607 pub(crate) fn read_builder_deposit_requests(
2608 block_header: &BlockHeader,
2609 db: &mut GeneralizedDatabase,
2610 vm_type: VMType,
2611 crypto: &dyn Crypto,
2612 ) -> Result<ExecutionReport, EvmError> {
2613 if let VMType::L2(_) = vm_type {
2614 return Err(EvmError::InvalidEVM(
2615 "read_builder_deposit_requests should not be called for L2 VM".to_string(),
2616 ));
2617 }
2618
2619 let report = generic_system_contract_levm(
2620 block_header,
2621 Bytes::new(),
2622 db,
2623 BUILDER_DEPOSIT_CONTRACT_ADDRESS.address,
2624 SYSTEM_ADDRESS,
2625 vm_type,
2626 crypto,
2627 )?;
2628
2629 match report.result {
2630 TxResult::Success => Ok(report),
2631 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
2633 "REVERT when reading builder deposit requests with error: {vm_error:?}. According to EIP-8282, the revert of this system call invalidates the block.",
2634 ))),
2635 }
2636 }
2637
2638 pub(crate) fn dequeue_builder_exit_requests(
2639 block_header: &BlockHeader,
2640 db: &mut GeneralizedDatabase,
2641 vm_type: VMType,
2642 crypto: &dyn Crypto,
2643 ) -> Result<ExecutionReport, EvmError> {
2644 if let VMType::L2(_) = vm_type {
2645 return Err(EvmError::InvalidEVM(
2646 "dequeue_builder_exit_requests should not be called for L2 VM".to_string(),
2647 ));
2648 }
2649
2650 let report = generic_system_contract_levm(
2651 block_header,
2652 Bytes::new(),
2653 db,
2654 BUILDER_EXIT_CONTRACT_ADDRESS.address,
2655 SYSTEM_ADDRESS,
2656 vm_type,
2657 crypto,
2658 )?;
2659
2660 match report.result {
2661 TxResult::Success => Ok(report),
2662 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
2664 "REVERT when dequeuing builder exit requests with error: {vm_error:?}. According to EIP-8282, the revert of this system call invalidates the block.",
2665 ))),
2666 }
2667 }
2668
2669 pub fn create_access_list(
2670 mut tx: GenericTransaction,
2671 header: &BlockHeader,
2672 db: &mut GeneralizedDatabase,
2673 vm_type: VMType,
2674 crypto: &dyn Crypto,
2675 ) -> Result<(ExecutionResult, AccessList), VMError> {
2676 let mut env = env_from_generic(&tx, header, db, vm_type)?;
2677
2678 adjust_disabled_base_fee(&mut env);
2679
2680 let converted_tx = generic_tx_to_transaction(&tx)?;
2681 let mut vm = vm_from_generic(&converted_tx, env.clone(), db, vm_type, crypto)?;
2682
2683 vm.stateless_execute()?;
2684
2685 tx.access_list = vm.substate.make_access_list();
2687 let converted_tx = generic_tx_to_transaction(&tx)?;
2688 let mut vm = vm_from_generic(&converted_tx, env, db, vm_type, crypto)?;
2689
2690 let report = vm.stateless_execute()?;
2691
2692 Ok((
2693 report.into(),
2694 tx.access_list
2695 .into_iter()
2696 .map(|x| (x.address, x.storage_keys))
2697 .collect(),
2698 ))
2699 }
2700
2701 pub fn prepare_block(
2702 block: &Block,
2703 db: &mut GeneralizedDatabase,
2704 vm_type: VMType,
2705 crypto: &dyn Crypto,
2706 ) -> Result<(), EvmError> {
2707 let chain_config = db.store.get_chain_config()?;
2708 let block_header = &block.header;
2709 let fork = chain_config.fork(block_header.timestamp);
2710
2711 if let VMType::L2(_) = vm_type {
2713 return Ok(());
2714 }
2715
2716 if block_header.parent_beacon_block_root.is_some() && fork >= Fork::Cancun {
2717 Self::beacon_root_contract_call(block_header, db, vm_type, crypto)?;
2718 }
2719
2720 if fork >= Fork::Prague {
2721 Self::process_block_hash_history(block_header, db, vm_type, crypto)?;
2723 }
2724 Ok(())
2725 }
2726}
2727
2728pub fn generic_system_contract_levm(
2729 block_header: &BlockHeader,
2730 calldata: Bytes,
2731 db: &mut GeneralizedDatabase,
2732 contract_address: Address,
2733 system_address: Address,
2734 vm_type: VMType,
2735 crypto: &dyn Crypto,
2736) -> Result<ExecutionReport, EvmError> {
2737 let chain_config = db.store.get_chain_config()?;
2738 let config = EVMConfig::new_from_chain_config(&chain_config, block_header);
2739 let env = Environment {
2740 origin: system_address,
2741 gas_limit: SYS_CALL_GAS_LIMIT,
2752 block_number: block_header.number,
2753 coinbase: block_header.coinbase,
2754 timestamp: block_header.timestamp,
2755 prev_randao: Some(block_header.prev_randao),
2756 base_fee_per_gas: U256::zero(),
2757 gas_price: U256::zero(),
2758 block_excess_blob_gas: block_header.excess_blob_gas,
2759 block_blob_gas_used: block_header.blob_gas_used,
2760 block_gas_limit: block_header.gas_limit,
2765 is_system_call: true,
2766 config,
2767 ..Default::default()
2768 };
2769
2770 debug_assert!(
2776 env.gas_price.is_zero() && env.base_fee_per_gas.is_zero(),
2777 "system calls must run with a zero gas price"
2778 );
2779
2780 if PRAGUE_SYSTEM_CONTRACTS
2790 .iter()
2791 .chain(AMSTERDAM_REQUEST_PREDEPLOYS.iter())
2792 .any(|contract| contract.address == contract_address)
2793 && db.get_account_code(contract_address)?.is_empty()
2794 {
2795 return Err(EvmError::SystemContractCallFailed(format!(
2796 "System contract: {contract_address} has no code after deployment"
2797 )));
2798 };
2799
2800 let tx = &Transaction::EIP1559Transaction(EIP1559Transaction {
2801 to: TxKind::Call(contract_address),
2802 value: U256::zero(),
2803 data: calldata,
2804 ..Default::default()
2805 });
2806 if let Some(recorder) = db.bal_recorder.as_mut() {
2808 recorder.enter_system_call();
2809 }
2810
2811 let result = VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)
2812 .and_then(|mut vm| vm.execute())
2813 .map_err(EvmError::from);
2814
2815 if let Some(recorder) = db.bal_recorder.as_mut() {
2817 recorder.exit_system_call();
2818 }
2819
2820 result
2821}
2822
2823#[allow(unreachable_code)]
2824#[allow(unused_variables)]
2825pub fn extract_all_requests_levm(
2826 receipts: &[Receipt],
2827 db: &mut GeneralizedDatabase,
2828 header: &BlockHeader,
2829 vm_type: VMType,
2830 crypto: &dyn Crypto,
2831) -> Result<Vec<Requests>, EvmError> {
2832 if let VMType::L2(_) = vm_type {
2833 return Err(EvmError::InvalidEVM(
2834 "extract_all_requests_levm should not be called for L2 VM".to_string(),
2835 ));
2836 }
2837
2838 let chain_config = db.store.get_chain_config()?;
2839 let fork = chain_config.fork(header.timestamp);
2840
2841 if fork < Fork::Prague {
2842 return Ok(Default::default());
2843 }
2844
2845 let withdrawals_data: Vec<u8> = LEVM::read_withdrawal_requests(header, db, vm_type, crypto)?
2846 .output
2847 .into();
2848 let consolidation_data: Vec<u8> =
2849 LEVM::dequeue_consolidation_requests(header, db, vm_type, crypto)?
2850 .output
2851 .into();
2852
2853 let deposits = Requests::from_deposit_receipts(chain_config.deposit_contract_address, receipts)
2854 .ok_or(EvmError::InvalidDepositRequest)?;
2855 let withdrawals = Requests::from_withdrawals_data(withdrawals_data);
2856 let consolidation = Requests::from_consolidation_data(consolidation_data);
2857
2858 let mut requests = vec![deposits, withdrawals, consolidation];
2859
2860 if fork >= Fork::Amsterdam {
2864 requests.reserve_exact(2);
2866 let builder_deposit_data: Vec<u8> =
2867 LEVM::read_builder_deposit_requests(header, db, vm_type, crypto)?
2868 .output
2869 .into();
2870 let builder_exit_data: Vec<u8> =
2871 LEVM::dequeue_builder_exit_requests(header, db, vm_type, crypto)?
2872 .output
2873 .into();
2874 requests.push(Requests::from_builder_deposit_data(builder_deposit_data));
2875 requests.push(Requests::from_builder_exit_data(builder_exit_data));
2876 }
2877
2878 Ok(requests)
2879}
2880
2881pub fn calculate_gas_price_for_generic(tx: &GenericTransaction, basefee: u64) -> U256 {
2884 if !tx.gas_price.is_zero() {
2885 tx.gas_price
2887 } else {
2888 min(
2890 tx.max_priority_fee_per_gas.unwrap_or(0) + basefee,
2891 tx.max_fee_per_gas.unwrap_or(0),
2892 )
2893 .into()
2894 }
2895}
2896
2897pub fn calculate_gas_price_for_tx(
2898 tx: &Transaction,
2899 mut fee_per_gas: u64,
2900 vm_type: &VMType,
2901) -> Result<U256, VMError> {
2902 let Some(max_priority_fee) = tx.max_priority_fee() else {
2903 return Ok(tx.gas_price());
2905 };
2906
2907 let max_fee_per_gas = tx.max_fee_per_gas().ok_or(VMError::TxValidation(
2908 TxValidationError::InsufficientMaxFeePerGas,
2909 ))?;
2910
2911 if let VMType::L2(fee_config) = vm_type
2912 && let Some(operator_fee_config) = &fee_config.operator_fee_config
2913 {
2914 fee_per_gas += operator_fee_config.operator_fee_per_gas;
2915 }
2916
2917 if fee_per_gas > max_fee_per_gas {
2918 return Err(VMError::TxValidation(
2919 TxValidationError::InsufficientMaxFeePerGas,
2920 ));
2921 }
2922
2923 Ok(min(max_priority_fee + fee_per_gas, max_fee_per_gas).into())
2924}
2925
2926fn adjust_disabled_base_fee(env: &mut Environment) {
2930 if env.gas_price == U256::zero() {
2931 env.base_fee_per_gas = U256::zero();
2932 }
2933 if env
2934 .tx_max_fee_per_blob_gas
2935 .is_some_and(|v| v == U256::zero())
2936 {
2937 env.block_excess_blob_gas = None;
2938 }
2939}
2940
2941fn adjust_disabled_l2_fees(env: &Environment, vm_type: VMType) -> VMType {
2943 if env.gas_price == U256::zero()
2944 && let VMType::L2(fee_config) = vm_type
2945 {
2946 return VMType::L2(FeeConfig {
2948 operator_fee_config: None,
2949 l1_fee_config: None,
2950 ..fee_config
2951 });
2952 }
2953 vm_type
2954}
2955
2956fn env_from_generic(
2957 tx: &GenericTransaction,
2958 header: &BlockHeader,
2959 db: &GeneralizedDatabase,
2960 vm_type: VMType,
2961) -> Result<Environment, VMError> {
2962 let chain_config = db.store.get_chain_config()?;
2963 let gas_price =
2964 calculate_gas_price_for_generic(tx, header.base_fee_per_gas.unwrap_or(INITIAL_BASE_FEE));
2965 let block_excess_blob_gas = header.excess_blob_gas;
2966 let config = EVMConfig::new_from_chain_config(&chain_config, header);
2967
2968 let slot_number = if let VMType::L2(_) = vm_type {
2971 U256::zero()
2972 } else if config.fork >= Fork::Amsterdam {
2973 header
2974 .slot_number
2975 .map(U256::from)
2976 .ok_or(VMError::Internal(InternalError::Custom(
2977 "slot_number must be present in Amsterdam+ blocks".to_string(),
2978 )))?
2979 } else {
2980 header.slot_number.map(U256::from).unwrap_or(U256::zero())
2983 };
2984
2985 Ok(Environment {
2986 origin: tx.from.0.into(),
2987 gas_limit: tx
2988 .gas
2989 .unwrap_or(get_max_allowed_gas_limit(header.gas_limit, config.fork)), config,
2991 block_number: header.number,
2992 coinbase: header.coinbase,
2993 timestamp: header.timestamp,
2994 prev_randao: Some(header.prev_randao),
2995 slot_number,
2996 chain_id: chain_config.chain_id.into(),
2997 base_fee_per_gas: header.base_fee_per_gas.unwrap_or_default().into(),
2998 base_blob_fee_per_gas: get_base_fee_per_blob_gas(block_excess_blob_gas, &config)?,
2999 gas_price,
3000 block_excess_blob_gas,
3001 block_blob_gas_used: header.blob_gas_used,
3002 tx_blob_hashes: tx.blob_versioned_hashes.clone(),
3003 tx_max_priority_fee_per_gas: tx.max_priority_fee_per_gas.map(U256::from),
3004 tx_max_fee_per_gas: tx.max_fee_per_gas.map(U256::from),
3005 tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
3006 tx_nonce: tx.nonce.unwrap_or_default(),
3007 block_gas_limit: header.gas_limit,
3008 difficulty: header.difficulty,
3009 is_privileged: false,
3010 fee_token: tx.fee_token,
3011 disable_balance_check: false,
3012 is_system_call: false,
3013 })
3014}
3015
3016fn generic_tx_to_transaction(tx: &GenericTransaction) -> Result<Transaction, VMError> {
3021 Ok(match &tx.authorization_list {
3022 Some(authorization_list) => Transaction::EIP7702Transaction(EIP7702Transaction {
3023 to: match tx.to {
3024 TxKind::Call(to) => to,
3025 TxKind::Create => {
3026 return Err(InternalError::msg("Generic Tx cannot be create type").into());
3027 }
3028 },
3029 value: tx.value,
3030 data: tx.input.clone(),
3031 access_list: tx
3032 .access_list
3033 .iter()
3034 .map(|list| (list.address, list.storage_keys.clone()))
3035 .collect(),
3036 authorization_list: authorization_list
3037 .iter()
3038 .map(|auth| Into::<AuthorizationTuple>::into(auth.clone()))
3039 .collect(),
3040 ..Default::default()
3041 }),
3042 None => Transaction::EIP1559Transaction(EIP1559Transaction {
3043 to: tx.to.clone(),
3044 value: tx.value,
3045 data: tx.input.clone(),
3046 access_list: tx
3047 .access_list
3048 .iter()
3049 .map(|list| (list.address, list.storage_keys.clone()))
3050 .collect(),
3051 ..Default::default()
3052 }),
3053 })
3054}
3055
3056fn vm_from_generic<'a>(
3057 tx: &'a Transaction,
3058 env: Environment,
3059 db: &'a mut GeneralizedDatabase,
3060 vm_type: VMType,
3061 crypto: &'a dyn Crypto,
3062) -> Result<VM<'a>, VMError> {
3063 let vm_type = adjust_disabled_l2_fees(&env, vm_type);
3064 VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)
3065}
3066
3067pub fn get_max_allowed_gas_limit(block_gas_limit: u64, fork: Fork) -> u64 {
3068 if fork >= Fork::Osaka && fork < Fork::Amsterdam {
3076 POST_OSAKA_GAS_LIMIT_CAP
3077 } else {
3078 block_gas_limit
3079 }
3080}
3081
3082#[allow(dead_code)]
3090fn describe_balance_diff(expected: U256, actual: U256) -> String {
3091 let (sign, mag) = if expected >= actual {
3092 ("+", expected - actual)
3093 } else {
3094 ("-", actual - expected)
3095 };
3096 let Ok(mag_u128) = u128::try_from(mag) else {
3097 return format!("{sign}{mag}");
3098 };
3099 if mag_u128 == 0 {
3100 return "0".to_string();
3101 }
3102 let cpsb: u128 = 1530;
3103 let consts = [
3105 ("NEW_ACCOUNT", 120u128),
3106 ("STORAGE_SET", 64),
3107 ("AUTH_BASE", 23),
3108 ("AUTH_TOTAL", 143),
3109 ];
3110 for &gp in &[10u128, 1, 7, 100, 1000, 1_000_000_000] {
3112 if !mag_u128.is_multiple_of(gp) {
3113 continue;
3114 }
3115 let gas = mag_u128 / gp;
3116 if !gas.is_multiple_of(cpsb) {
3117 continue;
3118 }
3119 let bytes = gas / cpsb;
3120 for (name, c) in consts {
3121 if bytes.is_multiple_of(c) {
3122 let n = bytes / c;
3123 return format!(
3124 "{sign}{mag_u128} wei (= {gas} gas at {gp} wei/gas = {n}× {name}_state_gas)"
3125 );
3126 }
3127 }
3128 }
3129 format!("{sign}{mag_u128} wei")
3130}
3131
3132#[cfg(test)]
3133mod bal_tests {
3134 use super::*;
3135 use ethrex_common::H256;
3136 use ethrex_common::types::AccountState;
3137 use ethrex_common::types::block_access_list::{
3138 AccountChanges, BalanceChange, NonceChange, SlotChange, StorageChange,
3139 };
3140 use ethrex_levm::errors::DatabaseError;
3141
3142 fn addr(byte: u8) -> Address {
3143 let mut a = Address::zero();
3144 a.0[19] = byte;
3145 a
3146 }
3147
3148 struct MockStore {
3150 accounts: FxHashMap<Address, AccountState>,
3151 }
3152
3153 impl MockStore {
3154 fn new() -> Self {
3155 Self {
3156 accounts: FxHashMap::default(),
3157 }
3158 }
3159
3160 fn with_account(mut self, address: Address, state: AccountState) -> Self {
3161 self.accounts.insert(address, state);
3162 self
3163 }
3164 }
3165
3166 impl Database for MockStore {
3167 fn get_account_state(&self, address: Address) -> Result<AccountState, DatabaseError> {
3168 Ok(self.accounts.get(&address).copied().unwrap_or_default())
3169 }
3170 fn get_storage_value(&self, _: Address, _: H256) -> Result<U256, DatabaseError> {
3171 Ok(U256::zero())
3172 }
3173 fn get_block_hash(&self, _: u64) -> Result<H256, DatabaseError> {
3174 Ok(H256::zero())
3175 }
3176 fn get_chain_config(&self) -> Result<ethrex_common::types::ChainConfig, DatabaseError> {
3177 Err(DatabaseError::Custom("not implemented".into()))
3178 }
3179 fn get_account_code(&self, _: H256) -> Result<ethrex_common::types::Code, DatabaseError> {
3180 Ok(ethrex_common::types::Code::from_bytecode(
3181 Bytes::new(),
3182 ðrex_crypto::NativeCrypto,
3183 ))
3184 }
3185 fn get_code_metadata(
3186 &self,
3187 _: H256,
3188 ) -> Result<ethrex_common::types::CodeMetadata, DatabaseError> {
3189 Ok(ethrex_common::types::CodeMetadata { length: 0 })
3190 }
3191 }
3192
3193 #[test]
3194 fn test_bal_to_account_updates_basic() {
3195 let address = addr(1);
3197 let store = MockStore::new().with_account(
3198 address,
3199 AccountState {
3200 balance: U256::from(100),
3201 nonce: 5,
3202 code_hash: *EMPTY_KECCAK_HASH,
3203 storage_root: H256::zero(),
3204 },
3205 );
3206
3207 let bal = BlockAccessList::from_accounts(vec![
3208 AccountChanges::new(address)
3209 .with_balance_changes(vec![
3210 BalanceChange::new(1, U256::from(90)),
3211 BalanceChange::new(2, U256::from(80)),
3212 ])
3213 .with_nonce_changes(vec![NonceChange::new(1, 6)])
3214 .with_storage_changes(vec![SlotChange::with_changes(
3215 U256::from(42),
3216 vec![StorageChange::new(1, U256::from(999))],
3217 )]),
3218 ]);
3219
3220 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3221 assert_eq!(updates.len(), 1);
3222 let u = &updates[0];
3223 assert_eq!(u.address, address);
3224 assert!(!u.removed);
3225 let info = u.info.as_ref().unwrap();
3226 assert_eq!(info.balance, U256::from(80));
3228 assert_eq!(info.nonce, 6);
3229 assert_eq!(info.code_hash, *EMPTY_KECCAK_HASH);
3230 let key = ethrex_common::utils::u256_to_h256(U256::from(42));
3232 assert_eq!(*u.added_storage.get(&key).unwrap(), U256::from(999));
3233 }
3234
3235 #[test]
3236 fn test_bal_to_account_updates_highest_index_wins() {
3237 let address = addr(2);
3239 let store = MockStore::new().with_account(
3240 address,
3241 AccountState {
3242 balance: U256::from(1000),
3243 nonce: 0,
3244 code_hash: *EMPTY_KECCAK_HASH,
3245 storage_root: H256::zero(),
3246 },
3247 );
3248
3249 let bal = BlockAccessList::from_accounts(vec![
3250 AccountChanges::new(address).with_balance_changes(vec![
3251 BalanceChange::new(1, U256::from(900)),
3252 BalanceChange::new(2, U256::from(800)),
3253 BalanceChange::new(3, U256::from(700)),
3254 ]),
3255 ]);
3256
3257 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3258 assert_eq!(updates.len(), 1);
3259 assert_eq!(updates[0].info.as_ref().unwrap().balance, U256::from(700));
3260 }
3261
3262 #[test]
3263 fn test_bal_to_account_updates_reads_only_skipped() {
3264 let address = addr(3);
3266 let store = MockStore::new();
3267
3268 let bal = BlockAccessList::from_accounts(vec![
3269 AccountChanges::new(address).with_storage_reads(vec![U256::from(1)]),
3270 ]);
3271
3272 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3273 assert!(updates.is_empty());
3274 }
3275
3276 #[test]
3277 fn test_bal_to_account_updates_removal() {
3278 let address = addr(4);
3280 let store = MockStore::new().with_account(
3281 address,
3282 AccountState {
3283 balance: U256::from(50),
3284 nonce: 1,
3285 code_hash: *EMPTY_KECCAK_HASH,
3286 storage_root: H256::zero(),
3287 },
3288 );
3289
3290 let bal = BlockAccessList::from_accounts(vec![
3291 AccountChanges::new(address)
3292 .with_balance_changes(vec![BalanceChange::new(1, U256::zero())])
3293 .with_nonce_changes(vec![NonceChange::new(1, 0)]),
3294 ]);
3295
3296 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3297 assert_eq!(updates.len(), 1);
3298 assert!(updates[0].removed);
3299 }
3300
3301 #[test]
3302 fn test_bal_to_account_updates_storage_zero() {
3303 let address = addr(5);
3305 let store = MockStore::new();
3306
3307 let bal = BlockAccessList::from_accounts(vec![
3308 AccountChanges::new(address).with_storage_changes(vec![SlotChange::with_changes(
3309 U256::from(7),
3310 vec![StorageChange::new(1, U256::zero())],
3311 )]),
3312 ]);
3313
3314 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3315 assert_eq!(updates.len(), 1);
3316 let key = ethrex_common::utils::u256_to_h256(U256::from(7));
3317 assert_eq!(*updates[0].added_storage.get(&key).unwrap(), U256::zero());
3318 }
3319
3320 #[test]
3321 fn test_bal_to_account_updates_code_deployment() {
3322 let address = addr(6);
3324 let store = MockStore::new();
3325 let code = Bytes::from(vec![0x60, 0x00, 0x60, 0x00, 0xf3]); let expected_hash =
3327 ethrex_common::types::Code::from_bytecode(code.clone(), ðrex_crypto::NativeCrypto)
3328 .hash;
3329
3330 let bal = BlockAccessList::from_accounts(vec![
3331 AccountChanges::new(address)
3332 .with_code_changes(vec![
3333 ethrex_common::types::block_access_list::CodeChange::new(1, code.clone()),
3334 ])
3335 .with_nonce_changes(vec![NonceChange::new(1, 1)]),
3336 ]);
3337
3338 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3339 assert_eq!(updates.len(), 1);
3340 let u = &updates[0];
3341 assert_eq!(u.info.as_ref().unwrap().code_hash, expected_hash);
3342 assert_eq!(u.code.as_ref().unwrap().code(), &code[..]);
3343 }
3344}
3345
3346#[cfg(test)]
3347mod system_call_coinbase_tests {
3348 use super::*;
3354 use ethrex_common::types::{AccountState, AccountUpdate, ChainConfig, Code, CodeMetadata};
3355 use ethrex_crypto::NativeCrypto;
3356 use ethrex_levm::db::Database;
3357 use ethrex_levm::errors::DatabaseError;
3358 use std::sync::Arc;
3359
3360 const HISTORY_RUNTIME_CODE: &str = concat!(
3362 "3373fffffffffffffffffffffffffffffffffffffffe1460465760203603604257",
3363 "5f35600143038111604257611fff81430311604257611fff900654",
3364 "5f5260205ff35b5f5ffd5b5f35611fff60014303065500",
3365 );
3366
3367 struct Store {
3368 chain_config: ChainConfig,
3369 history_code: Code,
3370 }
3371
3372 impl Database for Store {
3373 fn get_account_state(&self, address: Address) -> Result<AccountState, DatabaseError> {
3374 if address == HISTORY_STORAGE_ADDRESS.address {
3375 return Ok(AccountState {
3376 nonce: 1,
3377 code_hash: self.history_code.hash,
3378 ..Default::default()
3379 });
3380 }
3381 Ok(AccountState::default())
3382 }
3383 fn get_storage_value(&self, _: Address, _: H256) -> Result<U256, DatabaseError> {
3384 Ok(U256::zero())
3385 }
3386 fn get_block_hash(&self, _: u64) -> Result<H256, DatabaseError> {
3387 Ok(H256::zero())
3388 }
3389 fn get_chain_config(&self) -> Result<ChainConfig, DatabaseError> {
3390 Ok(self.chain_config)
3391 }
3392 fn get_account_code(&self, code_hash: H256) -> Result<Code, DatabaseError> {
3393 if code_hash == self.history_code.hash {
3394 return Ok(self.history_code.clone());
3395 }
3396 Ok(Code::default())
3397 }
3398 fn get_code_metadata(&self, code_hash: H256) -> Result<CodeMetadata, DatabaseError> {
3399 let length = if code_hash == self.history_code.hash {
3400 self.history_code.len() as u64
3401 } else {
3402 0
3403 };
3404 Ok(CodeMetadata { length })
3405 }
3406 }
3407
3408 fn history_code() -> Code {
3409 let bytes = hex::decode(HISTORY_RUNTIME_CODE).expect("history runtime code is valid hex");
3410 Code::from_bytecode(Bytes::from(bytes), &NativeCrypto)
3411 }
3412
3413 fn prague_db() -> GeneralizedDatabase {
3414 GeneralizedDatabase::new(Arc::new(Store {
3415 chain_config: ChainConfig {
3416 prague_time: Some(0),
3417 ..Default::default()
3418 },
3419 history_code: history_code(),
3420 }))
3421 }
3422
3423 fn parent_hash_value(parent_hash: H256) -> U256 {
3424 U256::from_big_endian(parent_hash.as_bytes())
3425 }
3426
3427 fn history_slot(block_number: u64) -> H256 {
3428 H256::from_low_u64_be((block_number - 1) % 8191)
3429 }
3430
3431 fn run_history_update(coinbase: Address) -> (Option<U256>, Vec<AccountUpdate>, H256) {
3435 let mut db = prague_db();
3436 let parent_hash = H256::from_low_u64_be(0x2935);
3437 let block_number = 42;
3438 let header = BlockHeader {
3439 parent_hash,
3440 coinbase,
3441 number: block_number,
3442 timestamp: 1,
3443 ..Default::default()
3444 };
3445
3446 LEVM::process_block_hash_history(&header, &mut db, VMType::L1, &NativeCrypto)
3447 .expect("history system call executes");
3448
3449 let slot = history_slot(block_number);
3450 let stored_value = db
3451 .current_accounts_state
3452 .get(&HISTORY_STORAGE_ADDRESS.address)
3453 .and_then(|account| account.storage.get(&slot).copied());
3454 let updates =
3455 LEVM::get_state_transitions(&mut db).expect("state transitions are generated");
3456
3457 (stored_value, updates, parent_hash)
3458 }
3459
3460 fn assert_history_write_emitted(coinbase: Address) {
3461 let (stored_value, updates, parent_hash) = run_history_update(coinbase);
3462 let slot = history_slot(42);
3463 assert_eq!(
3464 stored_value,
3465 Some(parent_hash_value(parent_hash)),
3466 "history storage must hold the parent hash after the system call"
3467 );
3468 assert!(
3469 updates.iter().any(|update| {
3470 update.address == HISTORY_STORAGE_ADDRESS.address
3471 && update.added_storage.get(&slot) == Some(&parent_hash_value(parent_hash))
3472 }),
3473 "the history-contract storage write must be emitted as a state update"
3474 );
3475 }
3476
3477 #[test]
3478 fn ordinary_coinbase_preserves_history_storage_write() {
3479 assert_history_write_emitted(Address::from_low_u64_be(0xbeef));
3480 }
3481
3482 #[test]
3485 fn history_address_coinbase_preserves_history_storage_write() {
3486 assert_history_write_emitted(HISTORY_STORAGE_ADDRESS.address);
3487 }
3488}