1use ethrex_common::constants::EMPTY_KECCAK_HASH;
2use ethrex_common::tracing::{PrePostState, PrestateAccountState, PrestateResult, PrestateTrace};
3use ethrex_common::types::{Block, 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 vm.execute()?;
308
309 let callframe = vm.get_trace_result()?;
310
311 Ok(vec![callframe])
313 }
314
315 pub fn trace_block_calls(
321 db: &mut GeneralizedDatabase,
322 block: &Block,
323 only_top_call: bool,
324 with_log: bool,
325 vm_type: VMType,
326 crypto: &dyn Crypto,
327 ) -> Result<Vec<(H256, CallTrace)>, EvmError> {
328 Self::rerun_block(db, block, Some(0), vm_type, crypto, None)?;
329 let (config, chain_id, base_blob_fee) = block_trace_env_config(db, &block.header)?;
330 let mut traces = Vec::with_capacity(block.body.transactions.len());
331 let mut log_index_base = 0u64;
334 for (tx, sender) in block
335 .body
336 .get_transactions_with_sender(crypto)
337 .map_err(|e| EvmError::Transaction(e.to_string()))?
338 {
339 let env = Self::setup_env_with_config(
340 tx,
341 sender,
342 &block.header,
343 config,
344 chain_id,
345 vm_type,
346 base_blob_fee,
347 )?;
348 let trace = Self::run_call_trace(
349 db,
350 env,
351 tx,
352 only_top_call,
353 with_log,
354 log_index_base,
355 vm_type,
356 crypto,
357 None,
358 )?;
359 log_index_base = log_index_base.saturating_add(trace.iter().map(count_call_logs).sum());
360 traces.push((tx.hash(crypto), trace));
361 }
362 Ok(traces)
363 }
364
365 pub fn trace_block_prestate(
368 db: &mut GeneralizedDatabase,
369 block: &Block,
370 diff_mode: bool,
371 include_empty: bool,
372 vm_type: VMType,
373 crypto: &dyn Crypto,
374 ) -> Result<Vec<(H256, PrestateResult)>, EvmError> {
375 Self::rerun_block(db, block, Some(0), vm_type, crypto, None)?;
376 let (config, chain_id, base_blob_fee) = block_trace_env_config(db, &block.header)?;
377 let mut traces = Vec::with_capacity(block.body.transactions.len());
378 for (tx, sender) in block
379 .body
380 .get_transactions_with_sender(crypto)
381 .map_err(|e| EvmError::Transaction(e.to_string()))?
382 {
383 let env = Self::setup_env_with_config(
384 tx,
385 sender,
386 &block.header,
387 config,
388 chain_id,
389 vm_type,
390 base_blob_fee,
391 )?;
392 let trace =
393 Self::run_prestate_trace(db, env, tx, diff_mode, include_empty, vm_type, crypto)?;
394 traces.push((tx.hash(crypto), trace));
395 }
396 Ok(traces)
397 }
398
399 pub fn trace_block_opcodes(
402 db: &mut GeneralizedDatabase,
403 block: &Block,
404 cfg: OpcodeTracerConfig,
405 vm_type: VMType,
406 crypto: &dyn Crypto,
407 ) -> Result<Vec<(H256, OpcodeTraceResult)>, EvmError> {
408 Self::rerun_block(db, block, Some(0), vm_type, crypto, None)?;
409 let (config, chain_id, base_blob_fee) = block_trace_env_config(db, &block.header)?;
410 let mut traces = Vec::with_capacity(block.body.transactions.len());
411 for (tx, sender) in block
412 .body
413 .get_transactions_with_sender(crypto)
414 .map_err(|e| EvmError::Transaction(e.to_string()))?
415 {
416 let env = Self::setup_env_with_config(
417 tx,
418 sender,
419 &block.header,
420 config,
421 chain_id,
422 vm_type,
423 base_blob_fee,
424 )?;
425 let trace = Self::run_opcode_trace(db, env, tx, cfg.clone(), vm_type, crypto)?;
426 traces.push((tx.hash(crypto), trace));
427 }
428 Ok(traces)
429 }
430}
431
432fn count_call_logs(frame: &CallTraceFrame) -> u64 {
435 let own = u64::try_from(frame.logs.len()).unwrap_or(u64::MAX);
436 frame.calls.iter().fold(own, |acc, subcall| {
437 acc.saturating_add(count_call_logs(subcall))
438 })
439}
440
441fn block_trace_env_config(
444 db: &GeneralizedDatabase,
445 header: &BlockHeader,
446) -> Result<(EVMConfig, u64, U256), EvmError> {
447 let chain_config = db.store.get_chain_config()?;
448 let config = EVMConfig::new_from_chain_config(&chain_config, header);
449 let base_blob_fee = get_base_fee_per_blob_gas(header.excess_blob_gas, &config)?;
450 Ok((config, chain_config.chain_id, base_blob_fee))
451}
452
453fn prepare_call_env(
459 tx: &GenericTransaction,
460 block_header: &BlockHeader,
461 db: &GeneralizedDatabase,
462 vm_type: VMType,
463) -> Result<(Environment, Transaction), EvmError> {
464 let mut env = env_from_generic(tx, block_header, db, vm_type)?;
465 env.disable_gas_allowance_check = true;
469 adjust_disabled_base_fee(&mut env);
470 let converted_tx = generic_tx_to_transaction(tx)?;
471 Ok((env, converted_tx))
472}
473
474fn find_touched_accounts<'a>(
478 pre_snapshot: &'a CacheDB,
479 post_cache: &'a CacheDB,
480 db: &'a GeneralizedDatabase,
481) -> Vec<(Address, &'a LevmAccount, &'a LevmAccount)> {
482 let mut touched = Vec::new();
483
484 for (addr, post_account) in post_cache {
485 let pre_account = match pre_snapshot.get(addr) {
486 Some(pre) => pre,
487 None => {
488 let Some(initial) = db.initial_accounts_state.get(addr) else {
489 continue;
490 };
491 initial
492 }
493 };
494
495 touched.push((*addr, pre_account, post_account));
496 }
497
498 touched
499}
500
501fn build_account_output(
505 account: &LevmAccount,
506 db: &GeneralizedDatabase,
507) -> Result<PrestateAccountState, EvmError> {
508 let has_code = account.info.code_hash != *EMPTY_KECCAK_HASH;
509 let code = if has_code {
510 get_preloaded_code(db, &account.info.code_hash)?
511 } else {
512 bytes::Bytes::new()
513 };
514 let code_hash = if has_code {
515 account.info.code_hash
516 } else {
517 H256::zero()
518 };
519
520 let storage = account
521 .storage
522 .iter()
523 .map(|(k, v)| (*k, H256::from_uint(v)))
524 .collect();
525
526 Ok(PrestateAccountState {
527 balance: Some(account.info.balance),
528 nonce: account.info.nonce,
529 code,
530 code_hash,
531 storage,
532 })
533}
534
535fn get_preloaded_code(db: &GeneralizedDatabase, hash: &H256) -> Result<bytes::Bytes, EvmError> {
537 db.codes
538 .get(hash)
539 .map(|c| c.code_bytes())
540 .ok_or_else(|| EvmError::Custom(format!("missing preloaded code for {hash:?}")))
541}
542
543fn build_post_output(
547 addr: Address,
548 pre_account: &LevmAccount,
549 post_account: &LevmAccount,
550 pre_snapshot: &CacheDB,
551 db: &GeneralizedDatabase,
552) -> Result<Option<PrestateAccountState>, EvmError> {
553 let mut state = PrestateAccountState::default();
554 let mut modified = false;
555
556 if pre_account.info.balance != post_account.info.balance {
557 state.balance = Some(post_account.info.balance);
558 modified = true;
559 }
560 if pre_account.info.nonce != post_account.info.nonce {
561 state.nonce = post_account.info.nonce;
562 modified = true;
563 }
564 if pre_account.info.code_hash != post_account.info.code_hash {
565 if post_account.info.code_hash != *EMPTY_KECCAK_HASH {
566 state.code_hash = post_account.info.code_hash;
567 state.code = get_preloaded_code(db, &post_account.info.code_hash)?;
568 }
569 modified = true;
570 }
571
572 for (key, post_val) in &post_account.storage {
573 let pre_val = pre_storage_value(addr, key, pre_snapshot, db).unwrap_or_default();
574 if pre_val == *post_val {
575 continue;
576 }
577 modified = true;
578 if !post_val.is_zero() {
580 state.storage.insert(*key, H256::from_uint(post_val));
581 }
582 }
583
584 Ok(modified.then_some(state))
585}
586
587fn pre_storage_value(
590 addr: Address,
591 slot: &H256,
592 pre_snapshot: &CacheDB,
593 db: &GeneralizedDatabase,
594) -> Option<U256> {
595 if let Some(account) = pre_snapshot.get(&addr)
596 && let Some(value) = account.storage.get(slot)
597 {
598 return Some(*value);
599 }
600 db.initial_accounts_state
601 .get(&addr)
602 .and_then(|a| a.storage.get(slot).copied())
603}
604
605fn build_pre_state_map(
613 pre_snapshot: &CacheDB,
614 post_cache: &CacheDB,
615 db: &GeneralizedDatabase,
616) -> Result<PrestateTrace, EvmError> {
617 let mut result = PrestateTrace::new();
618
619 for (addr, pre_account, post_account) in find_touched_accounts(pre_snapshot, post_cache, db) {
620 let mut state = build_account_output(pre_account, db)?;
621
622 if pre_snapshot.contains_key(&addr)
627 && let Some(initial) = db.initial_accounts_state.get(&addr)
628 {
629 for (k, v) in &initial.storage {
630 state
631 .storage
632 .entry(*k)
633 .or_insert_with(|| H256::from_uint(v));
634 }
635 }
636
637 let pre_cached_storage = pre_snapshot.get(&addr).map(|a| &a.storage);
638 state.storage.retain(|k, _| {
639 if !post_account.storage.contains_key(k) {
640 return false;
641 }
642 match pre_cached_storage {
643 Some(pre) if pre.contains_key(k) => pre.get(k) != post_account.storage.get(k),
644 _ => true,
645 }
646 });
647
648 result.insert(addr, state);
649 }
650
651 Ok(result)
652}
653
654fn preload_touched_codes(
658 pre_snapshot: &CacheDB,
659 db: &mut GeneralizedDatabase,
660) -> Result<(), EvmError> {
661 let hashes: Vec<H256> = db
662 .current_accounts_state
663 .iter()
664 .flat_map(|(addr, post)| {
665 let pre_hash = pre_snapshot
666 .get(addr)
667 .or_else(|| db.initial_accounts_state.get(addr))
668 .map(|a| a.info.code_hash)
669 .unwrap_or_default();
670 [post.info.code_hash, pre_hash]
671 })
672 .filter(|h| *h != *EMPTY_KECCAK_HASH)
673 .collect();
674
675 for hash in hashes {
676 db.get_code(hash)?;
677 }
678 Ok(())
679}
680
681fn build_post_state_map(
684 pre_snapshot: &CacheDB,
685 post_cache: &CacheDB,
686 db: &GeneralizedDatabase,
687) -> Result<(PrestateTrace, std::collections::HashSet<Address>), EvmError> {
688 let mut post = PrestateTrace::new();
689 let mut modified_or_destroyed = std::collections::HashSet::new();
690
691 for (addr, pre_account, post_account) in find_touched_accounts(pre_snapshot, post_cache, db) {
692 if matches!(
693 post_account.status,
694 AccountStatus::Destroyed | AccountStatus::DestroyedModified,
695 ) {
696 modified_or_destroyed.insert(addr);
697 continue;
698 }
699
700 if let Some(state) = build_post_output(addr, pre_account, post_account, pre_snapshot, db)? {
701 modified_or_destroyed.insert(addr);
702 post.insert(addr, state);
703 }
704 }
705
706 Ok((post, modified_or_destroyed))
707}
708
709fn filter_diff_pre_storage(pre: &mut PrestateTrace, post_cache: &CacheDB) {
712 for (addr, state) in pre.iter_mut() {
713 let post_storage = post_cache.get(addr).map(|a| &a.storage);
714 state.storage.retain(|k, v| {
715 if v.is_zero() {
716 return false;
717 }
718 let post_val = post_storage
719 .and_then(|s| s.get(k).copied())
720 .unwrap_or_default();
721 *v != H256::from_uint(&post_val)
722 });
723 }
724}