1use alloy_consensus::BlockHeader as _;
2use alloy_eips::{BlockId, BlockNumHash};
3use alloy_evm::block::calc::{base_block_reward, 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 futures::{FutureExt, StreamExt};
20use jsonrpsee::core::RpcResult;
21use reth_chainspec::ChainSpecProvider;
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, EthConfig};
30use reth_storage_api::{BlockNumReader, BlockReader};
31use reth_tasks::{cancel::is_cancelled, pool::BlockingTaskGuard};
32use reth_transaction_pool::{PoolPooledTx, PoolTransaction, TransactionPool};
33use revm::DatabaseCommit;
34use revm_inspectors::{
35 opcode::OpcodeGasInspector,
36 storage::StorageInspector,
37 tracing::{TracingInspector, TracingInspectorConfig},
38};
39use serde::{Deserialize, Serialize};
40use std::sync::Arc;
41use tokio::sync::{AcquireError, OwnedSemaphorePermit};
42
43const TRACE_FILTER_BLOCK_BUFFER_SIZE: usize = 4;
45const TRACE_FILTER_FETCH_CHUNK_SIZE: usize = 16;
47
48pub struct TraceApi<Eth> {
52 inner: Arc<TraceApiInner<Eth>>,
53}
54
55impl<Eth> TraceApi<Eth> {
58 pub fn new(
60 eth_api: Eth,
61 blocking_task_guard: BlockingTaskGuard,
62 eth_config: EthConfig,
63 ) -> Self {
64 let inner = Arc::new(TraceApiInner { eth_api, blocking_task_guard, eth_config });
65 Self { inner }
66 }
67
68 async fn acquire_trace_permit(
70 &self,
71 ) -> std::result::Result<OwnedSemaphorePermit, AcquireError> {
72 self.inner.blocking_task_guard.clone().acquire_owned().await
73 }
74
75 pub fn eth_api(&self) -> &Eth {
77 &self.inner.eth_api
78 }
79}
80
81impl<Eth: RpcNodeCore> TraceApi<Eth> {
82 pub fn provider(&self) -> &Eth::Provider {
84 self.inner.eth_api.provider()
85 }
86}
87
88impl<Eth> TraceApi<Eth>
91where
92 Eth: Trace + Call + LoadPendingBlock + LoadTransaction + 'static,
95{
96 pub async fn trace_call(
98 &self,
99 trace_request: TraceCallRequest<RpcTxReq<Eth::NetworkTypes>>,
100 ) -> Result<TraceResults, Eth::Error> {
101 let at = trace_request.block_id.unwrap_or_default();
102 let config = TracingInspectorConfig::from_parity_config(&trace_request.trace_types);
103 let overrides =
104 EvmOverrides::new(trace_request.state_overrides, trace_request.block_overrides);
105 let mut inspector = TracingInspector::new(config);
106 let this = self.clone();
107 self.eth_api()
108 .spawn_with_call_at(trace_request.call, at, overrides, move |db, evm_env, tx_env| {
109 let res = this.eth_api().inspect(&mut *db, evm_env, tx_env, &mut inspector)?;
110 let trace_res = inspector
111 .into_parity_builder()
112 .into_trace_results_with_state(&res, &trace_request.trace_types, &db)
113 .map_err(Eth::Error::from_eth_err)?;
114 Ok(trace_res)
115 })
116 .boxed()
117 .await
118 }
119
120 pub async fn trace_raw_transaction(
122 &self,
123 tx: Bytes,
124 trace_types: HashSet<TraceType>,
125 block_id: Option<BlockId>,
126 ) -> Result<TraceResults, Eth::Error> {
127 let tx = self
128 .eth_api()
129 .recover_raw_transaction::<PoolPooledTx<Eth::Pool>>(&tx)?
130 .map(<Eth::Pool as TransactionPool>::Transaction::pooled_into_consensus);
131
132 let (evm_env, at) = self.eth_api().evm_env_at(block_id.unwrap_or_default()).await?;
133
134 self.eth_api()
135 .spawn_with_state_at_block(at, move |this, mut db| {
136 let mut inspector =
137 TracingInspector::new(TracingInspectorConfig::from_parity_config(&trace_types));
138 let res = this.inspect(&mut db, evm_env, tx, &mut inspector)?;
139
140 inspector
141 .into_parity_builder()
142 .into_trace_results_with_state(&res, &trace_types, &db)
143 .map_err(Eth::Error::from_eth_err)
144 })
145 .await
146 }
147
148 pub async fn trace_call_many(
153 &self,
154 calls: Vec<(RpcTxReq<Eth::NetworkTypes>, HashSet<TraceType>)>,
155 block_id: Option<BlockId>,
156 ) -> Result<Vec<TraceResults>, Eth::Error> {
157 let at = block_id.unwrap_or_default();
158 let (evm_env, at) = self.eth_api().evm_env_at(at).await?;
159
160 self.eth_api()
162 .spawn_with_state_at_block(at, move |eth_api, mut db| {
163 let mut results = Vec::with_capacity(calls.len());
164 let mut calls = calls.into_iter().peekable();
165
166 while let Some((call, trace_types)) = calls.next() {
167 if is_cancelled() {
168 return Err(EthApiError::InternalEthError.into())
169 }
170 let (evm_env, tx_env) = eth_api.prepare_call_env(
171 evm_env.clone(),
172 call,
173 &mut db,
174 Default::default(),
175 )?;
176 let config = TracingInspectorConfig::from_parity_config(&trace_types);
177 let mut inspector = TracingInspector::new(config);
178 let res = eth_api.inspect(&mut db, evm_env, tx_env, &mut inspector)?;
179
180 let trace_res = inspector
181 .into_parity_builder()
182 .into_trace_results_with_state(&res, &trace_types, &db)
183 .map_err(Eth::Error::from_eth_err)?;
184
185 results.push(trace_res);
186
187 if calls.peek().is_some() {
190 db.commit(res.state)
191 }
192 }
193
194 Ok(results)
195 })
196 .await
197 }
198
199 pub async fn replay_transaction(
201 &self,
202 hash: B256,
203 trace_types: HashSet<TraceType>,
204 ) -> Result<Option<TraceResultsWithTransactionHash>, Eth::Error> {
205 let config = TracingInspectorConfig::from_parity_config(&trace_types);
206 self.eth_api()
207 .spawn_trace_transaction_in_block(hash, config, move |_, inspector, res, db| {
208 let trace_res = inspector
209 .into_parity_builder()
210 .into_trace_results_with_state(&res, &trace_types, &db)
211 .map_err(Eth::Error::from_eth_err)?;
212 Ok(TraceResultsWithTransactionHash {
213 transaction_hash: hash,
214 full_trace: trace_res,
215 })
216 })
217 .await
218 }
219
220 pub async fn trace_get(
224 &self,
225 hash: B256,
226 indices: Vec<usize>,
227 ) -> Result<Option<LocalizedTransactionTrace>, Eth::Error> {
228 self.eth_api()
229 .spawn_trace_transaction_in_block(
230 hash,
231 TracingInspectorConfig::default_parity(),
232 move |tx_info, inspector, _, _| {
233 Ok(inspector
234 .into_parity_builder()
235 .into_localized_transaction_traces_iter(tx_info)
236 .find(|trace| trace.trace.trace_address == indices))
237 },
238 )
239 .await
240 .map(Option::flatten)
241 }
242
243 pub async fn trace_get_index(
248 &self,
249 hash: B256,
250 index: usize,
251 ) -> Result<Option<LocalizedTransactionTrace>, Eth::Error> {
252 self.eth_api()
253 .spawn_trace_transaction_in_block(
254 hash,
255 TracingInspectorConfig::default_parity(),
256 move |tx_info, inspector, _, _| {
257 Ok(inspector
258 .into_parity_builder()
259 .into_localized_transaction_traces_iter(tx_info)
260 .nth(index))
261 },
262 )
263 .await
264 .map(Option::flatten)
265 }
266
267 pub async fn trace_transaction(
269 &self,
270 hash: B256,
271 ) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
272 self.eth_api()
273 .spawn_trace_transaction_in_block(
274 hash,
275 TracingInspectorConfig::default_parity(),
276 move |tx_info, inspector, _, _| {
277 let traces =
278 inspector.into_parity_builder().into_localized_transaction_traces(tx_info);
279 Ok(traces)
280 },
281 )
282 .await
283 }
284
285 pub async fn trace_transaction_opcode_gas(
288 &self,
289 tx_hash: B256,
290 ) -> Result<Option<TransactionOpcodeGas>, Eth::Error> {
291 self.eth_api()
292 .spawn_trace_transaction_in_block_with_inspector(
293 tx_hash,
294 OpcodeGasInspector::default(),
295 move |_tx_info, inspector, _res, _| {
296 let trace = TransactionOpcodeGas {
297 transaction_hash: tx_hash,
298 opcode_gas: inspector.opcode_gas_iter().collect(),
299 };
300 Ok(trace)
301 },
302 )
303 .await
304 }
305
306 fn calculate_base_block_reward<H: BlockHeader>(&self, header: &H) -> Option<u128> {
312 if header.number() == 0 {
313 return None
314 }
315
316 base_block_reward(self.provider().chain_spec(), header.number())
317 }
318
319 fn extract_reward_traces<H: BlockHeader>(
323 &self,
324 header: &H,
325 block_hash: BlockHash,
326 ommers: Option<&[H]>,
327 base_block_reward: u128,
328 ) -> Vec<LocalizedTransactionTrace> {
329 let ommers_cnt = ommers.map(|o| o.len()).unwrap_or_default();
330 let mut traces = Vec::with_capacity(ommers_cnt + 1);
331
332 let block = BlockNumHash::new(header.number(), block_hash);
333 let block_reward = block_reward(base_block_reward, ommers_cnt);
334 traces.push(
335 RewardAction {
336 author: header.beneficiary(),
337 reward_type: RewardType::Block,
338 value: U256::from(block_reward),
339 }
340 .into_localized_trace(block),
341 );
342
343 let Some(ommers) = ommers else { return traces };
344
345 for uncle in ommers {
346 let uncle_reward = ommer_reward(base_block_reward, header.number(), uncle.number());
347 traces.push(
348 RewardAction {
349 author: uncle.beneficiary(),
350 reward_type: RewardType::Uncle,
351 value: U256::from(uncle_reward),
352 }
353 .into_localized_trace(block),
354 );
355 }
356 traces
357 }
358}
359
360impl<Eth> TraceApi<Eth>
361where
362 Eth: TraceExt + 'static,
365{
366 pub async fn trace_filter(
371 &self,
372 filter: TraceFilter,
373 ) -> Result<Vec<LocalizedTransactionTrace>, Eth::Error> {
374 let matcher = Arc::new(filter.matcher());
376 let TraceFilter { from_block, to_block, mut after, count, .. } = filter;
377
378 let latest_block = self.provider().best_block_number().map_err(Eth::Error::from_eth_err)?;
379 let start = from_block.unwrap_or(latest_block);
380 if start > latest_block {
381 return Err(EthApiError::HeaderNotFound(start.into()).into());
383 }
384 let end = to_block.unwrap_or(latest_block);
385 if end > latest_block {
386 return Err(EthApiError::HeaderNotFound(end.into()).into());
387 }
388
389 let earliest_block =
391 self.provider().earliest_block_number().map_err(Eth::Error::from_eth_err)?;
392 if start < earliest_block {
393 return Err(EthApiError::PrunedHistoryUnavailable {
394 requested: start,
395 earliest_available: earliest_block,
396 }
397 .into());
398 }
399
400 if start > end {
401 return Err(EthApiError::InvalidParams(
402 "invalid parameters: fromBlock cannot be greater than toBlock".to_string(),
403 )
404 .into())
405 }
406
407 let distance = end.saturating_sub(start);
409 if distance > self.inner.eth_config.max_trace_filter_blocks {
410 return Err(EthApiError::InvalidParams(format!(
411 "Block range too large; currently limited to {} blocks",
412 self.inner.eth_config.max_trace_filter_blocks
413 ))
414 .into())
415 }
416
417 let mut all_traces = Vec::new();
418 let block_buffer_size =
419 self.inner.eth_config.max_tracing_requests.clamp(1, TRACE_FILTER_BLOCK_BUFFER_SIZE);
420 let mut include_reward_traces = true;
421
422 for chunk_start in (start..=end).step_by(TRACE_FILTER_FETCH_CHUNK_SIZE) {
423 let chunk_end = (chunk_start + TRACE_FILTER_FETCH_CHUNK_SIZE as u64 - 1).min(end);
424
425 let blocks = self
426 .eth_api()
427 .spawn_blocking_io(move |this| {
428 let blocks = this
429 .provider()
430 .recovered_block_range(chunk_start..=chunk_end)
431 .map_err(Eth::Error::from_eth_err)?;
432
433 Ok(blocks.into_iter().map(Arc::new).collect::<Vec<_>>())
434 })
435 .await?;
436
437 let mut block_replays = futures::stream::iter(blocks)
438 .map(|block| {
439 let this = self.clone();
440 let matcher = matcher.clone();
441
442 let block_hash = block.hash();
443
444 async move {
445 let permit = this.acquire_trace_permit().await;
446 let traces = this
447 .eth_api()
448 .trace_block_until(
449 block_hash.into(),
450 Some(block.clone()),
451 None,
452 TracingInspectorConfig::default_parity(),
453 move |tx_info, mut ctx| {
454 let _block_replay_permit = &permit;
456 let mut traces = ctx
457 .take_inspector()
458 .into_parity_builder()
459 .into_localized_transaction_traces(tx_info);
460 traces.retain(|trace| matcher.matches(&trace.trace));
461 Ok(Some(traces))
462 },
463 )
464 .await?;
465
466 Ok::<_, Eth::Error>((block, traces))
467 }
468 })
469 .buffered(block_buffer_size);
470
471 while let Some(block_replay) = block_replays.next().await {
472 let (block, traces) = block_replay?;
473 let reward_traces = if include_reward_traces {
474 if let Some(base_block_reward) =
475 self.calculate_base_block_reward(block.header())
476 {
477 self.extract_reward_traces(
478 block.header(),
479 block.hash(),
480 block.body().ommers(),
481 base_block_reward,
482 )
483 .into_iter()
484 .filter(|trace| matcher.matches(&trace.trace))
485 .collect::<Vec<_>>()
486 } else {
487 include_reward_traces = block.number() == 0;
491 Vec::new()
492 }
493 } else {
494 Vec::new()
495 };
496
497 if let Some(traces) = traces {
498 all_traces.extend(traces.into_iter().flatten().flatten());
499 }
500 all_traces.extend(reward_traces);
501
502 if let Some(traces) =
503 apply_trace_filter_pagination(&mut all_traces, &mut after, count)
504 {
505 return Ok(traces)
506 }
507 }
508 }
509
510 if let Some(cutoff) = after.map(|a| a as usize) &&
513 cutoff >= all_traces.len()
514 {
515 return Ok(vec![])
516 }
517
518 Ok(all_traces)
519 }
520
521 pub async fn trace_block(
523 &self,
524 block_id: BlockId,
525 ) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
526 let Some(block) = self.eth_api().recovered_block(block_id).await? else {
527 return Err(EthApiError::HeaderNotFound(block_id).into());
528 };
529
530 let mut traces = self
531 .eth_api()
532 .trace_block_with(
533 block_id,
534 Some(block.clone()),
535 TracingInspectorConfig::default_parity(),
536 |tx_info, mut ctx| {
537 let traces = ctx
538 .take_inspector()
539 .into_parity_builder()
540 .into_localized_transaction_traces(tx_info);
541 Ok(traces)
542 },
543 )
544 .await?
545 .map(|traces| traces.into_iter().flatten().collect::<Vec<_>>());
546
547 if let Some(traces) = traces.as_mut() &&
548 let Some(base_block_reward) = self.calculate_base_block_reward(block.header())
549 {
550 traces.extend(self.extract_reward_traces(
551 block.header(),
552 block.hash(),
553 block.body().ommers(),
554 base_block_reward,
555 ));
556 }
557
558 Ok(traces)
559 }
560
561 pub async fn replay_block_transactions(
563 &self,
564 block_id: BlockId,
565 trace_types: HashSet<TraceType>,
566 ) -> Result<Option<Vec<TraceResultsWithTransactionHash>>, Eth::Error> {
567 self.eth_api()
568 .trace_block_with(
569 block_id,
570 None,
571 TracingInspectorConfig::from_parity_config(&trace_types),
572 move |tx_info, mut ctx| {
573 let full_trace = ctx
574 .take_inspector()
575 .into_parity_builder()
576 .into_trace_results_with_state_parts(
577 &ctx.result,
578 ctx.state,
579 &trace_types,
580 &ctx.db,
581 )
582 .map_err(Eth::Error::from_eth_err)?;
583
584 let trace = TraceResultsWithTransactionHash {
585 transaction_hash: tx_info.hash.expect("tx hash is set"),
586 full_trace,
587 };
588 Ok(trace)
589 },
590 )
591 .await
592 }
593
594 pub async fn trace_block_opcode_gas(
599 &self,
600 block_id: BlockId,
601 ) -> Result<Option<BlockOpcodeGas>, Eth::Error> {
602 let Some(block) = self.eth_api().recovered_block(block_id).await? else {
603 return Err(EthApiError::HeaderNotFound(block_id).into());
604 };
605
606 let Some(transactions) = self
607 .eth_api()
608 .trace_block_inspector(
609 block_id,
610 Some(block.clone()),
611 OpcodeGasInspector::default,
612 move |tx_info, ctx| {
613 let trace = TransactionOpcodeGas {
614 transaction_hash: tx_info.hash.expect("tx hash is set"),
615 opcode_gas: ctx.inspector.opcode_gas_iter().collect(),
616 };
617 Ok(trace)
618 },
619 )
620 .await?
621 else {
622 return Ok(None);
623 };
624
625 Ok(Some(BlockOpcodeGas {
626 block_hash: block.hash(),
627 block_number: block.number(),
628 transactions,
629 }))
630 }
631
632 pub async fn trace_block_storage_access(
635 &self,
636 block_id: BlockId,
637 ) -> Result<Option<BlockStorageAccess>, Eth::Error> {
638 let Some(block) = self.eth_api().recovered_block(block_id).await? else {
639 return Err(EthApiError::HeaderNotFound(block_id).into());
640 };
641
642 let Some(transactions) = self
643 .eth_api()
644 .trace_block_inspector(
645 block_id,
646 Some(block.clone()),
647 StorageInspector::default,
648 move |tx_info, mut ctx| {
649 let unique_loads = ctx.inspector.unique_loads();
650 let warm_loads = ctx.inspector.warm_loads();
651 let trace = TransactionStorageAccess {
652 transaction_hash: tx_info.hash.expect("tx hash is set"),
653 storage_access: ctx.take_inspector().into_accessed_slots(),
654 unique_loads,
655 warm_loads,
656 };
657 Ok(trace)
658 },
659 )
660 .await?
661 else {
662 return Ok(None);
663 };
664
665 Ok(Some(BlockStorageAccess {
666 block_hash: block.hash(),
667 block_number: block.number(),
668 transactions,
669 }))
670 }
671}
672
673fn apply_trace_filter_pagination(
674 all_traces: &mut Vec<LocalizedTransactionTrace>,
675 after: &mut Option<u64>,
676 count: Option<u64>,
677) -> Option<Vec<LocalizedTransactionTrace>> {
678 if let Some(cutoff) = after.map(|a| a as usize) &&
680 cutoff < all_traces.len()
681 {
682 all_traces.drain(..cutoff);
683 *after = None;
685 }
686
687 if after.is_none() &&
689 let Some(count) = count
690 {
691 let count = count as usize;
692 if count < all_traces.len() {
693 all_traces.truncate(count);
694 return Some(std::mem::take(all_traces))
695 }
696 }
697
698 None
699}
700
701#[async_trait]
702impl<Eth> TraceApiServer<RpcTxReq<Eth::NetworkTypes>> for TraceApi<Eth>
703where
704 Eth: TraceExt + 'static,
705{
706 async fn trace_call(
710 &self,
711 call: RpcTxReq<Eth::NetworkTypes>,
712 trace_types: HashSet<TraceType>,
713 block_id: Option<BlockId>,
714 state_overrides: Option<StateOverride>,
715 block_overrides: Option<Box<BlockOverrides>>,
716 ) -> RpcResult<TraceResults> {
717 let _permit = self.acquire_trace_permit().await;
718 let request =
719 TraceCallRequest { call, trace_types, block_id, state_overrides, block_overrides };
720 Ok(Self::trace_call(self, request).await.map_err(Into::into)?)
721 }
722
723 async fn trace_call_many(
725 &self,
726 calls: Vec<(RpcTxReq<Eth::NetworkTypes>, HashSet<TraceType>)>,
727 block_id: Option<BlockId>,
728 ) -> RpcResult<Vec<TraceResults>> {
729 let _permit = self.acquire_trace_permit().await;
730 Ok(Self::trace_call_many(self, calls, block_id).await.map_err(Into::into)?)
731 }
732
733 async fn trace_raw_transaction(
735 &self,
736 data: Bytes,
737 trace_types: HashSet<TraceType>,
738 block_id: Option<BlockId>,
739 ) -> RpcResult<TraceResults> {
740 let _permit = self.acquire_trace_permit().await;
741 Ok(Self::trace_raw_transaction(self, data, trace_types, block_id)
742 .await
743 .map_err(Into::into)?)
744 }
745
746 async fn replay_block_transactions(
748 &self,
749 block_id: BlockId,
750 trace_types: HashSet<TraceType>,
751 ) -> RpcResult<Option<Vec<TraceResultsWithTransactionHash>>> {
752 let _permit = self.acquire_trace_permit().await;
753 Ok(Self::replay_block_transactions(self, block_id, trace_types)
754 .await
755 .map_err(Into::into)?)
756 }
757
758 async fn replay_transaction(
760 &self,
761 transaction: B256,
762 trace_types: HashSet<TraceType>,
763 ) -> RpcResult<Option<TraceResultsWithTransactionHash>> {
764 let _permit = self.acquire_trace_permit().await;
765 Ok(Self::replay_transaction(self, transaction, trace_types).await.map_err(Into::into)?)
766 }
767
768 async fn trace_block(
770 &self,
771 block_id: BlockId,
772 ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
773 let _permit = self.acquire_trace_permit().await;
774 Ok(Self::trace_block(self, block_id).await.map_err(Into::into)?)
775 }
776
777 async fn trace_filter(&self, filter: TraceFilter) -> RpcResult<Vec<LocalizedTransactionTrace>> {
784 Ok(Self::trace_filter(self, filter).await.map_err(Into::into)?)
785 }
786
787 async fn trace_get(
790 &self,
791 hash: B256,
792 indices: Vec<Index>,
793 ) -> RpcResult<Option<LocalizedTransactionTrace>> {
794 let _permit = self.acquire_trace_permit().await;
795 Ok(Self::trace_get(self, hash, indices.into_iter().map(Into::into).collect())
796 .await
797 .map_err(Into::into)?)
798 }
799
800 async fn trace_transaction(
802 &self,
803 hash: B256,
804 ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
805 let _permit = self.acquire_trace_permit().await;
806 Ok(Self::trace_transaction(self, hash).await.map_err(Into::into)?)
807 }
808
809 async fn trace_transaction_opcode_gas(
811 &self,
812 tx_hash: B256,
813 ) -> RpcResult<Option<TransactionOpcodeGas>> {
814 let _permit = self.acquire_trace_permit().await;
815 Ok(Self::trace_transaction_opcode_gas(self, tx_hash).await.map_err(Into::into)?)
816 }
817
818 async fn trace_block_opcode_gas(&self, block_id: BlockId) -> RpcResult<Option<BlockOpcodeGas>> {
820 let _permit = self.acquire_trace_permit().await;
821 Ok(Self::trace_block_opcode_gas(self, block_id).await.map_err(Into::into)?)
822 }
823}
824
825impl<Eth> std::fmt::Debug for TraceApi<Eth> {
826 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
827 f.debug_struct("TraceApi").finish_non_exhaustive()
828 }
829}
830impl<Eth> Clone for TraceApi<Eth> {
831 fn clone(&self) -> Self {
832 Self { inner: Arc::clone(&self.inner) }
833 }
834}
835
836struct TraceApiInner<Eth> {
837 eth_api: Eth,
839 blocking_task_guard: BlockingTaskGuard,
841 eth_config: EthConfig,
843}
844
845#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
848#[serde(rename_all = "camelCase")]
849pub struct TransactionStorageAccess {
850 pub transaction_hash: B256,
852 pub storage_access: HashMap<Address, HashMap<B256, u64>>,
854 pub unique_loads: u64,
856 pub warm_loads: u64,
858}
859
860#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
862#[serde(rename_all = "camelCase")]
863pub struct BlockStorageAccess {
864 pub block_hash: BlockHash,
866 pub block_number: u64,
868 pub transactions: Vec<TransactionStorageAccess>,
870}
871
872#[cfg(test)]
873mod tests {
874 use super::*;
875 use crate::EthApiBuilder;
876 use alloy_consensus::Header;
877 use alloy_genesis::Genesis;
878 use alloy_rpc_types_eth::TransactionRequest;
879 use reth_chainspec::ChainSpecBuilder;
880 use reth_db_common::init::init_genesis;
881 use reth_ethereum_primitives::{Block, BlockBody};
882 use reth_evm_ethereum::EthEvmConfig;
883 use reth_network_api::noop::NoopNetwork;
884 use reth_primitives_traits::Block as _;
885 use reth_provider::{
886 providers::BlockchainProvider,
887 test_utils::{
888 create_test_provider_factory_with_chain_spec, ExtendedAccount, MockEthProvider,
889 },
890 BlockWriter, StageCheckpointWriter,
891 };
892 use reth_transaction_pool::test_utils::testing_pool;
893
894 #[tokio::test]
895 async fn trace_call_many_defaults_to_latest() {
896 let provider = MockEthProvider::default();
897 let target = Address::with_last_byte(0x42);
898 provider.add_account(
900 target,
901 ExtendedAccount::new(0, U256::ZERO)
902 .with_bytecode("4360005260206000f3".parse().unwrap()),
903 );
904 let header = Header { number: 1, gas_limit: 30_000_000, ..Default::default() };
905 provider.add_block(header.hash_slow(), Block { header, body: BlockBody::default() });
906 let eth_api = EthApiBuilder::new(
907 provider.clone(),
908 testing_pool(),
909 NoopNetwork::default(),
910 EthEvmConfig::new(provider.chain_spec()),
911 )
912 .build();
913 let api = TraceApi::new(eth_api, BlockingTaskGuard::new(1), EthConfig::default());
914 let calls = vec![(TransactionRequest::default().to(target), HashSet::default())];
915
916 let omitted = api.trace_call_many(calls.clone(), None).await.unwrap();
917 let latest = api.trace_call_many(calls.clone(), Some(BlockId::latest())).await.unwrap();
918 let request = serde_json::json!({
919 "jsonrpc": "2.0", "id": 1, "method": "trace_callMany", "params": [calls.clone()],
920 });
921 let pending = api.trace_call_many(calls, Some(BlockId::pending())).await.unwrap();
922 assert_eq!(omitted, latest);
923 assert_ne!(omitted, pending);
924 assert_eq!(U256::from_be_slice(&omitted[0].output), U256::from(1));
925 assert_eq!(U256::from_be_slice(&pending[0].output), U256::from(2));
926
927 let module = api.into_rpc();
928 let (response, _) = module.raw_json_request(&request.to_string(), 1).await.unwrap();
929 let response: serde_json::Value = serde_json::from_str(response.get()).unwrap();
930 assert_eq!(response["result"], serde_json::to_value(latest).unwrap());
931 }
932
933 #[tokio::test]
934 async fn trace_filter_defaults_to_latest() {
935 let provider = MockEthProvider::default();
936 for number in [1, 2] {
937 let header = Header { number, gas_limit: 30_000_000, ..Default::default() };
938 provider.add_block(header.hash_slow(), Block { header, body: BlockBody::default() });
939 }
940 let eth_api = EthApiBuilder::new(
941 provider.clone(),
942 testing_pool(),
943 NoopNetwork::default(),
944 EthEvmConfig::new(provider.chain_spec()),
945 )
946 .build();
947 let api = TraceApi::new(
948 eth_api,
949 BlockingTaskGuard::new(1),
950 EthConfig::default().max_trace_filter_blocks(1),
951 );
952
953 let omitted = api.trace_filter(TraceFilter::default()).await.unwrap();
954 let latest =
955 api.trace_filter(TraceFilter::default().from_block(2).to_block(2)).await.unwrap();
956 assert_eq!(omitted, latest);
957 assert!(api.trace_filter(TraceFilter::default().from_block(1).to_block(2)).await.is_ok());
958
959 let module = api.into_rpc();
960 let request = serde_json::json!({
961 "jsonrpc": "2.0", "id": 1, "method": "trace_filter", "params": [{"toBlock": "0x1"}],
962 });
963 let (response, _) = module.raw_json_request(&request.to_string(), 1).await.unwrap();
964 let response: serde_json::Value = serde_json::from_str(response.get()).unwrap();
965 assert_eq!(
966 response["error"],
967 serde_json::json!({
968 "code": -32602,
969 "message": "invalid parameters: fromBlock cannot be greater than toBlock",
970 })
971 );
972 }
973
974 #[tokio::test]
975 async fn trace_get_selects_tree_paths() {
976 use crate::EthApiBuilder;
977 use alloy_consensus::{Header, TxLegacy};
978 use alloy_primitives::{Signature, TxKind};
979 use reth_chain_state::CanonStateNotification;
980 use reth_ethereum_primitives::{Block, BlockBody, TransactionSigned};
981 use reth_evm_ethereum::EthEvmConfig;
982 use reth_execution_types::{Chain, ExecutionOutcome};
983 use reth_network_api::noop::NoopNetwork;
984 use reth_primitives_traits::{RecoveredBlock, SignerRecoverable};
985 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
986 use reth_rpc_eth_types::cache::cache_new_blocks_task;
987 use reth_transaction_pool::test_utils::testing_pool;
988
989 let provider = MockEthProvider::default();
990 for (address, code) in [
992 (0x42, "60006000600060006000604361fffff15060006000600060006000604561fffff15000"),
993 (0x43, "600060006000600060006044611000f15000"),
994 ] {
995 provider.add_account(
996 Address::with_last_byte(address),
997 ExtendedAccount::new(0, U256::ZERO).with_bytecode(code.parse().unwrap()),
998 );
999 }
1000 let tx = TransactionSigned::new_unhashed(
1001 TxLegacy {
1002 gas_limit: 500_000,
1003 to: TxKind::Call(Address::with_last_byte(0x42)),
1004 value: U256::from(1),
1005 ..Default::default()
1006 }
1007 .into(),
1008 Signature::test_signature(),
1009 );
1010 let hash = *tx.hash();
1011 let sender = tx.recover_signer().unwrap();
1012 provider.add_account(sender, ExtendedAccount::new(0, U256::from(1_000_000)));
1013 let parent = Header { gas_limit: 30_000_000, ..Default::default() };
1014 let parent_hash = parent.hash_slow();
1015 provider.add_header(parent_hash, parent);
1016 let block = Block {
1017 header: Header { parent_hash, number: 1, gas_limit: 30_000_000, ..Default::default() },
1018 body: BlockBody { transactions: vec![tx], ..Default::default() },
1019 };
1020 let block_hash = block.header.hash_slow();
1021 provider.add_block(block_hash, block.clone());
1022 let eth_api = EthApiBuilder::new(
1023 provider.clone(),
1024 testing_pool(),
1025 NoopNetwork::default(),
1026 EthEvmConfig::new(provider.chain_spec()),
1027 )
1028 .build();
1029 cache_new_blocks_task(
1031 eth_api.cache().clone(),
1032 futures::stream::iter([CanonStateNotification::Commit {
1033 new: Arc::new(Chain::new(
1034 [RecoveredBlock::new_unhashed(block, vec![sender])],
1035 ExecutionOutcome {
1036 receipts: vec![vec![]],
1037 first_block: 1,
1038 ..Default::default()
1039 },
1040 Default::default(),
1041 )),
1042 }]),
1043 )
1044 .await;
1045 let api = TraceApi::new(eth_api, BlockingTaskGuard::new(1), EthConfig::default());
1046 let module = api.clone().into_rpc();
1047 let traces = api.trace_transaction(hash).await.unwrap().unwrap();
1048 let paths: Vec<_> = traces.iter().map(|trace| trace.trace.trace_address.clone()).collect();
1049 assert_eq!(paths, [vec![], vec![0], vec![0, 0], vec![1]]);
1050 for (path, expected) in [
1051 (vec![], Some(&traces[0])),
1052 (vec![0], Some(&traces[1])),
1053 (vec![0, 0], Some(&traces[2])),
1054 (vec![1], Some(&traces[3])),
1055 (vec![2], None),
1056 (vec![0, 1], None),
1057 (vec![1, 0], None),
1058 ] {
1059 let indices: Vec<_> = path.iter().map(|index| format!("0x{index:x}")).collect();
1060 let request = serde_json::json!({
1061 "jsonrpc": "2.0", "id": 1, "method": "trace_get", "params": [hash, indices],
1062 });
1063 let (response, _) = module.raw_json_request(&request.to_string(), 1).await.unwrap();
1064 let response: serde_json::Value = serde_json::from_str(response.get()).unwrap();
1065 assert!(response.get("error").is_none(), "{response}");
1066 assert_eq!(response["result"], serde_json::to_value(expected).unwrap());
1067 }
1068 assert!(api.trace_get(B256::ZERO, vec![]).await.unwrap().is_none());
1069 assert!(api.trace_get(B256::ZERO, vec![0, 0]).await.unwrap().is_none());
1070 assert_eq!(api.trace_get_index(hash, 2).await.unwrap().as_ref(), Some(&traces[2]));
1071 }
1072
1073 #[tokio::test]
1074 async fn replay_missing_transaction_returns_null() {
1075 use crate::EthApiBuilder;
1076 use reth_evm_ethereum::EthEvmConfig;
1077 use reth_network_api::noop::NoopNetwork;
1078 use reth_provider::test_utils::MockEthProvider;
1079 use reth_transaction_pool::test_utils::testing_pool;
1080
1081 let provider = MockEthProvider::default();
1082 let eth_api = EthApiBuilder::new(
1083 provider.clone(),
1084 testing_pool(),
1085 NoopNetwork::default(),
1086 EthEvmConfig::new(provider.chain_spec()),
1087 )
1088 .build();
1089 let api = TraceApi::new(eth_api, BlockingTaskGuard::new(1), EthConfig::default());
1090 let module = api.into_rpc();
1091 for types in [serde_json::json!([]), serde_json::json!(["trace", "stateDiff", "vmTrace"])] {
1092 let request = serde_json::json!({
1093 "jsonrpc": "2.0",
1094 "id": 1,
1095 "method": "trace_replayTransaction",
1096 "params": [B256::with_last_byte(1), types],
1097 });
1098 let (response, _) = module.raw_json_request(&request.to_string(), 1).await.unwrap();
1099 let response: serde_json::Value = serde_json::from_str(response.get()).unwrap();
1100 assert_eq!(response, serde_json::json!({"jsonrpc": "2.0", "id": 1, "result": null}));
1101 }
1102 }
1103
1104 #[tokio::test]
1105 async fn replay_transaction_includes_transaction_hash() {
1106 use crate::EthApiBuilder;
1107 use alloy_consensus::{Header, TxLegacy};
1108 use alloy_primitives::{Signature, TxKind};
1109 use reth_chain_state::CanonStateNotification;
1110 use reth_ethereum_primitives::{Block, BlockBody, TransactionSigned};
1111 use reth_evm_ethereum::EthEvmConfig;
1112 use reth_execution_types::{Chain, ExecutionOutcome};
1113 use reth_network_api::noop::NoopNetwork;
1114 use reth_primitives_traits::{RecoveredBlock, SignerRecoverable};
1115 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1116 use reth_rpc_eth_types::cache::cache_new_blocks_task;
1117 use reth_transaction_pool::test_utils::testing_pool;
1118
1119 let provider = MockEthProvider::default();
1120 let tx = TransactionSigned::new_unhashed(
1121 TxLegacy {
1122 gas_limit: 21_000,
1123 to: TxKind::Call(Address::with_last_byte(0x42)),
1124 value: U256::from(1),
1125 ..Default::default()
1126 }
1127 .into(),
1128 Signature::test_signature(),
1129 );
1130 let hash = *tx.hash();
1131 let sender = tx.recover_signer().unwrap();
1132 provider.add_account(sender, ExtendedAccount::new(0, U256::from(1_000_000)));
1133 let parent = Header { gas_limit: 30_000_000, ..Default::default() };
1134 let parent_hash = parent.hash_slow();
1135 provider.add_header(parent_hash, parent);
1136 let block = Block {
1137 header: Header { parent_hash, number: 1, gas_limit: 30_000_000, ..Default::default() },
1138 body: BlockBody { transactions: vec![tx], ..Default::default() },
1139 };
1140 let block_hash = block.header.hash_slow();
1141 provider.add_block(block_hash, block.clone());
1142 let eth_api = EthApiBuilder::new(
1143 provider.clone(),
1144 testing_pool(),
1145 NoopNetwork::default(),
1146 EthEvmConfig::new(provider.chain_spec()),
1147 )
1148 .build();
1149 cache_new_blocks_task(
1151 eth_api.cache().clone(),
1152 futures::stream::iter([CanonStateNotification::Commit {
1153 new: Arc::new(Chain::new(
1154 [RecoveredBlock::new_unhashed(block, vec![sender])],
1155 ExecutionOutcome {
1156 receipts: vec![vec![]],
1157 first_block: 1,
1158 ..Default::default()
1159 },
1160 Default::default(),
1161 )),
1162 }]),
1163 )
1164 .await;
1165 let api = TraceApi::new(eth_api, BlockingTaskGuard::new(1), EthConfig::default());
1166 let module = api.clone().into_rpc();
1167 for types in
1168 [HashSet::default(), HashSet::from_iter([TraceType::Trace, TraceType::StateDiff])]
1169 {
1170 let request = serde_json::json!({
1171 "jsonrpc": "2.0", "id": 1, "method": "trace_replayTransaction",
1172 "params": [hash, types],
1173 });
1174 let (response, _) = module.raw_json_request(&request.to_string(), 1).await.unwrap();
1175 let response: serde_json::Value = serde_json::from_str(response.get()).unwrap();
1176 assert!(response.get("error").is_none(), "{response}");
1177 assert_eq!(response["result"]["transactionHash"], serde_json::json!(hash));
1178
1179 let block_replay =
1180 api.replay_block_transactions(block_hash.into(), types).await.unwrap().unwrap();
1181 assert_eq!(response["result"], serde_json::to_value(&block_replay[0]).unwrap());
1182 }
1183 }
1184
1185 #[tokio::test]
1186 async fn genesis_block_has_no_reward_trace() {
1187 let coinbase = Address::repeat_byte(0x11);
1189 let genesis = Genesis::default().with_gas_limit(30_000_000).with_coinbase(coinbase);
1190 let chain_spec = Arc::new(ChainSpecBuilder::mainnet().genesis(genesis).build());
1191 let factory = create_test_provider_factory_with_chain_spec(chain_spec);
1192 let genesis_hash = init_genesis(&factory).unwrap();
1193 let block = Block {
1194 header: Header {
1195 parent_hash: genesis_hash,
1196 number: 1,
1197 beneficiary: coinbase,
1198 gas_limit: 30_000_000,
1199 ..Default::default()
1200 },
1201 body: BlockBody::default(),
1202 };
1203 let provider_rw = factory.provider_rw().unwrap();
1204 provider_rw.insert_block(&block.seal_slow().try_recover().unwrap()).unwrap();
1205 provider_rw.update_pipeline_stages(1, false).unwrap();
1206 provider_rw.commit().unwrap();
1207
1208 let provider = BlockchainProvider::new(factory).unwrap();
1209 let eth_api = EthApiBuilder::new(
1210 provider.clone(),
1211 testing_pool(),
1212 NoopNetwork::default(),
1213 EthEvmConfig::new(provider.chain_spec()),
1214 )
1215 .build();
1216 let api = TraceApi::new(eth_api, BlockingTaskGuard::new(1), EthConfig::default());
1217
1218 assert_eq!(api.trace_block(0.into()).await.unwrap(), Some(vec![]));
1219 assert_eq!(
1220 trace_order(&api.trace_block(1.into()).await.unwrap().unwrap()),
1221 [(1, None, true)]
1222 );
1223 for (to_block, expected) in [(0, vec![]), (1, vec![(1, None, true)])] {
1224 let filter =
1225 TraceFilter { from_block: Some(0), to_block: Some(to_block), ..Default::default() };
1226 assert_eq!(trace_order(&api.trace_filter(filter).await.unwrap()), expected);
1227 }
1228 }
1229
1230 fn localized_transaction_trace(
1231 block_number: u64,
1232 transaction_position: u64,
1233 ) -> LocalizedTransactionTrace {
1234 LocalizedTransactionTrace {
1235 block_hash: Some(B256::ZERO),
1236 block_number: Some(block_number),
1237 transaction_hash: Some(B256::ZERO),
1238 transaction_position: Some(transaction_position),
1239 trace: TransactionTrace::default(),
1240 }
1241 }
1242
1243 fn localized_reward_trace(block_number: u64) -> LocalizedTransactionTrace {
1244 RewardAction { author: Address::ZERO, reward_type: RewardType::Block, value: U256::ZERO }
1245 .into_localized_trace(BlockNumHash::new(block_number, B256::ZERO))
1246 }
1247
1248 fn trace_order(traces: &[LocalizedTransactionTrace]) -> Vec<(u64, Option<u64>, bool)> {
1249 traces
1250 .iter()
1251 .map(|trace| {
1252 (
1253 trace.block_number.unwrap(),
1254 trace.transaction_position,
1255 trace.trace.action.is_reward(),
1256 )
1257 })
1258 .collect()
1259 }
1260
1261 #[test]
1262 fn trace_filter_paginates_after_per_block_reward_order() {
1263 let mut all_traces = vec![
1264 localized_transaction_trace(1, 0),
1265 localized_reward_trace(1),
1266 localized_transaction_trace(2, 0),
1267 localized_reward_trace(2),
1268 ];
1269
1270 let mut after = Some(1);
1271 let paginated =
1272 apply_trace_filter_pagination(&mut all_traces, &mut after, Some(1)).unwrap();
1273
1274 assert_eq!(trace_order(&paginated), vec![(1, None, true)]);
1275 }
1276
1277 #[tokio::test]
1278 async fn replay_block_vmtrace_includes_root_and_callcode_bytecode() {
1279 use crate::EthApiBuilder;
1280 use alloy_consensus::{Header, TxLegacy};
1281 use alloy_primitives::{hex, Signature, TxKind};
1282 use reth_chain_state::CanonStateNotification;
1283 use reth_ethereum_primitives::{Block, BlockBody, TransactionSigned};
1284 use reth_evm_ethereum::EthEvmConfig;
1285 use reth_execution_types::{Chain, ExecutionOutcome};
1286 use reth_network_api::noop::NoopNetwork;
1287 use reth_primitives_traits::{RecoveredBlock, SignerRecoverable};
1288 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1289 use reth_rpc_eth_types::cache::cache_new_blocks_task;
1290 use reth_transaction_pool::test_utils::testing_pool;
1291
1292 let provider = MockEthProvider::default();
1293 let target = Address::with_last_byte(0x42);
1294 let child = Address::with_last_byte(0x43);
1295 let root_code: Bytes =
1297 hex!("60005460010160005560006000600060006000604361fffff25060005460005260206000f3")
1298 .into();
1299 let child_code: Bytes = hex!("60025000").into();
1300 provider.add_account(
1301 target,
1302 ExtendedAccount::new(0, U256::ZERO).with_bytecode(root_code.clone()),
1303 );
1304 provider.add_account(
1305 child,
1306 ExtendedAccount::new(0, U256::ZERO).with_bytecode(child_code.clone()),
1307 );
1308 let mut transactions = Vec::new();
1309 for nonce in 0..2 {
1310 let tx = TransactionSigned::new_unhashed(
1311 TxLegacy {
1312 nonce,
1313 gas_limit: 500_000,
1314 to: TxKind::Call(target),
1315 ..Default::default()
1316 }
1317 .into(),
1318 Signature::test_signature(),
1319 );
1320 provider.add_account(
1321 tx.recover_signer().unwrap(),
1322 ExtendedAccount::new(nonce, U256::from(1_000_000)),
1323 );
1324 transactions.push(tx);
1325 }
1326 let parent = Header { gas_limit: 30_000_000, ..Default::default() };
1327 let parent_hash = parent.hash_slow();
1328 provider.add_header(parent_hash, parent);
1329 let block = Block {
1330 header: Header { parent_hash, number: 1, gas_limit: 30_000_000, ..Default::default() },
1331 body: BlockBody { transactions, ..Default::default() },
1332 };
1333 let block_hash = block.header.hash_slow();
1334 let senders =
1335 block.body.transactions.iter().map(|tx| tx.recover_signer().unwrap()).collect();
1336 provider.add_block(block_hash, block.clone());
1337 let recovered = RecoveredBlock::new_unhashed(block, senders);
1338 let eth_api = EthApiBuilder::new(
1339 provider.clone(),
1340 testing_pool(),
1341 NoopNetwork::default(),
1342 EthEvmConfig::new(provider.chain_spec()),
1343 )
1344 .build();
1345 cache_new_blocks_task(
1347 eth_api.cache().clone(),
1348 futures::stream::iter([CanonStateNotification::Commit {
1349 new: Arc::new(Chain::new(
1350 [recovered],
1351 ExecutionOutcome {
1352 receipts: vec![vec![]],
1353 first_block: 1,
1354 ..Default::default()
1355 },
1356 Default::default(),
1357 )),
1358 }]),
1359 )
1360 .await;
1361 let api = TraceApi::new(eth_api, BlockingTaskGuard::new(1), EthConfig::default());
1362 for types in [
1363 HashSet::from_iter([TraceType::VmTrace]),
1364 HashSet::from_iter([TraceType::VmTrace, TraceType::Trace, TraceType::StateDiff]),
1365 HashSet::from_iter([TraceType::Trace, TraceType::StateDiff]),
1366 ] {
1367 let traces = api
1368 .replay_block_transactions(block_hash.into(), types.clone())
1369 .await
1370 .unwrap()
1371 .unwrap();
1372 assert_eq!(traces.len(), 2);
1373 for (index, trace) in traces.into_iter().enumerate() {
1374 assert_eq!(
1375 trace.full_trace.output.as_ref(),
1376 U256::from(index + 1).to_be_bytes::<32>()
1377 );
1378 if types.contains(&TraceType::VmTrace) {
1379 let vm = trace.full_trace.vm_trace.as_ref().unwrap();
1380 assert_eq!(vm.code, root_code);
1381 assert_eq!(
1382 vm.ops.iter().find_map(|op| op.sub.as_ref()).unwrap().code,
1383 child_code
1384 );
1385 }
1386 let individual = api
1387 .replay_transaction(trace.transaction_hash, types.clone())
1388 .await
1389 .unwrap()
1390 .unwrap();
1391 assert_eq!(individual.transaction_hash, trace.transaction_hash);
1392 assert_eq!(trace.full_trace, individual.full_trace);
1393 }
1394 }
1395 }
1396}