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::{FutureExt, 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 .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 = 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
132 self.eth_api()
133 .spawn_with_state_at_block(at, move |this, mut db| {
134 let mut inspector =
135 TracingInspector::new(TracingInspectorConfig::from_parity_config(&trace_types));
136 let res = this.inspect(&mut db, evm_env, tx, &mut inspector)?;
137
138 inspector
139 .into_parity_builder()
140 .into_trace_results_with_state(&res, &trace_types, &db)
141 .map_err(Eth::Error::from_eth_err)
142 })
143 .await
144 }
145
146 pub async fn trace_call_many(
151 &self,
152 calls: Vec<(RpcTxReq<Eth::NetworkTypes>, HashSet<TraceType>)>,
153 block_id: Option<BlockId>,
154 ) -> Result<Vec<TraceResults>, Eth::Error> {
155 let at = block_id.unwrap_or(BlockId::pending());
156 let (evm_env, at) = self.eth_api().evm_env_at(at).await?;
157
158 self.eth_api()
160 .spawn_with_state_at_block(at, move |eth_api, mut db| {
161 let mut results = Vec::with_capacity(calls.len());
162 let mut calls = calls.into_iter().peekable();
163
164 while let Some((call, trace_types)) = calls.next() {
165 let (evm_env, tx_env) = eth_api.prepare_call_env(
166 evm_env.clone(),
167 call,
168 &mut db,
169 Default::default(),
170 )?;
171 let config = TracingInspectorConfig::from_parity_config(&trace_types);
172 let mut inspector = TracingInspector::new(config);
173 let res = eth_api.inspect(&mut db, evm_env, tx_env, &mut inspector)?;
174
175 let trace_res = inspector
176 .into_parity_builder()
177 .into_trace_results_with_state(&res, &trace_types, &db)
178 .map_err(Eth::Error::from_eth_err)?;
179
180 results.push(trace_res);
181
182 if calls.peek().is_some() {
185 db.commit(res.state)
186 }
187 }
188
189 Ok(results)
190 })
191 .await
192 }
193
194 pub async fn replay_transaction(
196 &self,
197 hash: B256,
198 trace_types: HashSet<TraceType>,
199 ) -> Result<TraceResults, Eth::Error> {
200 let config = TracingInspectorConfig::from_parity_config(&trace_types);
201 self.eth_api()
202 .spawn_trace_transaction_in_block(hash, config, move |_, inspector, res, db| {
203 let trace_res = inspector
204 .into_parity_builder()
205 .into_trace_results_with_state(&res, &trace_types, &db)
206 .map_err(Eth::Error::from_eth_err)?;
207 Ok(trace_res)
208 })
209 .await
210 .transpose()
211 .ok_or(EthApiError::TransactionNotFound)?
212 }
213
214 pub async fn trace_get(
221 &self,
222 hash: B256,
223 indices: Vec<usize>,
224 ) -> Result<Option<LocalizedTransactionTrace>, Eth::Error> {
225 if indices.len() != 1 {
226 return Ok(None)
228 }
229 self.trace_get_index(hash, indices[0]).await
230 }
231
232 pub async fn trace_get_index(
236 &self,
237 hash: B256,
238 index: usize,
239 ) -> Result<Option<LocalizedTransactionTrace>, Eth::Error> {
240 self.eth_api()
241 .spawn_trace_transaction_in_block(
242 hash,
243 TracingInspectorConfig::default_parity(),
244 move |tx_info, inspector, _, _| {
245 Ok(inspector
246 .into_parity_builder()
247 .into_localized_transaction_traces_iter(tx_info)
248 .nth(index))
249 },
250 )
251 .await
252 .map(Option::flatten)
253 }
254
255 pub async fn trace_transaction(
257 &self,
258 hash: B256,
259 ) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
260 self.eth_api()
261 .spawn_trace_transaction_in_block(
262 hash,
263 TracingInspectorConfig::default_parity(),
264 move |tx_info, inspector, _, _| {
265 let traces =
266 inspector.into_parity_builder().into_localized_transaction_traces(tx_info);
267 Ok(traces)
268 },
269 )
270 .await
271 }
272
273 pub async fn trace_transaction_opcode_gas(
276 &self,
277 tx_hash: B256,
278 ) -> Result<Option<TransactionOpcodeGas>, Eth::Error> {
279 self.eth_api()
280 .spawn_trace_transaction_in_block_with_inspector(
281 tx_hash,
282 OpcodeGasInspector::default(),
283 move |_tx_info, inspector, _res, _| {
284 let trace = TransactionOpcodeGas {
285 transaction_hash: tx_hash,
286 opcode_gas: inspector.opcode_gas_iter().collect(),
287 };
288 Ok(trace)
289 },
290 )
291 .await
292 }
293
294 fn calculate_base_block_reward<H: BlockHeader>(
299 &self,
300 header: &H,
301 ) -> Result<Option<u128>, Eth::Error> {
302 let chain_spec = self.provider().chain_spec();
303
304 if chain_spec.is_paris_active_at_block(header.number()) {
305 return Ok(None)
306 }
307
308 Ok(Some(base_block_reward_pre_merge(&chain_spec, header.number())))
309 }
310
311 fn extract_reward_traces<H: BlockHeader>(
315 &self,
316 header: &H,
317 block_hash: BlockHash,
318 ommers: Option<&[H]>,
319 base_block_reward: u128,
320 ) -> Vec<LocalizedTransactionTrace> {
321 let ommers_cnt = ommers.map(|o| o.len()).unwrap_or_default();
322 let mut traces = Vec::with_capacity(ommers_cnt + 1);
323
324 let block_reward = block_reward(base_block_reward, ommers_cnt);
325 traces.push(reward_trace(
326 block_hash,
327 header,
328 RewardAction {
329 author: header.beneficiary(),
330 reward_type: RewardType::Block,
331 value: U256::from(block_reward),
332 },
333 ));
334
335 let Some(ommers) = ommers else { return traces };
336
337 for uncle in ommers {
338 let uncle_reward = ommer_reward(base_block_reward, header.number(), uncle.number());
339 traces.push(reward_trace(
340 block_hash,
341 header,
342 RewardAction {
343 author: uncle.beneficiary(),
344 reward_type: RewardType::Uncle,
345 value: U256::from(uncle_reward),
346 },
347 ));
348 }
349 traces
350 }
351}
352
353impl<Eth> TraceApi<Eth>
354where
355 Eth: TraceExt + 'static,
358{
359 pub async fn trace_filter(
364 &self,
365 filter: TraceFilter,
366 ) -> Result<Vec<LocalizedTransactionTrace>, Eth::Error> {
367 let matcher = Arc::new(filter.matcher());
369 let TraceFilter { from_block, to_block, mut after, count, .. } = filter;
370 let start = from_block.unwrap_or(0);
371
372 let latest_block = self.provider().best_block_number().map_err(Eth::Error::from_eth_err)?;
373 if start > latest_block {
374 return Err(EthApiError::HeaderNotFound(start.into()).into());
376 }
377 let end = to_block.unwrap_or(latest_block);
378 if end > latest_block {
379 return Err(EthApiError::HeaderNotFound(end.into()).into());
380 }
381
382 let earliest_block =
384 self.provider().earliest_block_number().map_err(Eth::Error::from_eth_err)?;
385 if start < earliest_block {
386 return Err(EthApiError::PrunedHistoryUnavailable {
387 requested: start,
388 earliest_available: earliest_block,
389 }
390 .into());
391 }
392
393 if start > end {
394 return Err(EthApiError::InvalidParams(
395 "invalid parameters: fromBlock cannot be greater than toBlock".to_string(),
396 )
397 .into())
398 }
399
400 let distance = end.saturating_sub(start);
402 if distance > self.inner.eth_config.max_trace_filter_blocks {
403 return Err(EthApiError::InvalidParams(format!(
404 "Block range too large; currently limited to {} blocks",
405 self.inner.eth_config.max_trace_filter_blocks
406 ))
407 .into())
408 }
409
410 let mut all_traces = Vec::new();
411 let block_buffer_size =
412 self.inner.eth_config.max_tracing_requests.clamp(1, TRACE_FILTER_BLOCK_BUFFER_SIZE);
413 let mut include_reward_traces = true;
414
415 for chunk_start in (start..=end).step_by(TRACE_FILTER_FETCH_CHUNK_SIZE) {
416 let chunk_end = (chunk_start + TRACE_FILTER_FETCH_CHUNK_SIZE as u64 - 1).min(end);
417
418 let blocks = self
419 .eth_api()
420 .spawn_blocking_io(move |this| {
421 let blocks = this
422 .provider()
423 .recovered_block_range(chunk_start..=chunk_end)
424 .map_err(Eth::Error::from_eth_err)?;
425
426 Ok(blocks.into_iter().map(Arc::new).collect::<Vec<_>>())
427 })
428 .await?;
429
430 let mut block_replays = futures::stream::iter(blocks)
431 .map(|block| {
432 let this = self.clone();
433 let matcher = matcher.clone();
434
435 let block_hash = block.hash();
436
437 async move {
438 let permit = this.acquire_trace_permit().await;
439 let traces = this
440 .eth_api()
441 .trace_block_until(
442 block_hash.into(),
443 Some(block.clone()),
444 None,
445 TracingInspectorConfig::default_parity(),
446 move |tx_info, mut ctx| {
447 let _block_replay_permit = &permit;
449 let mut traces = ctx
450 .take_inspector()
451 .into_parity_builder()
452 .into_localized_transaction_traces(tx_info);
453 traces.retain(|trace| matcher.matches(&trace.trace));
454 Ok(Some(traces))
455 },
456 )
457 .await?;
458
459 Ok::<_, Eth::Error>((block, traces))
460 }
461 })
462 .buffered(block_buffer_size);
463
464 while let Some(block_replay) = block_replays.next().await {
465 let (block, traces) = block_replay?;
466 let reward_traces = if include_reward_traces {
467 if let Some(base_block_reward) =
468 self.calculate_base_block_reward(block.header())?
469 {
470 self.extract_reward_traces(
471 block.header(),
472 block.hash(),
473 block.body().ommers(),
474 base_block_reward,
475 )
476 .into_iter()
477 .filter(|trace| matcher.matches(&trace.trace))
478 .collect::<Vec<_>>()
479 } else {
480 include_reward_traces = false;
483 Vec::new()
484 }
485 } else {
486 Vec::new()
487 };
488
489 if let Some(traces) = traces {
490 all_traces.extend(traces.into_iter().flatten().flatten());
491 }
492 all_traces.extend(reward_traces);
493
494 if let Some(traces) =
495 apply_trace_filter_pagination(&mut all_traces, &mut after, count)
496 {
497 return Ok(traces)
498 }
499 }
500 }
501
502 if let Some(cutoff) = after.map(|a| a as usize) &&
505 cutoff >= all_traces.len()
506 {
507 return Ok(vec![])
508 }
509
510 Ok(all_traces)
511 }
512
513 pub async fn trace_block(
515 &self,
516 block_id: BlockId,
517 ) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
518 let Some(block) = self.eth_api().recovered_block(block_id).await? else {
519 return Err(EthApiError::HeaderNotFound(block_id).into());
520 };
521
522 let mut traces = self
523 .eth_api()
524 .trace_block_with(
525 block_id,
526 Some(block.clone()),
527 TracingInspectorConfig::default_parity(),
528 |tx_info, mut ctx| {
529 let traces = ctx
530 .take_inspector()
531 .into_parity_builder()
532 .into_localized_transaction_traces(tx_info);
533 Ok(traces)
534 },
535 )
536 .await?
537 .map(|traces| traces.into_iter().flatten().collect::<Vec<_>>());
538
539 if let Some(traces) = traces.as_mut() &&
540 let Some(base_block_reward) = self.calculate_base_block_reward(block.header())?
541 {
542 traces.extend(self.extract_reward_traces(
543 block.header(),
544 block.hash(),
545 block.body().ommers(),
546 base_block_reward,
547 ));
548 }
549
550 Ok(traces)
551 }
552
553 pub async fn replay_block_transactions(
555 &self,
556 block_id: BlockId,
557 trace_types: HashSet<TraceType>,
558 ) -> Result<Option<Vec<TraceResultsWithTransactionHash>>, Eth::Error> {
559 self.eth_api()
560 .trace_block_with(
561 block_id,
562 None,
563 TracingInspectorConfig::from_parity_config(&trace_types),
564 move |tx_info, mut ctx| {
565 let mut full_trace = ctx
566 .take_inspector()
567 .into_parity_builder()
568 .into_trace_results(&ctx.result, &trace_types);
569
570 if let Some(ref mut state_diff) = full_trace.state_diff {
573 populate_state_diff(state_diff, &ctx.db, ctx.state.iter())
574 .map_err(Eth::Error::from_eth_err)?;
575 }
576
577 let trace = TraceResultsWithTransactionHash {
578 transaction_hash: tx_info.hash.expect("tx hash is set"),
579 full_trace,
580 };
581 Ok(trace)
582 },
583 )
584 .await
585 }
586
587 pub async fn trace_block_opcode_gas(
592 &self,
593 block_id: BlockId,
594 ) -> Result<Option<BlockOpcodeGas>, Eth::Error> {
595 let Some(block) = self.eth_api().recovered_block(block_id).await? else {
596 return Err(EthApiError::HeaderNotFound(block_id).into());
597 };
598
599 let Some(transactions) = self
600 .eth_api()
601 .trace_block_inspector(
602 block_id,
603 Some(block.clone()),
604 OpcodeGasInspector::default,
605 move |tx_info, ctx| {
606 let trace = TransactionOpcodeGas {
607 transaction_hash: tx_info.hash.expect("tx hash is set"),
608 opcode_gas: ctx.inspector.opcode_gas_iter().collect(),
609 };
610 Ok(trace)
611 },
612 )
613 .await?
614 else {
615 return Ok(None);
616 };
617
618 Ok(Some(BlockOpcodeGas {
619 block_hash: block.hash(),
620 block_number: block.number(),
621 transactions,
622 }))
623 }
624
625 pub async fn trace_block_storage_access(
628 &self,
629 block_id: BlockId,
630 ) -> Result<Option<BlockStorageAccess>, Eth::Error> {
631 let Some(block) = self.eth_api().recovered_block(block_id).await? else {
632 return Err(EthApiError::HeaderNotFound(block_id).into());
633 };
634
635 let Some(transactions) = self
636 .eth_api()
637 .trace_block_inspector(
638 block_id,
639 Some(block.clone()),
640 StorageInspector::default,
641 move |tx_info, mut ctx| {
642 let unique_loads = ctx.inspector.unique_loads();
643 let warm_loads = ctx.inspector.warm_loads();
644 let trace = TransactionStorageAccess {
645 transaction_hash: tx_info.hash.expect("tx hash is set"),
646 storage_access: ctx.take_inspector().into_accessed_slots(),
647 unique_loads,
648 warm_loads,
649 };
650 Ok(trace)
651 },
652 )
653 .await?
654 else {
655 return Ok(None);
656 };
657
658 Ok(Some(BlockStorageAccess {
659 block_hash: block.hash(),
660 block_number: block.number(),
661 transactions,
662 }))
663 }
664}
665
666fn apply_trace_filter_pagination(
667 all_traces: &mut Vec<LocalizedTransactionTrace>,
668 after: &mut Option<u64>,
669 count: Option<u64>,
670) -> Option<Vec<LocalizedTransactionTrace>> {
671 if let Some(cutoff) = after.map(|a| a as usize) &&
673 cutoff < all_traces.len()
674 {
675 all_traces.drain(..cutoff);
676 *after = None;
678 }
679
680 if after.is_none() &&
682 let Some(count) = count
683 {
684 let count = count as usize;
685 if count < all_traces.len() {
686 all_traces.truncate(count);
687 return Some(std::mem::take(all_traces))
688 }
689 }
690
691 None
692}
693
694#[async_trait]
695impl<Eth> TraceApiServer<RpcTxReq<Eth::NetworkTypes>> for TraceApi<Eth>
696where
697 Eth: TraceExt + 'static,
698{
699 async fn trace_call(
703 &self,
704 call: RpcTxReq<Eth::NetworkTypes>,
705 trace_types: HashSet<TraceType>,
706 block_id: Option<BlockId>,
707 state_overrides: Option<StateOverride>,
708 block_overrides: Option<Box<BlockOverrides>>,
709 ) -> RpcResult<TraceResults> {
710 let _permit = self.acquire_trace_permit().await;
711 let request =
712 TraceCallRequest { call, trace_types, block_id, state_overrides, block_overrides };
713 Ok(Self::trace_call(self, request).await.map_err(Into::into)?)
714 }
715
716 async fn trace_call_many(
718 &self,
719 calls: Vec<(RpcTxReq<Eth::NetworkTypes>, HashSet<TraceType>)>,
720 block_id: Option<BlockId>,
721 ) -> RpcResult<Vec<TraceResults>> {
722 let _permit = self.acquire_trace_permit().await;
723 Ok(Self::trace_call_many(self, calls, block_id).await.map_err(Into::into)?)
724 }
725
726 async fn trace_raw_transaction(
728 &self,
729 data: Bytes,
730 trace_types: HashSet<TraceType>,
731 block_id: Option<BlockId>,
732 ) -> RpcResult<TraceResults> {
733 let _permit = self.acquire_trace_permit().await;
734 Ok(Self::trace_raw_transaction(self, data, trace_types, block_id)
735 .await
736 .map_err(Into::into)?)
737 }
738
739 async fn replay_block_transactions(
741 &self,
742 block_id: BlockId,
743 trace_types: HashSet<TraceType>,
744 ) -> RpcResult<Option<Vec<TraceResultsWithTransactionHash>>> {
745 let _permit = self.acquire_trace_permit().await;
746 Ok(Self::replay_block_transactions(self, block_id, trace_types)
747 .await
748 .map_err(Into::into)?)
749 }
750
751 async fn replay_transaction(
753 &self,
754 transaction: B256,
755 trace_types: HashSet<TraceType>,
756 ) -> RpcResult<TraceResults> {
757 let _permit = self.acquire_trace_permit().await;
758 Ok(Self::replay_transaction(self, transaction, trace_types).await.map_err(Into::into)?)
759 }
760
761 async fn trace_block(
763 &self,
764 block_id: BlockId,
765 ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
766 let _permit = self.acquire_trace_permit().await;
767 Ok(Self::trace_block(self, block_id).await.map_err(Into::into)?)
768 }
769
770 async fn trace_filter(&self, filter: TraceFilter) -> RpcResult<Vec<LocalizedTransactionTrace>> {
777 Ok(Self::trace_filter(self, filter).await.map_err(Into::into)?)
778 }
779
780 async fn trace_get(
783 &self,
784 hash: B256,
785 indices: Vec<Index>,
786 ) -> RpcResult<Option<LocalizedTransactionTrace>> {
787 let _permit = self.acquire_trace_permit().await;
788 Ok(Self::trace_get(self, hash, indices.into_iter().map(Into::into).collect())
789 .await
790 .map_err(Into::into)?)
791 }
792
793 async fn trace_transaction(
795 &self,
796 hash: B256,
797 ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
798 let _permit = self.acquire_trace_permit().await;
799 Ok(Self::trace_transaction(self, hash).await.map_err(Into::into)?)
800 }
801
802 async fn trace_transaction_opcode_gas(
804 &self,
805 tx_hash: B256,
806 ) -> RpcResult<Option<TransactionOpcodeGas>> {
807 let _permit = self.acquire_trace_permit().await;
808 Ok(Self::trace_transaction_opcode_gas(self, tx_hash).await.map_err(Into::into)?)
809 }
810
811 async fn trace_block_opcode_gas(&self, block_id: BlockId) -> RpcResult<Option<BlockOpcodeGas>> {
813 let _permit = self.acquire_trace_permit().await;
814 Ok(Self::trace_block_opcode_gas(self, block_id).await.map_err(Into::into)?)
815 }
816}
817
818impl<Eth> std::fmt::Debug for TraceApi<Eth> {
819 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
820 f.debug_struct("TraceApi").finish_non_exhaustive()
821 }
822}
823impl<Eth> Clone for TraceApi<Eth> {
824 fn clone(&self) -> Self {
825 Self { inner: Arc::clone(&self.inner) }
826 }
827}
828
829struct TraceApiInner<Eth> {
830 eth_api: Eth,
832 blocking_task_guard: BlockingTaskGuard,
834 eth_config: EthConfig,
836}
837
838#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
841#[serde(rename_all = "camelCase")]
842pub struct TransactionStorageAccess {
843 pub transaction_hash: B256,
845 pub storage_access: HashMap<Address, HashMap<B256, u64>>,
847 pub unique_loads: u64,
849 pub warm_loads: u64,
851}
852
853#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
855#[serde(rename_all = "camelCase")]
856pub struct BlockStorageAccess {
857 pub block_hash: BlockHash,
859 pub block_number: u64,
861 pub transactions: Vec<TransactionStorageAccess>,
863}
864
865fn reward_trace<H: BlockHeader>(
868 block_hash: BlockHash,
869 header: &H,
870 reward: RewardAction,
871) -> LocalizedTransactionTrace {
872 LocalizedTransactionTrace {
873 block_hash: Some(block_hash),
874 block_number: Some(header.number()),
875 transaction_hash: None,
876 transaction_position: None,
877 trace: TransactionTrace {
878 trace_address: vec![],
879 subtraces: 0,
880 action: Action::Reward(reward),
881 error: None,
882 result: None,
883 },
884 }
885}
886
887#[cfg(test)]
888mod tests {
889 use super::*;
890
891 fn localized_transaction_trace(
892 block_number: u64,
893 transaction_position: u64,
894 ) -> LocalizedTransactionTrace {
895 LocalizedTransactionTrace {
896 block_hash: Some(B256::ZERO),
897 block_number: Some(block_number),
898 transaction_hash: Some(B256::ZERO),
899 transaction_position: Some(transaction_position),
900 trace: TransactionTrace::default(),
901 }
902 }
903
904 fn localized_reward_trace(block_number: u64) -> LocalizedTransactionTrace {
905 LocalizedTransactionTrace {
906 block_hash: Some(B256::ZERO),
907 block_number: Some(block_number),
908 transaction_hash: None,
909 transaction_position: None,
910 trace: TransactionTrace {
911 trace_address: vec![],
912 subtraces: 0,
913 action: Action::Reward(RewardAction {
914 author: Address::ZERO,
915 reward_type: RewardType::Block,
916 value: U256::ZERO,
917 }),
918 error: None,
919 result: None,
920 },
921 }
922 }
923
924 fn trace_order(traces: &[LocalizedTransactionTrace]) -> Vec<(u64, Option<u64>, bool)> {
925 traces
926 .iter()
927 .map(|trace| {
928 (
929 trace.block_number.unwrap(),
930 trace.transaction_position,
931 trace.trace.action.is_reward(),
932 )
933 })
934 .collect()
935 }
936
937 #[test]
938 fn trace_filter_paginates_after_per_block_reward_order() {
939 let mut all_traces = vec![
940 localized_transaction_trace(1, 0),
941 localized_reward_trace(1),
942 localized_transaction_trace(2, 0),
943 localized_reward_trace(2),
944 ];
945
946 let mut after = Some(1);
947 let paginated =
948 apply_trace_filter_pagination(&mut all_traces, &mut after, Some(1)).unwrap();
949
950 assert_eq!(trace_order(&paginated), vec![(1, None, true)]);
951 }
952}