1pub mod db;
2mod tracing;
3
4use super::{BlockExecutionResult, TxGasBreakdown};
5use crate::system_contracts::{
6 BEACON_ROOTS_ADDRESS, CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, HISTORY_STORAGE_ADDRESS,
7 PRAGUE_SYSTEM_CONTRACTS, SYSTEM_ADDRESS, WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
8};
9use crate::{EvmError, ExecutionResult};
10use bytes::Bytes;
11#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
12use ethrex_common::H256;
13#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
14use ethrex_common::constants::EMPTY_KECCAK_HASH;
15#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
16use ethrex_common::types::Code;
17#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
18use ethrex_common::types::TxType;
19use ethrex_common::types::block_access_list::BlockAccessList;
20#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
21use ethrex_common::types::block_access_list::{
22 BalAddressIndex, find_exact_change_balance, find_exact_change_code, find_exact_change_nonce,
23 find_exact_change_storage, has_exact_change_balance, has_exact_change_code,
24 has_exact_change_nonce, has_exact_change_storage,
25};
26use ethrex_common::types::fee_config::FeeConfig;
27use ethrex_common::types::{AuthorizationTuple, EIP7702Transaction};
28#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
29use ethrex_common::utils::u256_from_big_endian_const;
30use ethrex_common::{
31 Address, U256,
32 types::{
33 AccessList, AccountUpdate, Block, BlockHeader, EIP1559Transaction, Fork, GWEI_TO_WEI,
34 GenericTransaction, INITIAL_BASE_FEE, Receipt, Transaction, TxKind, Withdrawal,
35 requests::Requests,
36 },
37};
38#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
39use ethrex_common::{BigEndianHash, validate_block_access_list_size, validate_header_bal_indices};
40use ethrex_crypto::Crypto;
41use ethrex_levm::EVMConfig;
42#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
43use ethrex_levm::account::{AccountStatus, LevmAccount};
44use ethrex_levm::call_frame::Stack;
45use ethrex_levm::constants::{
46 POST_OSAKA_GAS_LIMIT_CAP, STACK_LIMIT, SYS_CALL_GAS_LIMIT, TX_BASE_COST,
47 TX_MAX_GAS_LIMIT_AMSTERDAM,
48};
49use ethrex_levm::db::gen_db::GeneralizedDatabase;
50#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
51use ethrex_levm::db::gen_db::{
52 LazyBalCursor, code_from_bal, post_value_at_or_before, seed_one_address_info_from_bal,
53};
54#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
55use ethrex_levm::db::{Database, gen_db::CacheDB};
56use ethrex_levm::errors::{InternalError, TxValidationError};
57use ethrex_levm::memory::Memory;
58#[cfg(feature = "perf_opcode_timings")]
59use ethrex_levm::timings::{OPCODE_TIMINGS, PRECOMPILES_TIMINGS};
60use ethrex_levm::tracing::LevmCallTracer;
61use ethrex_levm::utils::get_base_fee_per_blob_gas;
62use ethrex_levm::utils::intrinsic_gas_dimensions;
63use ethrex_levm::vm::VMType;
64use ethrex_levm::{
65 Environment,
66 errors::{ExecutionReport, TxResult, VMError},
67 vm::VM,
68};
69#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
70use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
71#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
72use rustc_hash::{FxHashMap, FxHashSet};
73use std::cmp::min;
74#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
75use std::sync::Arc;
76#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
77use std::sync::atomic::AtomicBool;
78use std::sync::atomic::{AtomicUsize, Ordering};
79use std::sync::mpsc::Sender;
80
81#[derive(Debug)]
87pub struct LEVM;
88
89fn check_gas_limit(
91 block_gas_used: u64,
92 tx_gas_limit: u64,
93 block_gas_limit: u64,
94) -> Result<(), EvmError> {
95 if tx_gas_limit > block_gas_limit.saturating_sub(block_gas_used) {
96 return Err(EvmError::Transaction(format!(
97 "Gas allowance exceeded: \
98 used {block_gas_used} + tx limit {tx_gas_limit} > block limit {block_gas_limit}"
99 )));
100 }
101 Ok(())
102}
103
104pub fn check_2d_gas_allowance(
119 tx: &Transaction,
120 fork: Fork,
121 block_gas_used_regular: u64,
122 block_gas_used_state: u64,
123 block_gas_limit: u64,
124) -> Result<(), EvmError> {
125 let (intrinsic_regular, intrinsic_state) = intrinsic_gas_dimensions(tx, fork, block_gas_limit)
126 .map_err(|e| EvmError::Transaction(format!("intrinsic gas computation failed: {e}")))?;
127
128 let tx_gas = tx.gas_limit();
129 let regular_available = block_gas_limit.saturating_sub(block_gas_used_regular);
130 let state_available = block_gas_limit.saturating_sub(block_gas_used_state);
131
132 let regular_contrib = tx_gas
137 .saturating_sub(intrinsic_state)
138 .min(TX_MAX_GAS_LIMIT_AMSTERDAM);
139 if regular_contrib > regular_available {
140 return Err(EvmError::Transaction(format!(
141 "Gas allowance exceeded: regular dim worst-case {regular_contrib} > \
142 available {regular_available} (block_gas_used_regular={block_gas_used_regular}, \
143 block_gas_limit={block_gas_limit})"
144 )));
145 }
146
147 let state_contrib = tx_gas.saturating_sub(intrinsic_regular);
149 if state_contrib > state_available {
150 return Err(EvmError::Transaction(format!(
151 "Gas allowance exceeded: state dim worst-case {state_contrib} > \
152 available {state_available} (block_gas_used_state={block_gas_used_state}, \
153 block_gas_limit={block_gas_limit})"
154 )));
155 }
156
157 Ok(())
158}
159
160#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
163#[derive(Debug, thiserror::Error)]
164enum BalValidationError {
165 #[error("{0}")]
166 Mismatch(String),
167 #[error("{0}")]
168 Database(String),
169}
170
171impl LEVM {
172 pub fn execute_block(
177 block: &Block,
178 db: &mut GeneralizedDatabase,
179 vm_type: VMType,
180 crypto: &dyn Crypto,
181 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
182 let chain_config = db.store.get_chain_config()?;
183 let is_amsterdam = chain_config.is_amsterdam_activated(block.header.timestamp);
184
185 debug_assert!(
190 block.body.transactions.len() < u32::MAX as usize,
191 "tx count overflows u32 BlockAccessIndex"
192 );
193
194 if is_amsterdam {
196 db.enable_bal_recording();
197 db.set_bal_index(0);
199 }
200
201 Self::prepare_block(block, db, vm_type, crypto)?;
202
203 let evm_config = EVMConfig::new_from_chain_config(&chain_config, &block.header);
207 let chain_id = chain_config.chain_id;
208 let base_blob_fee_per_gas =
209 get_base_fee_per_blob_gas(block.header.excess_blob_gas, &evm_config)?;
210 let mut shared_stack_pool = Vec::with_capacity(STACK_LIMIT);
212 let mut shared_memory_pool = Vec::with_capacity(1);
213
214 let n_txs = block.body.transactions.len();
215 let mut receipts = Vec::with_capacity(n_txs);
216 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(n_txs);
217 let mut cumulative_gas_used = 0_u64;
219 let mut block_gas_used = 0_u64;
221 let mut block_regular_gas_used = 0_u64;
223 let mut block_state_gas_used = 0_u64;
224 let transactions_with_sender =
225 block
226 .body
227 .get_transactions_with_sender(crypto)
228 .map_err(|error| {
229 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
230 })?;
231
232 for (tx_idx, (tx, tx_sender)) in transactions_with_sender.into_iter().enumerate() {
233 if !is_amsterdam {
240 check_gas_limit(cumulative_gas_used, tx.gas_limit(), block.header.gas_limit)?;
241 }
242
243 if is_amsterdam {
245 check_2d_gas_allowance(
246 tx,
247 Fork::Amsterdam,
248 block_regular_gas_used,
249 block_state_gas_used,
250 block.header.gas_limit,
251 )?;
252 }
253
254 if is_amsterdam {
256 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
257 db.set_bal_index(bal_index);
258
259 if let Some(recorder) = db.bal_recorder_mut() {
261 recorder.record_touched_address(tx_sender);
262 if let TxKind::Call(to) = tx.to() {
263 recorder.record_touched_address(to);
264 }
265 }
266 }
267
268 let report = Self::execute_tx_in_block(
269 tx,
270 tx_sender,
271 &block.header,
272 db,
273 vm_type,
274 base_blob_fee_per_gas,
275 &mut shared_stack_pool,
276 &mut shared_memory_pool,
277 false,
278 crypto,
279 evm_config,
280 chain_id,
281 )?;
282
283 tx_gas_breakdowns.push(TxGasBreakdown::from_report(tx_idx, tx.hash(), &report));
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<&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 = 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 bal,
459 merkleizer.as_ref(),
460 queue_length,
461 system_seed,
462 crypto,
463 &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 Fork::Amsterdam,
653 block_regular_gas_used,
654 block_state_gas_used,
655 block.header.gas_limit,
656 )?;
657 }
658
659 if is_amsterdam {
661 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
662 db.set_bal_index(bal_index);
663
664 if let Some(recorder) = db.bal_recorder_mut() {
666 recorder.record_touched_address(tx_sender);
667 if let TxKind::Call(to) = tx.to() {
668 recorder.record_touched_address(to);
669 }
670 }
671 }
672
673 let report = Self::execute_tx_in_block(
674 tx,
675 tx_sender,
676 &block.header,
677 db,
678 vm_type,
679 base_blob_fee_per_gas,
680 &mut shared_stack_pool,
681 &mut shared_memory_pool,
682 false,
683 crypto,
684 evm_config,
685 chain_id,
686 )?;
687
688 tx_gas_breakdowns.push(TxGasBreakdown::from_report(tx_idx, tx.hash(), &report));
689
690 if queue_length.load(Ordering::Relaxed) == 0 && tx_since_last_flush > 5 {
691 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
692 tx_since_last_flush = 0;
693 } else {
694 tx_since_last_flush += 1;
695 }
696
697 cumulative_gas_used += report.gas_spent;
699
700 let tx_state_gas = report.state_gas_used;
703 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
704 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
705 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
706
707 if is_amsterdam {
708 block_gas_used = block_regular_gas_used.max(block_state_gas_used);
710
711 if block_regular_gas_used > block.header.gas_limit
716 || block_state_gas_used > block.header.gas_limit
717 {
718 return Err(EvmError::Transaction(format!(
719 "Gas allowance exceeded: Block gas used overflow: \
720 block_gas_used {block_gas_used} > block_gas_limit {}",
721 block.header.gas_limit
722 )));
723 }
724 } else {
725 block_gas_used = block_gas_used.saturating_add(report.gas_used);
726 }
727
728 let receipt = Receipt::new(
729 tx.tx_type(),
730 matches!(report.result, TxResult::Success),
731 cumulative_gas_used,
732 report.logs,
733 );
734
735 receipts.push(receipt);
736 }
737
738 if is_amsterdam && block_gas_used > block.header.gas_limit {
742 return Err(EvmError::Transaction(format!(
743 "Gas allowance exceeded: Block gas used overflow: \
744 block_gas_used {block_gas_used} > block_gas_limit {}",
745 block.header.gas_limit
746 )));
747 }
748
749 #[cfg(feature = "perf_opcode_timings")]
750 {
751 let mut timings = OPCODE_TIMINGS.lock().expect("poison");
752 timings.inc_tx_count(receipts.len());
753 timings.inc_block_count();
754 ::tracing::info!("{}", timings.info_pretty());
755 let precompiles_timings = PRECOMPILES_TIMINGS.lock().expect("poison");
756 ::tracing::info!("{}", precompiles_timings.info_pretty());
757 }
758
759 if queue_length.load(Ordering::Relaxed) == 0 {
760 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
761 }
762
763 if is_amsterdam {
766 let post_tx_index =
767 u32::try_from(block.body.transactions.len() + 1).unwrap_or(u32::MAX);
768 db.set_bal_index(post_tx_index);
769
770 if let Some(withdrawals) = &block.body.withdrawals
772 && let Some(recorder) = db.bal_recorder_mut()
773 {
774 recorder.extend_touched_addresses(withdrawals.iter().map(|w| w.address));
775 }
776 }
777
778 let requests = match vm_type {
782 VMType::L1 => extract_all_requests_levm(&receipts, db, &block.header, vm_type, crypto)?,
783 VMType::L2(_) => Default::default(),
784 };
785
786 if let Some(withdrawals) = &block.body.withdrawals {
787 Self::process_withdrawals(db, withdrawals)?;
788 }
789 LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
790
791 let bal = db.take_bal();
793
794 Ok((
795 BlockExecutionResult {
796 receipts,
797 requests,
798 block_gas_used,
799 tx_gas_breakdowns,
800 },
801 bal,
802 ))
803 }
804
805 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
810 fn bal_to_account_updates(
811 bal: &BlockAccessList,
812 store: &dyn Database,
813 ) -> Result<Vec<AccountUpdate>, EvmError> {
814 use ethrex_common::types::AccountInfo;
815
816 let mut updates = Vec::new();
817
818 let write_addrs: Vec<Address> = bal
820 .accounts()
821 .iter()
822 .filter(|ac| {
823 !ac.balance_changes.is_empty()
824 || !ac.nonce_changes.is_empty()
825 || !ac.code_changes.is_empty()
826 || !ac.storage_changes.is_empty()
827 })
828 .map(|ac| ac.address)
829 .collect();
830 store
831 .prefetch_accounts(&write_addrs)
832 .map_err(|e| EvmError::Custom(format!("bal_to_account_updates prefetch: {e}")))?;
833
834 for acct_changes in bal.accounts() {
835 let addr = acct_changes.address;
836
837 let has_writes = !acct_changes.balance_changes.is_empty()
839 || !acct_changes.nonce_changes.is_empty()
840 || !acct_changes.code_changes.is_empty()
841 || !acct_changes.storage_changes.is_empty();
842 if !has_writes {
843 continue;
844 }
845
846 let prestate = store
848 .get_account_state(addr)
849 .map_err(|e| EvmError::Custom(format!("bal_to_account_updates: {e}")))?;
850
851 let balance = acct_changes
853 .balance_changes
854 .last()
855 .map(|c| c.post_balance)
856 .unwrap_or(prestate.balance);
857
858 let nonce = acct_changes
860 .nonce_changes
861 .last()
862 .map(|c| c.post_nonce)
863 .unwrap_or(prestate.nonce);
864
865 let (code_hash, code) = if let Some(c) = acct_changes.code_changes.last() {
867 code_from_bal(&c.new_code)
868 } else {
869 (prestate.code_hash, None)
870 };
871
872 let mut added_storage = FxHashMap::with_capacity_and_hasher(
874 acct_changes.storage_changes.len(),
875 Default::default(),
876 );
877 for slot_change in &acct_changes.storage_changes {
878 if let Some(last) = slot_change.slot_changes.last() {
879 let key = ethrex_common::utils::u256_to_h256(slot_change.slot);
880 added_storage.insert(key, last.post_value);
881 }
882 }
883
884 let post_empty = balance.is_zero() && nonce == 0 && code_hash == *EMPTY_KECCAK_HASH;
886 let pre_empty = prestate.balance.is_zero()
887 && prestate.nonce == 0
888 && prestate.code_hash == *EMPTY_KECCAK_HASH;
889 let removed = post_empty && !pre_empty;
890
891 let balance_changed = acct_changes
892 .balance_changes
893 .last()
894 .is_some_and(|c| c.post_balance != prestate.balance);
895 let nonce_changed = acct_changes
896 .nonce_changes
897 .last()
898 .is_some_and(|c| c.post_nonce != prestate.nonce);
899 let code_changed = acct_changes.code_changes.last().is_some();
900 let acc_info_updated = balance_changed || nonce_changed || code_changed;
901
902 if !removed && !acc_info_updated && added_storage.is_empty() {
903 continue;
904 }
905
906 let info = if acc_info_updated {
907 Some(AccountInfo {
908 code_hash,
909 balance,
910 nonce,
911 })
912 } else {
913 None
914 };
915
916 let update = AccountUpdate {
917 address: addr,
918 removed,
919 info,
920 code,
921 added_storage,
922 removed_storage: false,
929 };
930 updates.push(update);
931 }
932
933 Ok(updates)
934 }
935
936 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
953 fn seed_db_from_bal(
954 db: &mut GeneralizedDatabase,
955 bal: &BlockAccessList,
956 max_idx: u32,
957 accounts_by_min_index: &[(u32, usize)],
958 ) -> Result<(), EvmError> {
959 let end = accounts_by_min_index.partition_point(|(min_idx, _)| *min_idx <= max_idx);
960 let bal_accounts = bal.accounts();
961 for &(_, acct_idx) in &accounts_by_min_index[..end] {
962 seed_one_address_info_from_bal(db, bal, acct_idx, max_idx)
963 .map_err(|e| EvmError::Custom(format!("seed_db_from_bal: {e}")))?;
964
965 let acct_changes = &bal_accounts[acct_idx];
966 if acct_changes.storage_changes.is_empty() {
967 continue;
968 }
969 let any_storage = acct_changes.storage_changes.iter().any(|sc| {
970 sc.slot_changes
971 .first()
972 .is_some_and(|c| c.block_access_index <= max_idx)
973 });
974 if !any_storage {
975 continue;
976 }
977 let addr = acct_changes.address;
978 if !db.current_accounts_state.contains_key(&addr) {
979 db.get_account(addr)
980 .map_err(|e| EvmError::Custom(format!("seed storage: {e}")))?;
981 }
982 let acc = db
983 .get_account_mut(addr)
984 .map_err(|e| EvmError::Custom(format!("seed storage mut: {e}")))?;
985 for sc in &acct_changes.storage_changes {
986 if let Some(value) = post_value_at_or_before(sc, max_idx) {
987 acc.storage
988 .insert(ethrex_common::utils::u256_to_h256(sc.slot), value);
989 }
990 }
991 }
992 Ok(())
993 }
994
995 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1002 #[allow(clippy::too_many_arguments, clippy::type_complexity)]
1003 fn execute_block_parallel(
1004 block: &Block,
1005 txs_with_sender: &[(&Transaction, Address)],
1006 db: &mut GeneralizedDatabase,
1007 vm_type: VMType,
1008 bal: &BlockAccessList,
1009 merkleizer: Option<&Sender<Vec<AccountUpdate>>>,
1010 queue_length: &AtomicUsize,
1011 system_seed: Arc<CacheDB>,
1012 crypto: &dyn Crypto,
1013 validation_index: &BalAddressIndex,
1014 ) -> Result<
1015 (
1016 Vec<Receipt>,
1017 u64,
1018 FxHashSet<(Address, H256)>,
1019 FxHashSet<Address>,
1020 Vec<TxGasBreakdown>,
1021 ),
1022 EvmError,
1023 > {
1024 let store = db.store.clone();
1025 let header = &block.header;
1026 let n_txs = txs_with_sender.len();
1027 let chain_config = store.get_chain_config()?;
1032 let is_amsterdam = chain_config.is_amsterdam_activated(header.timestamp);
1033 let evm_config = EVMConfig::new_from_chain_config(&chain_config, header);
1036 let chain_id = chain_config.chain_id;
1037 let base_blob_fee_per_gas = get_base_fee_per_blob_gas(header.excess_blob_gas, &evm_config)?;
1039 debug_assert!(
1040 is_amsterdam,
1041 "execute_block_parallel invoked on non-Amsterdam block"
1042 );
1043
1044 if let Some(merkleizer) = merkleizer {
1048 let account_updates = Self::bal_to_account_updates(bal, store.as_ref())?;
1049 merkleizer
1050 .send(account_updates)
1051 .map_err(|e| EvmError::Custom(format!("merkleizer send failed: {e}")))?;
1052 queue_length.fetch_add(1, Ordering::Relaxed);
1053 }
1054
1055 let mut unread_storage_reads: FxHashSet<(Address, H256)> = FxHashSet::default();
1058 let mut unaccessed_pure_accounts: FxHashSet<Address> = FxHashSet::default();
1061 for acct in bal.accounts() {
1062 for &slot in &acct.storage_reads {
1063 let key = ethrex_common::utils::u256_to_h256(slot);
1064 unread_storage_reads.insert((acct.address, key));
1065 }
1066 let is_pure = acct.storage_changes.is_empty()
1067 && acct.storage_reads.is_empty()
1068 && acct.balance_changes.is_empty()
1069 && acct.nonce_changes.is_empty()
1070 && acct.code_changes.is_empty();
1071 if is_pure {
1072 unaccessed_pure_accounts.insert(acct.address);
1073 }
1074 }
1075
1076 for addr in system_seed.keys() {
1082 if *addr == SYSTEM_ADDRESS {
1083 continue;
1084 }
1085 unaccessed_pure_accounts.remove(addr);
1086 }
1087
1088 unread_storage_reads.retain(|(addr, key)| {
1090 !system_seed
1091 .get(addr)
1092 .is_some_and(|a| a.storage.contains_key(key))
1093 });
1094
1095 let arc_bal = Arc::new(bal.clone());
1097 let arc_idx = Arc::new(validation_index.clone());
1098
1099 type TxExecResult = (
1113 usize,
1114 TxType,
1115 ExecutionReport,
1116 FxHashSet<Address>, Vec<(Address, H256)>, Vec<Address>, Option<EvmError>, );
1121
1122 let exec_results: Result<Vec<TxExecResult>, EvmError> = (0..n_txs)
1123 .into_par_iter()
1124 .map(|tx_idx| -> Result<_, EvmError> {
1125 let (tx, sender) = &txs_with_sender[tx_idx];
1126 let mut tx_db = GeneralizedDatabase::new_with_shared_base_and_capacity(
1127 store.clone(),
1128 system_seed.clone(),
1129 32,
1130 );
1131 tx_db.lazy_bal = Some(LazyBalCursor {
1132 bal: arc_bal.clone(),
1133 bal_index: u32::try_from(tx_idx + 1).unwrap_or(u32::MAX),
1134 index: arc_idx.clone(),
1135 });
1136 let mut stack_pool = Vec::with_capacity(8);
1139 let mut memory_pool = Vec::with_capacity(1);
1141
1142 tx_db.accessed_accounts =
1146 Some(FxHashSet::with_capacity_and_hasher(16, Default::default()));
1147
1148 tx_db.enable_bal_recording();
1153 let bal_index = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
1154 tx_db.set_bal_index(bal_index);
1155 if let Some(recorder) = tx_db.bal_recorder_mut() {
1156 recorder.record_touched_address(*sender);
1157 if let TxKind::Call(to) = tx.to() {
1158 recorder.record_touched_address(to);
1159 }
1160 }
1161
1162 let report = LEVM::execute_tx_in_block(
1163 tx,
1164 *sender,
1165 header,
1166 &mut tx_db,
1167 vm_type,
1168 base_blob_fee_per_gas,
1169 &mut stack_pool,
1170 &mut memory_pool,
1171 false,
1172 crypto,
1173 evm_config,
1174 chain_id,
1175 )?;
1176
1177 let current_state = std::mem::take(&mut tx_db.current_accounts_state);
1178 let codes = std::mem::take(&mut tx_db.codes);
1179 let tracked = tx_db.accessed_accounts.take().unwrap_or_default();
1180 let (shadow_touched, shadow_reads) = tx_db
1181 .bal_recorder
1182 .take()
1183 .map(|mut r| (r.take_touched_addresses(), r.take_storage_reads()))
1184 .unwrap_or_default();
1185
1186 let mut reads_satisfied: Vec<(Address, H256)> =
1194 Vec::with_capacity(current_state.len() * 4);
1195 let mut destroyed: Vec<Address> = Vec::new();
1199 for (addr, acct) in ¤t_state {
1200 if matches!(
1201 acct.status,
1202 AccountStatus::Destroyed | AccountStatus::DestroyedModified
1203 ) {
1204 destroyed.push(*addr);
1205 } else {
1206 for key in acct.storage.keys() {
1207 reads_satisfied.push((*addr, *key));
1208 }
1209 }
1210 }
1211
1212 let deferred_bal_err: Option<EvmError> = (|| -> Result<(), EvmError> {
1218 let bal_idx = u32::try_from(tx_idx + 1).unwrap_or(u32::MAX);
1219 let seed_idx = u32::try_from(tx_idx).unwrap_or(u32::MAX);
1220 Self::validate_tx_execution(
1221 bal_idx,
1222 seed_idx,
1223 ¤t_state,
1224 &codes,
1225 bal,
1226 validation_index,
1227 &system_seed,
1228 &store,
1229 )
1230 .map_err(|e| {
1231 EvmError::Custom(format!("BAL validation failed for tx {tx_idx}: {e}"))
1232 })?;
1233
1234 for addr in &shadow_touched {
1236 if !validation_index.addr_to_idx.contains_key(addr) {
1237 return Err(EvmError::Custom(format!(
1238 "BAL validation failed for tx {tx_idx}: account {addr:?} was \
1239 accessed during execution but is missing from BAL"
1240 )));
1241 }
1242 }
1243 for (addr, slot) in &shadow_reads {
1244 let Some(&bal_acct_idx) = validation_index.addr_to_idx.get(addr) else {
1245 continue;
1247 };
1248 let acct = &bal.accounts()[bal_acct_idx];
1249 let in_changes = acct
1250 .storage_changes
1251 .binary_search_by(|sc| sc.slot.cmp(slot))
1252 .is_ok();
1253 let in_reads = acct.storage_reads.contains(slot);
1254 if !in_changes && !in_reads {
1255 return Err(EvmError::Custom(format!(
1256 "BAL validation failed for tx {tx_idx}: storage slot {slot} of \
1257 account {addr:?} was read during execution but is missing from \
1258 BAL (no storage_changes or storage_reads entry)"
1259 )));
1260 }
1261 }
1262 Ok(())
1263 })()
1264 .err();
1265
1266 drop(current_state);
1267 drop(codes);
1268
1269 Ok((
1270 tx_idx,
1271 tx.tx_type(),
1272 report,
1273 tracked,
1274 reads_satisfied,
1275 destroyed,
1276 deferred_bal_err,
1277 ))
1278 })
1279 .collect();
1280
1281 let mut exec_results = exec_results?;
1282
1283 exec_results.sort_unstable_by_key(|(idx, _, _, _, _, _, _)| *idx);
1290
1291 let mut block_regular_gas_used = 0_u64;
1296 let mut block_state_gas_used = 0_u64;
1297 let mut tx_gas_breakdowns: Vec<TxGasBreakdown> = Vec::with_capacity(exec_results.len());
1298 for (tx_idx, _, report, _, _, _, _) in &exec_results {
1299 let (tx, _) = txs_with_sender
1300 .get(*tx_idx)
1301 .ok_or_else(|| EvmError::Custom(format!("tx index {tx_idx} out of bounds")))?;
1302 if is_amsterdam {
1303 check_2d_gas_allowance(
1304 tx,
1305 Fork::Amsterdam,
1306 block_regular_gas_used,
1307 block_state_gas_used,
1308 header.gas_limit,
1309 )?;
1310 }
1311
1312 tx_gas_breakdowns.push(TxGasBreakdown::from_report(*tx_idx, tx.hash(), report));
1313
1314 let tx_state_gas = report.state_gas_used;
1315 let tx_regular_gas = report.gas_used.saturating_sub(tx_state_gas);
1316 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas);
1317 block_state_gas_used = block_state_gas_used.saturating_add(tx_state_gas);
1318 }
1319 let block_gas_used = block_regular_gas_used.max(block_state_gas_used);
1320 if block_gas_used > header.gas_limit {
1322 return Err(EvmError::Transaction(format!(
1323 "Gas allowance exceeded: Block gas used overflow: \
1324 block_gas_used {block_gas_used} > block_gas_limit {}",
1325 header.gas_limit
1326 )));
1327 }
1328
1329 for (_, _, _, _, _, _, deferred) in &mut exec_results {
1332 if let Some(err) = deferred.take() {
1333 return Err(err);
1334 }
1335 }
1336
1337 for (_, _, _, tracked_accounts, reads_satisfied, destroyed, _) in &exec_results {
1340 if !unread_storage_reads.is_empty() {
1341 for addr in destroyed {
1342 unread_storage_reads.retain(|&(a, _)| a != *addr);
1343 }
1344 for pair in reads_satisfied {
1345 unread_storage_reads.remove(pair);
1346 }
1347 }
1348 if !unaccessed_pure_accounts.is_empty() {
1352 unaccessed_pure_accounts.remove(&header.coinbase);
1353 for addr in tracked_accounts {
1354 unaccessed_pure_accounts.remove(addr);
1355 }
1356 }
1357 }
1358
1359 let mut receipts = Vec::with_capacity(n_txs);
1361 let mut cumulative_gas_used = 0_u64;
1362 for (_, tx_type, report, _, _, _, _) in exec_results {
1363 cumulative_gas_used += report.gas_spent;
1364 let receipt = Receipt::new(
1365 tx_type,
1366 matches!(report.result, TxResult::Success),
1367 cumulative_gas_used,
1368 report.logs,
1369 );
1370 receipts.push(receipt);
1371 }
1372
1373 Ok((
1374 receipts,
1375 block_gas_used,
1376 unread_storage_reads,
1377 unaccessed_pure_accounts,
1378 tx_gas_breakdowns,
1379 ))
1380 }
1381
1382 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1385 fn seeded_balance(
1386 seed_idx: u32,
1387 acct: ðrex_common::types::block_access_list::AccountChanges,
1388 system_seed: &CacheDB,
1389 store: &Arc<dyn Database>,
1390 ) -> Result<U256, BalValidationError> {
1391 let pos = acct
1392 .balance_changes
1393 .partition_point(|c| c.block_access_index <= seed_idx);
1394 if pos > 0 {
1395 Ok(acct.balance_changes[pos - 1].post_balance)
1396 } else if let Some(a) = system_seed.get(&acct.address) {
1397 Ok(a.info.balance)
1398 } else {
1399 store
1400 .get_account_state(acct.address)
1401 .map(|a| a.balance)
1402 .map_err(|e| {
1403 BalValidationError::Database(format!(
1404 "DB error reading balance for {:?}: {e}",
1405 acct.address
1406 ))
1407 })
1408 }
1409 }
1410
1411 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1414 fn seeded_nonce(
1415 seed_idx: u32,
1416 acct: ðrex_common::types::block_access_list::AccountChanges,
1417 system_seed: &CacheDB,
1418 store: &Arc<dyn Database>,
1419 ) -> Result<u64, BalValidationError> {
1420 let pos = acct
1421 .nonce_changes
1422 .partition_point(|c| c.block_access_index <= seed_idx);
1423 if pos > 0 {
1424 Ok(acct.nonce_changes[pos - 1].post_nonce)
1425 } else if let Some(a) = system_seed.get(&acct.address) {
1426 Ok(a.info.nonce)
1427 } else {
1428 store
1429 .get_account_state(acct.address)
1430 .map(|a| a.nonce)
1431 .map_err(|e| {
1432 BalValidationError::Database(format!(
1433 "DB error reading nonce for {:?}: {e}",
1434 acct.address
1435 ))
1436 })
1437 }
1438 }
1439
1440 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1456 #[allow(clippy::too_many_arguments)]
1457 fn validate_tx_execution(
1458 bal_idx: u32,
1459 seed_idx: u32,
1460 current_state: &FxHashMap<Address, LevmAccount>,
1461 codes: &FxHashMap<H256, Code>,
1462 bal: &BlockAccessList,
1463 index: &BalAddressIndex,
1464 system_seed: &CacheDB,
1465 store: &Arc<dyn Database>,
1466 ) -> Result<(), BalValidationError> {
1467 if let Some(active_accounts) = index.tx_to_accounts.get(&bal_idx) {
1470 for &acct_inner_idx in active_accounts {
1471 let acct = &bal.accounts()[acct_inner_idx];
1472 let addr = acct.address;
1473 let actual = current_state.get(&addr);
1474
1475 if let Some(expected) = find_exact_change_balance(&acct.balance_changes, bal_idx) {
1477 match actual {
1478 Some(a) if a.info.balance == expected => {}
1479 Some(a) => {
1480 return Err(BalValidationError::Mismatch(format!(
1481 "account {addr:?} balance mismatch at index {bal_idx}: BAL={expected}, exec={} (diff={})",
1482 a.info.balance,
1483 describe_balance_diff(expected, a.info.balance),
1484 )));
1485 }
1486 None => {
1487 let seeded = Self::seeded_balance(seed_idx, acct, system_seed, store)?;
1492 if expected != seeded {
1493 let all_bal_indices: Vec<u32> = acct
1495 .balance_changes
1496 .iter()
1497 .map(|c| c.block_access_index)
1498 .collect();
1499 let all_nonce_indices: Vec<u32> = acct
1500 .nonce_changes
1501 .iter()
1502 .map(|c| c.block_access_index)
1503 .collect();
1504 let all_storage_indices: Vec<(u32, u64)> = acct
1505 .storage_changes
1506 .iter()
1507 .flat_map(|sc| {
1508 sc.slot_changes
1509 .iter()
1510 .map(|c| (c.block_access_index, sc.slot.low_u64()))
1511 })
1512 .collect();
1513 let code_indices: Vec<u32> = acct
1514 .code_changes
1515 .iter()
1516 .map(|c| c.block_access_index)
1517 .collect();
1518 return Err(BalValidationError::Mismatch(format!(
1519 "account {addr:?} has BAL balance change at {bal_idx} \
1520 but not in execution state (expected={expected}, pre={seeded}, \
1521 all_bal_idx={all_bal_indices:?}, nonce_idx={all_nonce_indices:?}, \
1522 storage_idx={all_storage_indices:?}, code_idx={code_indices:?})"
1523 )));
1524 }
1525 }
1526 }
1527 }
1528
1529 if let Some(expected) = find_exact_change_nonce(&acct.nonce_changes, bal_idx) {
1531 match actual {
1532 Some(a) if a.info.nonce == expected => {}
1533 Some(a) => {
1534 return Err(BalValidationError::Mismatch(format!(
1535 "account {addr:?} nonce mismatch at index {bal_idx}: BAL={expected}, exec={}",
1536 a.info.nonce
1537 )));
1538 }
1539 None => {
1540 let seeded = Self::seeded_nonce(seed_idx, acct, system_seed, store)?;
1541 if expected != seeded {
1542 return Err(BalValidationError::Mismatch(format!(
1543 "account {addr:?} has BAL nonce change at {bal_idx} \
1544 but not in execution state (expected={expected}, pre={seeded})"
1545 )));
1546 }
1547 }
1548 }
1549 }
1550
1551 if let Some(expected_code) = find_exact_change_code(&acct.code_changes, bal_idx) {
1553 match actual {
1554 Some(a) => {
1555 let actual_code = if let Some(c) = codes.get(&a.info.code_hash) {
1556 c.code_bytes()
1557 } else {
1558 let c = store.get_account_code(a.info.code_hash).map_err(|e| {
1559 BalValidationError::Database(format!(
1560 "DB error reading account code for {addr:?}: {e}"
1561 ))
1562 })?;
1563 c.code_bytes()
1564 };
1565 if actual_code != *expected_code {
1566 return Err(BalValidationError::Mismatch(format!(
1567 "account {addr:?} code mismatch at index {bal_idx}"
1568 )));
1569 }
1570 }
1571 None => {
1572 let code_hash = if let Some(a) = system_seed.get(&addr) {
1576 a.info.code_hash
1577 } else {
1578 store
1579 .get_account_state(addr)
1580 .map(|a| a.code_hash)
1581 .map_err(|e| {
1582 BalValidationError::Database(format!(
1583 "DB error reading account state for {addr:?}: {e}"
1584 ))
1585 })?
1586 };
1587 let pre_code = if let Some(c) = codes.get(&code_hash) {
1588 c.code_bytes()
1589 } else {
1590 let c = store.get_account_code(code_hash).map_err(|e| {
1591 BalValidationError::Database(format!(
1592 "DB error reading account code for hash \
1593 {code_hash:?}: {e}"
1594 ))
1595 })?;
1596 c.code_bytes()
1597 };
1598 if *expected_code != pre_code {
1599 return Err(BalValidationError::Mismatch(format!(
1600 "account {addr:?} has BAL code change at {bal_idx} \
1601 but not in execution state"
1602 )));
1603 }
1604 }
1605 }
1606 }
1607
1608 for sc in &acct.storage_changes {
1610 if let Some(expected_value) =
1611 find_exact_change_storage(&sc.slot_changes, bal_idx)
1612 {
1613 let key = ethrex_common::utils::u256_to_h256(sc.slot);
1614 let actual_value = actual.and_then(|a| a.storage.get(&key)).copied();
1615 if actual_value != Some(expected_value) {
1616 if actual.is_none() || actual_value.is_none() {
1618 let pre_value =
1619 store.get_storage_value(addr, key).map_err(|e| {
1620 BalValidationError::Database(format!(
1621 "DB error reading storage for {addr:?} slot {}: {e}",
1622 sc.slot
1623 ))
1624 })?;
1625 if expected_value == pre_value {
1626 continue; }
1628 }
1629 return Err(BalValidationError::Mismatch(format!(
1630 "account {addr:?} storage slot {} mismatch at index {bal_idx}: \
1631 BAL={expected_value}, exec={actual_value:?}",
1632 sc.slot
1633 )));
1634 }
1635 }
1636 }
1637 }
1638 }
1639
1640 for (addr, account) in current_state {
1643 if account.is_unmodified() {
1644 continue;
1645 }
1646
1647 let Some(&bal_acct_idx) = index.addr_to_idx.get(addr) else {
1648 let pre = system_seed
1652 .get(addr)
1653 .map(|a| (a.info.balance, a.info.nonce, a.info.code_hash))
1654 .or_else(|| {
1655 store
1656 .get_account_state(*addr)
1657 .ok()
1658 .map(|a| (a.balance, a.nonce, a.code_hash))
1659 })
1660 .unwrap_or_default();
1661 let post = (
1662 account.info.balance,
1663 account.info.nonce,
1664 account.info.code_hash,
1665 );
1666 if pre != post {
1667 return Err(BalValidationError::Mismatch(format!(
1668 "account {addr:?} was modified by execution but is absent from BAL"
1669 )));
1670 }
1671 continue;
1672 };
1673
1674 let acct = &bal.accounts()[bal_acct_idx];
1675
1676 if !has_exact_change_balance(&acct.balance_changes, bal_idx) {
1678 let seeded_pos = acct
1679 .balance_changes
1680 .partition_point(|c| c.block_access_index <= seed_idx);
1681 let seeded = if seeded_pos > 0 {
1682 acct.balance_changes[seeded_pos - 1].post_balance
1683 } else {
1684 system_seed
1686 .get(addr)
1687 .map(|a| a.info.balance)
1688 .unwrap_or_else(|| {
1689 store
1690 .get_account_state(*addr)
1691 .map(|a| a.balance)
1692 .unwrap_or_default()
1693 })
1694 };
1695 if account.info.balance != seeded {
1696 return Err(BalValidationError::Mismatch(format!(
1697 "account {addr:?} balance changed by execution ({}) but BAL has no \
1698 balance change at index {bal_idx} (seeded={seeded})",
1699 account.info.balance
1700 )));
1701 }
1702 }
1703
1704 if !has_exact_change_nonce(&acct.nonce_changes, bal_idx) {
1706 let seeded_pos = acct
1707 .nonce_changes
1708 .partition_point(|c| c.block_access_index <= seed_idx);
1709 let seeded = if seeded_pos > 0 {
1710 acct.nonce_changes[seeded_pos - 1].post_nonce
1711 } else {
1712 system_seed
1713 .get(addr)
1714 .map(|a| a.info.nonce)
1715 .unwrap_or_else(|| {
1716 store
1717 .get_account_state(*addr)
1718 .map(|a| a.nonce)
1719 .unwrap_or_default()
1720 })
1721 };
1722 if account.info.nonce != seeded {
1723 return Err(BalValidationError::Mismatch(format!(
1724 "account {addr:?} nonce changed by execution ({}) but BAL has no \
1725 nonce change at index {bal_idx} (seeded={seeded})",
1726 account.info.nonce
1727 )));
1728 }
1729 }
1730
1731 if !has_exact_change_code(&acct.code_changes, bal_idx) {
1735 let seeded_pos = acct
1736 .code_changes
1737 .partition_point(|c| c.block_access_index <= seed_idx);
1738 let seeded_hash = if seeded_pos > 0 {
1739 let seeded_code = &acct.code_changes[seeded_pos - 1].new_code;
1740 if seeded_code.is_empty() {
1741 *EMPTY_KECCAK_HASH
1742 } else {
1743 ethrex_common::utils::keccak(seeded_code)
1744 }
1745 } else {
1746 system_seed
1748 .get(addr)
1749 .map(|a| a.info.code_hash)
1750 .unwrap_or_else(|| {
1751 store
1752 .get_account_state(*addr)
1753 .map(|a| a.code_hash)
1754 .unwrap_or(*EMPTY_KECCAK_HASH)
1755 })
1756 };
1757 if account.info.code_hash != seeded_hash {
1758 return Err(BalValidationError::Mismatch(format!(
1759 "account {addr:?} code changed by execution but BAL has no \
1760 code change at index {bal_idx} (seeded_hash={seeded_hash:?})"
1761 )));
1762 }
1763 }
1764
1765 for (key_h256, &value) in &account.storage {
1767 let slot_u256 = u256_from_big_endian_const(key_h256.0);
1768 let pos = acct
1770 .storage_changes
1771 .partition_point(|sc| sc.slot < slot_u256);
1772 if pos < acct.storage_changes.len() && acct.storage_changes[pos].slot == slot_u256 {
1773 let sc = &acct.storage_changes[pos];
1774 if !has_exact_change_storage(&sc.slot_changes, bal_idx) {
1775 let seeded_pos = sc
1776 .slot_changes
1777 .partition_point(|c| c.block_access_index <= seed_idx);
1778 if seeded_pos > 0 {
1779 let seeded = sc.slot_changes[seeded_pos - 1].post_value;
1780 if value != seeded {
1781 return Err(BalValidationError::Mismatch(format!(
1782 "account {addr:?} storage slot {slot_u256} changed by \
1783 execution ({value}) but BAL has no change at index \
1784 {bal_idx} (seeded={seeded})"
1785 )));
1786 }
1787 }
1788 }
1789 }
1790 }
1793 }
1794
1795 Ok(())
1796 }
1797
1798 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
1810 fn validate_bal_withdrawal_index(
1811 db: &GeneralizedDatabase,
1812 bal: &BlockAccessList,
1813 withdrawal_idx: u32,
1814 index: &BalAddressIndex,
1815 ) -> Result<(), EvmError> {
1816 for acct in bal.accounts() {
1819 let addr = acct.address;
1820 let actual = db.current_accounts_state.get(&addr);
1821
1822 if let Some(expected) = find_exact_change_balance(&acct.balance_changes, withdrawal_idx)
1824 {
1825 match actual {
1826 Some(a) if a.info.balance == expected => {}
1827 Some(a) => {
1828 return Err(EvmError::Custom(format!(
1829 "BAL validation failed for withdrawal: account {addr:?} balance \
1830 mismatch at index {withdrawal_idx}: BAL={expected}, actual={}",
1831 a.info.balance
1832 )));
1833 }
1834 None => {
1835 return Err(EvmError::Custom(format!(
1836 "BAL validation failed for withdrawal: account {addr:?} has \
1837 balance change at index {withdrawal_idx} but was not touched \
1838 by withdrawal/request phase"
1839 )));
1840 }
1841 }
1842 }
1843
1844 if let Some(expected) = find_exact_change_nonce(&acct.nonce_changes, withdrawal_idx) {
1846 match actual {
1847 Some(a) if a.info.nonce == expected => {}
1848 Some(a) => {
1849 return Err(EvmError::Custom(format!(
1850 "BAL validation failed for withdrawal: account {addr:?} nonce \
1851 mismatch at index {withdrawal_idx}: BAL={expected}, actual={}",
1852 a.info.nonce
1853 )));
1854 }
1855 None => {
1856 return Err(EvmError::Custom(format!(
1857 "BAL validation failed for withdrawal: account {addr:?} has \
1858 nonce change at index {withdrawal_idx} but was not touched \
1859 by withdrawal/request phase"
1860 )));
1861 }
1862 }
1863 }
1864
1865 if let Some(expected_code) = find_exact_change_code(&acct.code_changes, withdrawal_idx)
1867 {
1868 let code_hash = if expected_code.is_empty() {
1869 *EMPTY_KECCAK_HASH
1870 } else {
1871 ethrex_common::utils::keccak(expected_code)
1872 };
1873 match actual {
1874 Some(a) if a.info.code_hash == code_hash => {}
1875 Some(_) => {
1876 return Err(EvmError::Custom(format!(
1877 "BAL validation failed for withdrawal: account {addr:?} code \
1878 mismatch at index {withdrawal_idx}"
1879 )));
1880 }
1881 None => {
1882 return Err(EvmError::Custom(format!(
1883 "BAL validation failed for withdrawal: account {addr:?} has \
1884 code change at index {withdrawal_idx} but was not touched \
1885 by withdrawal/request phase"
1886 )));
1887 }
1888 }
1889 }
1890
1891 for sc in &acct.storage_changes {
1893 if let Some(expected_value) =
1894 find_exact_change_storage(&sc.slot_changes, withdrawal_idx)
1895 {
1896 let key = ethrex_common::utils::u256_to_h256(sc.slot);
1897 let actual_value = actual.and_then(|a| a.storage.get(&key)).copied();
1898 if actual_value != Some(expected_value) {
1899 return Err(EvmError::Custom(format!(
1900 "BAL validation failed for withdrawal: account {addr:?} storage \
1901 slot {} mismatch at index {withdrawal_idx}: BAL={expected_value}, \
1902 actual={actual_value:?}",
1903 sc.slot
1904 )));
1905 }
1906 }
1907 }
1908 }
1909
1910 for (addr, account) in &db.current_accounts_state {
1913 if account.is_unmodified() {
1914 continue;
1915 }
1916
1917 let Some(&bal_acct_idx) = index.addr_to_idx.get(addr) else {
1918 let pre_state = db.store.get_account_state(*addr).map_err(|e| {
1922 EvmError::Custom(format!(
1923 "BAL validation failed for withdrawal: db error reading \
1924 account {addr:?}: {e}"
1925 ))
1926 })?;
1927 let pre = (pre_state.balance, pre_state.nonce, pre_state.code_hash);
1928 let post = (
1929 account.info.balance,
1930 account.info.nonce,
1931 account.info.code_hash,
1932 );
1933 if pre != post {
1934 return Err(EvmError::Custom(format!(
1935 "BAL validation failed for withdrawal: account {addr:?} was modified \
1936 during withdrawal/request phase but is absent from BAL"
1937 )));
1938 }
1939 for (key_h256, &value) in &account.storage {
1942 let pre_value = db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
1943 EvmError::Custom(format!(
1944 "BAL validation failed for withdrawal: db error reading \
1945 storage {addr:?}[{}]: {e}",
1946 u256_from_big_endian_const(key_h256.0)
1947 ))
1948 })?;
1949 if value != pre_value {
1950 return Err(EvmError::Custom(format!(
1951 "BAL validation failed for withdrawal: account {addr:?} storage \
1952 slot {} changed during withdrawal/request phase but is absent \
1953 from BAL",
1954 u256_from_big_endian_const(key_h256.0)
1955 )));
1956 }
1957 }
1958 continue;
1959 };
1960
1961 let acct = &bal.accounts()[bal_acct_idx];
1962
1963 if !has_exact_change_balance(&acct.balance_changes, withdrawal_idx) {
1966 let seeded = match acct.balance_changes.last() {
1967 Some(c) => c.post_balance,
1968 None => {
1969 db.store
1970 .get_account_state(*addr)
1971 .map_err(|e| {
1972 EvmError::Custom(format!(
1973 "BAL validation failed for withdrawal: db error reading \
1974 account {addr:?}: {e}"
1975 ))
1976 })?
1977 .balance
1978 }
1979 };
1980 if account.info.balance != seeded {
1981 return Err(EvmError::Custom(format!(
1982 "BAL validation failed for withdrawal: account {addr:?} balance \
1983 changed during withdrawal/request phase ({}) but BAL has no \
1984 balance change at index {withdrawal_idx} (last_bal={seeded})",
1985 account.info.balance
1986 )));
1987 }
1988 }
1989
1990 if !has_exact_change_nonce(&acct.nonce_changes, withdrawal_idx) {
1992 let seeded = match acct.nonce_changes.last() {
1993 Some(c) => c.post_nonce,
1994 None => {
1995 db.store
1996 .get_account_state(*addr)
1997 .map_err(|e| {
1998 EvmError::Custom(format!(
1999 "BAL validation failed for withdrawal: db error reading \
2000 account {addr:?}: {e}"
2001 ))
2002 })?
2003 .nonce
2004 }
2005 };
2006 if account.info.nonce != seeded {
2007 return Err(EvmError::Custom(format!(
2008 "BAL validation failed for withdrawal: account {addr:?} nonce \
2009 changed during withdrawal/request phase ({}) but BAL has no \
2010 nonce change at index {withdrawal_idx} (last_bal={seeded})",
2011 account.info.nonce
2012 )));
2013 }
2014 }
2015
2016 if !has_exact_change_code(&acct.code_changes, withdrawal_idx) {
2018 let seeded_hash = match acct.code_changes.last() {
2019 Some(c) if c.new_code.is_empty() => *EMPTY_KECCAK_HASH,
2020 Some(c) => ethrex_common::utils::keccak(&c.new_code),
2021 None => {
2022 db.store
2023 .get_account_state(*addr)
2024 .map_err(|e| {
2025 EvmError::Custom(format!(
2026 "BAL validation failed for withdrawal: db error reading \
2027 account {addr:?}: {e}"
2028 ))
2029 })?
2030 .code_hash
2031 }
2032 };
2033 if account.info.code_hash != seeded_hash {
2034 return Err(EvmError::Custom(format!(
2035 "BAL validation failed for withdrawal: account {addr:?} code \
2036 changed during withdrawal/request phase but BAL has no \
2037 code change at index {withdrawal_idx} \
2038 (actual={:?}, last_bal={seeded_hash:?})",
2039 account.info.code_hash
2040 )));
2041 }
2042 }
2043
2044 for (key_h256, &value) in &account.storage {
2047 let slot_u256 = u256_from_big_endian_const(key_h256.0);
2048 let pos = acct
2049 .storage_changes
2050 .partition_point(|sc| sc.slot < slot_u256);
2051 if pos < acct.storage_changes.len() && acct.storage_changes[pos].slot == slot_u256 {
2052 let sc = &acct.storage_changes[pos];
2053 if !has_exact_change_storage(&sc.slot_changes, withdrawal_idx) {
2054 let seeded = match sc.slot_changes.last() {
2057 Some(c) => c.post_value,
2058 None => db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
2059 EvmError::Custom(format!(
2060 "BAL validation failed for withdrawal: db error reading \
2061 storage {addr:?}[{slot_u256}]: {e}"
2062 ))
2063 })?,
2064 };
2065 if value != seeded {
2066 return Err(EvmError::Custom(format!(
2067 "BAL validation failed for withdrawal: account {addr:?} \
2068 storage slot {slot_u256} changed during withdrawal/request \
2069 phase ({value}) but BAL has no change at index \
2070 {withdrawal_idx} (last_bal={seeded})"
2071 )));
2072 }
2073 }
2074 } else {
2075 let pre_value = db.store.get_storage_value(*addr, *key_h256).map_err(|e| {
2078 EvmError::Custom(format!(
2079 "BAL validation failed for withdrawal: db error reading \
2080 storage {addr:?}[{slot_u256}]: {e}"
2081 ))
2082 })?;
2083 if value != pre_value {
2084 return Err(EvmError::Custom(format!(
2085 "BAL validation failed for withdrawal: account {addr:?} \
2086 storage slot {slot_u256} changed during withdrawal/request \
2087 phase ({value}) but slot is absent from BAL storage_changes \
2088 (pre={pre_value})"
2089 )));
2090 }
2091 }
2092 }
2093 }
2094
2095 Ok(())
2096 }
2097
2098 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2109 pub fn warm_block(
2110 block: &Block,
2111 store: Arc<dyn Database>,
2112 vm_type: VMType,
2113 crypto: &dyn Crypto,
2114 cancelled: &AtomicBool,
2115 ) -> Result<(), EvmError> {
2116 let mut db = GeneralizedDatabase::new(store.clone());
2117
2118 let txs_with_sender = block
2119 .body
2120 .get_transactions_with_sender(crypto)
2121 .map_err(|error| {
2122 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
2123 })?;
2124
2125 let mut sender_groups: FxHashMap<Address, Vec<&Transaction>> = FxHashMap::default();
2127 for (tx, sender) in &txs_with_sender {
2128 sender_groups.entry(*sender).or_default().push(tx);
2129 }
2130
2131 let chain_config = store.get_chain_config()?;
2134 let evm_config = EVMConfig::new_from_chain_config(&chain_config, &block.header);
2135 let chain_id = chain_config.chain_id;
2136 let base_blob_fee_per_gas =
2138 get_base_fee_per_blob_gas(block.header.excess_blob_gas, &evm_config)?;
2139
2140 sender_groups.into_par_iter().for_each_with(
2143 Vec::with_capacity(STACK_LIMIT),
2144 |stack_pool, (sender, txs)| {
2145 if cancelled.load(Ordering::Relaxed) {
2146 return;
2147 }
2148 let mut memory_pool = Vec::with_capacity(1);
2152 let mut group_db = GeneralizedDatabase::new(store.clone());
2154 for tx in txs {
2157 let _ = Self::execute_tx_in_block(
2158 tx,
2159 sender,
2160 &block.header,
2161 &mut group_db,
2162 vm_type,
2163 base_blob_fee_per_gas,
2164 stack_pool,
2165 &mut memory_pool,
2166 true,
2167 crypto,
2168 evm_config,
2169 chain_id,
2170 );
2171 }
2172 },
2173 );
2174
2175 if cancelled.load(Ordering::Relaxed) {
2176 return Ok(());
2177 }
2178
2179 for withdrawal in block
2180 .body
2181 .withdrawals
2182 .iter()
2183 .flatten()
2184 .filter(|withdrawal| withdrawal.amount > 0)
2185 {
2186 db.get_account_mut(withdrawal.address).map_err(|_| {
2187 EvmError::DB(format!(
2188 "Withdrawal account {} not found",
2189 withdrawal.address
2190 ))
2191 })?;
2192 }
2193 Ok(())
2194 }
2195
2196 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2199 pub fn bal_storage_slots(bal: &BlockAccessList) -> Vec<(Address, H256)> {
2200 bal.accounts()
2201 .iter()
2202 .flat_map(|ac| {
2203 ac.all_storage_slots()
2204 .map(move |slot| (ac.address, H256::from_uint(&slot)))
2205 })
2206 .collect()
2207 }
2208
2209 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
2218 pub fn warm_block_from_bal(
2219 bal: &BlockAccessList,
2220 store: Arc<dyn Database>,
2221 cancelled: &AtomicBool,
2222 ) -> Result<(), EvmError> {
2223 let accounts = bal.accounts();
2224 if accounts.is_empty() {
2225 return Ok(());
2226 }
2227
2228 let account_addresses: Vec<Address> = accounts.iter().map(|ac| ac.address).collect();
2234 store
2235 .prefetch_accounts(&account_addresses)
2236 .map_err(|e| EvmError::Custom(format!("prefetch_accounts: {e}")))?;
2237
2238 if cancelled.load(Ordering::Relaxed) {
2239 return Ok(());
2240 }
2241
2242 let code_hashes: Vec<ethrex_common::H256> = accounts
2246 .par_iter()
2247 .filter_map(|ac| {
2248 store
2249 .get_account_state(ac.address)
2250 .ok()
2251 .filter(|s| s.code_hash != *EMPTY_KECCAK_HASH)
2252 .map(|s| s.code_hash)
2253 })
2254 .collect();
2255 code_hashes.par_iter().for_each(|&h| {
2256 let _ = store.get_account_code(h);
2257 });
2258
2259 Ok(())
2260 }
2261
2262 fn send_state_transitions_tx(
2263 merkleizer: &Sender<Vec<AccountUpdate>>,
2264 db: &mut GeneralizedDatabase,
2265 queue_length: &AtomicUsize,
2266 ) -> Result<(), EvmError> {
2267 let transitions = LEVM::get_state_transitions_tx(db)?;
2268 merkleizer
2269 .send(transitions)
2270 .map_err(|e| EvmError::Custom(format!("send failed: {e}")))?;
2271 queue_length.fetch_add(1, Ordering::Relaxed);
2272 Ok(())
2273 }
2274
2275 fn setup_env(
2276 tx: &Transaction,
2277 tx_sender: Address,
2278 block_header: &BlockHeader,
2279 db: &GeneralizedDatabase,
2280 vm_type: VMType,
2281 ) -> Result<Environment, EvmError> {
2282 let chain_config = db.store.get_chain_config()?;
2286 let config = EVMConfig::new_from_chain_config(&chain_config, block_header);
2287 let base_blob_fee_per_gas =
2288 get_base_fee_per_blob_gas(block_header.excess_blob_gas, &config)?;
2289 Self::setup_env_with_config(
2290 tx,
2291 tx_sender,
2292 block_header,
2293 config,
2294 chain_config.chain_id,
2295 vm_type,
2296 base_blob_fee_per_gas,
2297 )
2298 }
2299
2300 fn setup_env_with_config(
2304 tx: &Transaction,
2305 tx_sender: Address,
2306 block_header: &BlockHeader,
2307 config: EVMConfig,
2308 chain_id: u64,
2309 vm_type: VMType,
2310 base_blob_fee_per_gas: U256,
2311 ) -> Result<Environment, EvmError> {
2312 let gas_price: U256 = calculate_gas_price_for_tx(
2313 tx,
2314 block_header.base_fee_per_gas.unwrap_or_default(),
2315 &vm_type,
2316 )?;
2317
2318 let block_excess_blob_gas = block_header.excess_blob_gas;
2319 let env = Environment {
2320 origin: tx_sender,
2321 gas_limit: tx.gas_limit(),
2322 config,
2323 block_number: block_header.number,
2324 coinbase: block_header.coinbase,
2325 timestamp: block_header.timestamp,
2326 prev_randao: Some(block_header.prev_randao),
2327 slot_number: block_header
2328 .slot_number
2329 .map(U256::from)
2330 .unwrap_or(U256::zero()),
2331 chain_id: chain_id.into(),
2332 base_fee_per_gas: block_header.base_fee_per_gas.unwrap_or_default().into(),
2333 base_blob_fee_per_gas,
2334 gas_price,
2335 block_excess_blob_gas,
2336 block_blob_gas_used: block_header.blob_gas_used,
2337 tx_blob_hashes: tx.blob_versioned_hashes(),
2338 tx_max_priority_fee_per_gas: tx.max_priority_fee().map(U256::from),
2339 tx_max_fee_per_gas: tx.max_fee_per_gas().map(U256::from),
2340 tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas(),
2341 tx_nonce: tx.nonce(),
2342 block_gas_limit: block_header.gas_limit,
2343 difficulty: block_header.difficulty,
2344 is_privileged: matches!(tx, Transaction::PrivilegedL2Transaction(_)),
2345 fee_token: tx.fee_token(),
2346 disable_balance_check: false,
2347 is_system_call: false,
2348 };
2349
2350 Ok(env)
2351 }
2352
2353 pub fn execute_tx(
2354 tx: &Transaction,
2356 tx_sender: Address,
2358 block_header: &BlockHeader,
2360 db: &mut GeneralizedDatabase,
2361 vm_type: VMType,
2362 crypto: &dyn Crypto,
2363 ) -> Result<ExecutionReport, EvmError> {
2364 let env = Self::setup_env(tx, tx_sender, block_header, db, vm_type)?;
2365 let mut vm = VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)?;
2366
2367 vm.execute().map_err(VMError::into)
2368 }
2369
2370 #[allow(clippy::too_many_arguments)]
2373 fn execute_tx_in_block(
2374 tx: &Transaction,
2376 tx_sender: Address,
2378 block_header: &BlockHeader,
2380 db: &mut GeneralizedDatabase,
2381 vm_type: VMType,
2382 base_blob_fee_per_gas: U256,
2383 stack_pool: &mut Vec<Stack>,
2384 memory_pool: &mut Vec<Memory>,
2385 disable_balance_check: bool,
2386 crypto: &dyn Crypto,
2387 config: EVMConfig,
2388 chain_id: u64,
2389 ) -> Result<ExecutionReport, EvmError> {
2390 let mut env = Self::setup_env_with_config(
2391 tx,
2392 tx_sender,
2393 block_header,
2394 config,
2395 chain_id,
2396 vm_type,
2397 base_blob_fee_per_gas,
2398 )?;
2399 env.disable_balance_check = disable_balance_check;
2400 let mut vm = VM::new_pooled(
2404 env,
2405 db,
2406 tx,
2407 LevmCallTracer::disabled(),
2408 vm_type,
2409 crypto,
2410 stack_pool,
2411 memory_pool,
2412 )?;
2413 let result = vm.execute().map_err(VMError::into);
2414 vm.reclaim_into(stack_pool, memory_pool);
2416 result
2417 }
2418
2419 pub fn undo_last_tx(db: &mut GeneralizedDatabase) -> Result<(), EvmError> {
2420 db.undo_last_transaction()?;
2421 Ok(())
2422 }
2423
2424 pub fn simulate_tx_from_generic(
2425 tx: &GenericTransaction,
2427 block_header: &BlockHeader,
2429 db: &mut GeneralizedDatabase,
2430 vm_type: VMType,
2431 crypto: &dyn Crypto,
2432 ) -> Result<ExecutionResult, EvmError> {
2433 let mut env = env_from_generic(tx, block_header, db, vm_type)?;
2434
2435 env.block_gas_limit = i64::MAX as u64; adjust_disabled_base_fee(&mut env);
2438
2439 let converted_tx = generic_tx_to_transaction(tx)?;
2440 let mut vm = vm_from_generic(&converted_tx, env, db, vm_type, crypto)?;
2441
2442 vm.execute()
2443 .map(|value| value.into())
2444 .map_err(VMError::into)
2445 }
2446
2447 pub fn get_state_transitions(
2448 db: &mut GeneralizedDatabase,
2449 ) -> Result<Vec<AccountUpdate>, EvmError> {
2450 Ok(db.get_state_transitions()?)
2451 }
2452
2453 pub fn get_state_transitions_tx(
2454 db: &mut GeneralizedDatabase,
2455 ) -> Result<Vec<AccountUpdate>, EvmError> {
2456 Ok(db.get_state_transitions_tx()?)
2457 }
2458
2459 pub fn process_withdrawals(
2460 db: &mut GeneralizedDatabase,
2461 withdrawals: &[Withdrawal],
2462 ) -> Result<(), EvmError> {
2463 for (address, increment) in withdrawals
2465 .iter()
2466 .filter(|withdrawal| withdrawal.amount > 0)
2467 .map(|w| (w.address, u128::from(w.amount) * u128::from(GWEI_TO_WEI)))
2468 {
2469 let account = db
2470 .get_account_mut(address)
2471 .map_err(|_| EvmError::DB(format!("Withdrawal account {address} not found")))?;
2472
2473 let initial_balance = account.info.balance;
2474 account.info.balance += increment.into();
2475 let new_balance = account.info.balance;
2476
2477 if let Some(recorder) = db.bal_recorder_mut() {
2479 recorder.set_initial_balance(address, initial_balance);
2480 recorder.record_balance_change(address, new_balance);
2481 }
2482 }
2483 Ok(())
2484 }
2485
2486 pub fn beacon_root_contract_call(
2488 block_header: &BlockHeader,
2489 db: &mut GeneralizedDatabase,
2490 vm_type: VMType,
2491 crypto: &dyn Crypto,
2492 ) -> Result<(), EvmError> {
2493 if let VMType::L2(_) = vm_type {
2494 return Err(EvmError::InvalidEVM(
2495 "beacon_root_contract_call should not be called for L2 VM".to_string(),
2496 ));
2497 }
2498
2499 let beacon_root = block_header.parent_beacon_block_root.ok_or_else(|| {
2500 EvmError::Header("parent_beacon_block_root field is missing".to_string())
2501 })?;
2502
2503 generic_system_contract_levm(
2504 block_header,
2505 Bytes::copy_from_slice(beacon_root.as_bytes()),
2506 db,
2507 BEACON_ROOTS_ADDRESS.address,
2508 SYSTEM_ADDRESS,
2509 vm_type,
2510 crypto,
2511 )?;
2512 Ok(())
2513 }
2514
2515 pub fn process_block_hash_history(
2516 block_header: &BlockHeader,
2517 db: &mut GeneralizedDatabase,
2518 vm_type: VMType,
2519 crypto: &dyn Crypto,
2520 ) -> Result<(), EvmError> {
2521 if let VMType::L2(_) = vm_type {
2522 return Err(EvmError::InvalidEVM(
2523 "process_block_hash_history should not be called for L2 VM".to_string(),
2524 ));
2525 }
2526
2527 generic_system_contract_levm(
2528 block_header,
2529 Bytes::copy_from_slice(block_header.parent_hash.as_bytes()),
2530 db,
2531 HISTORY_STORAGE_ADDRESS.address,
2532 SYSTEM_ADDRESS,
2533 vm_type,
2534 crypto,
2535 )?;
2536 Ok(())
2537 }
2538 pub(crate) fn read_withdrawal_requests(
2539 block_header: &BlockHeader,
2540 db: &mut GeneralizedDatabase,
2541 vm_type: VMType,
2542 crypto: &dyn Crypto,
2543 ) -> Result<ExecutionReport, EvmError> {
2544 if let VMType::L2(_) = vm_type {
2545 return Err(EvmError::InvalidEVM(
2546 "read_withdrawal_requests should not be called for L2 VM".to_string(),
2547 ));
2548 }
2549
2550 let report = generic_system_contract_levm(
2551 block_header,
2552 Bytes::new(),
2553 db,
2554 WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS.address,
2555 SYSTEM_ADDRESS,
2556 vm_type,
2557 crypto,
2558 )?;
2559
2560 match report.result {
2561 TxResult::Success => Ok(report),
2562 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
2564 "REVERT when reading withdrawal requests with error: {vm_error:?}. According to EIP-7002, the revert of this system call invalidates the block.",
2565 ))),
2566 }
2567 }
2568
2569 pub(crate) fn dequeue_consolidation_requests(
2570 block_header: &BlockHeader,
2571 db: &mut GeneralizedDatabase,
2572 vm_type: VMType,
2573 crypto: &dyn Crypto,
2574 ) -> Result<ExecutionReport, EvmError> {
2575 if let VMType::L2(_) = vm_type {
2576 return Err(EvmError::InvalidEVM(
2577 "dequeue_consolidation_requests should not be called for L2 VM".to_string(),
2578 ));
2579 }
2580
2581 let report = generic_system_contract_levm(
2582 block_header,
2583 Bytes::new(),
2584 db,
2585 CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS.address,
2586 SYSTEM_ADDRESS,
2587 vm_type,
2588 crypto,
2589 )?;
2590
2591 match report.result {
2592 TxResult::Success => Ok(report),
2593 TxResult::Revert(vm_error) => Err(EvmError::SystemContractCallFailed(format!(
2595 "REVERT when dequeuing consolidation requests with error: {vm_error:?}. According to EIP-7251, the revert of this system call invalidates the block.",
2596 ))),
2597 }
2598 }
2599
2600 pub fn create_access_list(
2601 mut tx: GenericTransaction,
2602 header: &BlockHeader,
2603 db: &mut GeneralizedDatabase,
2604 vm_type: VMType,
2605 crypto: &dyn Crypto,
2606 ) -> Result<(ExecutionResult, AccessList), VMError> {
2607 let mut env = env_from_generic(&tx, header, db, vm_type)?;
2608
2609 adjust_disabled_base_fee(&mut env);
2610
2611 let converted_tx = generic_tx_to_transaction(&tx)?;
2612 let mut vm = vm_from_generic(&converted_tx, env.clone(), db, vm_type, crypto)?;
2613
2614 vm.stateless_execute()?;
2615
2616 tx.access_list = vm.substate.make_access_list();
2618 let converted_tx = generic_tx_to_transaction(&tx)?;
2619 let mut vm = vm_from_generic(&converted_tx, env, db, vm_type, crypto)?;
2620
2621 let report = vm.stateless_execute()?;
2622
2623 Ok((
2624 report.into(),
2625 tx.access_list
2626 .into_iter()
2627 .map(|x| (x.address, x.storage_keys))
2628 .collect(),
2629 ))
2630 }
2631
2632 pub fn prepare_block(
2633 block: &Block,
2634 db: &mut GeneralizedDatabase,
2635 vm_type: VMType,
2636 crypto: &dyn Crypto,
2637 ) -> Result<(), EvmError> {
2638 let chain_config = db.store.get_chain_config()?;
2639 let block_header = &block.header;
2640 let fork = chain_config.fork(block_header.timestamp);
2641
2642 if let VMType::L2(_) = vm_type {
2644 return Ok(());
2645 }
2646
2647 if block_header.parent_beacon_block_root.is_some() && fork >= Fork::Cancun {
2648 Self::beacon_root_contract_call(block_header, db, vm_type, crypto)?;
2649 }
2650
2651 if fork >= Fork::Prague {
2652 Self::process_block_hash_history(block_header, db, vm_type, crypto)?;
2654 }
2655 Ok(())
2656 }
2657}
2658
2659pub fn generic_system_contract_levm(
2660 block_header: &BlockHeader,
2661 calldata: Bytes,
2662 db: &mut GeneralizedDatabase,
2663 contract_address: Address,
2664 system_address: Address,
2665 vm_type: VMType,
2666 crypto: &dyn Crypto,
2667) -> Result<ExecutionReport, EvmError> {
2668 let chain_config = db.store.get_chain_config()?;
2669 let config = EVMConfig::new_from_chain_config(&chain_config, block_header);
2670 let env = Environment {
2671 origin: system_address,
2672 gas_limit: SYS_CALL_GAS_LIMIT + TX_BASE_COST,
2675 block_number: block_header.number,
2676 coinbase: block_header.coinbase,
2677 timestamp: block_header.timestamp,
2678 prev_randao: Some(block_header.prev_randao),
2679 base_fee_per_gas: U256::zero(),
2680 gas_price: U256::zero(),
2681 block_excess_blob_gas: block_header.excess_blob_gas,
2682 block_blob_gas_used: block_header.blob_gas_used,
2683 block_gas_limit: block_header.gas_limit,
2688 is_system_call: true,
2689 config,
2690 ..Default::default()
2691 };
2692
2693 debug_assert!(
2699 env.gas_price.is_zero() && env.base_fee_per_gas.is_zero(),
2700 "system calls must run with a zero gas price"
2701 );
2702
2703 if PRAGUE_SYSTEM_CONTRACTS
2709 .iter()
2710 .any(|contract| contract.address == contract_address)
2711 && db.get_account_code(contract_address)?.is_empty()
2712 {
2713 return Err(EvmError::SystemContractCallFailed(format!(
2714 "System contract: {contract_address} has no code after deployment"
2715 )));
2716 };
2717
2718 let tx = &Transaction::EIP1559Transaction(EIP1559Transaction {
2719 to: TxKind::Call(contract_address),
2720 value: U256::zero(),
2721 data: calldata,
2722 ..Default::default()
2723 });
2724 if let Some(recorder) = db.bal_recorder.as_mut() {
2726 recorder.enter_system_call();
2727 }
2728
2729 let result = VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)
2730 .and_then(|mut vm| vm.execute())
2731 .map_err(EvmError::from);
2732
2733 if let Some(recorder) = db.bal_recorder.as_mut() {
2735 recorder.exit_system_call();
2736 }
2737
2738 result
2739}
2740
2741#[allow(unreachable_code)]
2742#[allow(unused_variables)]
2743pub fn extract_all_requests_levm(
2744 receipts: &[Receipt],
2745 db: &mut GeneralizedDatabase,
2746 header: &BlockHeader,
2747 vm_type: VMType,
2748 crypto: &dyn Crypto,
2749) -> Result<Vec<Requests>, EvmError> {
2750 if let VMType::L2(_) = vm_type {
2751 return Err(EvmError::InvalidEVM(
2752 "extract_all_requests_levm should not be called for L2 VM".to_string(),
2753 ));
2754 }
2755
2756 let chain_config = db.store.get_chain_config()?;
2757 let fork = chain_config.fork(header.timestamp);
2758
2759 if fork < Fork::Prague {
2760 return Ok(Default::default());
2761 }
2762
2763 let withdrawals_data: Vec<u8> = LEVM::read_withdrawal_requests(header, db, vm_type, crypto)?
2764 .output
2765 .into();
2766 let consolidation_data: Vec<u8> =
2767 LEVM::dequeue_consolidation_requests(header, db, vm_type, crypto)?
2768 .output
2769 .into();
2770
2771 let deposits = Requests::from_deposit_receipts(chain_config.deposit_contract_address, receipts)
2772 .ok_or(EvmError::InvalidDepositRequest)?;
2773 let withdrawals = Requests::from_withdrawals_data(withdrawals_data);
2774 let consolidation = Requests::from_consolidation_data(consolidation_data);
2775
2776 Ok(vec![deposits, withdrawals, consolidation])
2777}
2778
2779pub fn calculate_gas_price_for_generic(tx: &GenericTransaction, basefee: u64) -> U256 {
2782 if !tx.gas_price.is_zero() {
2783 tx.gas_price
2785 } else {
2786 min(
2788 tx.max_priority_fee_per_gas.unwrap_or(0) + basefee,
2789 tx.max_fee_per_gas.unwrap_or(0),
2790 )
2791 .into()
2792 }
2793}
2794
2795pub fn calculate_gas_price_for_tx(
2796 tx: &Transaction,
2797 mut fee_per_gas: u64,
2798 vm_type: &VMType,
2799) -> Result<U256, VMError> {
2800 let Some(max_priority_fee) = tx.max_priority_fee() else {
2801 return Ok(tx.gas_price());
2803 };
2804
2805 let max_fee_per_gas = tx.max_fee_per_gas().ok_or(VMError::TxValidation(
2806 TxValidationError::InsufficientMaxFeePerGas,
2807 ))?;
2808
2809 if let VMType::L2(fee_config) = vm_type
2810 && let Some(operator_fee_config) = &fee_config.operator_fee_config
2811 {
2812 fee_per_gas += operator_fee_config.operator_fee_per_gas;
2813 }
2814
2815 if fee_per_gas > max_fee_per_gas {
2816 return Err(VMError::TxValidation(
2817 TxValidationError::InsufficientMaxFeePerGas,
2818 ));
2819 }
2820
2821 Ok(min(max_priority_fee + fee_per_gas, max_fee_per_gas).into())
2822}
2823
2824fn adjust_disabled_base_fee(env: &mut Environment) {
2828 if env.gas_price == U256::zero() {
2829 env.base_fee_per_gas = U256::zero();
2830 }
2831 if env
2832 .tx_max_fee_per_blob_gas
2833 .is_some_and(|v| v == U256::zero())
2834 {
2835 env.block_excess_blob_gas = None;
2836 }
2837}
2838
2839fn adjust_disabled_l2_fees(env: &Environment, vm_type: VMType) -> VMType {
2841 if env.gas_price == U256::zero()
2842 && let VMType::L2(fee_config) = vm_type
2843 {
2844 return VMType::L2(FeeConfig {
2846 operator_fee_config: None,
2847 l1_fee_config: None,
2848 ..fee_config
2849 });
2850 }
2851 vm_type
2852}
2853
2854fn env_from_generic(
2855 tx: &GenericTransaction,
2856 header: &BlockHeader,
2857 db: &GeneralizedDatabase,
2858 vm_type: VMType,
2859) -> Result<Environment, VMError> {
2860 let chain_config = db.store.get_chain_config()?;
2861 let gas_price =
2862 calculate_gas_price_for_generic(tx, header.base_fee_per_gas.unwrap_or(INITIAL_BASE_FEE));
2863 let block_excess_blob_gas = header.excess_blob_gas;
2864 let config = EVMConfig::new_from_chain_config(&chain_config, header);
2865
2866 let slot_number = if let VMType::L2(_) = vm_type {
2869 U256::zero()
2870 } else if config.fork >= Fork::Amsterdam {
2871 header
2872 .slot_number
2873 .map(U256::from)
2874 .ok_or(VMError::Internal(InternalError::Custom(
2875 "slot_number must be present in Amsterdam+ blocks".to_string(),
2876 )))?
2877 } else {
2878 header.slot_number.map(U256::from).unwrap_or(U256::zero())
2881 };
2882
2883 Ok(Environment {
2884 origin: tx.from.0.into(),
2885 gas_limit: tx
2886 .gas
2887 .unwrap_or(get_max_allowed_gas_limit(header.gas_limit, config.fork)), config,
2889 block_number: header.number,
2890 coinbase: header.coinbase,
2891 timestamp: header.timestamp,
2892 prev_randao: Some(header.prev_randao),
2893 slot_number,
2894 chain_id: chain_config.chain_id.into(),
2895 base_fee_per_gas: header.base_fee_per_gas.unwrap_or_default().into(),
2896 base_blob_fee_per_gas: get_base_fee_per_blob_gas(block_excess_blob_gas, &config)?,
2897 gas_price,
2898 block_excess_blob_gas,
2899 block_blob_gas_used: header.blob_gas_used,
2900 tx_blob_hashes: tx.blob_versioned_hashes.clone(),
2901 tx_max_priority_fee_per_gas: tx.max_priority_fee_per_gas.map(U256::from),
2902 tx_max_fee_per_gas: tx.max_fee_per_gas.map(U256::from),
2903 tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
2904 tx_nonce: tx.nonce.unwrap_or_default(),
2905 block_gas_limit: header.gas_limit,
2906 difficulty: header.difficulty,
2907 is_privileged: false,
2908 fee_token: tx.fee_token,
2909 disable_balance_check: false,
2910 is_system_call: false,
2911 })
2912}
2913
2914fn generic_tx_to_transaction(tx: &GenericTransaction) -> Result<Transaction, VMError> {
2919 Ok(match &tx.authorization_list {
2920 Some(authorization_list) => Transaction::EIP7702Transaction(EIP7702Transaction {
2921 to: match tx.to {
2922 TxKind::Call(to) => to,
2923 TxKind::Create => {
2924 return Err(InternalError::msg("Generic Tx cannot be create type").into());
2925 }
2926 },
2927 value: tx.value,
2928 data: tx.input.clone(),
2929 access_list: tx
2930 .access_list
2931 .iter()
2932 .map(|list| (list.address, list.storage_keys.clone()))
2933 .collect(),
2934 authorization_list: authorization_list
2935 .iter()
2936 .map(|auth| Into::<AuthorizationTuple>::into(auth.clone()))
2937 .collect(),
2938 ..Default::default()
2939 }),
2940 None => Transaction::EIP1559Transaction(EIP1559Transaction {
2941 to: tx.to.clone(),
2942 value: tx.value,
2943 data: tx.input.clone(),
2944 access_list: tx
2945 .access_list
2946 .iter()
2947 .map(|list| (list.address, list.storage_keys.clone()))
2948 .collect(),
2949 ..Default::default()
2950 }),
2951 })
2952}
2953
2954fn vm_from_generic<'a>(
2955 tx: &'a Transaction,
2956 env: Environment,
2957 db: &'a mut GeneralizedDatabase,
2958 vm_type: VMType,
2959 crypto: &'a dyn Crypto,
2960) -> Result<VM<'a>, VMError> {
2961 let vm_type = adjust_disabled_l2_fees(&env, vm_type);
2962 VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)
2963}
2964
2965pub fn get_max_allowed_gas_limit(block_gas_limit: u64, fork: Fork) -> u64 {
2966 if fork >= Fork::Osaka {
2967 POST_OSAKA_GAS_LIMIT_CAP
2968 } else {
2969 block_gas_limit
2970 }
2971}
2972
2973#[allow(dead_code)]
2981fn describe_balance_diff(expected: U256, actual: U256) -> String {
2982 let (sign, mag) = if expected >= actual {
2983 ("+", expected - actual)
2984 } else {
2985 ("-", actual - expected)
2986 };
2987 let Ok(mag_u128) = u128::try_from(mag) else {
2988 return format!("{sign}{mag}");
2989 };
2990 if mag_u128 == 0 {
2991 return "0".to_string();
2992 }
2993 let cpsb: u128 = 1530;
2994 let consts = [
2996 ("NEW_ACCOUNT", 120u128),
2997 ("STORAGE_SET", 64),
2998 ("AUTH_BASE", 23),
2999 ("AUTH_TOTAL", 143),
3000 ];
3001 for &gp in &[10u128, 1, 7, 100, 1000, 1_000_000_000] {
3003 if !mag_u128.is_multiple_of(gp) {
3004 continue;
3005 }
3006 let gas = mag_u128 / gp;
3007 if !gas.is_multiple_of(cpsb) {
3008 continue;
3009 }
3010 let bytes = gas / cpsb;
3011 for (name, c) in consts {
3012 if bytes.is_multiple_of(c) {
3013 let n = bytes / c;
3014 return format!(
3015 "{sign}{mag_u128} wei (= {gas} gas at {gp} wei/gas = {n}× {name}_state_gas)"
3016 );
3017 }
3018 }
3019 }
3020 format!("{sign}{mag_u128} wei")
3021}
3022
3023#[cfg(test)]
3024mod bal_tests {
3025 use super::*;
3026 use ethrex_common::H256;
3027 use ethrex_common::types::AccountState;
3028 use ethrex_common::types::block_access_list::{
3029 AccountChanges, BalanceChange, NonceChange, SlotChange, StorageChange,
3030 };
3031 use ethrex_levm::errors::DatabaseError;
3032
3033 fn addr(byte: u8) -> Address {
3034 let mut a = Address::zero();
3035 a.0[19] = byte;
3036 a
3037 }
3038
3039 struct MockStore {
3041 accounts: FxHashMap<Address, AccountState>,
3042 }
3043
3044 impl MockStore {
3045 fn new() -> Self {
3046 Self {
3047 accounts: FxHashMap::default(),
3048 }
3049 }
3050
3051 fn with_account(mut self, address: Address, state: AccountState) -> Self {
3052 self.accounts.insert(address, state);
3053 self
3054 }
3055 }
3056
3057 impl Database for MockStore {
3058 fn get_account_state(&self, address: Address) -> Result<AccountState, DatabaseError> {
3059 Ok(self.accounts.get(&address).copied().unwrap_or_default())
3060 }
3061 fn get_storage_value(&self, _: Address, _: H256) -> Result<U256, DatabaseError> {
3062 Ok(U256::zero())
3063 }
3064 fn get_block_hash(&self, _: u64) -> Result<H256, DatabaseError> {
3065 Ok(H256::zero())
3066 }
3067 fn get_chain_config(&self) -> Result<ethrex_common::types::ChainConfig, DatabaseError> {
3068 Err(DatabaseError::Custom("not implemented".into()))
3069 }
3070 fn get_account_code(&self, _: H256) -> Result<ethrex_common::types::Code, DatabaseError> {
3071 Ok(ethrex_common::types::Code::from_bytecode(
3072 Bytes::new(),
3073 ðrex_crypto::NativeCrypto,
3074 ))
3075 }
3076 fn get_code_metadata(
3077 &self,
3078 _: H256,
3079 ) -> Result<ethrex_common::types::CodeMetadata, DatabaseError> {
3080 Ok(ethrex_common::types::CodeMetadata { length: 0 })
3081 }
3082 }
3083
3084 #[test]
3085 fn test_bal_to_account_updates_basic() {
3086 let address = addr(1);
3088 let store = MockStore::new().with_account(
3089 address,
3090 AccountState {
3091 balance: U256::from(100),
3092 nonce: 5,
3093 code_hash: *EMPTY_KECCAK_HASH,
3094 storage_root: H256::zero(),
3095 },
3096 );
3097
3098 let bal = BlockAccessList::from_accounts(vec![
3099 AccountChanges::new(address)
3100 .with_balance_changes(vec![
3101 BalanceChange::new(1, U256::from(90)),
3102 BalanceChange::new(2, U256::from(80)),
3103 ])
3104 .with_nonce_changes(vec![NonceChange::new(1, 6)])
3105 .with_storage_changes(vec![SlotChange::with_changes(
3106 U256::from(42),
3107 vec![StorageChange::new(1, U256::from(999))],
3108 )]),
3109 ]);
3110
3111 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3112 assert_eq!(updates.len(), 1);
3113 let u = &updates[0];
3114 assert_eq!(u.address, address);
3115 assert!(!u.removed);
3116 let info = u.info.as_ref().unwrap();
3117 assert_eq!(info.balance, U256::from(80));
3119 assert_eq!(info.nonce, 6);
3120 assert_eq!(info.code_hash, *EMPTY_KECCAK_HASH);
3121 let key = ethrex_common::utils::u256_to_h256(U256::from(42));
3123 assert_eq!(*u.added_storage.get(&key).unwrap(), U256::from(999));
3124 }
3125
3126 #[test]
3127 fn test_bal_to_account_updates_highest_index_wins() {
3128 let address = addr(2);
3130 let store = MockStore::new().with_account(
3131 address,
3132 AccountState {
3133 balance: U256::from(1000),
3134 nonce: 0,
3135 code_hash: *EMPTY_KECCAK_HASH,
3136 storage_root: H256::zero(),
3137 },
3138 );
3139
3140 let bal = BlockAccessList::from_accounts(vec![
3141 AccountChanges::new(address).with_balance_changes(vec![
3142 BalanceChange::new(1, U256::from(900)),
3143 BalanceChange::new(2, U256::from(800)),
3144 BalanceChange::new(3, U256::from(700)),
3145 ]),
3146 ]);
3147
3148 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3149 assert_eq!(updates.len(), 1);
3150 assert_eq!(updates[0].info.as_ref().unwrap().balance, U256::from(700));
3151 }
3152
3153 #[test]
3154 fn test_bal_to_account_updates_reads_only_skipped() {
3155 let address = addr(3);
3157 let store = MockStore::new();
3158
3159 let bal = BlockAccessList::from_accounts(vec![
3160 AccountChanges::new(address).with_storage_reads(vec![U256::from(1)]),
3161 ]);
3162
3163 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3164 assert!(updates.is_empty());
3165 }
3166
3167 #[test]
3168 fn test_bal_to_account_updates_removal() {
3169 let address = addr(4);
3171 let store = MockStore::new().with_account(
3172 address,
3173 AccountState {
3174 balance: U256::from(50),
3175 nonce: 1,
3176 code_hash: *EMPTY_KECCAK_HASH,
3177 storage_root: H256::zero(),
3178 },
3179 );
3180
3181 let bal = BlockAccessList::from_accounts(vec![
3182 AccountChanges::new(address)
3183 .with_balance_changes(vec![BalanceChange::new(1, U256::zero())])
3184 .with_nonce_changes(vec![NonceChange::new(1, 0)]),
3185 ]);
3186
3187 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3188 assert_eq!(updates.len(), 1);
3189 assert!(updates[0].removed);
3190 }
3191
3192 #[test]
3193 fn test_bal_to_account_updates_storage_zero() {
3194 let address = addr(5);
3196 let store = MockStore::new();
3197
3198 let bal = BlockAccessList::from_accounts(vec![
3199 AccountChanges::new(address).with_storage_changes(vec![SlotChange::with_changes(
3200 U256::from(7),
3201 vec![StorageChange::new(1, U256::zero())],
3202 )]),
3203 ]);
3204
3205 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3206 assert_eq!(updates.len(), 1);
3207 let key = ethrex_common::utils::u256_to_h256(U256::from(7));
3208 assert_eq!(*updates[0].added_storage.get(&key).unwrap(), U256::zero());
3209 }
3210
3211 #[test]
3212 fn test_bal_to_account_updates_code_deployment() {
3213 let address = addr(6);
3215 let store = MockStore::new();
3216 let code = Bytes::from(vec![0x60, 0x00, 0x60, 0x00, 0xf3]); let expected_hash =
3218 ethrex_common::types::Code::from_bytecode(code.clone(), ðrex_crypto::NativeCrypto)
3219 .hash;
3220
3221 let bal = BlockAccessList::from_accounts(vec![
3222 AccountChanges::new(address)
3223 .with_code_changes(vec![
3224 ethrex_common::types::block_access_list::CodeChange::new(1, code.clone()),
3225 ])
3226 .with_nonce_changes(vec![NonceChange::new(1, 1)]),
3227 ]);
3228
3229 let updates = LEVM::bal_to_account_updates(&bal, &store).unwrap();
3230 assert_eq!(updates.len(), 1);
3231 let u = &updates[0];
3232 assert_eq!(u.info.as_ref().unwrap().code_hash, expected_hash);
3233 assert_eq!(u.code.as_ref().unwrap().code(), &code[..]);
3234 }
3235}
3236
3237#[cfg(test)]
3238mod system_call_coinbase_tests {
3239 use super::*;
3245 use ethrex_common::types::{AccountState, AccountUpdate, ChainConfig, Code, CodeMetadata};
3246 use ethrex_crypto::NativeCrypto;
3247 use ethrex_levm::db::Database;
3248 use ethrex_levm::errors::DatabaseError;
3249 use std::sync::Arc;
3250
3251 const HISTORY_RUNTIME_CODE: &str = concat!(
3253 "3373fffffffffffffffffffffffffffffffffffffffe1460465760203603604257",
3254 "5f35600143038111604257611fff81430311604257611fff900654",
3255 "5f5260205ff35b5f5ffd5b5f35611fff60014303065500",
3256 );
3257
3258 struct Store {
3259 chain_config: ChainConfig,
3260 history_code: Code,
3261 }
3262
3263 impl Database for Store {
3264 fn get_account_state(&self, address: Address) -> Result<AccountState, DatabaseError> {
3265 if address == HISTORY_STORAGE_ADDRESS.address {
3266 return Ok(AccountState {
3267 nonce: 1,
3268 code_hash: self.history_code.hash,
3269 ..Default::default()
3270 });
3271 }
3272 Ok(AccountState::default())
3273 }
3274 fn get_storage_value(&self, _: Address, _: H256) -> Result<U256, DatabaseError> {
3275 Ok(U256::zero())
3276 }
3277 fn get_block_hash(&self, _: u64) -> Result<H256, DatabaseError> {
3278 Ok(H256::zero())
3279 }
3280 fn get_chain_config(&self) -> Result<ChainConfig, DatabaseError> {
3281 Ok(self.chain_config)
3282 }
3283 fn get_account_code(&self, code_hash: H256) -> Result<Code, DatabaseError> {
3284 if code_hash == self.history_code.hash {
3285 return Ok(self.history_code.clone());
3286 }
3287 Ok(Code::default())
3288 }
3289 fn get_code_metadata(&self, code_hash: H256) -> Result<CodeMetadata, DatabaseError> {
3290 let length = if code_hash == self.history_code.hash {
3291 self.history_code.len() as u64
3292 } else {
3293 0
3294 };
3295 Ok(CodeMetadata { length })
3296 }
3297 }
3298
3299 fn history_code() -> Code {
3300 let bytes = hex::decode(HISTORY_RUNTIME_CODE).expect("history runtime code is valid hex");
3301 Code::from_bytecode(Bytes::from(bytes), &NativeCrypto)
3302 }
3303
3304 fn prague_db() -> GeneralizedDatabase {
3305 GeneralizedDatabase::new(Arc::new(Store {
3306 chain_config: ChainConfig {
3307 prague_time: Some(0),
3308 ..Default::default()
3309 },
3310 history_code: history_code(),
3311 }))
3312 }
3313
3314 fn parent_hash_value(parent_hash: H256) -> U256 {
3315 U256::from_big_endian(parent_hash.as_bytes())
3316 }
3317
3318 fn history_slot(block_number: u64) -> H256 {
3319 H256::from_low_u64_be((block_number - 1) % 8191)
3320 }
3321
3322 fn run_history_update(coinbase: Address) -> (Option<U256>, Vec<AccountUpdate>, H256) {
3326 let mut db = prague_db();
3327 let parent_hash = H256::from_low_u64_be(0x2935);
3328 let block_number = 42;
3329 let header = BlockHeader {
3330 parent_hash,
3331 coinbase,
3332 number: block_number,
3333 timestamp: 1,
3334 ..Default::default()
3335 };
3336
3337 LEVM::process_block_hash_history(&header, &mut db, VMType::L1, &NativeCrypto)
3338 .expect("history system call executes");
3339
3340 let slot = history_slot(block_number);
3341 let stored_value = db
3342 .current_accounts_state
3343 .get(&HISTORY_STORAGE_ADDRESS.address)
3344 .and_then(|account| account.storage.get(&slot).copied());
3345 let updates =
3346 LEVM::get_state_transitions(&mut db).expect("state transitions are generated");
3347
3348 (stored_value, updates, parent_hash)
3349 }
3350
3351 fn assert_history_write_emitted(coinbase: Address) {
3352 let (stored_value, updates, parent_hash) = run_history_update(coinbase);
3353 let slot = history_slot(42);
3354 assert_eq!(
3355 stored_value,
3356 Some(parent_hash_value(parent_hash)),
3357 "history storage must hold the parent hash after the system call"
3358 );
3359 assert!(
3360 updates.iter().any(|update| {
3361 update.address == HISTORY_STORAGE_ADDRESS.address
3362 && update.added_storage.get(&slot) == Some(&parent_hash_value(parent_hash))
3363 }),
3364 "the history-contract storage write must be emitted as a state update"
3365 );
3366 }
3367
3368 #[test]
3369 fn ordinary_coinbase_preserves_history_storage_write() {
3370 assert_history_write_emitted(Address::from_low_u64_be(0xbeef));
3371 }
3372
3373 #[test]
3376 fn history_address_coinbase_preserves_history_storage_write() {
3377 assert_history_write_emitted(HISTORY_STORAGE_ADDRESS.address);
3378 }
3379}