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 futures::StreamExt;
20use jsonrpsee::core::RpcResult;
21use reth_chainspec::{ChainSpecProvider, EthereumHardforks};
22use reth_evm::ConfigureEvm;
23use reth_primitives_traits::{BlockBody, BlockHeader};
24use reth_rpc_api::TraceApiServer;
25use reth_rpc_convert::RpcTxReq;
26use reth_rpc_eth_api::{
27 helpers::{Call, LoadPendingBlock, LoadTransaction, Trace, TraceExt},
28 FromEthApiError, RpcNodeCore,
29};
30use reth_rpc_eth_types::{error::EthApiError, utils::recover_raw_transaction, EthConfig};
31use reth_storage_api::{BlockNumReader, BlockReader};
32use reth_tasks::pool::BlockingTaskGuard;
33use reth_transaction_pool::{PoolPooledTx, PoolTransaction, TransactionPool};
34use revm::DatabaseCommit;
35use revm_inspectors::{
36 opcode::OpcodeGasInspector,
37 storage::StorageInspector,
38 tracing::{parity::populate_state_diff, TracingInspector, TracingInspectorConfig},
39};
40use serde::{Deserialize, Serialize};
41use std::sync::Arc;
42use tokio::sync::{AcquireError, OwnedSemaphorePermit};
43
44const TRACE_FILTER_BLOCK_BUFFER_SIZE: usize = 4;
46const TRACE_FILTER_FETCH_CHUNK_SIZE: usize = 16;
48
49pub struct TraceApi<Eth> {
53 inner: Arc<TraceApiInner<Eth>>,
54}
55
56impl<Eth> TraceApi<Eth> {
59 pub fn new(
61 eth_api: Eth,
62 blocking_task_guard: BlockingTaskGuard,
63 eth_config: EthConfig,
64 ) -> Self {
65 let inner = Arc::new(TraceApiInner { eth_api, blocking_task_guard, eth_config });
66 Self { inner }
67 }
68
69 async fn acquire_trace_permit(
71 &self,
72 ) -> std::result::Result<OwnedSemaphorePermit, AcquireError> {
73 self.inner.blocking_task_guard.clone().acquire_owned().await
74 }
75
76 pub fn eth_api(&self) -> &Eth {
78 &self.inner.eth_api
79 }
80}
81
82impl<Eth: RpcNodeCore> TraceApi<Eth> {
83 pub fn provider(&self) -> &Eth::Provider {
85 self.inner.eth_api.provider()
86 }
87}
88
89impl<Eth> TraceApi<Eth>
92where
93 Eth: Trace + Call + LoadPendingBlock + LoadTransaction + 'static,
96{
97 pub async fn trace_call(
99 &self,
100 trace_request: TraceCallRequest<RpcTxReq<Eth::NetworkTypes>>,
101 ) -> Result<TraceResults, Eth::Error> {
102 let at = trace_request.block_id.unwrap_or_default();
103 let config = TracingInspectorConfig::from_parity_config(&trace_request.trace_types);
104 let overrides =
105 EvmOverrides::new(trace_request.state_overrides, trace_request.block_overrides);
106 let mut inspector = TracingInspector::new(config);
107 let this = self.clone();
108 self.eth_api()
109 .spawn_with_call_at(trace_request.call, at, overrides, move |db, evm_env, tx_env| {
110 let res = this.eth_api().inspect(&mut *db, evm_env, tx_env, &mut inspector)?;
111 let trace_res = inspector
112 .into_parity_builder()
113 .into_trace_results_with_state(&res, &trace_request.trace_types, &db)
114 .map_err(Eth::Error::from_eth_err)?;
115 Ok(trace_res)
116 })
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 = recover_raw_transaction::<PoolPooledTx<Eth::Pool>>(&tx)?
128 .map(<Eth::Pool as TransactionPool>::Transaction::pooled_into_consensus);
129
130 let (evm_env, at) = self.eth_api().evm_env_at(block_id.unwrap_or_default()).await?;
131 let tx_env = self.eth_api().evm_config().tx_env(tx);
132
133 let config = TracingInspectorConfig::from_parity_config(&trace_types);
134
135 self.eth_api()
136 .spawn_trace_at_with_state(evm_env, tx_env, config, at, move |inspector, res, db| {
137 inspector
138 .into_parity_builder()
139 .into_trace_results_with_state(&res, &trace_types, &db)
140 .map_err(Eth::Error::from_eth_err)
141 })
142 .await
143 }
144
145 pub async fn trace_call_many(
150 &self,
151 calls: Vec<(RpcTxReq<Eth::NetworkTypes>, HashSet<TraceType>)>,
152 block_id: Option<BlockId>,
153 ) -> Result<Vec<TraceResults>, Eth::Error> {
154 let at = block_id.unwrap_or(BlockId::pending());
155 let (evm_env, at) = self.eth_api().evm_env_at(at).await?;
156
157 self.eth_api()
159 .spawn_with_state_at_block(at, move |eth_api, mut db| {
160 let mut results = Vec::with_capacity(calls.len());
161 let mut calls = calls.into_iter().peekable();
162
163 while let Some((call, trace_types)) = calls.next() {
164 let (evm_env, tx_env) = eth_api.prepare_call_env(
165 evm_env.clone(),
166 call,
167 &mut db,
168 Default::default(),
169 )?;
170 let config = TracingInspectorConfig::from_parity_config(&trace_types);
171 let mut inspector = TracingInspector::new(config);
172 let res = eth_api.inspect(&mut db, evm_env, tx_env, &mut inspector)?;
173
174 let trace_res = inspector
175 .into_parity_builder()
176 .into_trace_results_with_state(&res, &trace_types, &db)
177 .map_err(Eth::Error::from_eth_err)?;
178
179 results.push(trace_res);
180
181 if calls.peek().is_some() {
184 db.commit(res.state)
185 }
186 }
187
188 Ok(results)
189 })
190 .await
191 }
192
193 pub async fn replay_transaction(
195 &self,
196 hash: B256,
197 trace_types: HashSet<TraceType>,
198 ) -> Result<TraceResults, Eth::Error> {
199 let config = TracingInspectorConfig::from_parity_config(&trace_types);
200 self.eth_api()
201 .spawn_trace_transaction_in_block(hash, config, move |_, inspector, res, db| {
202 let trace_res = inspector
203 .into_parity_builder()
204 .into_trace_results_with_state(&res, &trace_types, &db)
205 .map_err(Eth::Error::from_eth_err)?;
206 Ok(trace_res)
207 })
208 .await
209 .transpose()
210 .ok_or(EthApiError::TransactionNotFound)?
211 }
212
213 pub async fn trace_get(
220 &self,
221 hash: B256,
222 indices: Vec<usize>,
223 ) -> Result<Option<LocalizedTransactionTrace>, Eth::Error> {
224 if indices.len() != 1 {
225 return Ok(None)
227 }
228 self.trace_get_index(hash, indices[0]).await
229 }
230
231 pub async fn trace_get_index(
235 &self,
236 hash: B256,
237 index: usize,
238 ) -> Result<Option<LocalizedTransactionTrace>, Eth::Error> {
239 Ok(self.trace_transaction(hash).await?.and_then(|traces| traces.into_iter().nth(index)))
240 }
241
242 pub async fn trace_transaction(
244 &self,
245 hash: B256,
246 ) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
247 self.eth_api()
248 .spawn_trace_transaction_in_block(
249 hash,
250 TracingInspectorConfig::default_parity(),
251 move |tx_info, inspector, _, _| {
252 let traces =
253 inspector.into_parity_builder().into_localized_transaction_traces(tx_info);
254 Ok(traces)
255 },
256 )
257 .await
258 }
259
260 pub async fn trace_transaction_opcode_gas(
263 &self,
264 tx_hash: B256,
265 ) -> Result<Option<TransactionOpcodeGas>, Eth::Error> {
266 self.eth_api()
267 .spawn_trace_transaction_in_block_with_inspector(
268 tx_hash,
269 OpcodeGasInspector::default(),
270 move |_tx_info, inspector, _res, _| {
271 let trace = TransactionOpcodeGas {
272 transaction_hash: tx_hash,
273 opcode_gas: inspector.opcode_gas_iter().collect(),
274 };
275 Ok(trace)
276 },
277 )
278 .await
279 }
280
281 fn calculate_base_block_reward<H: BlockHeader>(
286 &self,
287 header: &H,
288 ) -> Result<Option<u128>, Eth::Error> {
289 let chain_spec = self.provider().chain_spec();
290
291 if chain_spec.is_paris_active_at_block(header.number()) {
292 return Ok(None)
293 }
294
295 Ok(Some(base_block_reward_pre_merge(&chain_spec, header.number())))
296 }
297
298 fn extract_reward_traces<H: BlockHeader>(
302 &self,
303 header: &H,
304 block_hash: BlockHash,
305 ommers: Option<&[H]>,
306 base_block_reward: u128,
307 ) -> Vec<LocalizedTransactionTrace> {
308 let ommers_cnt = ommers.map(|o| o.len()).unwrap_or_default();
309 let mut traces = Vec::with_capacity(ommers_cnt + 1);
310
311 let block_reward = block_reward(base_block_reward, ommers_cnt);
312 traces.push(reward_trace(
313 block_hash,
314 header,
315 RewardAction {
316 author: header.beneficiary(),
317 reward_type: RewardType::Block,
318 value: U256::from(block_reward),
319 },
320 ));
321
322 let Some(ommers) = ommers else { return traces };
323
324 for uncle in ommers {
325 let uncle_reward = ommer_reward(base_block_reward, header.number(), uncle.number());
326 traces.push(reward_trace(
327 block_hash,
328 header,
329 RewardAction {
330 author: uncle.beneficiary(),
331 reward_type: RewardType::Uncle,
332 value: U256::from(uncle_reward),
333 },
334 ));
335 }
336 traces
337 }
338}
339
340impl<Eth> TraceApi<Eth>
341where
342 Eth: TraceExt + 'static,
345{
346 pub async fn trace_filter(
351 &self,
352 filter: TraceFilter,
353 ) -> Result<Vec<LocalizedTransactionTrace>, Eth::Error> {
354 let matcher = Arc::new(filter.matcher());
356 let TraceFilter { from_block, to_block, mut after, count, .. } = filter;
357 let start = from_block.unwrap_or(0);
358
359 let latest_block = self.provider().best_block_number().map_err(Eth::Error::from_eth_err)?;
360 if start > latest_block {
361 return Err(EthApiError::HeaderNotFound(start.into()).into());
363 }
364 let end = to_block.unwrap_or(latest_block);
365 if end > latest_block {
366 return Err(EthApiError::HeaderNotFound(end.into()).into());
367 }
368
369 let earliest_block =
371 self.provider().earliest_block_number().map_err(Eth::Error::from_eth_err)?;
372 if start < earliest_block {
373 return Err(EthApiError::PrunedHistoryUnavailable.into());
374 }
375
376 if start > end {
377 return Err(EthApiError::InvalidParams(
378 "invalid parameters: fromBlock cannot be greater than toBlock".to_string(),
379 )
380 .into())
381 }
382
383 let distance = end.saturating_sub(start);
385 if distance > self.inner.eth_config.max_trace_filter_blocks {
386 return Err(EthApiError::InvalidParams(format!(
387 "Block range too large; currently limited to {} blocks",
388 self.inner.eth_config.max_trace_filter_blocks
389 ))
390 .into())
391 }
392
393 let mut all_traces = Vec::new();
394 let block_buffer_size =
395 self.inner.eth_config.max_tracing_requests.clamp(1, TRACE_FILTER_BLOCK_BUFFER_SIZE);
396 let mut include_reward_traces = true;
397
398 for chunk_start in (start..=end).step_by(TRACE_FILTER_FETCH_CHUNK_SIZE) {
399 let chunk_end = (chunk_start + TRACE_FILTER_FETCH_CHUNK_SIZE as u64 - 1).min(end);
400
401 let blocks = self
402 .eth_api()
403 .spawn_blocking_io(move |this| {
404 let blocks = this
405 .provider()
406 .recovered_block_range(chunk_start..=chunk_end)
407 .map_err(Eth::Error::from_eth_err)?;
408
409 Ok(blocks.into_iter().map(Arc::new).collect::<Vec<_>>())
410 })
411 .await?;
412
413 let mut block_replays = futures::stream::iter(blocks)
414 .map(|block| {
415 let this = self.clone();
416 let matcher = matcher.clone();
417
418 let block_hash = block.hash();
419
420 async move {
421 let permit = this.acquire_trace_permit().await;
422 let traces = this
423 .eth_api()
424 .trace_block_until(
425 block_hash.into(),
426 Some(block.clone()),
427 None,
428 TracingInspectorConfig::default_parity(),
429 move |tx_info, mut ctx| {
430 let _block_replay_permit = &permit;
432 let mut traces = ctx
433 .take_inspector()
434 .into_parity_builder()
435 .into_localized_transaction_traces(tx_info);
436 traces.retain(|trace| matcher.matches(&trace.trace));
437 Ok(Some(traces))
438 },
439 )
440 .await?;
441
442 Ok::<_, Eth::Error>((block, traces))
443 }
444 })
445 .buffered(block_buffer_size);
446
447 while let Some(block_replay) = block_replays.next().await {
448 let (block, traces) = block_replay?;
449 let reward_traces = if include_reward_traces {
450 if let Some(base_block_reward) =
451 self.calculate_base_block_reward(block.header())?
452 {
453 self.extract_reward_traces(
454 block.header(),
455 block.hash(),
456 block.body().ommers(),
457 base_block_reward,
458 )
459 .into_iter()
460 .filter(|trace| matcher.matches(&trace.trace))
461 .collect::<Vec<_>>()
462 } else {
463 include_reward_traces = false;
466 Vec::new()
467 }
468 } else {
469 Vec::new()
470 };
471
472 if let Some(traces) = traces {
473 all_traces.extend(traces.into_iter().flatten().flatten());
474 }
475 all_traces.extend(reward_traces);
476
477 if let Some(traces) =
478 apply_trace_filter_pagination(&mut all_traces, &mut after, count)
479 {
480 return Ok(traces)
481 }
482 }
483 }
484
485 if let Some(cutoff) = after.map(|a| a as usize) &&
488 cutoff >= all_traces.len()
489 {
490 return Ok(vec![])
491 }
492
493 Ok(all_traces)
494 }
495
496 pub async fn trace_block(
498 &self,
499 block_id: BlockId,
500 ) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
501 let Some(block) = self.eth_api().recovered_block(block_id).await? else {
502 return Err(EthApiError::HeaderNotFound(block_id).into());
503 };
504
505 let mut traces = self
506 .eth_api()
507 .trace_block_with(
508 block_id,
509 Some(block.clone()),
510 TracingInspectorConfig::default_parity(),
511 |tx_info, mut ctx| {
512 let traces = ctx
513 .take_inspector()
514 .into_parity_builder()
515 .into_localized_transaction_traces(tx_info);
516 Ok(traces)
517 },
518 )
519 .await?
520 .map(|traces| traces.into_iter().flatten().collect::<Vec<_>>());
521
522 if let Some(traces) = traces.as_mut() &&
523 let Some(base_block_reward) = self.calculate_base_block_reward(block.header())?
524 {
525 traces.extend(self.extract_reward_traces(
526 block.header(),
527 block.hash(),
528 block.body().ommers(),
529 base_block_reward,
530 ));
531 }
532
533 Ok(traces)
534 }
535
536 pub async fn replay_block_transactions(
538 &self,
539 block_id: BlockId,
540 trace_types: HashSet<TraceType>,
541 ) -> Result<Option<Vec<TraceResultsWithTransactionHash>>, Eth::Error> {
542 self.eth_api()
543 .trace_block_with(
544 block_id,
545 None,
546 TracingInspectorConfig::from_parity_config(&trace_types),
547 move |tx_info, mut ctx| {
548 let mut full_trace = ctx
549 .take_inspector()
550 .into_parity_builder()
551 .into_trace_results(&ctx.result, &trace_types);
552
553 if let Some(ref mut state_diff) = full_trace.state_diff {
556 populate_state_diff(state_diff, &ctx.db, ctx.state.iter())
557 .map_err(Eth::Error::from_eth_err)?;
558 }
559
560 let trace = TraceResultsWithTransactionHash {
561 transaction_hash: tx_info.hash.expect("tx hash is set"),
562 full_trace,
563 };
564 Ok(trace)
565 },
566 )
567 .await
568 }
569
570 pub async fn trace_block_opcode_gas(
575 &self,
576 block_id: BlockId,
577 ) -> Result<Option<BlockOpcodeGas>, Eth::Error> {
578 let Some(block) = self.eth_api().recovered_block(block_id).await? else {
579 return Err(EthApiError::HeaderNotFound(block_id).into());
580 };
581
582 let Some(transactions) = self
583 .eth_api()
584 .trace_block_inspector(
585 block_id,
586 Some(block.clone()),
587 OpcodeGasInspector::default,
588 move |tx_info, ctx| {
589 let trace = TransactionOpcodeGas {
590 transaction_hash: tx_info.hash.expect("tx hash is set"),
591 opcode_gas: ctx.inspector.opcode_gas_iter().collect(),
592 };
593 Ok(trace)
594 },
595 )
596 .await?
597 else {
598 return Ok(None);
599 };
600
601 Ok(Some(BlockOpcodeGas {
602 block_hash: block.hash(),
603 block_number: block.number(),
604 transactions,
605 }))
606 }
607
608 pub async fn trace_block_storage_access(
611 &self,
612 block_id: BlockId,
613 ) -> Result<Option<BlockStorageAccess>, Eth::Error> {
614 let Some(block) = self.eth_api().recovered_block(block_id).await? else {
615 return Err(EthApiError::HeaderNotFound(block_id).into());
616 };
617
618 let Some(transactions) = self
619 .eth_api()
620 .trace_block_inspector(
621 block_id,
622 Some(block.clone()),
623 StorageInspector::default,
624 move |tx_info, mut ctx| {
625 let unique_loads = ctx.inspector.unique_loads();
626 let warm_loads = ctx.inspector.warm_loads();
627 let trace = TransactionStorageAccess {
628 transaction_hash: tx_info.hash.expect("tx hash is set"),
629 storage_access: ctx.take_inspector().into_accessed_slots(),
630 unique_loads,
631 warm_loads,
632 };
633 Ok(trace)
634 },
635 )
636 .await?
637 else {
638 return Ok(None);
639 };
640
641 Ok(Some(BlockStorageAccess {
642 block_hash: block.hash(),
643 block_number: block.number(),
644 transactions,
645 }))
646 }
647}
648
649fn apply_trace_filter_pagination(
650 all_traces: &mut Vec<LocalizedTransactionTrace>,
651 after: &mut Option<u64>,
652 count: Option<u64>,
653) -> Option<Vec<LocalizedTransactionTrace>> {
654 if let Some(cutoff) = after.map(|a| a as usize) &&
656 cutoff < all_traces.len()
657 {
658 all_traces.drain(..cutoff);
659 *after = None;
661 }
662
663 if after.is_none() &&
665 let Some(count) = count
666 {
667 let count = count as usize;
668 if count < all_traces.len() {
669 all_traces.truncate(count);
670 return Some(std::mem::take(all_traces))
671 }
672 }
673
674 None
675}
676
677#[async_trait]
678impl<Eth> TraceApiServer<RpcTxReq<Eth::NetworkTypes>> for TraceApi<Eth>
679where
680 Eth: TraceExt + 'static,
681{
682 async fn trace_call(
686 &self,
687 call: RpcTxReq<Eth::NetworkTypes>,
688 trace_types: HashSet<TraceType>,
689 block_id: Option<BlockId>,
690 state_overrides: Option<StateOverride>,
691 block_overrides: Option<Box<BlockOverrides>>,
692 ) -> RpcResult<TraceResults> {
693 let _permit = self.acquire_trace_permit().await;
694 let request =
695 TraceCallRequest { call, trace_types, block_id, state_overrides, block_overrides };
696 Ok(Self::trace_call(self, request).await.map_err(Into::into)?)
697 }
698
699 async fn trace_call_many(
701 &self,
702 calls: Vec<(RpcTxReq<Eth::NetworkTypes>, HashSet<TraceType>)>,
703 block_id: Option<BlockId>,
704 ) -> RpcResult<Vec<TraceResults>> {
705 let _permit = self.acquire_trace_permit().await;
706 Ok(Self::trace_call_many(self, calls, block_id).await.map_err(Into::into)?)
707 }
708
709 async fn trace_raw_transaction(
711 &self,
712 data: Bytes,
713 trace_types: HashSet<TraceType>,
714 block_id: Option<BlockId>,
715 ) -> RpcResult<TraceResults> {
716 let _permit = self.acquire_trace_permit().await;
717 Ok(Self::trace_raw_transaction(self, data, trace_types, block_id)
718 .await
719 .map_err(Into::into)?)
720 }
721
722 async fn replay_block_transactions(
724 &self,
725 block_id: BlockId,
726 trace_types: HashSet<TraceType>,
727 ) -> RpcResult<Option<Vec<TraceResultsWithTransactionHash>>> {
728 let _permit = self.acquire_trace_permit().await;
729 Ok(Self::replay_block_transactions(self, block_id, trace_types)
730 .await
731 .map_err(Into::into)?)
732 }
733
734 async fn replay_transaction(
736 &self,
737 transaction: B256,
738 trace_types: HashSet<TraceType>,
739 ) -> RpcResult<TraceResults> {
740 let _permit = self.acquire_trace_permit().await;
741 Ok(Self::replay_transaction(self, transaction, trace_types).await.map_err(Into::into)?)
742 }
743
744 async fn trace_block(
746 &self,
747 block_id: BlockId,
748 ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
749 let _permit = self.acquire_trace_permit().await;
750 Ok(Self::trace_block(self, block_id).await.map_err(Into::into)?)
751 }
752
753 async fn trace_filter(&self, filter: TraceFilter) -> RpcResult<Vec<LocalizedTransactionTrace>> {
760 Ok(Self::trace_filter(self, filter).await.map_err(Into::into)?)
761 }
762
763 async fn trace_get(
766 &self,
767 hash: B256,
768 indices: Vec<Index>,
769 ) -> RpcResult<Option<LocalizedTransactionTrace>> {
770 let _permit = self.acquire_trace_permit().await;
771 Ok(Self::trace_get(self, hash, indices.into_iter().map(Into::into).collect())
772 .await
773 .map_err(Into::into)?)
774 }
775
776 async fn trace_transaction(
778 &self,
779 hash: B256,
780 ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
781 let _permit = self.acquire_trace_permit().await;
782 Ok(Self::trace_transaction(self, hash).await.map_err(Into::into)?)
783 }
784
785 async fn trace_transaction_opcode_gas(
787 &self,
788 tx_hash: B256,
789 ) -> RpcResult<Option<TransactionOpcodeGas>> {
790 let _permit = self.acquire_trace_permit().await;
791 Ok(Self::trace_transaction_opcode_gas(self, tx_hash).await.map_err(Into::into)?)
792 }
793
794 async fn trace_block_opcode_gas(&self, block_id: BlockId) -> RpcResult<Option<BlockOpcodeGas>> {
796 let _permit = self.acquire_trace_permit().await;
797 Ok(Self::trace_block_opcode_gas(self, block_id).await.map_err(Into::into)?)
798 }
799}
800
801impl<Eth> std::fmt::Debug for TraceApi<Eth> {
802 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
803 f.debug_struct("TraceApi").finish_non_exhaustive()
804 }
805}
806impl<Eth> Clone for TraceApi<Eth> {
807 fn clone(&self) -> Self {
808 Self { inner: Arc::clone(&self.inner) }
809 }
810}
811
812struct TraceApiInner<Eth> {
813 eth_api: Eth,
815 blocking_task_guard: BlockingTaskGuard,
817 eth_config: EthConfig,
819}
820
821#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
824#[serde(rename_all = "camelCase")]
825pub struct TransactionStorageAccess {
826 pub transaction_hash: B256,
828 pub storage_access: HashMap<Address, HashMap<B256, u64>>,
830 pub unique_loads: u64,
832 pub warm_loads: u64,
834}
835
836#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
838#[serde(rename_all = "camelCase")]
839pub struct BlockStorageAccess {
840 pub block_hash: BlockHash,
842 pub block_number: u64,
844 pub transactions: Vec<TransactionStorageAccess>,
846}
847
848fn reward_trace<H: BlockHeader>(
851 block_hash: BlockHash,
852 header: &H,
853 reward: RewardAction,
854) -> LocalizedTransactionTrace {
855 LocalizedTransactionTrace {
856 block_hash: Some(block_hash),
857 block_number: Some(header.number()),
858 transaction_hash: None,
859 transaction_position: None,
860 trace: TransactionTrace {
861 trace_address: vec![],
862 subtraces: 0,
863 action: Action::Reward(reward),
864 error: None,
865 result: None,
866 },
867 }
868}
869
870#[cfg(test)]
871mod tests {
872 use super::*;
873
874 fn localized_transaction_trace(
875 block_number: u64,
876 transaction_position: u64,
877 ) -> LocalizedTransactionTrace {
878 LocalizedTransactionTrace {
879 block_hash: Some(B256::ZERO),
880 block_number: Some(block_number),
881 transaction_hash: Some(B256::ZERO),
882 transaction_position: Some(transaction_position),
883 trace: TransactionTrace::default(),
884 }
885 }
886
887 fn localized_reward_trace(block_number: u64) -> LocalizedTransactionTrace {
888 LocalizedTransactionTrace {
889 block_hash: Some(B256::ZERO),
890 block_number: Some(block_number),
891 transaction_hash: None,
892 transaction_position: None,
893 trace: TransactionTrace {
894 trace_address: vec![],
895 subtraces: 0,
896 action: Action::Reward(RewardAction {
897 author: Address::ZERO,
898 reward_type: RewardType::Block,
899 value: U256::ZERO,
900 }),
901 error: None,
902 result: None,
903 },
904 }
905 }
906
907 fn trace_order(traces: &[LocalizedTransactionTrace]) -> Vec<(u64, Option<u64>, bool)> {
908 traces
909 .iter()
910 .map(|trace| {
911 (
912 trace.block_number.unwrap(),
913 trace.transaction_position,
914 trace.trace.action.is_reward(),
915 )
916 })
917 .collect()
918 }
919
920 #[test]
921 fn trace_filter_paginates_after_per_block_reward_order() {
922 let mut all_traces = vec![
923 localized_transaction_trace(1, 0),
924 localized_reward_trace(1),
925 localized_transaction_trace(2, 0),
926 localized_reward_trace(2),
927 ];
928
929 let mut after = Some(1);
930 let paginated =
931 apply_trace_filter_pagination(&mut all_traces, &mut after, Some(1)).unwrap();
932
933 assert_eq!(trace_order(&paginated), vec![(1, None, true)]);
934 }
935}