1use futures::{select, FutureExt};
28use std::{
29 collections::{BTreeMap, HashMap},
30 future::Future,
31 marker::PhantomData,
32 sync::Arc,
33 time::{Duration, Instant},
34};
35use tokio::{
36 sync::{mpsc, oneshot, Semaphore},
37 time::interval,
38};
39use tracing::{instrument, Instrument};
40
41use sc_client_api::backend::{Backend, StateBackend, StorageProvider};
42use sc_service::SpawnTaskHandle;
43use sp_api::{ApiExt, Core, ProvideRuntimeApi};
44use sp_block_builder::BlockBuilder;
45use sp_blockchain::{
46 Backend as BlockchainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata,
47};
48use sp_runtime::traits::{BlakeTwo256, Block as BlockT, Header as HeaderT};
49use substrate_prometheus_endpoint::Registry as PrometheusRegistry;
50
51use ethereum_types::H256;
52use fc_rpc::lru_cache::LRUCacheByteLimited;
53use fc_storage::StorageOverride;
54use fp_rpc::EthereumRuntimeRPCApi;
55
56use moonbeam_client_evm_tracing::{
57 formatters::ResponseFormatter,
58 types::block::{self, TransactionTrace},
59};
60pub use moonbeam_rpc_core_trace::{FilterRequest, TraceServer};
61use moonbeam_rpc_core_types::{RequestBlockId, RequestBlockTag};
62use moonbeam_rpc_primitives_debug::DebugRuntimeApi;
63
64type TxsTraceRes = Result<Vec<TransactionTrace>, String>;
66
67type SharedTxsTraceRes = Result<Arc<Vec<TransactionTrace>>, Arc<String>>;
71
72const CACHE_LOG_TARGET: &str = "trace-cache";
74
75const TRACING_TIMEOUT_SECS: u64 = 60;
77
78pub struct Trace<B, C> {
80 _phantom: PhantomData<B>,
81 client: Arc<C>,
82 requester: CacheRequester,
83 max_count: u32,
84 max_block_range: u32,
85}
86
87impl<B, C> Clone for Trace<B, C> {
88 fn clone(&self) -> Self {
89 Self {
90 _phantom: PhantomData,
91 client: Arc::clone(&self.client),
92 requester: self.requester.clone(),
93 max_count: self.max_count,
94 max_block_range: self.max_block_range,
95 }
96 }
97}
98
99impl<B, C> Trace<B, C>
100where
101 B: BlockT<Hash = H256> + Send + Sync + 'static,
102 B::Header: HeaderT<Number = u32>,
103 C: HeaderMetadata<B, Error = BlockChainError> + HeaderBackend<B>,
104 C: Send + Sync + 'static,
105{
106 pub fn new(
108 client: Arc<C>,
109 requester: CacheRequester,
110 max_count: u32,
111 max_block_range: u32,
112 ) -> Self {
113 Self {
114 client,
115 requester,
116 max_count,
117 max_block_range,
118 _phantom: PhantomData,
119 }
120 }
121
122 fn block_id(&self, id: Option<RequestBlockId>) -> Result<u32, &'static str> {
124 match id {
125 Some(RequestBlockId::Number(n)) => Ok(n),
126 None | Some(RequestBlockId::Tag(RequestBlockTag::Latest)) => {
127 Ok(self.client.info().best_number)
128 }
129 Some(RequestBlockId::Tag(RequestBlockTag::Earliest)) => Ok(0),
130 Some(RequestBlockId::Tag(RequestBlockTag::Finalized)) => {
131 Ok(self.client.info().finalized_number)
132 }
133 Some(RequestBlockId::Tag(RequestBlockTag::Pending)) => {
134 Err("'pending' is not supported")
135 }
136 Some(RequestBlockId::Hash(_)) => Err("Block hash not supported"),
137 }
138 }
139
140 async fn filter(self, req: FilterRequest) -> TxsTraceRes {
142 let from_block = self.block_id(req.from_block)?;
143 let to_block = self.block_id(req.to_block)?;
144
145 let block_range = to_block.saturating_sub(from_block);
147 if block_range > self.max_block_range {
148 return Err(format!(
149 "block range is too wide (maximum {})",
150 self.max_block_range
151 ));
152 }
153
154 let block_heights = from_block..=to_block;
155
156 let count = req.count.unwrap_or(self.max_count);
157 if count > self.max_count {
158 return Err(format!(
159 "count ({}) can't be greater than maximum ({})",
160 count, self.max_count
161 ));
162 }
163
164 let mut block_hashes = vec![];
166 for block_height in block_heights {
167 if block_height == 0 {
168 continue; }
170
171 let block_hash = self
172 .client
173 .hash(block_height)
174 .map_err(|e| {
175 format!(
176 "Error when fetching block {} header : {:?}",
177 block_height, e
178 )
179 })?
180 .ok_or_else(|| format!("Block with height {} don't exist", block_height))?;
181
182 block_hashes.push(block_hash);
183 }
184
185 self.fetch_traces(req, &block_hashes, count as usize).await
187 }
188
189 async fn fetch_traces(
190 &self,
191 req: FilterRequest,
192 block_hashes: &[H256],
193 count: usize,
194 ) -> TxsTraceRes {
195 let from_address = req.from_address.unwrap_or_default();
196 let to_address = req.to_address.unwrap_or_default();
197
198 let mut traces_amount: i64 = -(req.after.unwrap_or(0) as i64);
199 let mut traces = vec![];
200
201 for &block_hash in block_hashes {
202 let block_traces = self
206 .requester
207 .get_traces(block_hash)
208 .await
209 .map_err(|arc_error| (*arc_error).clone())?;
210
211 let mut block_traces: Vec<_> = block_traces
213 .iter()
214 .filter(|trace| match trace.action {
215 block::TransactionTraceAction::Call { from, to, .. } => {
216 (from_address.is_empty() || from_address.contains(&from))
217 && (to_address.is_empty() || to_address.contains(&to))
218 }
219 block::TransactionTraceAction::Create { from, .. } => {
220 (from_address.is_empty() || from_address.contains(&from))
221 && to_address.is_empty()
222 }
223 block::TransactionTraceAction::Suicide { address, .. } => {
224 (from_address.is_empty() || from_address.contains(&address))
225 && to_address.is_empty()
226 }
227 })
228 .cloned()
229 .collect();
230
231 traces_amount += block_traces.len() as i64;
233 if traces_amount > 0 {
234 let traces_amount = traces_amount as usize;
235 if traces_amount < block_traces.len() {
238 let skip = block_traces.len() - traces_amount;
239 block_traces = block_traces.into_iter().skip(skip).collect();
240 }
241
242 traces.append(&mut block_traces);
243
244 if traces_amount >= count {
247 if req.count.is_none() {
248 return Err(format!(
249 "the amount of traces goes over the maximum ({}), please use 'after' \
250 and 'count' in your request",
251 self.max_count
252 ));
253 }
254
255 traces = traces.into_iter().take(count).collect();
256 break;
257 }
258 }
259 }
260
261 Ok(traces)
262 }
263}
264
265#[jsonrpsee::core::async_trait]
266impl<B, C> TraceServer for Trace<B, C>
267where
268 B: BlockT<Hash = H256> + Send + Sync + 'static,
269 B::Header: HeaderT<Number = u32>,
270 C: HeaderMetadata<B, Error = BlockChainError> + HeaderBackend<B>,
271 C: Send + Sync + 'static,
272{
273 async fn filter(
274 &self,
275 filter: FilterRequest,
276 ) -> jsonrpsee::core::RpcResult<Vec<TransactionTrace>> {
277 self.clone()
278 .filter(filter)
279 .await
280 .map_err(fc_rpc::internal_err)
281 }
282}
283
284enum CacheRequest {
286 GetTraces {
289 sender: oneshot::Sender<SharedTxsTraceRes>,
291 block: H256,
293 },
294}
295
296#[derive(Clone)]
298pub struct CacheRequester(mpsc::Sender<CacheRequest>);
299
300impl CacheRequester {
301 #[instrument(skip(self))]
307 pub async fn get_traces(&self, block: H256) -> SharedTxsTraceRes {
308 let (response_tx, response_rx) = oneshot::channel();
309 let sender = self.0.clone();
310
311 sender
312 .send(CacheRequest::GetTraces {
313 sender: response_tx,
314 block,
315 })
316 .await
317 .map_err(|e| {
318 Arc::new(format!(
319 "Trace cache task is overloaded or closed. Error : {:?}",
320 e
321 ))
322 })?;
323
324 response_rx
325 .await
326 .map_err(|e| {
327 Arc::new(format!(
328 "Trace cache task closed the response channel. Error : {:?}",
329 e
330 ))
331 })?
332 .map_err(|arc_error| {
333 Arc::new(format!("Failed to replay block. Error : {:?}", arc_error))
334 })
335 }
336}
337
338struct WaitListEntry {
340 created_at: Instant,
342 waiters: Vec<oneshot::Sender<SharedTxsTraceRes>>,
344}
345
346type WaitList = HashMap<H256, WaitListEntry>;
350
351enum BlockingTaskMessage {
353 Finished {
355 block_hash: H256,
356 result: TxsTraceRes,
357 duration: Duration,
358 },
359}
360
361struct CacheMetrics {
363 wait_list_size: substrate_prometheus_endpoint::Gauge<substrate_prometheus_endpoint::U64>,
365 wait_list_joins_total:
367 substrate_prometheus_endpoint::Counter<substrate_prometheus_endpoint::U64>,
368 tasks_spawned_total: substrate_prometheus_endpoint::Counter<substrate_prometheus_endpoint::U64>,
370 timeouts_total: substrate_prometheus_endpoint::Counter<substrate_prometheus_endpoint::U64>,
372 trace_duration_seconds: substrate_prometheus_endpoint::Histogram,
374}
375
376impl CacheMetrics {
377 fn register(
378 registry: &PrometheusRegistry,
379 ) -> Result<Self, substrate_prometheus_endpoint::PrometheusError> {
380 Ok(Self {
381 wait_list_size: substrate_prometheus_endpoint::register(
382 substrate_prometheus_endpoint::Gauge::new(
383 "trace_filter_wait_list_size",
384 "Current number of blocks in the wait list being traced",
385 )?,
386 registry,
387 )?,
388 wait_list_joins_total: substrate_prometheus_endpoint::register(
389 substrate_prometheus_endpoint::Counter::new(
390 "trace_filter_wait_list_joins_total",
391 "Total requests that joined an existing wait list entry",
392 )?,
393 registry,
394 )?,
395 tasks_spawned_total: substrate_prometheus_endpoint::register(
396 substrate_prometheus_endpoint::Counter::new(
397 "trace_filter_tasks_spawned_total",
398 "Total trace tasks spawned",
399 )?,
400 registry,
401 )?,
402 timeouts_total: substrate_prometheus_endpoint::register(
403 substrate_prometheus_endpoint::Counter::new(
404 "trace_filter_timeouts_total",
405 "Total trace operations that timed out",
406 )?,
407 registry,
408 )?,
409 trace_duration_seconds: substrate_prometheus_endpoint::register(
410 substrate_prometheus_endpoint::Histogram::with_opts(
411 substrate_prometheus_endpoint::HistogramOpts::new(
412 "trace_filter_trace_duration_seconds",
413 "Histogram of trace operation durations in seconds",
414 )
415 .buckets(vec![0.1, 0.5, 1.0, 2.0, 5.0, 10.0, 30.0, 60.0]),
416 )?,
417 registry,
418 )?,
419 })
420 }
421}
422
423pub struct CacheTask<B, C, BE> {
425 client: Arc<C>,
426 backend: Arc<BE>,
427 blocking_permits: Arc<Semaphore>,
428 cache: LRUCacheByteLimited<H256, Arc<Vec<TransactionTrace>>>,
429 wait_list: WaitList,
430 metrics: Option<CacheMetrics>,
431 _phantom: PhantomData<B>,
432}
433
434impl<B, C, BE> CacheTask<B, C, BE>
435where
436 BE: Backend<B> + 'static,
437 BE::State: StateBackend<BlakeTwo256>,
438 C: ProvideRuntimeApi<B>,
439 C: StorageProvider<B, BE>,
440 C: HeaderMetadata<B, Error = BlockChainError> + HeaderBackend<B>,
441 C: Send + Sync + 'static,
442 B: BlockT<Hash = H256> + Send + Sync + 'static,
443 B::Header: HeaderT<Number = u32>,
444 C::Api: BlockBuilder<B>,
445 C::Api: DebugRuntimeApi<B>,
446 C::Api: EthereumRuntimeRPCApi<B>,
447 C::Api: ApiExt<B>,
448{
449 pub fn create(
454 client: Arc<C>,
455 backend: Arc<BE>,
456 cache_size_bytes: u64,
457 blocking_permits: Arc<Semaphore>,
458 overrides: Arc<dyn StorageOverride<B>>,
459 prometheus: Option<PrometheusRegistry>,
460 spawn_handle: SpawnTaskHandle,
461 ) -> (impl Future<Output = ()>, CacheRequester) {
462 let (requester_tx, mut requester_rx) = mpsc::channel(10_000);
464
465 let task = async move {
467 let (blocking_tx, mut blocking_rx) =
468 mpsc::channel(blocking_permits.available_permits().saturating_mul(2));
469
470 let mut cleanup_interval = interval(Duration::from_secs(30));
472 cleanup_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
473
474 let metrics =
476 prometheus
477 .as_ref()
478 .and_then(|registry| match CacheMetrics::register(registry) {
479 Ok(metrics) => Some(metrics),
480 Err(e) => {
481 log::warn!(
482 target: CACHE_LOG_TARGET,
483 "Failed to register trace filter metrics: {:?}",
484 e
485 );
486 None
487 }
488 });
489
490 let mut inner = Self {
491 client,
492 backend,
493 blocking_permits,
494 cache: LRUCacheByteLimited::new(
495 "trace-filter-blocks-cache",
496 cache_size_bytes,
497 prometheus,
498 ),
499 wait_list: HashMap::new(),
500 metrics,
501 _phantom: Default::default(),
502 };
503
504 loop {
505 select! {
506 request = requester_rx.recv().fuse() => {
507 match request {
508 None => break,
509 Some(CacheRequest::GetTraces {sender, block}) =>
510 inner.request_get_traces(&blocking_tx, sender, block, overrides.clone(), &spawn_handle),
511 }
512 },
513 message = blocking_rx.recv().fuse() => {
514 if let Some(BlockingTaskMessage::Finished { block_hash, result, duration }) = message {
515 inner.blocking_finished(block_hash, result, duration);
516 }
517 },
518 _ = cleanup_interval.tick().fuse() => {
519 inner.cleanup_wait_list();
520 },
521 }
522 }
523 }
524 .instrument(tracing::debug_span!("trace_filter_cache"));
525
526 (task, CacheRequester(requester_tx))
527 }
528
529 fn request_get_traces(
534 &mut self,
535 blocking_tx: &mpsc::Sender<BlockingTaskMessage>,
536 sender: oneshot::Sender<SharedTxsTraceRes>,
537 block: H256,
538 overrides: Arc<dyn StorageOverride<B>>,
539 spawn_handle: &SpawnTaskHandle,
540 ) {
541 log::trace!(
542 target: CACHE_LOG_TARGET,
543 "Request received: block={}, wait_list_size={}",
544 block,
545 self.wait_list.len()
546 );
547
548 if let Some(cached) = self.cache.get(&block) {
550 log::trace!(
551 target: CACHE_LOG_TARGET,
552 "Cache hit: block={}",
553 block
554 );
555 let _ = sender.send(Ok(Arc::clone(&cached)));
557 return;
558 }
559
560 if let Some(entry) = self.wait_list.get_mut(&block) {
562 log::trace!(
563 target: CACHE_LOG_TARGET,
564 "Joining wait list: block={}, waiters={}",
565 block,
566 entry.waiters.len()
567 );
568 entry.waiters.push(sender);
569
570 if let Some(ref metrics) = self.metrics {
572 metrics.wait_list_joins_total.inc();
573 }
574
575 return;
576 }
577
578 self.wait_list.insert(
580 block,
581 WaitListEntry {
582 created_at: Instant::now(),
583 waiters: vec![sender],
584 },
585 );
586
587 log::debug!(
588 target: CACHE_LOG_TARGET,
589 "Spawning trace task: block={}, available_permits={}",
590 block,
591 self.blocking_permits.available_permits()
592 );
593
594 if let Some(ref metrics) = self.metrics {
596 metrics.tasks_spawned_total.inc();
597 metrics.wait_list_size.set(self.wait_list.len() as u64);
598 }
599
600 let blocking_permits = Arc::clone(&self.blocking_permits);
602 let client = Arc::clone(&self.client);
603 let backend = Arc::clone(&self.backend);
604 let blocking_tx = blocking_tx.clone();
605 let start_time = Instant::now();
606
607 spawn_handle.spawn(
608 "trace-block",
609 Some("trace-filter"),
610 async move {
611 let _permit = blocking_permits.acquire().await;
613
614 let result = match tokio::time::timeout(
616 Duration::from_secs(TRACING_TIMEOUT_SECS),
617 tokio::task::spawn_blocking(move || {
618 Self::cache_block(client, backend, block, overrides)
619 }),
620 )
621 .await
622 {
623 Err(_elapsed) => {
625 log::error!(
626 target: CACHE_LOG_TARGET,
627 "Tracing timeout for block {}",
628 block
629 );
630 Err(format!(
631 "Tracing timeout after {} seconds",
632 TRACING_TIMEOUT_SECS
633 ))
634 }
635 Ok(join_result) => {
637 match join_result {
638 Err(join_err) => Err(format!("Tracing panicked: {:?}", join_err)),
640 Ok(trace_result) => trace_result,
642 }
643 }
644 };
645
646 let duration = start_time.elapsed();
648 let _ = blocking_tx
649 .send(BlockingTaskMessage::Finished {
650 block_hash: block,
651 result,
652 duration,
653 })
654 .await;
655 }
656 .instrument(tracing::trace_span!("trace_block", block = %block)),
657 );
658 }
659
660 fn blocking_finished(&mut self, block_hash: H256, result: TxsTraceRes, duration: Duration) {
664 if let Some(entry) = self.wait_list.remove(&block_hash) {
666 let waiter_count = entry.waiters.len();
667
668 if let Some(ref metrics) = self.metrics {
670 metrics.wait_list_size.set(self.wait_list.len() as u64);
671 metrics
672 .trace_duration_seconds
673 .observe(duration.as_secs_f64());
674 }
675
676 match result {
677 Ok(traces) => {
678 let trace_count = traces.len();
679 let arc_traces = Arc::new(traces);
681
682 log::debug!(
683 target: CACHE_LOG_TARGET,
684 "Trace completed: block={}, traces={}, waiters={}, cached=true, duration={:?}",
685 block_hash,
686 trace_count,
687 waiter_count,
688 duration
689 );
690
691 for sender in entry.waiters {
693 let _ = sender.send(Ok(Arc::clone(&arc_traces)));
694 }
695
696 self.cache.put(block_hash, arc_traces);
698 }
699 Err(error) => {
700 log::warn!(
701 target: CACHE_LOG_TARGET,
702 "Trace failed: block={}, waiters={}, error={}",
703 block_hash,
704 waiter_count,
705 error
706 );
707
708 let arc_error = Arc::new(error);
710
711 for sender in entry.waiters {
713 let _ = sender.send(Err(Arc::clone(&arc_error)));
714 }
715 }
716 }
717 }
718 }
719
720 fn cleanup_wait_list(&mut self) {
723 let timeout = Duration::from_secs(TRACING_TIMEOUT_SECS + 10);
724 let now = Instant::now();
725
726 let mut to_remove = Vec::new();
727
728 for (block_hash, entry) in &self.wait_list {
729 if now.duration_since(entry.created_at) > timeout {
730 log::warn!(
731 target: CACHE_LOG_TARGET,
732 "Cleaning up orphaned wait list entry for block {}",
733 block_hash
734 );
735 to_remove.push(*block_hash);
736 }
737 }
738
739 log::debug!(
740 target: CACHE_LOG_TARGET,
741 "Wait list status: active_blocks={}, timed_out_block_requests={}",
742 self.wait_list.len(),
743 to_remove.len()
744 );
745
746 if !to_remove.is_empty() {
748 if let Some(ref metrics) = self.metrics {
749 for _ in &to_remove {
750 metrics.timeouts_total.inc();
751 }
752 }
753 }
754
755 let timeout_error =
757 Arc::new("Trace request timeout (task failed or was cancelled)".to_string());
758
759 for block_hash in to_remove {
760 if let Some(entry) = self.wait_list.remove(&block_hash) {
761 for sender in entry.waiters {
762 let _ = sender.send(Err(Arc::clone(&timeout_error)));
763 }
764 }
765 }
766
767 if let Some(ref metrics) = self.metrics {
769 metrics.wait_list_size.set(self.wait_list.len() as u64);
770 }
771 }
772
773 #[instrument(skip(client, backend, overrides))]
775 fn cache_block(
776 client: Arc<C>,
777 backend: Arc<BE>,
778 substrate_hash: H256,
779 overrides: Arc<dyn StorageOverride<B>>,
780 ) -> TxsTraceRes {
781 let api = client.runtime_api();
783 let block_header = client
784 .header(substrate_hash)
785 .map_err(|e| {
786 format!(
787 "Error when fetching substrate block {} header : {:?}",
788 substrate_hash, e
789 )
790 })?
791 .ok_or_else(|| format!("Substrate block {} don't exist", substrate_hash))?;
792
793 let height = *block_header.number();
794 let substrate_parent_hash = *block_header.parent_hash();
795
796 let (eth_block, eth_transactions) = match (
798 overrides.current_block(substrate_hash),
799 overrides.current_transaction_statuses(substrate_hash),
800 ) {
801 (Some(a), Some(b)) => (a, b),
802 _ => {
803 return Err(format!(
804 "Failed to get Ethereum block data for Substrate block {}",
805 substrate_hash
806 ))
807 }
808 };
809
810 let eth_block_hash = eth_block.header.hash();
811 let eth_tx_hashes = eth_transactions
812 .iter()
813 .map(|t| t.transaction_hash)
814 .collect();
815
816 let extrinsics = backend
818 .blockchain()
819 .body(substrate_hash)
820 .map_err(|e| {
821 format!(
822 "Blockchain error when fetching extrinsics of block {} : {:?}",
823 height, e
824 )
825 })?
826 .ok_or_else(|| format!("Could not find block {} when fetching extrinsics.", height))?;
827
828 let trace_api_version = if let Ok(Some(api_version)) =
830 api.api_version::<dyn DebugRuntimeApi<B>>(substrate_parent_hash)
831 {
832 api_version
833 } else {
834 return Err("Runtime api version call failed (trace)".to_string());
835 };
836
837 let f = || -> Result<_, String> {
839 let result = if trace_api_version >= 5 {
840 api.trace_block(
841 substrate_parent_hash,
842 extrinsics,
843 eth_tx_hashes,
844 &block_header,
845 )
846 } else {
847 let core_api_version = if let Ok(Some(api_version)) =
849 api.api_version::<dyn Core<B>>(substrate_parent_hash)
850 {
851 api_version
852 } else {
853 return Err("Runtime api version call failed (core)".to_string());
854 };
855
856 if core_api_version >= 5 {
862 api.initialize_block(substrate_parent_hash, &block_header)
863 .map_err(|e| format!("Runtime api access error: {:?}", e))?;
864 } else {
865 #[allow(deprecated)]
866 api.initialize_block_before_version_5(substrate_parent_hash, &block_header)
867 .map_err(|e| format!("Runtime api access error: {:?}", e))?;
868 }
869
870 #[allow(deprecated)]
871 api.trace_block_before_version_5(substrate_parent_hash, extrinsics, eth_tx_hashes)
872 };
873
874 result
875 .map_err(|e| format!("Blockchain error when replaying block {} : {:?}", height, e))?
876 .map_err(|e| {
877 tracing::warn!(
878 target: "tracing",
879 "Internal runtime error when replaying block {} : {:?}",
880 height,
881 e
882 );
883 format!(
884 "Internal runtime error when replaying block {} : {:?}",
885 height, e
886 )
887 })?;
888
889 Ok(moonbeam_rpc_primitives_debug::Response::Block)
890 };
891
892 let eth_transactions_by_index: BTreeMap<u32, H256> = eth_transactions
893 .iter()
894 .map(|t| (t.transaction_index, t.transaction_hash))
895 .collect();
896
897 let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
898 proxy.using(f)?;
899
900 let traces: Vec<TransactionTrace> =
901 moonbeam_client_evm_tracing::formatters::TraceFilter::format(proxy)
902 .ok_or("Fail to format proxy")?
903 .into_iter()
904 .filter_map(|mut trace| {
905 match eth_transactions_by_index.get(&trace.transaction_position) {
906 Some(transaction_hash) => {
907 trace.block_hash = eth_block_hash;
908 trace.block_number = height;
909 trace.transaction_hash = *transaction_hash;
910
911 if let block::TransactionTraceOutput::Error(ref mut error) =
913 trace.output
914 {
915 if error.as_slice() == b"execution reverted" {
916 *error = b"Reverted".to_vec();
917 }
918 }
919
920 Some(trace)
921 }
922 None => {
923 log::warn!(
924 target: "tracing",
925 "A trace in block {} does not map to any known ethereum transaction. Trace: {:?}",
926 height,
927 trace,
928 );
929 None
930 }
931 }
932 })
933 .collect();
934
935 Ok(traces)
936 }
937}