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_primitives_traits::{BlockBody, BlockHeader};
23use reth_rpc_api::TraceApiServer;
24use reth_rpc_convert::RpcTxReq;
25use reth_rpc_eth_api::{
26 helpers::{Call, LoadPendingBlock, LoadTransaction, Trace, TraceExt},
27 FromEthApiError, RpcNodeCore,
28};
29use reth_rpc_eth_types::{error::EthApiError, utils::recover_raw_transaction, EthConfig};
30use reth_storage_api::{BlockNumReader, BlockReader};
31use reth_tasks::pool::BlockingTaskGuard;
32use reth_transaction_pool::{PoolPooledTx, PoolTransaction, TransactionPool};
33use revm::DatabaseCommit;
34use revm_inspectors::{
35 opcode::OpcodeGasInspector,
36 storage::StorageInspector,
37 tracing::{parity::populate_state_diff, TracingInspector, TracingInspectorConfig},
38};
39use serde::{Deserialize, Serialize};
40use std::sync::Arc;
41use tokio::sync::{AcquireError, OwnedSemaphorePermit};
42
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 .await
117 }
118
119 pub async fn trace_raw_transaction(
121 &self,
122 tx: Bytes,
123 trace_types: HashSet<TraceType>,
124 block_id: Option<BlockId>,
125 ) -> Result<TraceResults, Eth::Error> {
126 let tx = recover_raw_transaction::<PoolPooledTx<Eth::Pool>>(&tx)?
127 .map(<Eth::Pool as TransactionPool>::Transaction::pooled_into_consensus);
128
129 let (evm_env, at) = self.eth_api().evm_env_at(block_id.unwrap_or_default()).await?;
130
131 self.eth_api()
132 .spawn_with_state_at_block(at, move |this, mut db| {
133 let mut inspector =
134 TracingInspector::new(TracingInspectorConfig::from_parity_config(&trace_types));
135 let res = this.inspect(&mut db, evm_env, tx, &mut inspector)?;
136
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 {
374 requested: start,
375 earliest_available: earliest_block,
376 }
377 .into());
378 }
379
380 if start > end {
381 return Err(EthApiError::InvalidParams(
382 "invalid parameters: fromBlock cannot be greater than toBlock".to_string(),
383 )
384 .into())
385 }
386
387 let distance = end.saturating_sub(start);
389 if distance > self.inner.eth_config.max_trace_filter_blocks {
390 return Err(EthApiError::InvalidParams(format!(
391 "Block range too large; currently limited to {} blocks",
392 self.inner.eth_config.max_trace_filter_blocks
393 ))
394 .into())
395 }
396
397 let mut all_traces = Vec::new();
398 let block_buffer_size =
399 self.inner.eth_config.max_tracing_requests.clamp(1, TRACE_FILTER_BLOCK_BUFFER_SIZE);
400 let mut include_reward_traces = true;
401
402 for chunk_start in (start..=end).step_by(TRACE_FILTER_FETCH_CHUNK_SIZE) {
403 let chunk_end = (chunk_start + TRACE_FILTER_FETCH_CHUNK_SIZE as u64 - 1).min(end);
404
405 let blocks = self
406 .eth_api()
407 .spawn_blocking_io(move |this| {
408 let blocks = this
409 .provider()
410 .recovered_block_range(chunk_start..=chunk_end)
411 .map_err(Eth::Error::from_eth_err)?;
412
413 Ok(blocks.into_iter().map(Arc::new).collect::<Vec<_>>())
414 })
415 .await?;
416
417 let mut block_replays = futures::stream::iter(blocks)
418 .map(|block| {
419 let this = self.clone();
420 let matcher = matcher.clone();
421
422 let block_hash = block.hash();
423
424 async move {
425 let permit = this.acquire_trace_permit().await;
426 let traces = this
427 .eth_api()
428 .trace_block_until(
429 block_hash.into(),
430 Some(block.clone()),
431 None,
432 TracingInspectorConfig::default_parity(),
433 move |tx_info, mut ctx| {
434 let _block_replay_permit = &permit;
436 let mut traces = ctx
437 .take_inspector()
438 .into_parity_builder()
439 .into_localized_transaction_traces(tx_info);
440 traces.retain(|trace| matcher.matches(&trace.trace));
441 Ok(Some(traces))
442 },
443 )
444 .await?;
445
446 Ok::<_, Eth::Error>((block, traces))
447 }
448 })
449 .buffered(block_buffer_size);
450
451 while let Some(block_replay) = block_replays.next().await {
452 let (block, traces) = block_replay?;
453 let reward_traces = if include_reward_traces {
454 if let Some(base_block_reward) =
455 self.calculate_base_block_reward(block.header())?
456 {
457 self.extract_reward_traces(
458 block.header(),
459 block.hash(),
460 block.body().ommers(),
461 base_block_reward,
462 )
463 .into_iter()
464 .filter(|trace| matcher.matches(&trace.trace))
465 .collect::<Vec<_>>()
466 } else {
467 include_reward_traces = false;
470 Vec::new()
471 }
472 } else {
473 Vec::new()
474 };
475
476 if let Some(traces) = traces {
477 all_traces.extend(traces.into_iter().flatten().flatten());
478 }
479 all_traces.extend(reward_traces);
480
481 if let Some(traces) =
482 apply_trace_filter_pagination(&mut all_traces, &mut after, count)
483 {
484 return Ok(traces)
485 }
486 }
487 }
488
489 if let Some(cutoff) = after.map(|a| a as usize) &&
492 cutoff >= all_traces.len()
493 {
494 return Ok(vec![])
495 }
496
497 Ok(all_traces)
498 }
499
500 pub async fn trace_block(
502 &self,
503 block_id: BlockId,
504 ) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
505 let Some(block) = self.eth_api().recovered_block(block_id).await? else {
506 return Err(EthApiError::HeaderNotFound(block_id).into());
507 };
508
509 let mut traces = self
510 .eth_api()
511 .trace_block_with(
512 block_id,
513 Some(block.clone()),
514 TracingInspectorConfig::default_parity(),
515 |tx_info, mut ctx| {
516 let traces = ctx
517 .take_inspector()
518 .into_parity_builder()
519 .into_localized_transaction_traces(tx_info);
520 Ok(traces)
521 },
522 )
523 .await?
524 .map(|traces| traces.into_iter().flatten().collect::<Vec<_>>());
525
526 if let Some(traces) = traces.as_mut() &&
527 let Some(base_block_reward) = self.calculate_base_block_reward(block.header())?
528 {
529 traces.extend(self.extract_reward_traces(
530 block.header(),
531 block.hash(),
532 block.body().ommers(),
533 base_block_reward,
534 ));
535 }
536
537 Ok(traces)
538 }
539
540 pub async fn replay_block_transactions(
542 &self,
543 block_id: BlockId,
544 trace_types: HashSet<TraceType>,
545 ) -> Result<Option<Vec<TraceResultsWithTransactionHash>>, Eth::Error> {
546 self.eth_api()
547 .trace_block_with(
548 block_id,
549 None,
550 TracingInspectorConfig::from_parity_config(&trace_types),
551 move |tx_info, mut ctx| {
552 let mut full_trace = ctx
553 .take_inspector()
554 .into_parity_builder()
555 .into_trace_results(&ctx.result, &trace_types);
556
557 if let Some(ref mut state_diff) = full_trace.state_diff {
560 populate_state_diff(state_diff, &ctx.db, ctx.state.iter())
561 .map_err(Eth::Error::from_eth_err)?;
562 }
563
564 let trace = TraceResultsWithTransactionHash {
565 transaction_hash: tx_info.hash.expect("tx hash is set"),
566 full_trace,
567 };
568 Ok(trace)
569 },
570 )
571 .await
572 }
573
574 pub async fn trace_block_opcode_gas(
579 &self,
580 block_id: BlockId,
581 ) -> Result<Option<BlockOpcodeGas>, Eth::Error> {
582 let Some(block) = self.eth_api().recovered_block(block_id).await? else {
583 return Err(EthApiError::HeaderNotFound(block_id).into());
584 };
585
586 let Some(transactions) = self
587 .eth_api()
588 .trace_block_inspector(
589 block_id,
590 Some(block.clone()),
591 OpcodeGasInspector::default,
592 move |tx_info, ctx| {
593 let trace = TransactionOpcodeGas {
594 transaction_hash: tx_info.hash.expect("tx hash is set"),
595 opcode_gas: ctx.inspector.opcode_gas_iter().collect(),
596 };
597 Ok(trace)
598 },
599 )
600 .await?
601 else {
602 return Ok(None);
603 };
604
605 Ok(Some(BlockOpcodeGas {
606 block_hash: block.hash(),
607 block_number: block.number(),
608 transactions,
609 }))
610 }
611
612 pub async fn trace_block_storage_access(
615 &self,
616 block_id: BlockId,
617 ) -> Result<Option<BlockStorageAccess>, Eth::Error> {
618 let Some(block) = self.eth_api().recovered_block(block_id).await? else {
619 return Err(EthApiError::HeaderNotFound(block_id).into());
620 };
621
622 let Some(transactions) = self
623 .eth_api()
624 .trace_block_inspector(
625 block_id,
626 Some(block.clone()),
627 StorageInspector::default,
628 move |tx_info, mut ctx| {
629 let unique_loads = ctx.inspector.unique_loads();
630 let warm_loads = ctx.inspector.warm_loads();
631 let trace = TransactionStorageAccess {
632 transaction_hash: tx_info.hash.expect("tx hash is set"),
633 storage_access: ctx.take_inspector().into_accessed_slots(),
634 unique_loads,
635 warm_loads,
636 };
637 Ok(trace)
638 },
639 )
640 .await?
641 else {
642 return Ok(None);
643 };
644
645 Ok(Some(BlockStorageAccess {
646 block_hash: block.hash(),
647 block_number: block.number(),
648 transactions,
649 }))
650 }
651}
652
653fn apply_trace_filter_pagination(
654 all_traces: &mut Vec<LocalizedTransactionTrace>,
655 after: &mut Option<u64>,
656 count: Option<u64>,
657) -> Option<Vec<LocalizedTransactionTrace>> {
658 if let Some(cutoff) = after.map(|a| a as usize) &&
660 cutoff < all_traces.len()
661 {
662 all_traces.drain(..cutoff);
663 *after = None;
665 }
666
667 if after.is_none() &&
669 let Some(count) = count
670 {
671 let count = count as usize;
672 if count < all_traces.len() {
673 all_traces.truncate(count);
674 return Some(std::mem::take(all_traces))
675 }
676 }
677
678 None
679}
680
681#[async_trait]
682impl<Eth> TraceApiServer<RpcTxReq<Eth::NetworkTypes>> for TraceApi<Eth>
683where
684 Eth: TraceExt + 'static,
685{
686 async fn trace_call(
690 &self,
691 call: RpcTxReq<Eth::NetworkTypes>,
692 trace_types: HashSet<TraceType>,
693 block_id: Option<BlockId>,
694 state_overrides: Option<StateOverride>,
695 block_overrides: Option<Box<BlockOverrides>>,
696 ) -> RpcResult<TraceResults> {
697 let _permit = self.acquire_trace_permit().await;
698 let request =
699 TraceCallRequest { call, trace_types, block_id, state_overrides, block_overrides };
700 Ok(Self::trace_call(self, request).await.map_err(Into::into)?)
701 }
702
703 async fn trace_call_many(
705 &self,
706 calls: Vec<(RpcTxReq<Eth::NetworkTypes>, HashSet<TraceType>)>,
707 block_id: Option<BlockId>,
708 ) -> RpcResult<Vec<TraceResults>> {
709 let _permit = self.acquire_trace_permit().await;
710 Ok(Self::trace_call_many(self, calls, block_id).await.map_err(Into::into)?)
711 }
712
713 async fn trace_raw_transaction(
715 &self,
716 data: Bytes,
717 trace_types: HashSet<TraceType>,
718 block_id: Option<BlockId>,
719 ) -> RpcResult<TraceResults> {
720 let _permit = self.acquire_trace_permit().await;
721 Ok(Self::trace_raw_transaction(self, data, trace_types, block_id)
722 .await
723 .map_err(Into::into)?)
724 }
725
726 async fn replay_block_transactions(
728 &self,
729 block_id: BlockId,
730 trace_types: HashSet<TraceType>,
731 ) -> RpcResult<Option<Vec<TraceResultsWithTransactionHash>>> {
732 let _permit = self.acquire_trace_permit().await;
733 Ok(Self::replay_block_transactions(self, block_id, trace_types)
734 .await
735 .map_err(Into::into)?)
736 }
737
738 async fn replay_transaction(
740 &self,
741 transaction: B256,
742 trace_types: HashSet<TraceType>,
743 ) -> RpcResult<TraceResults> {
744 let _permit = self.acquire_trace_permit().await;
745 Ok(Self::replay_transaction(self, transaction, trace_types).await.map_err(Into::into)?)
746 }
747
748 async fn trace_block(
750 &self,
751 block_id: BlockId,
752 ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
753 let _permit = self.acquire_trace_permit().await;
754 Ok(Self::trace_block(self, block_id).await.map_err(Into::into)?)
755 }
756
757 async fn trace_filter(&self, filter: TraceFilter) -> RpcResult<Vec<LocalizedTransactionTrace>> {
764 Ok(Self::trace_filter(self, filter).await.map_err(Into::into)?)
765 }
766
767 async fn trace_get(
770 &self,
771 hash: B256,
772 indices: Vec<Index>,
773 ) -> RpcResult<Option<LocalizedTransactionTrace>> {
774 let _permit = self.acquire_trace_permit().await;
775 Ok(Self::trace_get(self, hash, indices.into_iter().map(Into::into).collect())
776 .await
777 .map_err(Into::into)?)
778 }
779
780 async fn trace_transaction(
782 &self,
783 hash: B256,
784 ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
785 let _permit = self.acquire_trace_permit().await;
786 Ok(Self::trace_transaction(self, hash).await.map_err(Into::into)?)
787 }
788
789 async fn trace_transaction_opcode_gas(
791 &self,
792 tx_hash: B256,
793 ) -> RpcResult<Option<TransactionOpcodeGas>> {
794 let _permit = self.acquire_trace_permit().await;
795 Ok(Self::trace_transaction_opcode_gas(self, tx_hash).await.map_err(Into::into)?)
796 }
797
798 async fn trace_block_opcode_gas(&self, block_id: BlockId) -> RpcResult<Option<BlockOpcodeGas>> {
800 let _permit = self.acquire_trace_permit().await;
801 Ok(Self::trace_block_opcode_gas(self, block_id).await.map_err(Into::into)?)
802 }
803}
804
805impl<Eth> std::fmt::Debug for TraceApi<Eth> {
806 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
807 f.debug_struct("TraceApi").finish_non_exhaustive()
808 }
809}
810impl<Eth> Clone for TraceApi<Eth> {
811 fn clone(&self) -> Self {
812 Self { inner: Arc::clone(&self.inner) }
813 }
814}
815
816struct TraceApiInner<Eth> {
817 eth_api: Eth,
819 blocking_task_guard: BlockingTaskGuard,
821 eth_config: EthConfig,
823}
824
825#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
828#[serde(rename_all = "camelCase")]
829pub struct TransactionStorageAccess {
830 pub transaction_hash: B256,
832 pub storage_access: HashMap<Address, HashMap<B256, u64>>,
834 pub unique_loads: u64,
836 pub warm_loads: u64,
838}
839
840#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
842#[serde(rename_all = "camelCase")]
843pub struct BlockStorageAccess {
844 pub block_hash: BlockHash,
846 pub block_number: u64,
848 pub transactions: Vec<TransactionStorageAccess>,
850}
851
852fn reward_trace<H: BlockHeader>(
855 block_hash: BlockHash,
856 header: &H,
857 reward: RewardAction,
858) -> LocalizedTransactionTrace {
859 LocalizedTransactionTrace {
860 block_hash: Some(block_hash),
861 block_number: Some(header.number()),
862 transaction_hash: None,
863 transaction_position: None,
864 trace: TransactionTrace {
865 trace_address: vec![],
866 subtraces: 0,
867 action: Action::Reward(reward),
868 error: None,
869 result: None,
870 },
871 }
872}
873
874#[cfg(test)]
875mod tests {
876 use super::*;
877
878 fn localized_transaction_trace(
879 block_number: u64,
880 transaction_position: u64,
881 ) -> LocalizedTransactionTrace {
882 LocalizedTransactionTrace {
883 block_hash: Some(B256::ZERO),
884 block_number: Some(block_number),
885 transaction_hash: Some(B256::ZERO),
886 transaction_position: Some(transaction_position),
887 trace: TransactionTrace::default(),
888 }
889 }
890
891 fn localized_reward_trace(block_number: u64) -> LocalizedTransactionTrace {
892 LocalizedTransactionTrace {
893 block_hash: Some(B256::ZERO),
894 block_number: Some(block_number),
895 transaction_hash: None,
896 transaction_position: None,
897 trace: TransactionTrace {
898 trace_address: vec![],
899 subtraces: 0,
900 action: Action::Reward(RewardAction {
901 author: Address::ZERO,
902 reward_type: RewardType::Block,
903 value: U256::ZERO,
904 }),
905 error: None,
906 result: None,
907 },
908 }
909 }
910
911 fn trace_order(traces: &[LocalizedTransactionTrace]) -> Vec<(u64, Option<u64>, bool)> {
912 traces
913 .iter()
914 .map(|trace| {
915 (
916 trace.block_number.unwrap(),
917 trace.transaction_position,
918 trace.trace.action.is_reward(),
919 )
920 })
921 .collect()
922 }
923
924 #[test]
925 fn trace_filter_paginates_after_per_block_reward_order() {
926 let mut all_traces = vec![
927 localized_transaction_trace(1, 0),
928 localized_reward_trace(1),
929 localized_transaction_trace(2, 0),
930 localized_reward_trace(2),
931 ];
932
933 let mut after = Some(1);
934 let paginated =
935 apply_trace_filter_pagination(&mut all_traces, &mut after, Some(1)).unwrap();
936
937 assert_eq!(trace_order(&paginated), vec![(1, None, true)]);
938 }
939}