1use super::{Call, LoadBlock, LoadState, LoadTransaction};
4use crate::{FromEthApiError, FromEvmError};
5use alloy_consensus::{transaction::TxHashRef, BlockHeader};
6use alloy_eip7928::{bal::DecodedBal, BlockAccessIndex};
7use alloy_primitives::B256;
8use alloy_rpc_types_eth::{BlockId, TransactionInfo};
9use evm2::evm::Bal as EvmBal;
10use evm2_inspectors::tracing::{TracingInspector, TracingInspectorConfig};
11use futures::Future;
12use reth_errors::RethError;
13use reth_evm::{
14 execute::{BlockExecutor, BlockTransactionResult},
15 ConfigureEvm, Database, Evm, EvmEnvFor, EvmTypesFor, TxEnvFor, TxResultWithStateFor,
16};
17use reth_primitives_traits::{BlockBody, BlockTy, RecoveredBlock};
18use reth_rpc_eth_types::{cache::db::attach_bal_before_tx, EthApiError, StateCacheDb};
19use reth_storage_api::ProviderBlock;
20use reth_tasks::cancel::is_cancelled;
21use std::sync::Arc;
22
23pub struct TracingCtx<'a, Insp, EvmTypes: evm2::EvmTypes> {
25 pub result: &'a evm2::TxResultWithState<EvmTypes>,
27 pub db: &'a mut dyn evm2::evm::DynDatabase,
29 pub inspector: Insp,
31}
32
33impl<Insp, EvmTypes: evm2::EvmTypes> core::fmt::Debug for TracingCtx<'_, Insp, EvmTypes> {
34 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
35 f.debug_struct("TracingCtx").finish_non_exhaustive()
36 }
37}
38
39impl<Insp, EvmTypes: evm2::EvmTypes> TracingCtx<'_, Insp, EvmTypes> {
40 pub fn take_inspector(self) -> Insp {
42 self.inspector
43 }
44}
45
46pub trait Trace: LoadState<Error: FromEvmError<Self::Evm>> + Call {
48 fn inspect<DB, I>(
50 &self,
51 db: DB,
52 evm_env: EvmEnvFor<Self::Evm>,
53 tx_env: &TxEnvFor<Self::Evm>,
54 inspector: I,
55 ) -> Result<(I, TxResultWithStateFor<Self::Evm>), Self::Error>
56 where
57 DB: Database,
58 I: evm2::Inspector<EvmTypesFor<Self::Evm>> + 'static,
59 {
60 let mut evm = self.evm_config().evm_with_env(db, evm_env);
61 evm.transact_with_inspector(tx_env, inspector).map_err(Self::Error::from_evm_err)
62 }
63
64 fn spawn_trace_at_with_state<F, R>(
67 &self,
68 evm_env: EvmEnvFor<Self::Evm>,
69 tx_env: TxEnvFor<Self::Evm>,
70 config: TracingInspectorConfig,
71 at: BlockId,
72 f: F,
73 ) -> impl Future<Output = Result<R, Self::Error>> + Send
74 where
75 F: FnOnce(
76 TracingInspector,
77 TxResultWithStateFor<Self::Evm>,
78 StateCacheDb,
79 ) -> Result<R, Self::Error>
80 + Send
81 + 'static,
82 R: Send + 'static,
83 {
84 self.spawn_with_state_at_block(at, move |this, mut db| {
85 let (inspector, result) =
86 this.inspect(&mut db, evm_env, &tx_env, TracingInspector::new(config))?;
87 f(inspector, result, db)
88 })
89 }
90
91 fn spawn_trace_transaction_in_block<F, R>(
104 &self,
105 hash: B256,
106 config: TracingInspectorConfig,
107 f: F,
108 ) -> impl Future<Output = Result<Option<R>, Self::Error>> + Send
109 where
110 Self: LoadTransaction,
111 F: FnOnce(
112 TransactionInfo,
113 TracingInspector,
114 TxResultWithStateFor<Self::Evm>,
115 StateCacheDb,
116 ) -> Result<R, Self::Error>
117 + Send
118 + 'static,
119 R: Send + 'static,
120 {
121 self.spawn_trace_transaction_in_block_with_inspector(hash, TracingInspector::new(config), f)
122 }
123
124 fn spawn_trace_transaction_in_block_with_inspector<Insp, F, R>(
137 &self,
138 hash: B256,
139 inspector: Insp,
140 f: F,
141 ) -> impl Future<Output = Result<Option<R>, Self::Error>> + Send
142 where
143 Self: LoadTransaction,
144 F: FnOnce(
145 TransactionInfo,
146 Insp,
147 TxResultWithStateFor<Self::Evm>,
148 StateCacheDb,
149 ) -> Result<R, Self::Error>
150 + Send
151 + 'static,
152 Insp: evm2::Inspector<EvmTypesFor<Self::Evm>> + Send + 'static,
153 R: Send + 'static,
154 {
155 async move {
156 let Some((transaction, block, bal)) =
157 self.transaction_and_block_and_maybe_bal(hash).await?
158 else {
159 return Ok(None);
160 };
161 let (_, tx_info) = transaction.split();
162
163 self.spawn_with_state_at_block(block.parent_hash(), move |this, mut db| {
166 let (inspector, result, _) = this.inspect_transaction_in_block(
167 &block,
168 &mut db,
169 inspector,
170 tx_info.index.expect("transaction_and_block only returns block transactions")
173 as usize,
174 bal.as_deref(),
175 )?;
176 f(tx_info, inspector, result, db)
177 })
178 .await
179 .map(Some)
180 }
181 }
182
183 fn replay_block_until(
194 &self,
195 db: &mut StateCacheDb,
196 block: &RecoveredBlock<BlockTy<Self::Primitives>>,
197 target_tx_index: usize,
198 bal: Option<&DecodedBal<Arc<EvmBal>>>,
199 ) -> Result<(), Self::Error> {
200 if let Some(bal) = bal {
201 attach_bal_before_tx(db, bal, target_tx_index);
202 return Ok(())
203 }
204
205 self.apply_pre_execution_changes(block, db)?;
206
207 let evm_env = self.evm_env_for_header(block.sealed_block().sealed_header())?;
208 let mut executor = self.block_executor(block, db, evm_env)?;
209 self.replay_transactions_until_with_executor(
210 &mut executor,
211 block.transactions_recovered(),
212 target_tx_index,
213 )?;
214 let cache = executor.evm_mut().take_state_cache();
215 drop(executor);
216 db.cache.merge(cache);
217 Ok(())
218 }
219
220 #[expect(clippy::type_complexity)]
231 fn inspect_transaction_in_block<I>(
232 &self,
233 block: &RecoveredBlock<BlockTy<Self::Primitives>>,
234 db: &mut StateCacheDb,
235 inspector: I,
236 target_tx_index: usize,
237 bal: Option<&DecodedBal<Arc<EvmBal>>>,
238 ) -> Result<(I, TxResultWithStateFor<Self::Evm>, EvmEnvFor<Self::Evm>), Self::Error>
239 where
240 I: evm2::Inspector<EvmTypesFor<Self::Evm>> + 'static,
241 {
242 let Some(target_tx) = block.transactions_recovered().nth(target_tx_index) else {
243 return Err(EthApiError::InvalidParams(format!(
244 "tx_index {target_tx_index} out of bounds for block with {} transactions",
245 block.transaction_count()
246 ))
247 .into())
248 };
249
250 if let Some(bal) = bal {
251 attach_bal_before_tx(db, bal, target_tx_index);
253 } else {
254 self.apply_pre_execution_changes(block, db)?;
255 }
256
257 let evm_env = self.evm_env_for_header(block.sealed_block().sealed_header())?;
258 let mut executor = self.block_executor(block, db, evm_env.clone())?;
259
260 if bal.is_some() {
261 executor
262 .set_block_access_index(BlockAccessIndex::from_tx_index(target_tx_index as u64));
263 } else {
264 self.replay_transactions_until_with_executor(
265 &mut executor,
266 block.transactions_recovered(),
267 target_tx_index,
268 )?;
269 }
270 let (inspector, result) = executor
271 .inspect_transaction_without_commit(target_tx, inspector)
272 .map(|(inspector, output)| (inspector, output.into_result()))
273 .map_err(Self::Error::from_evm_err)?;
274 let cache = executor.evm_mut().take_state_cache();
275 drop(executor);
276 db.cache.merge(cache);
277 Ok((inspector, result, evm_env))
278 }
279
280 fn trace_block_until<F, R>(
287 &self,
288 block_id: BlockId,
289 block: Option<Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>>,
290 highest_index: Option<u64>,
291 config: TracingInspectorConfig,
292 f: F,
293 ) -> impl Future<Output = Result<Option<Vec<R>>, Self::Error>> + Send
294 where
295 Self: LoadBlock,
296 F: Fn(
297 TransactionInfo,
298 TracingCtx<'_, TracingInspector, EvmTypesFor<Self::Evm>>,
299 ) -> Result<R, Self::Error>
300 + Send
301 + 'static,
302 R: Send + 'static,
303 {
304 self.trace_block_until_with_inspector(
305 block_id,
306 block,
307 highest_index,
308 move || TracingInspector::new(config),
309 f,
310 )
311 }
312
313 fn trace_block_until_with_inspector<Setup, Insp, F, R>(
324 &self,
325 block_id: BlockId,
326 block: Option<Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>>,
327 highest_index: Option<u64>,
328 mut inspector_setup: Setup,
329 f: F,
330 ) -> impl Future<Output = Result<Option<Vec<R>>, Self::Error>> + Send
331 where
332 Self: LoadBlock,
333 F: Fn(
334 TransactionInfo,
335 TracingCtx<'_, Insp, EvmTypesFor<Self::Evm>>,
336 ) -> Result<R, Self::Error>
337 + Send
338 + 'static,
339 Setup: FnMut() -> Insp + Send + 'static,
340 Insp: evm2::Inspector<EvmTypesFor<Self::Evm>> + 'static,
341 R: Send + 'static,
342 {
343 async move {
344 let block =
345 if block.is_some() { block } else { self.recovered_block(block_id).await? };
346
347 let Some(block) = block else { return Ok(None) };
348 let evm_env = self.evm_env_for_header(block.sealed_block().sealed_header())?;
349
350 if block.body().transactions().is_empty() {
351 return Ok(Some(Vec::new()))
353 }
354
355 self.spawn_with_state_at_block(block.parent_hash(), move |this, mut db| {
359 let block_hash = block.hash();
360
361 let block_number = block.number();
362 let block_timestamp = block.timestamp();
363 let base_fee = block.base_fee_per_gas();
364
365 this.apply_pre_execution_changes(&block, &mut db)?;
366
367 let max_transactions = highest_index.map_or_else(
370 || block.body().transaction_count(),
371 |highest| {
372 highest as usize + 1
374 },
375 );
376 let tx_count = max_transactions.min(block.body().transaction_count());
377
378 let mut results = Vec::new();
379 let mut executor = this.block_executor(&block, &mut db, evm_env)?;
380
381 for (idx, tx) in block.transactions_recovered().take(max_transactions).enumerate() {
382 if is_cancelled() {
383 return Err(EthApiError::InternalEthError.into())
384 }
385 let tx_hash = *tx.tx_hash();
386 let (inspector, output) = executor
387 .inspect_transaction_without_commit(tx, inspector_setup())
388 .map_err(Self::Error::from_evm_err)?;
389 let tx_info = TransactionInfo {
390 hash: Some(tx_hash),
391 index: Some(idx as u64),
392 block_hash: Some(block_hash),
393 block_number: Some(block_number),
394 block_timestamp: Some(block_timestamp),
395 base_fee,
396 };
397 let item = f(
398 tx_info,
399 TracingCtx {
400 result: output.result(),
401 db: executor.evm_mut().state_db_mut(),
402 inspector,
403 },
404 )?;
405 if idx + 1 < tx_count {
408 executor.commit_transaction(output).map_err(Self::Error::from_evm_err)?;
409 }
410 results.push(item);
411 }
412
413 Ok(Some(results))
414 })
415 .await
416 }
417 }
418
419 fn trace_block_with<F, R>(
430 &self,
431 block_id: BlockId,
432 block: Option<Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>>,
433 config: TracingInspectorConfig,
434 f: F,
435 ) -> impl Future<Output = Result<Option<Vec<R>>, Self::Error>> + Send
436 where
437 Self: LoadBlock,
438 F: Fn(
439 TransactionInfo,
440 TracingCtx<'_, TracingInspector, EvmTypesFor<Self::Evm>>,
441 ) -> Result<R, Self::Error>
442 + Send
443 + 'static,
444 R: Send + 'static,
445 {
446 self.trace_block_until(block_id, block, None, config, f)
447 }
448
449 fn trace_block_inspector<Setup, Insp, F, R>(
464 &self,
465 block_id: BlockId,
466 block: Option<Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>>,
467 insp_setup: Setup,
468 f: F,
469 ) -> impl Future<Output = Result<Option<Vec<R>>, Self::Error>> + Send
470 where
471 Self: LoadBlock,
472 F: Fn(
473 TransactionInfo,
474 TracingCtx<'_, Insp, EvmTypesFor<Self::Evm>>,
475 ) -> Result<R, Self::Error>
476 + Send
477 + 'static,
478 Setup: FnMut() -> Insp + Send + 'static,
479 Insp: evm2::Inspector<EvmTypesFor<Self::Evm>> + 'static,
480 R: Send + 'static,
481 {
482 self.trace_block_until_with_inspector(block_id, block, None, insp_setup, f)
483 }
484
485 fn apply_pre_execution_changes(
491 &self,
492 block: &RecoveredBlock<ProviderBlock<Self::Provider>>,
493 db: &mut StateCacheDb,
494 ) -> Result<reth_trie_common::bundle_state::BundleState, Self::Error> {
495 let evm_env = self.evm_env_for_header(block.sealed_block().sealed_header())?;
496 let ctx = self
497 .evm_config()
498 .context_for_block(block.sealed_block())
499 .map_err(RethError::other)
500 .map_err(Self::Error::from_eth_err)?;
501 let changes = self
502 .evm_config()
503 .pre_block_state_changes(&mut *db, evm_env, ctx)
504 .map_err(|err| EthApiError::EvmCustom(err.to_string()))
505 .map_err(Self::Error::from_eth_err)?;
506 db.commit_source(&reth_execution_types::BundleSource(&changes));
507 Ok(changes)
508 }
509}