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 adjust_disabled_base_fee(&mut env);
484 let converted_tx = generic_tx_to_transaction(tx)?;
485 Ok((env, converted_tx))
486}
487
488fn find_touched_accounts<'a>(
492 pre_snapshot: &'a CacheDB,
493 post_cache: &'a CacheDB,
494 db: &'a GeneralizedDatabase,
495) -> Vec<(Address, &'a LevmAccount, &'a LevmAccount)> {
496 let mut touched = Vec::new();
497
498 for (addr, post_account) in post_cache {
499 let pre_account = match pre_snapshot.get(addr) {
500 Some(pre) => pre,
501 None => {
502 let Some(initial) = db.initial_accounts_state.get(addr) else {
503 continue;
504 };
505 initial
506 }
507 };
508
509 touched.push((*addr, pre_account, post_account));
510 }
511
512 touched
513}
514
515fn build_account_output(
519 account: &LevmAccount,
520 db: &GeneralizedDatabase,
521) -> Result<PrestateAccountState, EvmError> {
522 let has_code = account.info.code_hash != *EMPTY_KECCAK_HASH;
523 let code = if has_code {
524 get_preloaded_code(db, &account.info.code_hash)?
525 } else {
526 bytes::Bytes::new()
527 };
528 let code_hash = if has_code {
529 account.info.code_hash
530 } else {
531 H256::zero()
532 };
533
534 let storage = account
535 .storage
536 .iter()
537 .map(|(k, v)| (*k, H256::from_uint(v)))
538 .collect();
539
540 Ok(PrestateAccountState {
541 balance: Some(account.info.balance),
542 nonce: account.info.nonce,
543 code,
544 code_hash,
545 storage,
546 })
547}
548
549fn get_preloaded_code(db: &GeneralizedDatabase, hash: &H256) -> Result<bytes::Bytes, EvmError> {
551 db.codes
552 .get(hash)
553 .map(|c| c.code_bytes())
554 .ok_or_else(|| EvmError::Custom(format!("missing preloaded code for {hash:?}")))
555}
556
557fn build_post_output(
561 addr: Address,
562 pre_account: &LevmAccount,
563 post_account: &LevmAccount,
564 pre_snapshot: &CacheDB,
565 db: &GeneralizedDatabase,
566) -> Result<Option<PrestateAccountState>, EvmError> {
567 let mut state = PrestateAccountState::default();
568 let mut modified = false;
569
570 if pre_account.info.balance != post_account.info.balance {
571 state.balance = Some(post_account.info.balance);
572 modified = true;
573 }
574 if pre_account.info.nonce != post_account.info.nonce {
575 state.nonce = post_account.info.nonce;
576 modified = true;
577 }
578 if pre_account.info.code_hash != post_account.info.code_hash {
579 if post_account.info.code_hash != *EMPTY_KECCAK_HASH {
580 state.code_hash = post_account.info.code_hash;
581 state.code = get_preloaded_code(db, &post_account.info.code_hash)?;
582 }
583 modified = true;
584 }
585
586 for (key, post_val) in &post_account.storage {
587 let pre_val = pre_storage_value(addr, key, pre_snapshot, db).unwrap_or_default();
588 if pre_val == *post_val {
589 continue;
590 }
591 modified = true;
592 if !post_val.is_zero() {
594 state.storage.insert(*key, H256::from_uint(post_val));
595 }
596 }
597
598 Ok(modified.then_some(state))
599}
600
601fn pre_storage_value(
604 addr: Address,
605 slot: &H256,
606 pre_snapshot: &CacheDB,
607 db: &GeneralizedDatabase,
608) -> Option<U256> {
609 if let Some(account) = pre_snapshot.get(&addr)
610 && let Some(value) = account.storage.get(slot)
611 {
612 return Some(*value);
613 }
614 db.initial_accounts_state
615 .get(&addr)
616 .and_then(|a| a.storage.get(slot).copied())
617}
618
619fn build_pre_state_map(
627 pre_snapshot: &CacheDB,
628 post_cache: &CacheDB,
629 db: &GeneralizedDatabase,
630) -> Result<PrestateTrace, EvmError> {
631 let mut result = PrestateTrace::new();
632
633 for (addr, pre_account, post_account) in find_touched_accounts(pre_snapshot, post_cache, db) {
634 let mut state = build_account_output(pre_account, db)?;
635
636 if pre_snapshot.contains_key(&addr)
641 && let Some(initial) = db.initial_accounts_state.get(&addr)
642 {
643 for (k, v) in &initial.storage {
644 state
645 .storage
646 .entry(*k)
647 .or_insert_with(|| H256::from_uint(v));
648 }
649 }
650
651 let pre_cached_storage = pre_snapshot.get(&addr).map(|a| &a.storage);
652 state.storage.retain(|k, _| {
653 if !post_account.storage.contains_key(k) {
654 return false;
655 }
656 match pre_cached_storage {
657 Some(pre) if pre.contains_key(k) => pre.get(k) != post_account.storage.get(k),
658 _ => true,
659 }
660 });
661
662 result.insert(addr, state);
663 }
664
665 Ok(result)
666}
667
668fn preload_touched_codes(
672 pre_snapshot: &CacheDB,
673 db: &mut GeneralizedDatabase,
674) -> Result<(), EvmError> {
675 let hashes: Vec<H256> = db
676 .current_accounts_state
677 .iter()
678 .flat_map(|(addr, post)| {
679 let pre_hash = pre_snapshot
680 .get(addr)
681 .or_else(|| db.initial_accounts_state.get(addr))
682 .map(|a| a.info.code_hash)
683 .unwrap_or_default();
684 [post.info.code_hash, pre_hash]
685 })
686 .filter(|h| *h != *EMPTY_KECCAK_HASH)
687 .collect();
688
689 for hash in hashes {
690 db.get_code(hash)?;
691 }
692 Ok(())
693}
694
695fn build_post_state_map(
698 pre_snapshot: &CacheDB,
699 post_cache: &CacheDB,
700 db: &GeneralizedDatabase,
701) -> Result<(PrestateTrace, std::collections::HashSet<Address>), EvmError> {
702 let mut post = PrestateTrace::new();
703 let mut modified_or_destroyed = std::collections::HashSet::new();
704
705 for (addr, pre_account, post_account) in find_touched_accounts(pre_snapshot, post_cache, db) {
706 if matches!(
707 post_account.status,
708 AccountStatus::Destroyed | AccountStatus::DestroyedModified,
709 ) {
710 modified_or_destroyed.insert(addr);
711 continue;
712 }
713
714 if let Some(state) = build_post_output(addr, pre_account, post_account, pre_snapshot, db)? {
715 modified_or_destroyed.insert(addr);
716 post.insert(addr, state);
717 }
718 }
719
720 Ok((post, modified_or_destroyed))
721}
722
723fn filter_diff_pre_storage(pre: &mut PrestateTrace, post_cache: &CacheDB) {
726 for (addr, state) in pre.iter_mut() {
727 let post_storage = post_cache.get(addr).map(|a| &a.storage);
728 state.storage.retain(|k, v| {
729 if v.is_zero() {
730 return false;
731 }
732 let post_val = post_storage
733 .and_then(|s| s.get(k).copied())
734 .unwrap_or_default();
735 *v != H256::from_uint(&post_val)
736 });
737 }
738}