1use ethrex_common::constants::EMPTY_KECCAK_HASH;
2use ethrex_common::tracing::{PrePostState, PrestateAccountState, PrestateResult, PrestateTrace};
3use ethrex_common::types::{Block, Fork, GenericTransaction, Transaction};
4use ethrex_common::{
5 Address, BigEndianHash, H256, U256,
6 tracing::{CallTrace, OpcodeTraceResult},
7 types::BlockHeader,
8};
9use ethrex_crypto::Crypto;
10use ethrex_levm::StatelessValidator;
11use ethrex_levm::account::{AccountStatus, LevmAccount};
12use ethrex_levm::db::gen_db::CacheDB;
13use ethrex_levm::utils::get_base_fee_per_blob_gas;
14use ethrex_levm::vm::VMType;
15use ethrex_levm::{
16 EVMConfig, Environment,
17 db::gen_db::GeneralizedDatabase,
18 tracing::{LevmCallTracer, LevmOpcodeTracer, OpcodeTracerConfig},
19 vm::VM,
20};
21
22use crate::backends::levm::{
23 adjust_disabled_base_fee, env_from_generic, generic_tx_to_transaction,
24};
25use crate::{EvmError, backends::levm::LEVM};
26
27impl LEVM {
28 pub fn rerun_block(
31 db: &mut GeneralizedDatabase,
32 block: &Block,
33 stop_index: Option<usize>,
34 vm_type: VMType,
35 crypto: &dyn Crypto,
36 stateless_validator: Option<&dyn StatelessValidator>,
37 ) -> Result<(), EvmError> {
38 Self::prepare_block(block, db, vm_type, crypto)?;
39
40 for (index, (tx, sender)) in block
42 .body
43 .get_transactions_with_sender(crypto)
44 .map_err(|error| EvmError::Transaction(error.to_string()))?
45 .into_iter()
46 .enumerate()
47 {
48 if stop_index.is_some_and(|stop| stop == index) {
49 break;
50 }
51
52 Self::execute_tx(
53 tx,
54 sender,
55 &block.header,
56 db,
57 vm_type,
58 crypto,
59 stateless_validator,
60 )?;
61 }
62
63 if stop_index.is_none()
65 && let Some(withdrawals) = &block.body.withdrawals
66 {
67 Self::process_withdrawals(db, withdrawals)?;
68 };
69
70 Ok(())
71 }
72
73 pub fn trace_tx_prestate(
78 db: &mut GeneralizedDatabase,
79 block_header: &BlockHeader,
80 tx: &Transaction,
81 diff_mode: bool,
82 include_empty: bool,
83 vm_type: VMType,
84 crypto: &dyn Crypto,
85 ) -> Result<PrestateResult, EvmError> {
86 let sender = tx
87 .sender(crypto)
88 .map_err(|e| EvmError::Transaction(format!("Couldn't recover sender: {e}")))?;
89 let env = Self::setup_env(tx, sender, block_header, db, vm_type)?;
90 Self::run_prestate_trace(db, env, tx, diff_mode, include_empty, vm_type, crypto)
91 }
92
93 pub fn trace_call_prestate(
96 db: &mut GeneralizedDatabase,
97 block_header: &BlockHeader,
98 tx: &GenericTransaction,
99 diff_mode: bool,
100 include_empty: bool,
101 vm_type: VMType,
102 crypto: &dyn Crypto,
103 ) -> Result<PrestateResult, EvmError> {
104 let (env, converted) = prepare_call_env(tx, block_header, db, vm_type)?;
105 Self::run_prestate_trace(
106 db,
107 env,
108 &converted,
109 diff_mode,
110 include_empty,
111 vm_type,
112 crypto,
113 )
114 }
115
116 fn run_prestate_trace(
119 db: &mut GeneralizedDatabase,
120 env: Environment,
121 tx: &Transaction,
122 diff_mode: bool,
123 include_empty: bool,
124 vm_type: VMType,
125 crypto: &dyn Crypto,
126 ) -> Result<PrestateResult, EvmError> {
127 let pre_snapshot: CacheDB = db.current_accounts_state.clone();
128
129 let mut vm = VM::new(
130 env,
131 db,
132 tx,
133 LevmCallTracer::disabled(),
134 vm_type,
135 crypto,
136 None,
137 )?;
138 vm.execute()?;
139
140 preload_touched_codes(&pre_snapshot, db)?;
141
142 let mut pre_map = build_pre_state_map(&pre_snapshot, &db.current_accounts_state, db)?;
143
144 if diff_mode {
145 let (post_map, kept) =
146 build_post_state_map(&pre_snapshot, &db.current_accounts_state, db)?;
147 filter_diff_pre_storage(&mut pre_map, &db.current_accounts_state);
148 pre_map.retain(|addr, _| kept.contains(addr));
149 pre_map.retain(|_, state| !state.is_empty());
150 Ok(PrestateResult::Diff(PrePostState {
151 pre: pre_map,
152 post: post_map,
153 }))
154 } else {
155 if !include_empty {
156 pre_map.retain(|_, state| !state.is_empty());
157 }
158 Ok(PrestateResult::Prestate(pre_map))
159 }
160 }
161
162 pub fn trace_tx_opcodes(
164 db: &mut GeneralizedDatabase,
165 block_header: &BlockHeader,
166 tx: &Transaction,
167 cfg: OpcodeTracerConfig,
168 vm_type: VMType,
169 crypto: &dyn Crypto,
170 ) -> Result<OpcodeTraceResult, EvmError> {
171 let env = Self::setup_env(
172 tx,
173 tx.sender(crypto).map_err(|error| {
174 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
175 })?,
176 block_header,
177 db,
178 vm_type,
179 )?;
180 Self::run_opcode_trace(db, env, tx, cfg, vm_type, crypto)
181 }
182
183 pub fn trace_call_opcodes(
185 db: &mut GeneralizedDatabase,
186 block_header: &BlockHeader,
187 tx: &GenericTransaction,
188 cfg: OpcodeTracerConfig,
189 vm_type: VMType,
190 crypto: &dyn Crypto,
191 ) -> Result<OpcodeTraceResult, EvmError> {
192 let (env, converted) = prepare_call_env(tx, block_header, db, vm_type)?;
193 Self::run_opcode_trace(db, env, &converted, cfg, vm_type, crypto)
194 }
195
196 fn run_opcode_trace(
199 db: &mut GeneralizedDatabase,
200 env: Environment,
201 tx: &Transaction,
202 cfg: OpcodeTracerConfig,
203 vm_type: VMType,
204 crypto: &dyn Crypto,
205 ) -> Result<OpcodeTraceResult, EvmError> {
206 let mut vm = VM::new(
207 env,
208 db,
209 tx,
210 LevmCallTracer::disabled(),
211 vm_type,
212 crypto,
213 None,
214 )?;
215 vm.opcode_tracer = LevmOpcodeTracer::new(cfg);
216 vm.execute()?;
217 Ok(vm.opcode_tracer.take_result())
218 }
219
220 #[expect(clippy::too_many_arguments)]
224 pub fn trace_tx_calls(
225 db: &mut GeneralizedDatabase,
226 block_header: &BlockHeader,
227 tx: &Transaction,
228 only_top_call: bool,
229 with_log: bool,
230 log_index_base: u64,
231 vm_type: VMType,
232 crypto: &dyn Crypto,
233 stateless_validator: Option<&dyn StatelessValidator>,
234 ) -> Result<CallTrace, EvmError> {
235 let env = Self::setup_env(
236 tx,
237 tx.sender(crypto).map_err(|error| {
238 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
239 })?,
240 block_header,
241 db,
242 vm_type,
243 )?;
244 Self::run_call_trace(
245 db,
246 env,
247 tx,
248 only_top_call,
249 with_log,
250 log_index_base,
251 vm_type,
252 crypto,
253 stateless_validator,
254 )
255 .map(|(trace, _)| trace)
256 }
257
258 #[allow(clippy::too_many_arguments)]
260 pub fn trace_call_calls(
261 db: &mut GeneralizedDatabase,
262 block_header: &BlockHeader,
263 tx: &GenericTransaction,
264 only_top_call: bool,
265 with_log: bool,
266 log_index_base: u64,
267 vm_type: VMType,
268 crypto: &dyn Crypto,
269 ) -> Result<CallTrace, EvmError> {
270 let (env, converted) = prepare_call_env(tx, block_header, db, vm_type)?;
271 Self::run_call_trace(
272 db,
273 env,
274 &converted,
275 only_top_call,
276 with_log,
277 log_index_base,
278 vm_type,
279 crypto,
280 None,
281 )
282 .map(|(trace, _)| trace)
283 }
284
285 #[expect(clippy::too_many_arguments)]
289 fn run_call_trace(
290 db: &mut GeneralizedDatabase,
291 env: Environment,
292 tx: &Transaction,
293 only_top_call: bool,
294 with_log: bool,
295 log_index_base: u64,
296 vm_type: VMType,
297 crypto: &dyn Crypto,
298 stateless_validator: Option<&dyn StatelessValidator>,
299 ) -> Result<(CallTrace, u64), EvmError> {
300 let mut vm = VM::new(
301 env,
302 db,
303 tx,
304 LevmCallTracer::new(only_top_call, with_log, log_index_base),
305 vm_type,
306 crypto,
307 stateless_validator,
308 )?;
309
310 let report = vm.execute()?;
311 let fork = vm.env.config.fork;
312
313 let mut callframe = vm.get_trace_result()?;
314
315 if fork >= Fork::Amsterdam && report.frame_results.is_none() {
322 debug_assert!(
325 report.state_gas_used <= report.gas_used,
326 "state_gas_used ({}) exceeds gas_used ({})",
327 report.state_gas_used,
328 report.gas_used
329 );
330 callframe.regular_gas_used =
331 Some(report.gas_used.saturating_sub(report.state_gas_used));
332 callframe.state_gas_used = Some(report.state_gas_used);
333 callframe.gas_refund = Some(report.gas_refunded);
334 }
335
336 Ok((vec![callframe], vm.tracer.next_log_index))
338 }
339
340 pub fn trace_block_calls(
346 db: &mut GeneralizedDatabase,
347 block: &Block,
348 only_top_call: bool,
349 with_log: bool,
350 vm_type: VMType,
351 crypto: &dyn Crypto,
352 ) -> Result<Vec<(H256, CallTrace)>, EvmError> {
353 Self::rerun_block(db, block, Some(0), vm_type, crypto, None)?;
354 let (config, chain_id, base_blob_fee) = block_trace_env_config(db, &block.header)?;
355 let mut traces = Vec::with_capacity(block.body.transactions.len());
356 let mut log_index_base = 0u64;
359 for (tx, sender) in block
360 .body
361 .get_transactions_with_sender(crypto)
362 .map_err(|e| EvmError::Transaction(e.to_string()))?
363 {
364 let env = Self::setup_env_with_config(
365 tx,
366 sender,
367 &block.header,
368 config,
369 chain_id,
370 vm_type,
371 base_blob_fee,
372 )?;
373 let (trace, next_log_index) = Self::run_call_trace(
374 db,
375 env,
376 tx,
377 only_top_call,
378 with_log,
379 log_index_base,
380 vm_type,
381 crypto,
382 None,
383 )?;
384 log_index_base = next_log_index;
385 traces.push((tx.hash(crypto), trace));
386 }
387 Ok(traces)
388 }
389
390 pub fn trace_block_prestate(
393 db: &mut GeneralizedDatabase,
394 block: &Block,
395 diff_mode: bool,
396 include_empty: bool,
397 vm_type: VMType,
398 crypto: &dyn Crypto,
399 ) -> Result<Vec<(H256, PrestateResult)>, EvmError> {
400 Self::rerun_block(db, block, Some(0), vm_type, crypto, None)?;
401 let (config, chain_id, base_blob_fee) = block_trace_env_config(db, &block.header)?;
402 let mut traces = Vec::with_capacity(block.body.transactions.len());
403 for (tx, sender) in block
404 .body
405 .get_transactions_with_sender(crypto)
406 .map_err(|e| EvmError::Transaction(e.to_string()))?
407 {
408 let env = Self::setup_env_with_config(
409 tx,
410 sender,
411 &block.header,
412 config,
413 chain_id,
414 vm_type,
415 base_blob_fee,
416 )?;
417 let trace =
418 Self::run_prestate_trace(db, env, tx, diff_mode, include_empty, vm_type, crypto)?;
419 traces.push((tx.hash(crypto), trace));
420 }
421 Ok(traces)
422 }
423
424 pub fn trace_block_opcodes(
427 db: &mut GeneralizedDatabase,
428 block: &Block,
429 cfg: OpcodeTracerConfig,
430 vm_type: VMType,
431 crypto: &dyn Crypto,
432 ) -> Result<Vec<(H256, OpcodeTraceResult)>, EvmError> {
433 Self::rerun_block(db, block, Some(0), vm_type, crypto, None)?;
434 let (config, chain_id, base_blob_fee) = block_trace_env_config(db, &block.header)?;
435 let mut traces = Vec::with_capacity(block.body.transactions.len());
436 for (tx, sender) in block
437 .body
438 .get_transactions_with_sender(crypto)
439 .map_err(|e| EvmError::Transaction(e.to_string()))?
440 {
441 let env = Self::setup_env_with_config(
442 tx,
443 sender,
444 &block.header,
445 config,
446 chain_id,
447 vm_type,
448 base_blob_fee,
449 )?;
450 let trace = Self::run_opcode_trace(db, env, tx, cfg.clone(), vm_type, crypto)?;
451 traces.push((tx.hash(crypto), trace));
452 }
453 Ok(traces)
454 }
455}
456
457fn block_trace_env_config(
460 db: &GeneralizedDatabase,
461 header: &BlockHeader,
462) -> Result<(EVMConfig, u64, U256), EvmError> {
463 let chain_config = db.store.get_chain_config()?;
464 let config = EVMConfig::new_from_chain_config(&chain_config, header);
465 let base_blob_fee = get_base_fee_per_blob_gas(header.excess_blob_gas, &config)?;
466 Ok((config, chain_config.chain_id, base_blob_fee))
467}
468
469fn prepare_call_env(
475 tx: &GenericTransaction,
476 block_header: &BlockHeader,
477 db: &GeneralizedDatabase,
478 vm_type: VMType,
479) -> Result<(Environment, Transaction), EvmError> {
480 let mut env = env_from_generic(tx, block_header, db, vm_type)?;
483 env.disable_execution_gas_cap = true;
486 adjust_disabled_base_fee(&mut env);
487 let converted_tx = generic_tx_to_transaction(tx)?;
488 Ok((env, converted_tx))
489}
490
491fn find_touched_accounts<'a>(
495 pre_snapshot: &'a CacheDB,
496 post_cache: &'a CacheDB,
497 db: &'a GeneralizedDatabase,
498) -> Vec<(Address, &'a LevmAccount, &'a LevmAccount)> {
499 let mut touched = Vec::new();
500
501 for (addr, post_account) in post_cache {
502 let pre_account = match pre_snapshot.get(addr) {
503 Some(pre) => pre,
504 None => {
505 let Some(initial) = db.initial_accounts_state.get(addr) else {
506 continue;
507 };
508 initial
509 }
510 };
511
512 touched.push((*addr, pre_account, post_account));
513 }
514
515 touched
516}
517
518fn build_account_output(
522 account: &LevmAccount,
523 db: &GeneralizedDatabase,
524) -> Result<PrestateAccountState, EvmError> {
525 let has_code = account.info.code_hash != *EMPTY_KECCAK_HASH;
526 let code = if has_code {
527 get_preloaded_code(db, &account.info.code_hash)?
528 } else {
529 bytes::Bytes::new()
530 };
531 let code_hash = if has_code {
532 account.info.code_hash
533 } else {
534 H256::zero()
535 };
536
537 let storage = account
538 .storage
539 .iter()
540 .map(|(k, v)| (*k, H256::from_uint(v)))
541 .collect();
542
543 Ok(PrestateAccountState {
544 balance: Some(account.info.balance),
545 nonce: account.info.nonce,
546 code,
547 code_hash,
548 storage,
549 })
550}
551
552fn get_preloaded_code(db: &GeneralizedDatabase, hash: &H256) -> Result<bytes::Bytes, EvmError> {
554 db.codes
555 .get(hash)
556 .map(|c| c.code_bytes())
557 .ok_or_else(|| EvmError::Custom(format!("missing preloaded code for {hash:?}")))
558}
559
560fn build_post_output(
564 addr: Address,
565 pre_account: &LevmAccount,
566 post_account: &LevmAccount,
567 pre_snapshot: &CacheDB,
568 db: &GeneralizedDatabase,
569) -> Result<Option<PrestateAccountState>, EvmError> {
570 let mut state = PrestateAccountState::default();
571 let mut modified = false;
572
573 if pre_account.info.balance != post_account.info.balance {
574 state.balance = Some(post_account.info.balance);
575 modified = true;
576 }
577 if pre_account.info.nonce != post_account.info.nonce {
578 state.nonce = post_account.info.nonce;
579 modified = true;
580 }
581 if pre_account.info.code_hash != post_account.info.code_hash {
582 if post_account.info.code_hash != *EMPTY_KECCAK_HASH {
583 state.code_hash = post_account.info.code_hash;
584 state.code = get_preloaded_code(db, &post_account.info.code_hash)?;
585 }
586 modified = true;
587 }
588
589 for (key, post_val) in &post_account.storage {
590 let pre_val = pre_storage_value(addr, key, pre_snapshot, db).unwrap_or_default();
591 if pre_val == *post_val {
592 continue;
593 }
594 modified = true;
595 if !post_val.is_zero() {
597 state.storage.insert(*key, H256::from_uint(post_val));
598 }
599 }
600
601 Ok(modified.then_some(state))
602}
603
604fn pre_storage_value(
607 addr: Address,
608 slot: &H256,
609 pre_snapshot: &CacheDB,
610 db: &GeneralizedDatabase,
611) -> Option<U256> {
612 if let Some(account) = pre_snapshot.get(&addr)
613 && let Some(value) = account.storage.get(slot)
614 {
615 return Some(*value);
616 }
617 db.initial_accounts_state
618 .get(&addr)
619 .and_then(|a| a.storage.get(slot).copied())
620}
621
622fn build_pre_state_map(
630 pre_snapshot: &CacheDB,
631 post_cache: &CacheDB,
632 db: &GeneralizedDatabase,
633) -> Result<PrestateTrace, EvmError> {
634 let mut result = PrestateTrace::new();
635
636 for (addr, pre_account, post_account) in find_touched_accounts(pre_snapshot, post_cache, db) {
637 let mut state = build_account_output(pre_account, db)?;
638
639 if pre_snapshot.contains_key(&addr)
644 && let Some(initial) = db.initial_accounts_state.get(&addr)
645 {
646 for (k, v) in &initial.storage {
647 state
648 .storage
649 .entry(*k)
650 .or_insert_with(|| H256::from_uint(v));
651 }
652 }
653
654 let pre_cached_storage = pre_snapshot.get(&addr).map(|a| &a.storage);
655 state.storage.retain(|k, _| {
656 if !post_account.storage.contains_key(k) {
657 return false;
658 }
659 match pre_cached_storage {
660 Some(pre) if pre.contains_key(k) => pre.get(k) != post_account.storage.get(k),
661 _ => true,
662 }
663 });
664
665 result.insert(addr, state);
666 }
667
668 Ok(result)
669}
670
671fn preload_touched_codes(
675 pre_snapshot: &CacheDB,
676 db: &mut GeneralizedDatabase,
677) -> Result<(), EvmError> {
678 let hashes: Vec<H256> = db
679 .current_accounts_state
680 .iter()
681 .flat_map(|(addr, post)| {
682 let pre_hash = pre_snapshot
683 .get(addr)
684 .or_else(|| db.initial_accounts_state.get(addr))
685 .map(|a| a.info.code_hash)
686 .unwrap_or_default();
687 [post.info.code_hash, pre_hash]
688 })
689 .filter(|h| *h != *EMPTY_KECCAK_HASH)
690 .collect();
691
692 for hash in hashes {
693 db.get_code(hash)?;
694 }
695 Ok(())
696}
697
698fn build_post_state_map(
701 pre_snapshot: &CacheDB,
702 post_cache: &CacheDB,
703 db: &GeneralizedDatabase,
704) -> Result<(PrestateTrace, std::collections::HashSet<Address>), EvmError> {
705 let mut post = PrestateTrace::new();
706 let mut modified_or_destroyed = std::collections::HashSet::new();
707
708 for (addr, pre_account, post_account) in find_touched_accounts(pre_snapshot, post_cache, db) {
709 if matches!(
710 post_account.status,
711 AccountStatus::Destroyed | AccountStatus::DestroyedModified,
712 ) {
713 modified_or_destroyed.insert(addr);
714 continue;
715 }
716
717 if let Some(state) = build_post_output(addr, pre_account, post_account, pre_snapshot, db)? {
718 modified_or_destroyed.insert(addr);
719 post.insert(addr, state);
720 }
721 }
722
723 Ok((post, modified_or_destroyed))
724}
725
726fn filter_diff_pre_storage(pre: &mut PrestateTrace, post_cache: &CacheDB) {
729 for (addr, state) in pre.iter_mut() {
730 let post_storage = post_cache.get(addr).map(|a| &a.storage);
731 state.storage.retain(|k, v| {
732 if v.is_zero() {
733 return false;
734 }
735 let post_val = post_storage
736 .and_then(|s| s.get(k).copied())
737 .unwrap_or_default();
738 *v != H256::from_uint(&post_val)
739 });
740 }
741}