1use ethrex_common::constants::EMPTY_KECCAK_HASH;
2use ethrex_common::tracing::{PrePostState, PrestateAccountState, PrestateResult, PrestateTrace};
3use ethrex_common::types::{Block, Transaction};
4use ethrex_common::{
5 Address, BigEndianHash, H256, U256,
6 tracing::{CallTrace, OpcodeTraceResult},
7 types::BlockHeader,
8};
9use ethrex_crypto::Crypto;
10use ethrex_levm::account::{AccountStatus, LevmAccount};
11use ethrex_levm::db::gen_db::CacheDB;
12use ethrex_levm::vm::VMType;
13use ethrex_levm::{
14 db::gen_db::GeneralizedDatabase,
15 tracing::{LevmCallTracer, LevmOpcodeTracer, OpcodeTracerConfig},
16 vm::VM,
17};
18
19use crate::{EvmError, backends::levm::LEVM};
20
21impl LEVM {
22 pub fn rerun_block(
25 db: &mut GeneralizedDatabase,
26 block: &Block,
27 stop_index: Option<usize>,
28 vm_type: VMType,
29 crypto: &dyn Crypto,
30 ) -> Result<(), EvmError> {
31 Self::prepare_block(block, db, vm_type, crypto)?;
32
33 for (index, (tx, sender)) in block
35 .body
36 .get_transactions_with_sender(crypto)
37 .map_err(|error| EvmError::Transaction(error.to_string()))?
38 .into_iter()
39 .enumerate()
40 {
41 if stop_index.is_some_and(|stop| stop == index) {
42 break;
43 }
44
45 Self::execute_tx(tx, sender, &block.header, db, vm_type, crypto)?;
46 }
47
48 if stop_index.is_none()
50 && let Some(withdrawals) = &block.body.withdrawals
51 {
52 Self::process_withdrawals(db, withdrawals)?;
53 };
54
55 Ok(())
56 }
57
58 pub fn trace_tx_prestate(
63 db: &mut GeneralizedDatabase,
64 block_header: &BlockHeader,
65 tx: &Transaction,
66 diff_mode: bool,
67 include_empty: bool,
68 vm_type: VMType,
69 crypto: &dyn Crypto,
70 ) -> Result<PrestateResult, EvmError> {
71 let pre_snapshot: CacheDB = db.current_accounts_state.clone();
72
73 let sender = tx
74 .sender(crypto)
75 .map_err(|e| EvmError::Transaction(format!("Couldn't recover sender: {e}")))?;
76 let env = Self::setup_env(tx, sender, block_header, db, vm_type)?;
77 let mut vm = VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)?;
78 vm.execute()?;
79
80 preload_touched_codes(&pre_snapshot, db)?;
81
82 let mut pre_map = build_pre_state_map(&pre_snapshot, &db.current_accounts_state, db)?;
83
84 if diff_mode {
85 let (post_map, kept) =
86 build_post_state_map(&pre_snapshot, &db.current_accounts_state, db)?;
87 filter_diff_pre_storage(&mut pre_map, &db.current_accounts_state);
88 pre_map.retain(|addr, _| kept.contains(addr));
89 pre_map.retain(|_, state| !state.is_empty());
90 Ok(PrestateResult::Diff(PrePostState {
91 pre: pre_map,
92 post: post_map,
93 }))
94 } else {
95 if !include_empty {
96 pre_map.retain(|_, state| !state.is_empty());
97 }
98 Ok(PrestateResult::Prestate(pre_map))
99 }
100 }
101
102 pub fn trace_tx_opcodes(
104 db: &mut GeneralizedDatabase,
105 block_header: &BlockHeader,
106 tx: &Transaction,
107 cfg: OpcodeTracerConfig,
108 vm_type: VMType,
109 crypto: &dyn Crypto,
110 ) -> Result<OpcodeTraceResult, EvmError> {
111 let env = Self::setup_env(
112 tx,
113 tx.sender(crypto).map_err(|error| {
114 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
115 })?,
116 block_header,
117 db,
118 vm_type,
119 )?;
120 let mut vm = VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)?;
121 vm.opcode_tracer = LevmOpcodeTracer::new(cfg);
122 vm.execute()?;
123 Ok(vm.opcode_tracer.take_result())
124 }
125
126 pub fn trace_tx_calls(
128 db: &mut GeneralizedDatabase,
129 block_header: &BlockHeader,
130 tx: &Transaction,
131 only_top_call: bool,
132 with_log: bool,
133 vm_type: VMType,
134 crypto: &dyn Crypto,
135 ) -> Result<CallTrace, EvmError> {
136 let env = Self::setup_env(
137 tx,
138 tx.sender(crypto).map_err(|error| {
139 EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
140 })?,
141 block_header,
142 db,
143 vm_type,
144 )?;
145 let mut vm = VM::new(
146 env,
147 db,
148 tx,
149 LevmCallTracer::new(only_top_call, with_log),
150 vm_type,
151 crypto,
152 )?;
153
154 vm.execute()?;
155
156 let callframe = vm.get_trace_result()?;
157
158 Ok(vec![callframe])
160 }
161}
162
163fn find_touched_accounts<'a>(
167 pre_snapshot: &'a CacheDB,
168 post_cache: &'a CacheDB,
169 db: &'a GeneralizedDatabase,
170) -> Vec<(Address, &'a LevmAccount, &'a LevmAccount)> {
171 let mut touched = Vec::new();
172
173 for (addr, post_account) in post_cache {
174 let pre_account = match pre_snapshot.get(addr) {
175 Some(pre) => pre,
176 None => {
177 let Some(initial) = db.initial_accounts_state.get(addr) else {
178 continue;
179 };
180 initial
181 }
182 };
183
184 touched.push((*addr, pre_account, post_account));
185 }
186
187 touched
188}
189
190fn build_account_output(
194 account: &LevmAccount,
195 db: &GeneralizedDatabase,
196) -> Result<PrestateAccountState, EvmError> {
197 let has_code = account.info.code_hash != *EMPTY_KECCAK_HASH;
198 let code = if has_code {
199 get_preloaded_code(db, &account.info.code_hash)?
200 } else {
201 bytes::Bytes::new()
202 };
203 let code_hash = if has_code {
204 account.info.code_hash
205 } else {
206 H256::zero()
207 };
208
209 let storage = account
210 .storage
211 .iter()
212 .map(|(k, v)| (*k, H256::from_uint(v)))
213 .collect();
214
215 Ok(PrestateAccountState {
216 balance: Some(account.info.balance),
217 nonce: account.info.nonce,
218 code,
219 code_hash,
220 storage,
221 })
222}
223
224fn get_preloaded_code(db: &GeneralizedDatabase, hash: &H256) -> Result<bytes::Bytes, EvmError> {
226 db.codes
227 .get(hash)
228 .map(|c| c.code_bytes())
229 .ok_or_else(|| EvmError::Custom(format!("missing preloaded code for {hash:?}")))
230}
231
232fn build_post_output(
236 addr: Address,
237 pre_account: &LevmAccount,
238 post_account: &LevmAccount,
239 pre_snapshot: &CacheDB,
240 db: &GeneralizedDatabase,
241) -> Result<Option<PrestateAccountState>, EvmError> {
242 let mut state = PrestateAccountState::default();
243 let mut modified = false;
244
245 if pre_account.info.balance != post_account.info.balance {
246 state.balance = Some(post_account.info.balance);
247 modified = true;
248 }
249 if pre_account.info.nonce != post_account.info.nonce {
250 state.nonce = post_account.info.nonce;
251 modified = true;
252 }
253 if pre_account.info.code_hash != post_account.info.code_hash {
254 if post_account.info.code_hash != *EMPTY_KECCAK_HASH {
255 state.code_hash = post_account.info.code_hash;
256 state.code = get_preloaded_code(db, &post_account.info.code_hash)?;
257 }
258 modified = true;
259 }
260
261 for (key, post_val) in &post_account.storage {
262 let pre_val = pre_storage_value(addr, key, pre_snapshot, db).unwrap_or_default();
263 if pre_val == *post_val {
264 continue;
265 }
266 modified = true;
267 if !post_val.is_zero() {
269 state.storage.insert(*key, H256::from_uint(post_val));
270 }
271 }
272
273 Ok(modified.then_some(state))
274}
275
276fn pre_storage_value(
279 addr: Address,
280 slot: &H256,
281 pre_snapshot: &CacheDB,
282 db: &GeneralizedDatabase,
283) -> Option<U256> {
284 if let Some(account) = pre_snapshot.get(&addr)
285 && let Some(value) = account.storage.get(slot)
286 {
287 return Some(*value);
288 }
289 db.initial_accounts_state
290 .get(&addr)
291 .and_then(|a| a.storage.get(slot).copied())
292}
293
294fn build_pre_state_map(
302 pre_snapshot: &CacheDB,
303 post_cache: &CacheDB,
304 db: &GeneralizedDatabase,
305) -> Result<PrestateTrace, EvmError> {
306 let mut result = PrestateTrace::new();
307
308 for (addr, pre_account, post_account) in find_touched_accounts(pre_snapshot, post_cache, db) {
309 let mut state = build_account_output(pre_account, db)?;
310
311 if pre_snapshot.contains_key(&addr)
316 && let Some(initial) = db.initial_accounts_state.get(&addr)
317 {
318 for (k, v) in &initial.storage {
319 state
320 .storage
321 .entry(*k)
322 .or_insert_with(|| H256::from_uint(v));
323 }
324 }
325
326 let pre_cached_storage = pre_snapshot.get(&addr).map(|a| &a.storage);
327 state.storage.retain(|k, _| {
328 if !post_account.storage.contains_key(k) {
329 return false;
330 }
331 match pre_cached_storage {
332 Some(pre) if pre.contains_key(k) => pre.get(k) != post_account.storage.get(k),
333 _ => true,
334 }
335 });
336
337 result.insert(addr, state);
338 }
339
340 Ok(result)
341}
342
343fn preload_touched_codes(
347 pre_snapshot: &CacheDB,
348 db: &mut GeneralizedDatabase,
349) -> Result<(), EvmError> {
350 let hashes: Vec<H256> = db
351 .current_accounts_state
352 .iter()
353 .flat_map(|(addr, post)| {
354 let pre_hash = pre_snapshot
355 .get(addr)
356 .or_else(|| db.initial_accounts_state.get(addr))
357 .map(|a| a.info.code_hash)
358 .unwrap_or_default();
359 [post.info.code_hash, pre_hash]
360 })
361 .filter(|h| *h != *EMPTY_KECCAK_HASH)
362 .collect();
363
364 for hash in hashes {
365 db.get_code(hash)?;
366 }
367 Ok(())
368}
369
370fn build_post_state_map(
373 pre_snapshot: &CacheDB,
374 post_cache: &CacheDB,
375 db: &GeneralizedDatabase,
376) -> Result<(PrestateTrace, std::collections::HashSet<Address>), EvmError> {
377 let mut post = PrestateTrace::new();
378 let mut modified_or_destroyed = std::collections::HashSet::new();
379
380 for (addr, pre_account, post_account) in find_touched_accounts(pre_snapshot, post_cache, db) {
381 if matches!(
382 post_account.status,
383 AccountStatus::Destroyed | AccountStatus::DestroyedModified,
384 ) {
385 modified_or_destroyed.insert(addr);
386 continue;
387 }
388
389 if let Some(state) = build_post_output(addr, pre_account, post_account, pre_snapshot, db)? {
390 modified_or_destroyed.insert(addr);
391 post.insert(addr, state);
392 }
393 }
394
395 Ok((post, modified_or_destroyed))
396}
397
398fn filter_diff_pre_storage(pre: &mut PrestateTrace, post_cache: &CacheDB) {
401 for (addr, state) in pre.iter_mut() {
402 let post_storage = post_cache.get(addr).map(|a| &a.storage);
403 state.storage.retain(|k, v| {
404 if v.is_zero() {
405 return false;
406 }
407 let post_val = post_storage
408 .and_then(|s| s.get(k).copied())
409 .unwrap_or_default();
410 *v != H256::from_uint(&post_val)
411 });
412 }
413}