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