1use alloy_consensus::BlockHeader as _;
2use alloy_eips::BlockId;
3use alloy_evm::block::calc::{base_block_reward_pre_merge, block_reward, ommer_reward};
4use alloy_primitives::{
5 map::{HashMap, HashSet},
6 Address, BlockHash, Bytes, B256, U256,
7};
8use alloy_rpc_types_eth::{
9 state::{EvmOverrides, StateOverride},
10 BlockOverrides, Index,
11};
12use alloy_rpc_types_trace::{
13 filter::TraceFilter,
14 opcode::{BlockOpcodeGas, TransactionOpcodeGas},
15 parity::*,
16 tracerequest::TraceCallRequest,
17};
18use async_trait::async_trait;
19use jsonrpsee::core::RpcResult;
20use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardfork, MAINNET, SEPOLIA};
21use reth_evm::ConfigureEvm;
22use reth_primitives_traits::{BlockBody, BlockHeader};
23use reth_rpc_api::TraceApiServer;
24use reth_rpc_convert::RpcTxReq;
25use reth_rpc_eth_api::{
26 helpers::{Call, LoadPendingBlock, LoadTransaction, Trace, TraceExt},
27 FromEthApiError, RpcNodeCore,
28};
29use reth_rpc_eth_types::{error::EthApiError, utils::recover_raw_transaction, EthConfig};
30use reth_storage_api::{BlockNumReader, BlockReader};
31use reth_tasks::pool::BlockingTaskGuard;
32use reth_transaction_pool::{PoolPooledTx, PoolTransaction, TransactionPool};
33use revm::DatabaseCommit;
34use revm_inspectors::{
35 opcode::OpcodeGasInspector,
36 storage::StorageInspector,
37 tracing::{parity::populate_state_diff, TracingInspector, TracingInspectorConfig},
38};
39use serde::{Deserialize, Serialize};
40use std::sync::Arc;
41use tokio::sync::{AcquireError, OwnedSemaphorePermit};
42
43pub struct TraceApi<Eth> {
47 inner: Arc<TraceApiInner<Eth>>,
48}
49
50impl<Eth> TraceApi<Eth> {
53 pub fn new(
55 eth_api: Eth,
56 blocking_task_guard: BlockingTaskGuard,
57 eth_config: EthConfig,
58 ) -> Self {
59 let inner = Arc::new(TraceApiInner { eth_api, blocking_task_guard, eth_config });
60 Self { inner }
61 }
62
63 async fn acquire_trace_permit(
65 &self,
66 ) -> std::result::Result<OwnedSemaphorePermit, AcquireError> {
67 self.inner.blocking_task_guard.clone().acquire_owned().await
68 }
69
70 pub fn eth_api(&self) -> &Eth {
72 &self.inner.eth_api
73 }
74}
75
76impl<Eth: RpcNodeCore> TraceApi<Eth> {
77 pub fn provider(&self) -> &Eth::Provider {
79 self.inner.eth_api.provider()
80 }
81}
82
83impl<Eth> TraceApi<Eth>
86where
87 Eth: Trace + Call + LoadPendingBlock + LoadTransaction + 'static,
90{
91 pub async fn trace_call(
93 &self,
94 trace_request: TraceCallRequest<RpcTxReq<Eth::NetworkTypes>>,
95 ) -> Result<TraceResults, Eth::Error> {
96 let at = trace_request.block_id.unwrap_or_default();
97 let config = TracingInspectorConfig::from_parity_config(&trace_request.trace_types);
98 let overrides =
99 EvmOverrides::new(trace_request.state_overrides, trace_request.block_overrides);
100 let mut inspector = TracingInspector::new(config);
101 let this = self.clone();
102 self.eth_api()
103 .spawn_with_call_at(trace_request.call, at, overrides, move |db, evm_env, tx_env| {
104 let res = this.eth_api().inspect(&mut *db, evm_env, tx_env, &mut inspector)?;
105 let trace_res = inspector
106 .into_parity_builder()
107 .into_trace_results_with_state(&res, &trace_request.trace_types, &db)
108 .map_err(Eth::Error::from_eth_err)?;
109 Ok(trace_res)
110 })
111 .await
112 }
113
114 pub async fn trace_raw_transaction(
116 &self,
117 tx: Bytes,
118 trace_types: HashSet<TraceType>,
119 block_id: Option<BlockId>,
120 ) -> Result<TraceResults, Eth::Error> {
121 let tx = recover_raw_transaction::<PoolPooledTx<Eth::Pool>>(&tx)?
122 .map(<Eth::Pool as TransactionPool>::Transaction::pooled_into_consensus);
123
124 let (evm_env, at) = self.eth_api().evm_env_at(block_id.unwrap_or_default()).await?;
125 let tx_env = self.eth_api().evm_config().tx_env(tx);
126
127 let config = TracingInspectorConfig::from_parity_config(&trace_types);
128
129 self.eth_api()
130 .spawn_trace_at_with_state(evm_env, tx_env, config, at, move |inspector, res, db| {
131 inspector
132 .into_parity_builder()
133 .into_trace_results_with_state(&res, &trace_types, &db)
134 .map_err(Eth::Error::from_eth_err)
135 })
136 .await
137 }
138
139 pub async fn trace_call_many(
144 &self,
145 calls: Vec<(RpcTxReq<Eth::NetworkTypes>, HashSet<TraceType>)>,
146 block_id: Option<BlockId>,
147 ) -> Result<Vec<TraceResults>, Eth::Error> {
148 let at = block_id.unwrap_or(BlockId::pending());
149 let (evm_env, at) = self.eth_api().evm_env_at(at).await?;
150
151 self.eth_api()
153 .spawn_with_state_at_block(at, move |eth_api, mut db| {
154 let mut results = Vec::with_capacity(calls.len());
155 let mut calls = calls.into_iter().peekable();
156
157 while let Some((call, trace_types)) = calls.next() {
158 let (evm_env, tx_env) = eth_api.prepare_call_env(
159 evm_env.clone(),
160 call,
161 &mut db,
162 Default::default(),
163 )?;
164 let config = TracingInspectorConfig::from_parity_config(&trace_types);
165 let mut inspector = TracingInspector::new(config);
166 let res = eth_api.inspect(&mut db, evm_env, tx_env, &mut inspector)?;
167
168 let trace_res = inspector
169 .into_parity_builder()
170 .into_trace_results_with_state(&res, &trace_types, &db)
171 .map_err(Eth::Error::from_eth_err)?;
172
173 results.push(trace_res);
174
175 if calls.peek().is_some() {
178 db.commit(res.state)
181 }
182 }
183
184 Ok(results)
185 })
186 .await
187 }
188
189 pub async fn replay_transaction(
191 &self,
192 hash: B256,
193 trace_types: HashSet<TraceType>,
194 ) -> Result<TraceResults, Eth::Error> {
195 let config = TracingInspectorConfig::from_parity_config(&trace_types);
196 self.eth_api()
197 .spawn_trace_transaction_in_block(hash, config, move |_, inspector, res, db| {
198 let trace_res = inspector
199 .into_parity_builder()
200 .into_trace_results_with_state(&res, &trace_types, &db)
201 .map_err(Eth::Error::from_eth_err)?;
202 Ok(trace_res)
203 })
204 .await
205 .transpose()
206 .ok_or(EthApiError::TransactionNotFound)?
207 }
208
209 pub async fn trace_get(
216 &self,
217 hash: B256,
218 indices: Vec<usize>,
219 ) -> Result<Option<LocalizedTransactionTrace>, Eth::Error> {
220 if indices.len() != 1 {
221 return Ok(None)
223 }
224 self.trace_get_index(hash, indices[0]).await
225 }
226
227 pub async fn trace_get_index(
231 &self,
232 hash: B256,
233 index: usize,
234 ) -> Result<Option<LocalizedTransactionTrace>, Eth::Error> {
235 Ok(self.trace_transaction(hash).await?.and_then(|traces| traces.into_iter().nth(index)))
236 }
237
238 pub async fn trace_transaction(
240 &self,
241 hash: B256,
242 ) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
243 self.eth_api()
244 .spawn_trace_transaction_in_block(
245 hash,
246 TracingInspectorConfig::default_parity(),
247 move |tx_info, inspector, _, _| {
248 let traces =
249 inspector.into_parity_builder().into_localized_transaction_traces(tx_info);
250 Ok(traces)
251 },
252 )
253 .await
254 }
255
256 pub async fn trace_transaction_opcode_gas(
259 &self,
260 tx_hash: B256,
261 ) -> Result<Option<TransactionOpcodeGas>, Eth::Error> {
262 self.eth_api()
263 .spawn_trace_transaction_in_block_with_inspector(
264 tx_hash,
265 OpcodeGasInspector::default(),
266 move |_tx_info, inspector, _res, _| {
267 let trace = TransactionOpcodeGas {
268 transaction_hash: tx_hash,
269 opcode_gas: inspector.opcode_gas_iter().collect(),
270 };
271 Ok(trace)
272 },
273 )
274 .await
275 }
276
277 fn calculate_base_block_reward<H: BlockHeader>(
283 &self,
284 header: &H,
285 ) -> Result<Option<u128>, Eth::Error> {
286 let chain_spec = self.provider().chain_spec();
287 let is_paris_activated = if chain_spec.chain() == MAINNET.chain() {
288 Some(header.number()) >= EthereumHardfork::Paris.mainnet_activation_block()
289 } else if chain_spec.chain() == SEPOLIA.chain() {
290 Some(header.number()) >= EthereumHardfork::Paris.sepolia_activation_block()
291 } else {
292 true
293 };
294
295 if is_paris_activated {
296 return Ok(None)
297 }
298
299 Ok(Some(base_block_reward_pre_merge(&chain_spec, header.number())))
300 }
301
302 fn extract_reward_traces<H: BlockHeader>(
306 &self,
307 header: &H,
308 ommers: Option<&[H]>,
309 base_block_reward: u128,
310 ) -> Vec<LocalizedTransactionTrace> {
311 let ommers_cnt = ommers.map(|o| o.len()).unwrap_or_default();
312 let mut traces = Vec::with_capacity(ommers_cnt + 1);
313
314 let block_reward = block_reward(base_block_reward, ommers_cnt);
315 traces.push(reward_trace(
316 header,
317 RewardAction {
318 author: header.beneficiary(),
319 reward_type: RewardType::Block,
320 value: U256::from(block_reward),
321 },
322 ));
323
324 let Some(ommers) = ommers else { return traces };
325
326 for uncle in ommers {
327 let uncle_reward = ommer_reward(base_block_reward, header.number(), uncle.number());
328 traces.push(reward_trace(
329 header,
330 RewardAction {
331 author: uncle.beneficiary(),
332 reward_type: RewardType::Uncle,
333 value: U256::from(uncle_reward),
334 },
335 ));
336 }
337 traces
338 }
339}
340
341impl<Eth> TraceApi<Eth>
342where
343 Eth: TraceExt + 'static,
346{
347 pub async fn trace_filter(
352 &self,
353 filter: TraceFilter,
354 ) -> Result<Vec<LocalizedTransactionTrace>, Eth::Error> {
355 let matcher = Arc::new(filter.matcher());
357 let TraceFilter { from_block, to_block, mut after, count, .. } = filter;
358 let start = from_block.unwrap_or(0);
359
360 let latest_block = self.provider().best_block_number().map_err(Eth::Error::from_eth_err)?;
361 if start > latest_block {
362 return Err(EthApiError::HeaderNotFound(start.into()).into());
364 }
365 let end = to_block.unwrap_or(latest_block);
366
367 if start > end {
368 return Err(EthApiError::InvalidParams(
369 "invalid parameters: fromBlock cannot be greater than toBlock".to_string(),
370 )
371 .into())
372 }
373
374 let distance = end.saturating_sub(start);
376 if distance > self.inner.eth_config.max_trace_filter_blocks {
377 return Err(EthApiError::InvalidParams(
378 "Block range too large; currently limited to 100 blocks".to_string(),
379 )
380 .into())
381 }
382
383 let mut all_traces = Vec::new();
384 let mut block_traces = Vec::with_capacity(self.inner.eth_config.max_tracing_requests);
385 for chunk_start in (start..end).step_by(self.inner.eth_config.max_tracing_requests) {
386 let chunk_end =
387 std::cmp::min(chunk_start + self.inner.eth_config.max_tracing_requests as u64, end);
388
389 let blocks = self
391 .eth_api()
392 .spawn_blocking_io(move |this| {
393 Ok(this
394 .provider()
395 .recovered_block_range(chunk_start..=chunk_end)
396 .map_err(Eth::Error::from_eth_err)?
397 .into_iter()
398 .map(Arc::new)
399 .collect::<Vec<_>>())
400 })
401 .await?;
402
403 for block in &blocks {
405 let matcher = matcher.clone();
406 let traces = self.eth_api().trace_block_until(
407 block.hash().into(),
408 Some(block.clone()),
409 None,
410 TracingInspectorConfig::default_parity(),
411 move |tx_info, mut ctx| {
412 let mut traces = ctx
413 .take_inspector()
414 .into_parity_builder()
415 .into_localized_transaction_traces(tx_info);
416 traces.retain(|trace| matcher.matches(&trace.trace));
417 Ok(Some(traces))
418 },
419 );
420 block_traces.push(traces);
421 }
422
423 #[allow(clippy::iter_with_drain)]
424 let block_traces = futures::future::try_join_all(block_traces.drain(..)).await?;
425 all_traces.extend(block_traces.into_iter().flatten().flat_map(|traces| {
426 traces.into_iter().flatten().flat_map(|traces| traces.into_iter())
427 }));
428
429 for block in &blocks {
431 if let Some(base_block_reward) = self.calculate_base_block_reward(block.header())? {
432 all_traces.extend(
433 self.extract_reward_traces(
434 block.header(),
435 block.body().ommers(),
436 base_block_reward,
437 )
438 .into_iter()
439 .filter(|trace| matcher.matches(&trace.trace)),
440 );
441 } else {
442 break
445 }
446 }
447
448 if let Some(cutoff) = after.map(|a| a as usize) &&
450 cutoff < all_traces.len()
451 {
452 all_traces.drain(..cutoff);
453 after = None;
455 }
456
457 if let Some(count) = count {
459 let count = count as usize;
460 if count < all_traces.len() {
461 all_traces.truncate(count);
462 return Ok(all_traces)
463 }
464 };
465 }
466
467 if let Some(cutoff) = after.map(|a| a as usize) &&
470 cutoff >= all_traces.len()
471 {
472 return Ok(vec![])
473 }
474
475 Ok(all_traces)
476 }
477
478 pub async fn trace_block(
480 &self,
481 block_id: BlockId,
482 ) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
483 let traces = self.eth_api().trace_block_with(
484 block_id,
485 None,
486 TracingInspectorConfig::default_parity(),
487 |tx_info, mut ctx| {
488 let traces = ctx
489 .take_inspector()
490 .into_parity_builder()
491 .into_localized_transaction_traces(tx_info);
492 Ok(traces)
493 },
494 );
495
496 let block = self.eth_api().recovered_block(block_id);
497 let (maybe_traces, maybe_block) = futures::try_join!(traces, block)?;
498
499 let mut maybe_traces =
500 maybe_traces.map(|traces| traces.into_iter().flatten().collect::<Vec<_>>());
501
502 if let (Some(block), Some(traces)) = (maybe_block, maybe_traces.as_mut()) &&
503 let Some(base_block_reward) = self.calculate_base_block_reward(block.header())?
504 {
505 traces.extend(self.extract_reward_traces(
506 block.header(),
507 block.body().ommers(),
508 base_block_reward,
509 ));
510 }
511
512 Ok(maybe_traces)
513 }
514
515 pub async fn replay_block_transactions(
517 &self,
518 block_id: BlockId,
519 trace_types: HashSet<TraceType>,
520 ) -> Result<Option<Vec<TraceResultsWithTransactionHash>>, Eth::Error> {
521 self.eth_api()
522 .trace_block_with(
523 block_id,
524 None,
525 TracingInspectorConfig::from_parity_config(&trace_types),
526 move |tx_info, mut ctx| {
527 let mut full_trace = ctx
528 .take_inspector()
529 .into_parity_builder()
530 .into_trace_results(&ctx.result, &trace_types);
531
532 if let Some(ref mut state_diff) = full_trace.state_diff {
535 populate_state_diff(state_diff, &ctx.db, ctx.state.iter())
536 .map_err(Eth::Error::from_eth_err)?;
537 }
538
539 let trace = TraceResultsWithTransactionHash {
540 transaction_hash: tx_info.hash.expect("tx hash is set"),
541 full_trace,
542 };
543 Ok(trace)
544 },
545 )
546 .await
547 }
548
549 pub async fn trace_block_opcode_gas(
554 &self,
555 block_id: BlockId,
556 ) -> Result<Option<BlockOpcodeGas>, Eth::Error> {
557 let res = self
558 .eth_api()
559 .trace_block_inspector(
560 block_id,
561 None,
562 OpcodeGasInspector::default,
563 move |tx_info, ctx| {
564 let trace = TransactionOpcodeGas {
565 transaction_hash: tx_info.hash.expect("tx hash is set"),
566 opcode_gas: ctx.inspector.opcode_gas_iter().collect(),
567 };
568 Ok(trace)
569 },
570 )
571 .await?;
572
573 let Some(transactions) = res else { return Ok(None) };
574
575 let Some(block) = self.eth_api().recovered_block(block_id).await? else { return Ok(None) };
576
577 Ok(Some(BlockOpcodeGas {
578 block_hash: block.hash(),
579 block_number: block.number(),
580 transactions,
581 }))
582 }
583
584 pub async fn trace_block_storage_access(
587 &self,
588 block_id: BlockId,
589 ) -> Result<Option<BlockStorageAccess>, Eth::Error> {
590 let res = self
591 .eth_api()
592 .trace_block_inspector(
593 block_id,
594 None,
595 StorageInspector::default,
596 move |tx_info, ctx| {
597 let trace = TransactionStorageAccess {
598 transaction_hash: tx_info.hash.expect("tx hash is set"),
599 storage_access: ctx.inspector.accessed_slots().clone(),
600 unique_loads: ctx.inspector.unique_loads(),
601 warm_loads: ctx.inspector.warm_loads(),
602 };
603 Ok(trace)
604 },
605 )
606 .await?;
607
608 let Some(transactions) = res else { return Ok(None) };
609
610 let Some(block) = self.eth_api().recovered_block(block_id).await? else { return Ok(None) };
611
612 Ok(Some(BlockStorageAccess {
613 block_hash: block.hash(),
614 block_number: block.number(),
615 transactions,
616 }))
617 }
618}
619
620#[async_trait]
621impl<Eth> TraceApiServer<RpcTxReq<Eth::NetworkTypes>> for TraceApi<Eth>
622where
623 Eth: TraceExt + 'static,
624{
625 async fn trace_call(
629 &self,
630 call: RpcTxReq<Eth::NetworkTypes>,
631 trace_types: HashSet<TraceType>,
632 block_id: Option<BlockId>,
633 state_overrides: Option<StateOverride>,
634 block_overrides: Option<Box<BlockOverrides>>,
635 ) -> RpcResult<TraceResults> {
636 let _permit = self.acquire_trace_permit().await;
637 let request =
638 TraceCallRequest { call, trace_types, block_id, state_overrides, block_overrides };
639 Ok(Self::trace_call(self, request).await.map_err(Into::into)?)
640 }
641
642 async fn trace_call_many(
644 &self,
645 calls: Vec<(RpcTxReq<Eth::NetworkTypes>, HashSet<TraceType>)>,
646 block_id: Option<BlockId>,
647 ) -> RpcResult<Vec<TraceResults>> {
648 let _permit = self.acquire_trace_permit().await;
649 Ok(Self::trace_call_many(self, calls, block_id).await.map_err(Into::into)?)
650 }
651
652 async fn trace_raw_transaction(
654 &self,
655 data: Bytes,
656 trace_types: HashSet<TraceType>,
657 block_id: Option<BlockId>,
658 ) -> RpcResult<TraceResults> {
659 let _permit = self.acquire_trace_permit().await;
660 Ok(Self::trace_raw_transaction(self, data, trace_types, block_id)
661 .await
662 .map_err(Into::into)?)
663 }
664
665 async fn replay_block_transactions(
667 &self,
668 block_id: BlockId,
669 trace_types: HashSet<TraceType>,
670 ) -> RpcResult<Option<Vec<TraceResultsWithTransactionHash>>> {
671 let _permit = self.acquire_trace_permit().await;
672 Ok(Self::replay_block_transactions(self, block_id, trace_types)
673 .await
674 .map_err(Into::into)?)
675 }
676
677 async fn replay_transaction(
679 &self,
680 transaction: B256,
681 trace_types: HashSet<TraceType>,
682 ) -> RpcResult<TraceResults> {
683 let _permit = self.acquire_trace_permit().await;
684 Ok(Self::replay_transaction(self, transaction, trace_types).await.map_err(Into::into)?)
685 }
686
687 async fn trace_block(
689 &self,
690 block_id: BlockId,
691 ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
692 let _permit = self.acquire_trace_permit().await;
693 Ok(Self::trace_block(self, block_id).await.map_err(Into::into)?)
694 }
695
696 async fn trace_filter(&self, filter: TraceFilter) -> RpcResult<Vec<LocalizedTransactionTrace>> {
703 let _permit = self.inner.blocking_task_guard.clone().acquire_many_owned(2).await;
704 Ok(Self::trace_filter(self, filter).await.map_err(Into::into)?)
705 }
706
707 async fn trace_get(
710 &self,
711 hash: B256,
712 indices: Vec<Index>,
713 ) -> RpcResult<Option<LocalizedTransactionTrace>> {
714 let _permit = self.acquire_trace_permit().await;
715 Ok(Self::trace_get(self, hash, indices.into_iter().map(Into::into).collect())
716 .await
717 .map_err(Into::into)?)
718 }
719
720 async fn trace_transaction(
722 &self,
723 hash: B256,
724 ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
725 let _permit = self.acquire_trace_permit().await;
726 Ok(Self::trace_transaction(self, hash).await.map_err(Into::into)?)
727 }
728
729 async fn trace_transaction_opcode_gas(
731 &self,
732 tx_hash: B256,
733 ) -> RpcResult<Option<TransactionOpcodeGas>> {
734 let _permit = self.acquire_trace_permit().await;
735 Ok(Self::trace_transaction_opcode_gas(self, tx_hash).await.map_err(Into::into)?)
736 }
737
738 async fn trace_block_opcode_gas(&self, block_id: BlockId) -> RpcResult<Option<BlockOpcodeGas>> {
740 let _permit = self.acquire_trace_permit().await;
741 Ok(Self::trace_block_opcode_gas(self, block_id).await.map_err(Into::into)?)
742 }
743}
744
745impl<Eth> std::fmt::Debug for TraceApi<Eth> {
746 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
747 f.debug_struct("TraceApi").finish_non_exhaustive()
748 }
749}
750impl<Eth> Clone for TraceApi<Eth> {
751 fn clone(&self) -> Self {
752 Self { inner: Arc::clone(&self.inner) }
753 }
754}
755
756struct TraceApiInner<Eth> {
757 eth_api: Eth,
759 blocking_task_guard: BlockingTaskGuard,
761 eth_config: EthConfig,
763}
764
765#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
768#[serde(rename_all = "camelCase")]
769pub struct TransactionStorageAccess {
770 pub transaction_hash: B256,
772 pub storage_access: HashMap<Address, HashMap<B256, u64>>,
774 pub unique_loads: u64,
776 pub warm_loads: u64,
778}
779
780#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
782#[serde(rename_all = "camelCase")]
783pub struct BlockStorageAccess {
784 pub block_hash: BlockHash,
786 pub block_number: u64,
788 pub transactions: Vec<TransactionStorageAccess>,
790}
791
792fn reward_trace<H: BlockHeader>(header: &H, reward: RewardAction) -> LocalizedTransactionTrace {
795 LocalizedTransactionTrace {
796 block_hash: Some(header.hash_slow()),
797 block_number: Some(header.number()),
798 transaction_hash: None,
799 transaction_position: None,
800 trace: TransactionTrace {
801 trace_address: vec![],
802 subtraces: 0,
803 action: Action::Reward(reward),
804 error: None,
805 result: None,
806 },
807 }
808}