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, CallTraceFrame, 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 }
256
257 #[allow(clippy::too_many_arguments)]
259 pub fn trace_call_calls(
260 db: &mut GeneralizedDatabase,
261 block_header: &BlockHeader,
262 tx: &GenericTransaction,
263 only_top_call: bool,
264 with_log: bool,
265 log_index_base: u64,
266 vm_type: VMType,
267 crypto: &dyn Crypto,
268 ) -> Result<CallTrace, EvmError> {
269 let (env, converted) = prepare_call_env(tx, block_header, db, vm_type)?;
270 Self::run_call_trace(
271 db,
272 env,
273 &converted,
274 only_top_call,
275 with_log,
276 log_index_base,
277 vm_type,
278 crypto,
279 None,
280 )
281 }
282
283 #[expect(clippy::too_many_arguments)]
286 fn run_call_trace(
287 db: &mut GeneralizedDatabase,
288 env: Environment,
289 tx: &Transaction,
290 only_top_call: bool,
291 with_log: bool,
292 log_index_base: u64,
293 vm_type: VMType,
294 crypto: &dyn Crypto,
295 stateless_validator: Option<&dyn StatelessValidator>,
296 ) -> Result<CallTrace, EvmError> {
297 let mut vm = VM::new(
298 env,
299 db,
300 tx,
301 LevmCallTracer::new(only_top_call, with_log, log_index_base),
302 vm_type,
303 crypto,
304 stateless_validator,
305 )?;
306
307 let report = vm.execute()?;
308 let fork = vm.env.config.fork;
309
310 let mut callframe = vm.get_trace_result()?;
311
312 if fork >= Fork::Amsterdam && report.frame_results.is_none() {
319 debug_assert!(
322 report.state_gas_used <= report.gas_used,
323 "state_gas_used ({}) exceeds gas_used ({})",
324 report.state_gas_used,
325 report.gas_used
326 );
327 callframe.regular_gas_used =
328 Some(report.gas_used.saturating_sub(report.state_gas_used));
329 callframe.state_gas_used = Some(report.state_gas_used);
330 callframe.gas_refund = Some(report.gas_refunded);
331 }
332
333 Ok(vec![callframe])
335 }
336
337 pub fn trace_block_calls(
343 db: &mut GeneralizedDatabase,
344 block: &Block,
345 only_top_call: bool,
346 with_log: bool,
347 vm_type: VMType,
348 crypto: &dyn Crypto,
349 ) -> Result<Vec<(H256, CallTrace)>, EvmError> {
350 Self::rerun_block(db, block, Some(0), vm_type, crypto, None)?;
351 let (config, chain_id, base_blob_fee) = block_trace_env_config(db, &block.header)?;
352 let mut traces = Vec::with_capacity(block.body.transactions.len());
353 let mut log_index_base = 0u64;
356 for (tx, sender) in block
357 .body
358 .get_transactions_with_sender(crypto)
359 .map_err(|e| EvmError::Transaction(e.to_string()))?
360 {
361 let env = Self::setup_env_with_config(
362 tx,
363 sender,
364 &block.header,
365 config,
366 chain_id,
367 vm_type,
368 base_blob_fee,
369 )?;
370 let trace = Self::run_call_trace(
371 db,
372 env,
373 tx,
374 only_top_call,
375 with_log,
376 log_index_base,
377 vm_type,
378 crypto,
379 None,
380 )?;
381 log_index_base = log_index_base.saturating_add(trace.iter().map(count_call_logs).sum());
382 traces.push((tx.hash(crypto), trace));
383 }
384 Ok(traces)
385 }
386
387 pub fn trace_block_prestate(
390 db: &mut GeneralizedDatabase,
391 block: &Block,
392 diff_mode: bool,
393 include_empty: bool,
394 vm_type: VMType,
395 crypto: &dyn Crypto,
396 ) -> Result<Vec<(H256, PrestateResult)>, EvmError> {
397 Self::rerun_block(db, block, Some(0), vm_type, crypto, None)?;
398 let (config, chain_id, base_blob_fee) = block_trace_env_config(db, &block.header)?;
399 let mut traces = Vec::with_capacity(block.body.transactions.len());
400 for (tx, sender) in block
401 .body
402 .get_transactions_with_sender(crypto)
403 .map_err(|e| EvmError::Transaction(e.to_string()))?
404 {
405 let env = Self::setup_env_with_config(
406 tx,
407 sender,
408 &block.header,
409 config,
410 chain_id,
411 vm_type,
412 base_blob_fee,
413 )?;
414 let trace =
415 Self::run_prestate_trace(db, env, tx, diff_mode, include_empty, vm_type, crypto)?;
416 traces.push((tx.hash(crypto), trace));
417 }
418 Ok(traces)
419 }
420
421 pub fn trace_block_opcodes(
424 db: &mut GeneralizedDatabase,
425 block: &Block,
426 cfg: OpcodeTracerConfig,
427 vm_type: VMType,
428 crypto: &dyn Crypto,
429 ) -> Result<Vec<(H256, OpcodeTraceResult)>, EvmError> {
430 Self::rerun_block(db, block, Some(0), vm_type, crypto, None)?;
431 let (config, chain_id, base_blob_fee) = block_trace_env_config(db, &block.header)?;
432 let mut traces = Vec::with_capacity(block.body.transactions.len());
433 for (tx, sender) in block
434 .body
435 .get_transactions_with_sender(crypto)
436 .map_err(|e| EvmError::Transaction(e.to_string()))?
437 {
438 let env = Self::setup_env_with_config(
439 tx,
440 sender,
441 &block.header,
442 config,
443 chain_id,
444 vm_type,
445 base_blob_fee,
446 )?;
447 let trace = Self::run_opcode_trace(db, env, tx, cfg.clone(), vm_type, crypto)?;
448 traces.push((tx.hash(crypto), trace));
449 }
450 Ok(traces)
451 }
452}
453
454fn count_call_logs(frame: &CallTraceFrame) -> u64 {
457 let own = u64::try_from(frame.logs.len()).unwrap_or(u64::MAX);
458 frame.calls.iter().fold(own, |acc, subcall| {
459 acc.saturating_add(count_call_logs(subcall))
460 })
461}
462
463fn block_trace_env_config(
466 db: &GeneralizedDatabase,
467 header: &BlockHeader,
468) -> Result<(EVMConfig, u64, U256), EvmError> {
469 let chain_config = db.store.get_chain_config()?;
470 let config = EVMConfig::new_from_chain_config(&chain_config, header);
471 let base_blob_fee = get_base_fee_per_blob_gas(header.excess_blob_gas, &config)?;
472 Ok((config, chain_config.chain_id, base_blob_fee))
473}
474
475fn prepare_call_env(
481 tx: &GenericTransaction,
482 block_header: &BlockHeader,
483 db: &GeneralizedDatabase,
484 vm_type: VMType,
485) -> Result<(Environment, Transaction), EvmError> {
486 let mut env = env_from_generic(tx, block_header, db, vm_type)?;
489 adjust_disabled_base_fee(&mut env);
490 let converted_tx = generic_tx_to_transaction(tx)?;
491 Ok((env, converted_tx))
492}
493
494fn find_touched_accounts<'a>(
498 pre_snapshot: &'a CacheDB,
499 post_cache: &'a CacheDB,
500 db: &'a GeneralizedDatabase,
501) -> Vec<(Address, &'a LevmAccount, &'a LevmAccount)> {
502 let mut touched = Vec::new();
503
504 for (addr, post_account) in post_cache {
505 let pre_account = match pre_snapshot.get(addr) {
506 Some(pre) => pre,
507 None => {
508 let Some(initial) = db.initial_accounts_state.get(addr) else {
509 continue;
510 };
511 initial
512 }
513 };
514
515 touched.push((*addr, pre_account, post_account));
516 }
517
518 touched
519}
520
521fn build_account_output(
525 account: &LevmAccount,
526 db: &GeneralizedDatabase,
527) -> Result<PrestateAccountState, EvmError> {
528 let has_code = account.info.code_hash != *EMPTY_KECCAK_HASH;
529 let code = if has_code {
530 get_preloaded_code(db, &account.info.code_hash)?
531 } else {
532 bytes::Bytes::new()
533 };
534 let code_hash = if has_code {
535 account.info.code_hash
536 } else {
537 H256::zero()
538 };
539
540 let storage = account
541 .storage
542 .iter()
543 .map(|(k, v)| (*k, H256::from_uint(v)))
544 .collect();
545
546 Ok(PrestateAccountState {
547 balance: Some(account.info.balance),
548 nonce: account.info.nonce,
549 code,
550 code_hash,
551 storage,
552 })
553}
554
555fn get_preloaded_code(db: &GeneralizedDatabase, hash: &H256) -> Result<bytes::Bytes, EvmError> {
557 db.codes
558 .get(hash)
559 .map(|c| c.code_bytes())
560 .ok_or_else(|| EvmError::Custom(format!("missing preloaded code for {hash:?}")))
561}
562
563fn build_post_output(
567 addr: Address,
568 pre_account: &LevmAccount,
569 post_account: &LevmAccount,
570 pre_snapshot: &CacheDB,
571 db: &GeneralizedDatabase,
572) -> Result<Option<PrestateAccountState>, EvmError> {
573 let mut state = PrestateAccountState::default();
574 let mut modified = false;
575
576 if pre_account.info.balance != post_account.info.balance {
577 state.balance = Some(post_account.info.balance);
578 modified = true;
579 }
580 if pre_account.info.nonce != post_account.info.nonce {
581 state.nonce = post_account.info.nonce;
582 modified = true;
583 }
584 if pre_account.info.code_hash != post_account.info.code_hash {
585 if post_account.info.code_hash != *EMPTY_KECCAK_HASH {
586 state.code_hash = post_account.info.code_hash;
587 state.code = get_preloaded_code(db, &post_account.info.code_hash)?;
588 }
589 modified = true;
590 }
591
592 for (key, post_val) in &post_account.storage {
593 let pre_val = pre_storage_value(addr, key, pre_snapshot, db).unwrap_or_default();
594 if pre_val == *post_val {
595 continue;
596 }
597 modified = true;
598 if !post_val.is_zero() {
600 state.storage.insert(*key, H256::from_uint(post_val));
601 }
602 }
603
604 Ok(modified.then_some(state))
605}
606
607fn pre_storage_value(
610 addr: Address,
611 slot: &H256,
612 pre_snapshot: &CacheDB,
613 db: &GeneralizedDatabase,
614) -> Option<U256> {
615 if let Some(account) = pre_snapshot.get(&addr)
616 && let Some(value) = account.storage.get(slot)
617 {
618 return Some(*value);
619 }
620 db.initial_accounts_state
621 .get(&addr)
622 .and_then(|a| a.storage.get(slot).copied())
623}
624
625fn build_pre_state_map(
633 pre_snapshot: &CacheDB,
634 post_cache: &CacheDB,
635 db: &GeneralizedDatabase,
636) -> Result<PrestateTrace, EvmError> {
637 let mut result = PrestateTrace::new();
638
639 for (addr, pre_account, post_account) in find_touched_accounts(pre_snapshot, post_cache, db) {
640 let mut state = build_account_output(pre_account, db)?;
641
642 if pre_snapshot.contains_key(&addr)
647 && let Some(initial) = db.initial_accounts_state.get(&addr)
648 {
649 for (k, v) in &initial.storage {
650 state
651 .storage
652 .entry(*k)
653 .or_insert_with(|| H256::from_uint(v));
654 }
655 }
656
657 let pre_cached_storage = pre_snapshot.get(&addr).map(|a| &a.storage);
658 state.storage.retain(|k, _| {
659 if !post_account.storage.contains_key(k) {
660 return false;
661 }
662 match pre_cached_storage {
663 Some(pre) if pre.contains_key(k) => pre.get(k) != post_account.storage.get(k),
664 _ => true,
665 }
666 });
667
668 result.insert(addr, state);
669 }
670
671 Ok(result)
672}
673
674fn preload_touched_codes(
678 pre_snapshot: &CacheDB,
679 db: &mut GeneralizedDatabase,
680) -> Result<(), EvmError> {
681 let hashes: Vec<H256> = db
682 .current_accounts_state
683 .iter()
684 .flat_map(|(addr, post)| {
685 let pre_hash = pre_snapshot
686 .get(addr)
687 .or_else(|| db.initial_accounts_state.get(addr))
688 .map(|a| a.info.code_hash)
689 .unwrap_or_default();
690 [post.info.code_hash, pre_hash]
691 })
692 .filter(|h| *h != *EMPTY_KECCAK_HASH)
693 .collect();
694
695 for hash in hashes {
696 db.get_code(hash)?;
697 }
698 Ok(())
699}
700
701fn build_post_state_map(
704 pre_snapshot: &CacheDB,
705 post_cache: &CacheDB,
706 db: &GeneralizedDatabase,
707) -> Result<(PrestateTrace, std::collections::HashSet<Address>), EvmError> {
708 let mut post = PrestateTrace::new();
709 let mut modified_or_destroyed = std::collections::HashSet::new();
710
711 for (addr, pre_account, post_account) in find_touched_accounts(pre_snapshot, post_cache, db) {
712 if matches!(
713 post_account.status,
714 AccountStatus::Destroyed | AccountStatus::DestroyedModified,
715 ) {
716 modified_or_destroyed.insert(addr);
717 continue;
718 }
719
720 if let Some(state) = build_post_output(addr, pre_account, post_account, pre_snapshot, db)? {
721 modified_or_destroyed.insert(addr);
722 post.insert(addr, state);
723 }
724 }
725
726 Ok((post, modified_or_destroyed))
727}
728
729fn filter_diff_pre_storage(pre: &mut PrestateTrace, post_cache: &CacheDB) {
732 for (addr, state) in pre.iter_mut() {
733 let post_storage = post_cache.get(addr).map(|a| &a.storage);
734 state.storage.retain(|k, v| {
735 if v.is_zero() {
736 return false;
737 }
738 let post_val = post_storage
739 .and_then(|s| s.get(k).copied())
740 .unwrap_or_default();
741 *v != H256::from_uint(&post_val)
742 });
743 }
744}