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 mut api = client.runtime_api();
406
407 api.record_proof();
409 api.proof_recorder().map(|recorder| {
410 let ext = sp_trie::proof_size_extension::ProofSizeExt::new(recorder);
411 api.register_extension(ext);
412 });
413
414 let blockchain = backend.blockchain();
416 let Ok(hash) = client.expect_block_hash_from_id(&reference_id) else {
418 return Err(internal_err("Block header not found"));
419 };
420 let header = match client.header(hash) {
421 Ok(Some(h)) => h,
422 _ => return Err(internal_err("Block header not found")),
423 };
424
425 let parent_block_hash = *header.parent_hash();
427
428 let statuses = overrides
429 .current_transaction_statuses(hash)
430 .unwrap_or_default();
431
432 struct EthTxPartial {
434 transaction_hash: H256,
435 from: H160,
436 to: Option<H160>,
437 }
438
439 let eth_transactions_by_index: BTreeMap<u32, EthTxPartial> = statuses
441 .iter()
442 .map(|status| {
443 (
444 status.transaction_index,
445 EthTxPartial {
446 transaction_hash: status.transaction_hash,
447 from: status.from,
448 to: status.to,
449 },
450 )
451 })
452 .collect();
453
454 let eth_tx_hashes: Vec<_> = eth_transactions_by_index
455 .values()
456 .map(|tx| tx.transaction_hash)
457 .collect();
458
459 if eth_tx_hashes.is_empty() {
461 return Ok(Response::Block(vec![]));
462 }
463
464 let exts = blockchain
466 .body(hash)
467 .map_err(|e| internal_err(format!("Fail to read blockchain db: {:?}", e)))?
468 .unwrap_or_default();
469
470 let trace_api_version = if let Ok(Some(api_version)) =
472 api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
473 {
474 api_version
475 } else {
476 return Err(internal_err(
477 "Runtime api version call failed (trace)".to_string(),
478 ));
479 };
480
481 let f = || -> RpcResult<_> {
483 let result = if trace_api_version >= 5 {
484 api.trace_block(parent_block_hash, exts, eth_tx_hashes, &header)
486 } else {
487 let core_api_version = if let Ok(Some(api_version)) =
489 api.api_version::<dyn Core<B>>(parent_block_hash)
490 {
491 api_version
492 } else {
493 return Err(internal_err(
494 "Runtime api version call failed (core)".to_string(),
495 ));
496 };
497
498 if core_api_version >= 5 {
504 api.initialize_block(parent_block_hash, &header)
505 .map_err(|e| internal_err(format!("Runtime api access error: {:?}", e)))?;
506 } else {
507 #[allow(deprecated)]
508 api.initialize_block_before_version_5(parent_block_hash, &header)
509 .map_err(|e| internal_err(format!("Runtime api access error: {:?}", e)))?;
510 }
511
512 #[allow(deprecated)]
513 api.trace_block_before_version_5(parent_block_hash, exts, eth_tx_hashes)
514 };
515
516 result
517 .map_err(|e| {
518 internal_err(format!(
519 "Blockchain error when replaying block {} : {:?}",
520 reference_id, e
521 ))
522 })?
523 .map_err(|e| {
524 internal_err(format!(
525 "Internal runtime error when replaying block {} : {:?}",
526 reference_id, e
527 ))
528 })?;
529
530 Ok(moonbeam_rpc_primitives_debug::Response::Block)
531 };
532
533 let mut tx_position_offset = 0;
535
536 return match trace_type {
537 single::TraceType::CallList => {
538 let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
539 proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
540 proxy.using(f)?;
541 proxy.finish_transaction();
542 let response = match tracer_input {
543 TracerInput::CallTracer => {
544 let result =
545 moonbeam_client_evm_tracing::formatters::CallTracer::format(proxy)
546 .ok_or("Trace result is empty.")
547 .map_err(|e| internal_err(format!("{:?}", e)))?
548 .into_iter()
549 .filter_map(|mut trace: BlockTransactionTrace| {
550 if let Some(EthTxPartial {
551 transaction_hash,
552 from,
553 to,
554 }) = eth_transactions_by_index
555 .get(&(trace.tx_position - tx_position_offset))
556 {
557 let (trace_from, trace_to) = match trace.result {
559 TransactionTrace::Raw { .. } => {
560 (Default::default(), None)
561 }
562 TransactionTrace::CallList(_) => {
563 (Default::default(), None)
564 }
565 TransactionTrace::CallListNested(ref call) => {
566 match call {
567 single::Call::Blockscout(_) => {
568 (Default::default(), None)
569 }
570 single::Call::CallTracer(call) => (
571 call.from,
572 match call.inner {
573 CallTracerInner::Call {
574 to, ..
575 } => Some(to),
576 CallTracerInner::Create { .. } => None,
577 CallTracerInner::SelfDestruct {
578 ..
579 } => None,
580 },
581 ),
582 }
583 }
584 };
585 if trace_from == *from && trace_to == *to {
586 trace.tx_hash = *transaction_hash;
587 Some(trace)
588 } else {
589 tx_position_offset += 1;
593 None
594 }
595 } else {
596 tx_position_offset += 1;
599 None
600 }
601 })
602 .collect::<Vec<BlockTransactionTrace>>();
603
604 let n_txs = eth_transactions_by_index.len();
605 let n_traces = result.len();
606 if n_txs != n_traces {
607 log::warn!(
608 "The traces in block {:?} don't match with the number of ethereum transactions. (txs: {}, traces: {})",
609 request_block_id,
610 n_txs,
611 n_traces
612 );
613 }
614
615 Ok(result)
616 }
617 _ => Err(internal_err(
618 "Bug: failed to resolve the tracer format.".to_string(),
619 )),
620 }?;
621
622 Ok(Response::Block(response))
623 }
624 _ => Err(internal_err(
625 "debug_traceBlock functions currently only support callList mode (enabled
626 by providing `{{'tracer': 'callTracer'}}` in the request)."
627 .to_string(),
628 )),
629 };
630 }
631
632 fn handle_transaction_request(
640 client: Arc<C>,
641 backend: Arc<BE>,
642 frontier_backend: Arc<dyn fc_api::Backend<B> + Send + Sync>,
643 transaction_hash: H256,
644 params: Option<TraceParams>,
645 overrides: Arc<dyn StorageOverride<B>>,
646 raw_max_memory_usage: usize,
647 ) -> RpcResult<Response> {
648 let (tracer_input, trace_type, tracer_config) = Self::handle_params(params)?;
649
650 let (hash, index) =
651 match futures::executor::block_on(frontier_backend_client::load_transactions::<B, C>(
652 client.as_ref(),
653 frontier_backend.as_ref(),
654 transaction_hash,
655 false,
656 )) {
657 Ok(Some((hash, index))) => (hash, index as usize),
658 Ok(None) => return Err(internal_err("Transaction hash not found".to_string())),
659 Err(e) => return Err(e),
660 };
661
662 let reference_id =
663 match futures::executor::block_on(frontier_backend_client::load_hash::<B, C>(
664 client.as_ref(),
665 frontier_backend.as_ref(),
666 hash,
667 )) {
668 Ok(Some(hash)) => BlockId::Hash(hash),
669 Ok(_) => return Err(internal_err("Block hash not found".to_string())),
670 Err(e) => return Err(e),
671 };
672 let mut api = client.runtime_api();
674
675 api.record_proof();
677 api.proof_recorder().map(|recorder| {
678 let ext = sp_trie::proof_size_extension::ProofSizeExt::new(recorder);
679 api.register_extension(ext);
680 });
681
682 let blockchain = backend.blockchain();
684 let Ok(reference_hash) = client.expect_block_hash_from_id(&reference_id) else {
686 return Err(internal_err("Block header not found"));
687 };
688 let header = match client.header(reference_hash) {
689 Ok(Some(h)) => h,
690 _ => return Err(internal_err("Block header not found")),
691 };
692 let parent_block_hash = *header.parent_hash();
694
695 let exts = blockchain
697 .body(reference_hash)
698 .map_err(|e| internal_err(format!("Fail to read blockchain db: {:?}", e)))?
699 .unwrap_or_default();
700
701 let trace_api_version = if let Ok(Some(api_version)) =
703 api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
704 {
705 api_version
706 } else {
707 return Err(internal_err(
708 "Runtime api version call failed (trace)".to_string(),
709 ));
710 };
711
712 let reference_block = overrides.current_block(reference_hash);
713
714 if let Some(block) = reference_block {
716 let transactions = block.transactions;
717 if let Some(transaction) = transactions.get(index) {
718 let f = || -> RpcResult<_> {
719 let result = if trace_api_version >= 7 {
720 api.trace_transaction(parent_block_hash, exts, &transaction, &header)
722 } else if trace_api_version == 5 || trace_api_version == 6 {
723 let tx_v2 = match transaction {
725 ethereum::TransactionV3::Legacy(tx) => {
726 ethereum::TransactionV2::Legacy(tx.clone())
727 }
728 ethereum::TransactionV3::EIP2930(tx) => {
729 ethereum::TransactionV2::EIP2930(tx.clone())
730 }
731 ethereum::TransactionV3::EIP1559(tx) => {
732 ethereum::TransactionV2::EIP1559(tx.clone())
733 }
734 ethereum::TransactionV3::EIP7702(_) => return Err(internal_err(
735 "EIP-7702 transactions are supported starting from API version 7"
736 .to_string(),
737 )),
738 };
739
740 #[allow(deprecated)]
742 api.trace_transaction_before_version_7(
743 parent_block_hash,
744 exts,
745 &tx_v2,
746 &header,
747 )
748 } else {
749 let core_api_version = if let Ok(Some(api_version)) =
751 api.api_version::<dyn Core<B>>(parent_block_hash)
752 {
753 api_version
754 } else {
755 return Err(internal_err(
756 "Runtime api version call failed (core)".to_string(),
757 ));
758 };
759
760 if core_api_version >= 5 {
766 api.initialize_block(parent_block_hash, &header)
767 .map_err(|e| {
768 internal_err(format!("Runtime api access error: {:?}", e))
769 })?;
770 } else {
771 #[allow(deprecated)]
772 api.initialize_block_before_version_5(parent_block_hash, &header)
773 .map_err(|e| {
774 internal_err(format!("Runtime api access error: {:?}", e))
775 })?;
776 }
777
778 if trace_api_version == 4 {
779 let tx_v2 = match transaction {
781 ethereum::TransactionV3::Legacy(tx) => {
782 ethereum::TransactionV2::Legacy(tx.clone())
783 }
784 ethereum::TransactionV3::EIP2930(tx) => {
785 ethereum::TransactionV2::EIP2930(tx.clone())
786 }
787 ethereum::TransactionV3::EIP1559(tx) => {
788 ethereum::TransactionV2::EIP1559(tx.clone())
789 }
790 ethereum::TransactionV3::EIP7702(_) => {
791 return Err(internal_err(
792 "EIP-7702 transactions are supported starting from API version 7"
793 .to_string(),
794 ))
795 }
796 };
797
798 #[allow(deprecated)]
800 api.trace_transaction_before_version_5(parent_block_hash, exts, &tx_v2)
801 } else {
802 match transaction {
804 ethereum::TransactionV3::Legacy(tx) =>
805 {
806 #[allow(deprecated)]
807 api.trace_transaction_before_version_4(
808 parent_block_hash,
809 exts,
810 &tx,
811 )
812 }
813 _ => {
814 return Err(internal_err(
815 "Bug: pre-london runtime expects legacy transactions"
816 .to_string(),
817 ))
818 }
819 }
820 }
821 };
822
823 result
824 .map_err(|e| {
825 internal_err(format!(
826 "Runtime api access error (version {:?}): {:?}",
827 trace_api_version, e
828 ))
829 })?
830 .map_err(|e| internal_err(format!("DispatchError: {:?}", e)))?;
831
832 Ok(moonbeam_rpc_primitives_debug::Response::Single)
833 };
834
835 return match trace_type {
836 single::TraceType::Raw {
837 disable_storage,
838 disable_memory,
839 disable_stack,
840 } => {
841 let mut proxy = moonbeam_client_evm_tracing::listeners::Raw::new(
842 disable_storage,
843 disable_memory,
844 disable_stack,
845 raw_max_memory_usage,
846 );
847 proxy.using(f)?;
848 Ok(Response::Single(
849 moonbeam_client_evm_tracing::formatters::Raw::format(proxy).ok_or(
850 internal_err(
851 "replayed transaction generated too much data. \
852 try disabling memory or storage?",
853 ),
854 )?,
855 ))
856 }
857 single::TraceType::CallList => {
858 let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
859 proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
860 proxy.using(f)?;
861 proxy.finish_transaction();
862 let response = match tracer_input {
863 TracerInput::Blockscout => {
864 moonbeam_client_evm_tracing::formatters::Blockscout::format(proxy)
865 .ok_or("Trace result is empty.")
866 .map_err(|e| internal_err(format!("{:?}", e)))
867 }
868 TracerInput::CallTracer => {
869 let mut res =
870 moonbeam_client_evm_tracing::formatters::CallTracer::format(
871 proxy,
872 )
873 .ok_or("Trace result is empty.")
874 .map_err(|e| internal_err(format!("{:?}", e)))?;
875 Ok(res.pop().expect("Trace result is empty.").result)
876 }
877 _ => Err(internal_err(
878 "Bug: failed to resolve the tracer format.".to_string(),
879 )),
880 }?;
881 Ok(Response::Single(response))
882 }
883 not_supported => Err(internal_err(format!(
884 "Bug: `handle_transaction_request` does not support {:?}.",
885 not_supported
886 ))),
887 };
888 }
889 }
890 Err(internal_err("Runtime block call failed".to_string()))
891 }
892
893 fn handle_call_request(
894 client: Arc<C>,
895 frontier_backend: Arc<dyn fc_api::Backend<B> + Send + Sync>,
896 request_block_id: RequestBlockId,
897 call_params: TraceCallParams,
898 trace_params: Option<TraceParams>,
899 raw_max_memory_usage: usize,
900 ) -> RpcResult<Response> {
901 let (tracer_input, trace_type, tracer_config) = Self::handle_params(trace_params)?;
902
903 let reference_id: BlockId<B> = match request_block_id {
904 RequestBlockId::Number(n) => Ok(BlockId::Number(n.unique_saturated_into())),
905 RequestBlockId::Tag(RequestBlockTag::Latest) => {
906 Ok(BlockId::Number(client.info().best_number))
907 }
908 RequestBlockId::Tag(RequestBlockTag::Finalized) => {
909 Ok(BlockId::Hash(client.info().finalized_hash))
910 }
911 RequestBlockId::Tag(RequestBlockTag::Earliest) => {
912 Ok(BlockId::Number(0u32.unique_saturated_into()))
913 }
914 RequestBlockId::Tag(RequestBlockTag::Pending) => {
915 Err(internal_err("'pending' blocks are not supported"))
916 }
917 RequestBlockId::Hash(eth_hash) => {
918 match futures::executor::block_on(frontier_backend_client::load_hash::<B, C>(
919 client.as_ref(),
920 frontier_backend.as_ref(),
921 eth_hash,
922 )) {
923 Ok(Some(hash)) => Ok(BlockId::Hash(hash)),
924 Ok(_) => Err(internal_err("Block hash not found".to_string())),
925 Err(e) => Err(e),
926 }
927 }
928 }?;
929
930 let mut api = client.runtime_api();
932
933 api.record_proof();
935 api.proof_recorder().map(|recorder| {
936 let ext = sp_trie::proof_size_extension::ProofSizeExt::new(recorder);
937 api.register_extension(ext);
938 });
939
940 let Ok(hash) = client.expect_block_hash_from_id(&reference_id) else {
942 return Err(internal_err("Block header not found"));
943 };
944 let header = match client.header(hash) {
945 Ok(Some(h)) => h,
946 _ => return Err(internal_err("Block header not found")),
947 };
948 let parent_block_hash = *header.parent_hash();
950
951 let trace_api_version = if let Ok(Some(api_version)) =
953 api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
954 {
955 api_version
956 } else {
957 return Err(internal_err(
958 "Runtime api version call failed (trace)".to_string(),
959 ));
960 };
961
962 if trace_api_version <= 5 {
963 return Err(internal_err(
964 "debug_traceCall not supported with old runtimes".to_string(),
965 ));
966 }
967
968 let TraceCallParams {
969 from,
970 to,
971 gas_price,
972 max_fee_per_gas,
973 max_priority_fee_per_gas,
974 gas,
975 value,
976 data,
977 nonce,
978 access_list,
979 authorization_list,
980 ..
981 } = call_params;
982
983 let (max_fee_per_gas, max_priority_fee_per_gas) =
984 match (gas_price, max_fee_per_gas, max_priority_fee_per_gas) {
985 (gas_price, None, None) => {
986 let gas_price = if gas_price.unwrap_or_default().is_zero() {
989 None
990 } else {
991 gas_price
992 };
993 (gas_price, gas_price)
994 }
995 (_, max_fee, max_priority) => {
996 let max_fee = if max_fee.unwrap_or_default().is_zero() {
999 None
1000 } else {
1001 max_fee
1002 };
1003 if let Some(max_priority) = max_priority {
1005 let max_fee = max_fee.unwrap_or_default();
1006 if max_priority > max_fee {
1007 return Err(internal_err(
1008 "Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`",
1009 ));
1010 }
1011 }
1012 (max_fee, max_priority)
1013 }
1014 };
1015
1016 let gas_limit = match gas {
1017 Some(amount) => amount,
1018 None => {
1019 if let Some(block) = api
1020 .current_block(parent_block_hash)
1021 .map_err(|err| internal_err(format!("runtime error: {:?}", err)))?
1022 {
1023 block.header.gas_limit
1024 } else {
1025 return Err(internal_err(
1026 "block unavailable, cannot query gas limit".to_string(),
1027 ));
1028 }
1029 }
1030 };
1031 let data = data.map(|d| d.0).unwrap_or_default();
1032
1033 let access_list = access_list.unwrap_or_default();
1034
1035 let f = || -> RpcResult<_> {
1036 let _result = api
1037 .trace_call(
1038 parent_block_hash,
1039 &header,
1040 from.unwrap_or_default(),
1041 to,
1042 data,
1043 value.unwrap_or_default(),
1044 gas_limit,
1045 max_fee_per_gas,
1046 max_priority_fee_per_gas,
1047 nonce,
1048 Some(
1049 access_list
1050 .into_iter()
1051 .map(|item| (item.address, item.storage_keys))
1052 .collect(),
1053 ),
1054 authorization_list,
1055 )
1056 .map_err(|e| internal_err(format!("Runtime api access error: {:?}", e)))?
1057 .map_err(|e| internal_err(format!("DispatchError: {:?}", e)))?;
1058
1059 Ok(moonbeam_rpc_primitives_debug::Response::Single)
1060 };
1061
1062 return match trace_type {
1063 single::TraceType::Raw {
1064 disable_storage,
1065 disable_memory,
1066 disable_stack,
1067 } => {
1068 let mut proxy = moonbeam_client_evm_tracing::listeners::Raw::new(
1069 disable_storage,
1070 disable_memory,
1071 disable_stack,
1072 raw_max_memory_usage,
1073 );
1074 proxy.using(f)?;
1075 Ok(Response::Single(
1076 moonbeam_client_evm_tracing::formatters::Raw::format(proxy).ok_or(
1077 internal_err(
1078 "replayed transaction generated too much data. \
1079 try disabling memory or storage?",
1080 ),
1081 )?,
1082 ))
1083 }
1084 single::TraceType::CallList => {
1085 let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
1086 proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
1087 proxy.using(f)?;
1088 proxy.finish_transaction();
1089 let response = match tracer_input {
1090 TracerInput::Blockscout => {
1091 moonbeam_client_evm_tracing::formatters::Blockscout::format(proxy)
1092 .ok_or("Trace result is empty.")
1093 .map_err(|e| internal_err(format!("{:?}", e)))
1094 }
1095 TracerInput::CallTracer => {
1096 let mut res =
1097 moonbeam_client_evm_tracing::formatters::CallTracer::format(proxy)
1098 .ok_or("Trace result is empty.")
1099 .map_err(|e| internal_err(format!("{:?}", e)))?;
1100 Ok(res.pop().expect("Trace result is empty.").result)
1101 }
1102 _ => Err(internal_err(
1103 "Bug: failed to resolve the tracer format.".to_string(),
1104 )),
1105 }?;
1106 Ok(Response::Single(response))
1107 }
1108 not_supported => Err(internal_err(format!(
1109 "Bug: `handle_call_request` does not support {:?}.",
1110 not_supported
1111 ))),
1112 };
1113 }
1114}