1use futures::StreamExt;
17use jsonrpsee::core::{async_trait, RpcResult};
18pub use moonbeam_rpc_core_debug::{DebugServer, TraceCallParams, TraceParams};
19
20use tokio::{
21 self,
22 sync::{oneshot, Semaphore},
23};
24
25use ethereum;
26use ethereum_types::H256;
27use fc_rpc::{frontier_backend_client, internal_err};
28use fc_storage::StorageOverride;
29use fp_rpc::EthereumRuntimeRPCApi;
30use moonbeam_client_evm_tracing::formatters::call_tracer::CallTracerInner;
31use moonbeam_client_evm_tracing::types::block;
32use moonbeam_client_evm_tracing::types::block::BlockTransactionTrace;
33use moonbeam_client_evm_tracing::types::single::TransactionTrace;
34use moonbeam_client_evm_tracing::{formatters::ResponseFormatter, types::single};
35use moonbeam_rpc_core_types::{RequestBlockId, RequestBlockTag};
36use moonbeam_rpc_primitives_debug::{DebugRuntimeApi, TracerInput};
37use sc_client_api::backend::{Backend, StateBackend, StorageProvider};
38use sc_utils::mpsc::TracingUnboundedSender;
39use sp_api::{ApiExt, Core, ProvideRuntimeApi};
40use sp_block_builder::BlockBuilder;
41use sp_blockchain::{
42 Backend as BlockchainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata,
43};
44use sp_core::H160;
45use sp_runtime::{
46 generic::BlockId,
47 traits::{BlakeTwo256, Block as BlockT, Header as HeaderT, UniqueSaturatedInto},
48};
49use std::collections::BTreeMap;
50use std::{future::Future, marker::PhantomData, sync::Arc};
51
52pub enum RequesterInput {
53 Call((RequestBlockId, TraceCallParams)),
54 Transaction(H256),
55 Block(RequestBlockId),
56}
57
58pub enum Response {
59 Single(single::TransactionTrace),
60 Block(Vec<block::BlockTransactionTrace>),
61}
62
63pub type Responder = oneshot::Sender<RpcResult<Response>>;
64pub type DebugRequester =
65 TracingUnboundedSender<((RequesterInput, Option<TraceParams>), Responder)>;
66
67pub struct Debug {
68 pub requester: DebugRequester,
69}
70
71impl Debug {
72 pub fn new(requester: DebugRequester) -> Self {
73 Self { requester }
74 }
75}
76
77#[async_trait]
78impl DebugServer for Debug {
79 async fn trace_transaction(
82 &self,
83 transaction_hash: H256,
84 params: Option<TraceParams>,
85 ) -> RpcResult<single::TransactionTrace> {
86 let requester = self.requester.clone();
87
88 let (tx, rx) = oneshot::channel();
89 requester
91 .unbounded_send(((RequesterInput::Transaction(transaction_hash), params), tx))
92 .map_err(|err| {
93 internal_err(format!(
94 "failed to send request to debug service : {:?}",
95 err
96 ))
97 })?;
98
99 rx.await
101 .map_err(|err| internal_err(format!("debug service dropped the channel : {:?}", err)))?
102 .map(|res| match res {
103 Response::Single(res) => res,
104 _ => unreachable!(),
105 })
106 }
107
108 async fn trace_block(
109 &self,
110 id: RequestBlockId,
111 params: Option<TraceParams>,
112 ) -> RpcResult<Vec<BlockTransactionTrace>> {
113 let requester = self.requester.clone();
114
115 let (tx, rx) = oneshot::channel();
116 requester
118 .unbounded_send(((RequesterInput::Block(id), params), tx))
119 .map_err(|err| {
120 internal_err(format!(
121 "failed to send request to debug service : {:?}",
122 err
123 ))
124 })?;
125
126 rx.await
128 .map_err(|err| internal_err(format!("debug service dropped the channel : {:?}", err)))?
129 .map(|res| match res {
130 Response::Block(res) => res,
131 _ => unreachable!(),
132 })
133 }
134
135 async fn trace_call(
138 &self,
139 call_params: TraceCallParams,
140 id: RequestBlockId,
141 params: Option<TraceParams>,
142 ) -> RpcResult<single::TransactionTrace> {
143 let requester = self.requester.clone();
144
145 let (tx, rx) = oneshot::channel();
146 requester
148 .unbounded_send(((RequesterInput::Call((id, call_params)), params), tx))
149 .map_err(|err| {
150 internal_err(format!(
151 "failed to send request to debug service : {:?}",
152 err
153 ))
154 })?;
155
156 rx.await
158 .map_err(|err| internal_err(format!("debug service dropped the channel : {:?}", err)))?
159 .map(|res| match res {
160 Response::Single(res) => res,
161 _ => unreachable!(),
162 })
163 }
164}
165
166pub struct DebugHandler<B: BlockT, C, BE>(PhantomData<(B, C, BE)>);
167
168impl<B, C, BE> DebugHandler<B, C, BE>
169where
170 BE: Backend<B> + 'static,
171 BE::State: StateBackend<BlakeTwo256>,
172 C: ProvideRuntimeApi<B>,
173 C: StorageProvider<B, BE>,
174 C: HeaderMetadata<B, Error = BlockChainError> + HeaderBackend<B>,
175 C: Send + Sync + 'static,
176 B: BlockT<Hash = H256> + Send + Sync + 'static,
177 C::Api: BlockBuilder<B>,
178 C::Api: DebugRuntimeApi<B>,
179 C::Api: EthereumRuntimeRPCApi<B>,
180 C::Api: ApiExt<B>,
181{
182 pub fn task(
185 client: Arc<C>,
186 backend: Arc<BE>,
187 frontier_backend: Arc<dyn fc_api::Backend<B> + Send + Sync>,
188 permit_pool: Arc<Semaphore>,
189 overrides: Arc<dyn StorageOverride<B>>,
190 raw_max_memory_usage: usize,
191 ) -> (impl Future<Output = ()>, DebugRequester) {
192 let (tx, mut rx): (DebugRequester, _) =
193 sc_utils::mpsc::tracing_unbounded("debug-requester", 100_000);
194
195 let fut = async move {
196 loop {
197 match rx.next().await {
198 Some((
199 (RequesterInput::Transaction(transaction_hash), params),
200 response_tx,
201 )) => {
202 let client = client.clone();
203 let backend = backend.clone();
204 let frontier_backend = frontier_backend.clone();
205 let permit_pool = permit_pool.clone();
206 let overrides = overrides.clone();
207
208 tokio::task::spawn(async move {
209 let _ = response_tx.send(
210 async {
211 let _permit = permit_pool.acquire().await;
212 tokio::task::spawn_blocking(move || {
213 Self::handle_transaction_request(
214 client.clone(),
215 backend.clone(),
216 frontier_backend.clone(),
217 transaction_hash,
218 params,
219 overrides.clone(),
220 raw_max_memory_usage,
221 )
222 })
223 .await
224 .map_err(|e| {
225 internal_err(format!(
226 "Internal error on spawned task : {:?}",
227 e
228 ))
229 })?
230 }
231 .await,
232 );
233 });
234 }
235 Some((
236 (RequesterInput::Call((request_block_id, call_params)), params),
237 response_tx,
238 )) => {
239 let client = client.clone();
240 let frontier_backend = frontier_backend.clone();
241 let permit_pool = permit_pool.clone();
242
243 tokio::task::spawn(async move {
244 let _ = response_tx.send(
245 async {
246 let _permit = permit_pool.acquire().await;
247 tokio::task::spawn_blocking(move || {
248 Self::handle_call_request(
249 client.clone(),
250 frontier_backend.clone(),
251 request_block_id,
252 call_params,
253 params,
254 raw_max_memory_usage,
255 )
256 })
257 .await
258 .map_err(|e| {
259 internal_err(format!(
260 "Internal error on spawned task : {:?}",
261 e
262 ))
263 })?
264 }
265 .await,
266 );
267 });
268 }
269 Some(((RequesterInput::Block(request_block_id), params), response_tx)) => {
270 let client = client.clone();
271 let backend = backend.clone();
272 let frontier_backend = frontier_backend.clone();
273 let permit_pool = permit_pool.clone();
274 let overrides = overrides.clone();
275
276 tokio::task::spawn(async move {
277 let _ = response_tx.send(
278 async {
279 let _permit = permit_pool.acquire().await;
280
281 tokio::task::spawn_blocking(move || {
282 Self::handle_block_request(
283 client.clone(),
284 backend.clone(),
285 frontier_backend.clone(),
286 request_block_id,
287 params,
288 overrides.clone(),
289 )
290 })
291 .await
292 .map_err(|e| {
293 internal_err(format!(
294 "Internal error on spawned task : {:?}",
295 e
296 ))
297 })?
298 }
299 .await,
300 );
301 });
302 }
303 _ => {}
304 }
305 }
306 };
307 (fut, tx)
308 }
309
310 fn handle_params(
311 params: Option<TraceParams>,
312 ) -> RpcResult<(
313 TracerInput,
314 single::TraceType,
315 Option<single::TraceCallConfig>,
316 )> {
317 match params {
319 Some(TraceParams {
320 tracer: Some(tracer),
321 tracer_config,
322 ..
323 }) => {
324 const BLOCKSCOUT_JS_CODE_HASH: [u8; 16] =
325 hex_literal::hex!("94d9f08796f91eb13a2e82a6066882f7");
326 const BLOCKSCOUT_JS_CODE_HASH_V2: [u8; 16] =
327 hex_literal::hex!("89db13694675692951673a1e6e18ff02");
328 let hash = sp_io::hashing::twox_128(&tracer.as_bytes());
329 let tracer =
330 if hash == BLOCKSCOUT_JS_CODE_HASH || hash == BLOCKSCOUT_JS_CODE_HASH_V2 {
331 Some(TracerInput::Blockscout)
332 } else if tracer == "callTracer" {
333 Some(TracerInput::CallTracer)
334 } else {
335 None
336 };
337 if let Some(tracer) = tracer {
338 Ok((tracer, single::TraceType::CallList, tracer_config))
339 } else {
340 return Err(internal_err(format!(
341 "javascript based tracing is not available (hash :{:?})",
342 hash
343 )));
344 }
345 }
346 Some(params) => Ok((
347 TracerInput::None,
348 single::TraceType::Raw {
349 disable_storage: params.disable_storage.unwrap_or(false),
350 disable_memory: params.disable_memory.unwrap_or(false),
351 disable_stack: params.disable_stack.unwrap_or(false),
352 },
353 params.tracer_config,
354 )),
355 _ => Ok((
356 TracerInput::None,
357 single::TraceType::Raw {
358 disable_storage: false,
359 disable_memory: false,
360 disable_stack: false,
361 },
362 None,
363 )),
364 }
365 }
366
367 fn handle_block_request(
368 client: Arc<C>,
369 backend: Arc<BE>,
370 frontier_backend: Arc<dyn fc_api::Backend<B> + Send + Sync>,
371 request_block_id: RequestBlockId,
372 params: Option<TraceParams>,
373 overrides: Arc<dyn StorageOverride<B>>,
374 ) -> RpcResult<Response> {
375 let (tracer_input, trace_type, tracer_config) = Self::handle_params(params)?;
376
377 let reference_id: BlockId<B> = match request_block_id {
378 RequestBlockId::Number(n) => Ok(BlockId::Number(n.unique_saturated_into())),
379 RequestBlockId::Tag(RequestBlockTag::Latest) => {
380 Ok(BlockId::Number(client.info().best_number))
381 }
382 RequestBlockId::Tag(RequestBlockTag::Finalized) => {
383 Ok(BlockId::Hash(client.info().finalized_hash))
384 }
385 RequestBlockId::Tag(RequestBlockTag::Earliest) => {
386 Ok(BlockId::Number(0u32.unique_saturated_into()))
387 }
388 RequestBlockId::Tag(RequestBlockTag::Pending) => {
389 Err(internal_err("'pending' blocks are not supported"))
390 }
391 RequestBlockId::Hash(eth_hash) => {
392 match futures::executor::block_on(frontier_backend_client::load_hash::<B, C>(
393 client.as_ref(),
394 frontier_backend.as_ref(),
395 eth_hash,
396 )) {
397 Ok(Some(hash)) => Ok(BlockId::Hash(hash)),
398 Ok(_) => Err(internal_err("Block hash not found".to_string())),
399 Err(e) => Err(e),
400 }
401 }
402 }?;
403
404 let api = client.runtime_api();
406 let blockchain = backend.blockchain();
408 let Ok(hash) = client.expect_block_hash_from_id(&reference_id) else {
410 return Err(internal_err("Block header not found"));
411 };
412 let header = match client.header(hash) {
413 Ok(Some(h)) => h,
414 _ => return Err(internal_err("Block header not found")),
415 };
416
417 let parent_block_hash = *header.parent_hash();
419
420 let statuses = overrides
421 .current_transaction_statuses(hash)
422 .unwrap_or_default();
423
424 struct EthTxPartial {
426 transaction_hash: H256,
427 from: H160,
428 to: Option<H160>,
429 }
430
431 let eth_transactions_by_index: BTreeMap<u32, EthTxPartial> = statuses
433 .iter()
434 .map(|status| {
435 (
436 status.transaction_index,
437 EthTxPartial {
438 transaction_hash: status.transaction_hash,
439 from: status.from,
440 to: status.to,
441 },
442 )
443 })
444 .collect();
445
446 let eth_tx_hashes: Vec<_> = eth_transactions_by_index
447 .values()
448 .map(|tx| tx.transaction_hash)
449 .collect();
450
451 if eth_tx_hashes.is_empty() {
453 return Ok(Response::Block(vec![]));
454 }
455
456 let exts = blockchain
458 .body(hash)
459 .map_err(|e| internal_err(format!("Fail to read blockchain db: {:?}", e)))?
460 .unwrap_or_default();
461
462 let trace_api_version = if let Ok(Some(api_version)) =
464 api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
465 {
466 api_version
467 } else {
468 return Err(internal_err(
469 "Runtime api version call failed (trace)".to_string(),
470 ));
471 };
472
473 let f = || -> RpcResult<_> {
475 let result = if trace_api_version >= 5 {
476 api.trace_block(parent_block_hash, exts, eth_tx_hashes, &header)
478 } else {
479 let core_api_version = if let Ok(Some(api_version)) =
481 api.api_version::<dyn Core<B>>(parent_block_hash)
482 {
483 api_version
484 } else {
485 return Err(internal_err(
486 "Runtime api version call failed (core)".to_string(),
487 ));
488 };
489
490 if core_api_version >= 5 {
496 api.initialize_block(parent_block_hash, &header)
497 .map_err(|e| internal_err(format!("Runtime api access error: {:?}", e)))?;
498 } else {
499 #[allow(deprecated)]
500 api.initialize_block_before_version_5(parent_block_hash, &header)
501 .map_err(|e| internal_err(format!("Runtime api access error: {:?}", e)))?;
502 }
503
504 #[allow(deprecated)]
505 api.trace_block_before_version_5(parent_block_hash, exts, eth_tx_hashes)
506 };
507
508 result
509 .map_err(|e| {
510 internal_err(format!(
511 "Blockchain error when replaying block {} : {:?}",
512 reference_id, e
513 ))
514 })?
515 .map_err(|e| {
516 internal_err(format!(
517 "Internal runtime error when replaying block {} : {:?}",
518 reference_id, e
519 ))
520 })?;
521
522 Ok(moonbeam_rpc_primitives_debug::Response::Block)
523 };
524
525 let mut tx_position_offset = 0;
527
528 return match trace_type {
529 single::TraceType::CallList => {
530 let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
531 proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
532 proxy.using(f)?;
533 proxy.finish_transaction();
534 let response = match tracer_input {
535 TracerInput::CallTracer => {
536 let result =
537 moonbeam_client_evm_tracing::formatters::CallTracer::format(proxy)
538 .ok_or("Trace result is empty.")
539 .map_err(|e| internal_err(format!("{:?}", e)))?
540 .into_iter()
541 .filter_map(|mut trace: BlockTransactionTrace| {
542 if let Some(EthTxPartial {
543 transaction_hash,
544 from,
545 to,
546 }) = eth_transactions_by_index
547 .get(&(trace.tx_position - tx_position_offset))
548 {
549 let (trace_from, trace_to) = match trace.result {
551 TransactionTrace::Raw { .. } => {
552 (Default::default(), None)
553 }
554 TransactionTrace::CallList(_) => {
555 (Default::default(), None)
556 }
557 TransactionTrace::CallListNested(ref call) => {
558 match call {
559 single::Call::Blockscout(_) => {
560 (Default::default(), None)
561 }
562 single::Call::CallTracer(call) => (
563 call.from,
564 match call.inner {
565 CallTracerInner::Call {
566 to, ..
567 } => Some(to),
568 CallTracerInner::Create { .. } => None,
569 CallTracerInner::SelfDestruct {
570 ..
571 } => None,
572 },
573 ),
574 }
575 }
576 };
577 if trace_from == *from && trace_to == *to {
578 trace.tx_hash = *transaction_hash;
579 Some(trace)
580 } else {
581 tx_position_offset += 1;
585 None
586 }
587 } else {
588 tx_position_offset += 1;
591 None
592 }
593 })
594 .collect::<Vec<BlockTransactionTrace>>();
595
596 let n_txs = eth_transactions_by_index.len();
597 let n_traces = result.len();
598 if n_txs != n_traces {
599 log::warn!(
600 "The traces in block {:?} don't match with the number of ethereum transactions. (txs: {}, traces: {})",
601 request_block_id,
602 n_txs,
603 n_traces
604 );
605 }
606
607 Ok(result)
608 }
609 _ => Err(internal_err(
610 "Bug: failed to resolve the tracer format.".to_string(),
611 )),
612 }?;
613
614 Ok(Response::Block(response))
615 }
616 _ => Err(internal_err(
617 "debug_traceBlock functions currently only support callList mode (enabled
618 by providing `{{'tracer': 'callTracer'}}` in the request)."
619 .to_string(),
620 )),
621 };
622 }
623
624 fn handle_transaction_request(
632 client: Arc<C>,
633 backend: Arc<BE>,
634 frontier_backend: Arc<dyn fc_api::Backend<B> + Send + Sync>,
635 transaction_hash: H256,
636 params: Option<TraceParams>,
637 overrides: Arc<dyn StorageOverride<B>>,
638 raw_max_memory_usage: usize,
639 ) -> RpcResult<Response> {
640 let (tracer_input, trace_type, tracer_config) = Self::handle_params(params)?;
641
642 let (hash, index) =
643 match futures::executor::block_on(frontier_backend_client::load_transactions::<B, C>(
644 client.as_ref(),
645 frontier_backend.as_ref(),
646 transaction_hash,
647 false,
648 )) {
649 Ok(Some((hash, index))) => (hash, index as usize),
650 Ok(None) => return Err(internal_err("Transaction hash not found".to_string())),
651 Err(e) => return Err(e),
652 };
653
654 let reference_id =
655 match futures::executor::block_on(frontier_backend_client::load_hash::<B, C>(
656 client.as_ref(),
657 frontier_backend.as_ref(),
658 hash,
659 )) {
660 Ok(Some(hash)) => BlockId::Hash(hash),
661 Ok(_) => return Err(internal_err("Block hash not found".to_string())),
662 Err(e) => return Err(e),
663 };
664 let api = client.runtime_api();
666 let blockchain = backend.blockchain();
668 let Ok(reference_hash) = client.expect_block_hash_from_id(&reference_id) else {
670 return Err(internal_err("Block header not found"));
671 };
672 let header = match client.header(reference_hash) {
673 Ok(Some(h)) => h,
674 _ => return Err(internal_err("Block header not found")),
675 };
676 let parent_block_hash = *header.parent_hash();
678
679 let exts = blockchain
681 .body(reference_hash)
682 .map_err(|e| internal_err(format!("Fail to read blockchain db: {:?}", e)))?
683 .unwrap_or_default();
684
685 let trace_api_version = if let Ok(Some(api_version)) =
687 api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
688 {
689 api_version
690 } else {
691 return Err(internal_err(
692 "Runtime api version call failed (trace)".to_string(),
693 ));
694 };
695
696 let reference_block = overrides.current_block(reference_hash);
697
698 if let Some(block) = reference_block {
700 let transactions = block.transactions;
701 if let Some(transaction) = transactions.get(index) {
702 let f = || -> RpcResult<_> {
703 let result = if trace_api_version >= 7 {
704 api.trace_transaction(parent_block_hash, exts, &transaction, &header)
706 } else if trace_api_version == 5 || trace_api_version == 6 {
707 let tx_v2 = match transaction {
709 ethereum::TransactionV3::Legacy(tx) => {
710 ethereum::TransactionV2::Legacy(tx.clone())
711 }
712 ethereum::TransactionV3::EIP2930(tx) => {
713 ethereum::TransactionV2::EIP2930(tx.clone())
714 }
715 ethereum::TransactionV3::EIP1559(tx) => {
716 ethereum::TransactionV2::EIP1559(tx.clone())
717 }
718 ethereum::TransactionV3::EIP7702(_) => {
719 return Err(internal_err(
720 "EIP-7702 transactions are not supported".to_string(),
721 ))
722 }
723 };
724
725 #[allow(deprecated)]
727 api.trace_transaction_before_version_7(
728 parent_block_hash,
729 exts,
730 &tx_v2,
731 &header,
732 )
733 } else {
734 let core_api_version = if let Ok(Some(api_version)) =
736 api.api_version::<dyn Core<B>>(parent_block_hash)
737 {
738 api_version
739 } else {
740 return Err(internal_err(
741 "Runtime api version call failed (core)".to_string(),
742 ));
743 };
744
745 if core_api_version >= 5 {
751 api.initialize_block(parent_block_hash, &header)
752 .map_err(|e| {
753 internal_err(format!("Runtime api access error: {:?}", e))
754 })?;
755 } else {
756 #[allow(deprecated)]
757 api.initialize_block_before_version_5(parent_block_hash, &header)
758 .map_err(|e| {
759 internal_err(format!("Runtime api access error: {:?}", e))
760 })?;
761 }
762
763 if trace_api_version == 4 {
764 let tx_v2 = match transaction {
766 ethereum::TransactionV3::Legacy(tx) => {
767 ethereum::TransactionV2::Legacy(tx.clone())
768 }
769 ethereum::TransactionV3::EIP2930(tx) => {
770 ethereum::TransactionV2::EIP2930(tx.clone())
771 }
772 ethereum::TransactionV3::EIP1559(tx) => {
773 ethereum::TransactionV2::EIP1559(tx.clone())
774 }
775 ethereum::TransactionV3::EIP7702(_) => {
776 return Err(internal_err(
777 "EIP-7702 transactions are not supported".to_string(),
778 ))
779 }
780 };
781
782 #[allow(deprecated)]
784 api.trace_transaction_before_version_5(parent_block_hash, exts, &tx_v2)
785 } else {
786 match transaction {
788 ethereum::TransactionV3::Legacy(tx) =>
789 {
790 #[allow(deprecated)]
791 api.trace_transaction_before_version_4(
792 parent_block_hash,
793 exts,
794 &tx,
795 )
796 }
797 _ => {
798 return Err(internal_err(
799 "Bug: pre-london runtime expects legacy transactions"
800 .to_string(),
801 ))
802 }
803 }
804 }
805 };
806
807 result
808 .map_err(|e| {
809 internal_err(format!(
810 "Runtime api access error (version {:?}): {:?}",
811 trace_api_version, e
812 ))
813 })?
814 .map_err(|e| internal_err(format!("DispatchError: {:?}", e)))?;
815
816 Ok(moonbeam_rpc_primitives_debug::Response::Single)
817 };
818
819 return match trace_type {
820 single::TraceType::Raw {
821 disable_storage,
822 disable_memory,
823 disable_stack,
824 } => {
825 let mut proxy = moonbeam_client_evm_tracing::listeners::Raw::new(
826 disable_storage,
827 disable_memory,
828 disable_stack,
829 raw_max_memory_usage,
830 );
831 proxy.using(f)?;
832 Ok(Response::Single(
833 moonbeam_client_evm_tracing::formatters::Raw::format(proxy).ok_or(
834 internal_err(
835 "replayed transaction generated too much data. \
836 try disabling memory or storage?",
837 ),
838 )?,
839 ))
840 }
841 single::TraceType::CallList => {
842 let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
843 proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
844 proxy.using(f)?;
845 proxy.finish_transaction();
846 let response = match tracer_input {
847 TracerInput::Blockscout => {
848 moonbeam_client_evm_tracing::formatters::Blockscout::format(proxy)
849 .ok_or("Trace result is empty.")
850 .map_err(|e| internal_err(format!("{:?}", e)))
851 }
852 TracerInput::CallTracer => {
853 let mut res =
854 moonbeam_client_evm_tracing::formatters::CallTracer::format(
855 proxy,
856 )
857 .ok_or("Trace result is empty.")
858 .map_err(|e| internal_err(format!("{:?}", e)))?;
859 Ok(res.pop().expect("Trace result is empty.").result)
860 }
861 _ => Err(internal_err(
862 "Bug: failed to resolve the tracer format.".to_string(),
863 )),
864 }?;
865 Ok(Response::Single(response))
866 }
867 not_supported => Err(internal_err(format!(
868 "Bug: `handle_transaction_request` does not support {:?}.",
869 not_supported
870 ))),
871 };
872 }
873 }
874 Err(internal_err("Runtime block call failed".to_string()))
875 }
876
877 fn handle_call_request(
878 client: Arc<C>,
879 frontier_backend: Arc<dyn fc_api::Backend<B> + Send + Sync>,
880 request_block_id: RequestBlockId,
881 call_params: TraceCallParams,
882 trace_params: Option<TraceParams>,
883 raw_max_memory_usage: usize,
884 ) -> RpcResult<Response> {
885 let (tracer_input, trace_type, tracer_config) = Self::handle_params(trace_params)?;
886
887 let reference_id: BlockId<B> = match request_block_id {
888 RequestBlockId::Number(n) => Ok(BlockId::Number(n.unique_saturated_into())),
889 RequestBlockId::Tag(RequestBlockTag::Latest) => {
890 Ok(BlockId::Number(client.info().best_number))
891 }
892 RequestBlockId::Tag(RequestBlockTag::Finalized) => {
893 Ok(BlockId::Hash(client.info().finalized_hash))
894 }
895 RequestBlockId::Tag(RequestBlockTag::Earliest) => {
896 Ok(BlockId::Number(0u32.unique_saturated_into()))
897 }
898 RequestBlockId::Tag(RequestBlockTag::Pending) => {
899 Err(internal_err("'pending' blocks are not supported"))
900 }
901 RequestBlockId::Hash(eth_hash) => {
902 match futures::executor::block_on(frontier_backend_client::load_hash::<B, C>(
903 client.as_ref(),
904 frontier_backend.as_ref(),
905 eth_hash,
906 )) {
907 Ok(Some(hash)) => Ok(BlockId::Hash(hash)),
908 Ok(_) => Err(internal_err("Block hash not found".to_string())),
909 Err(e) => Err(e),
910 }
911 }
912 }?;
913
914 let api = client.runtime_api();
916 let Ok(hash) = client.expect_block_hash_from_id(&reference_id) else {
918 return Err(internal_err("Block header not found"));
919 };
920 let header = match client.header(hash) {
921 Ok(Some(h)) => h,
922 _ => return Err(internal_err("Block header not found")),
923 };
924 let parent_block_hash = *header.parent_hash();
926
927 let trace_api_version = if let Ok(Some(api_version)) =
929 api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
930 {
931 api_version
932 } else {
933 return Err(internal_err(
934 "Runtime api version call failed (trace)".to_string(),
935 ));
936 };
937
938 if trace_api_version <= 5 {
939 return Err(internal_err(
940 "debug_traceCall not supported with old runtimes".to_string(),
941 ));
942 }
943
944 let TraceCallParams {
945 from,
946 to,
947 gas_price,
948 max_fee_per_gas,
949 max_priority_fee_per_gas,
950 gas,
951 value,
952 data,
953 nonce,
954 access_list,
955 authorization_list,
956 ..
957 } = call_params;
958
959 let (max_fee_per_gas, max_priority_fee_per_gas) =
960 match (gas_price, max_fee_per_gas, max_priority_fee_per_gas) {
961 (gas_price, None, None) => {
962 let gas_price = if gas_price.unwrap_or_default().is_zero() {
965 None
966 } else {
967 gas_price
968 };
969 (gas_price, gas_price)
970 }
971 (_, max_fee, max_priority) => {
972 let max_fee = if max_fee.unwrap_or_default().is_zero() {
975 None
976 } else {
977 max_fee
978 };
979 if let Some(max_priority) = max_priority {
981 let max_fee = max_fee.unwrap_or_default();
982 if max_priority > max_fee {
983 return Err(internal_err(
984 "Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`",
985 ));
986 }
987 }
988 (max_fee, max_priority)
989 }
990 };
991
992 let gas_limit = match gas {
993 Some(amount) => amount,
994 None => {
995 if let Some(block) = api
996 .current_block(parent_block_hash)
997 .map_err(|err| internal_err(format!("runtime error: {:?}", err)))?
998 {
999 block.header.gas_limit
1000 } else {
1001 return Err(internal_err(
1002 "block unavailable, cannot query gas limit".to_string(),
1003 ));
1004 }
1005 }
1006 };
1007 let data = data.map(|d| d.0).unwrap_or_default();
1008
1009 let access_list = access_list.unwrap_or_default();
1010
1011 let f = || -> RpcResult<_> {
1012 let _result = api
1013 .trace_call(
1014 parent_block_hash,
1015 &header,
1016 from.unwrap_or_default(),
1017 to,
1018 data,
1019 value.unwrap_or_default(),
1020 gas_limit,
1021 max_fee_per_gas,
1022 max_priority_fee_per_gas,
1023 nonce,
1024 Some(
1025 access_list
1026 .into_iter()
1027 .map(|item| (item.address, item.storage_keys))
1028 .collect(),
1029 ),
1030 authorization_list,
1031 )
1032 .map_err(|e| internal_err(format!("Runtime api access error: {:?}", e)))?
1033 .map_err(|e| internal_err(format!("DispatchError: {:?}", e)))?;
1034
1035 Ok(moonbeam_rpc_primitives_debug::Response::Single)
1036 };
1037
1038 return match trace_type {
1039 single::TraceType::Raw {
1040 disable_storage,
1041 disable_memory,
1042 disable_stack,
1043 } => {
1044 let mut proxy = moonbeam_client_evm_tracing::listeners::Raw::new(
1045 disable_storage,
1046 disable_memory,
1047 disable_stack,
1048 raw_max_memory_usage,
1049 );
1050 proxy.using(f)?;
1051 Ok(Response::Single(
1052 moonbeam_client_evm_tracing::formatters::Raw::format(proxy).ok_or(
1053 internal_err(
1054 "replayed transaction generated too much data. \
1055 try disabling memory or storage?",
1056 ),
1057 )?,
1058 ))
1059 }
1060 single::TraceType::CallList => {
1061 let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
1062 proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
1063 proxy.using(f)?;
1064 proxy.finish_transaction();
1065 let response = match tracer_input {
1066 TracerInput::Blockscout => {
1067 moonbeam_client_evm_tracing::formatters::Blockscout::format(proxy)
1068 .ok_or("Trace result is empty.")
1069 .map_err(|e| internal_err(format!("{:?}", e)))
1070 }
1071 TracerInput::CallTracer => {
1072 let mut res =
1073 moonbeam_client_evm_tracing::formatters::CallTracer::format(proxy)
1074 .ok_or("Trace result is empty.")
1075 .map_err(|e| internal_err(format!("{:?}", e)))?;
1076 Ok(res.pop().expect("Trace result is empty.").result)
1077 }
1078 _ => Err(internal_err(
1079 "Bug: failed to resolve the tracer format.".to_string(),
1080 )),
1081 }?;
1082 Ok(Response::Single(response))
1083 }
1084 not_supported => Err(internal_err(format!(
1085 "Bug: `handle_call_request` does not support {:?}.",
1086 not_supported
1087 ))),
1088 };
1089 }
1090}