moonbeam_rpc_debug/
lib.rs

1// Copyright 2019-2025 PureStake Inc.
2// This file is part of Moonbeam.
3
4// Moonbeam is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Moonbeam is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16use 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    /// Handler for `debug_traceTransaction` request. Communicates with the service-defined task
80    /// using channels.
81    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        // Send a message from the rpc handler to the service level task.
90        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        // Receive a message from the service level task and send the rpc response.
100        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        // Send a message from the rpc handler to the service level task.
117        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        // Receive a message from the service level task and send the rpc response.
127        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    /// Handler for `debug_traceCall` request. Communicates with the service-defined task
136    /// using channels.
137    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        // Send a message from the rpc handler to the service level task.
147        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        // Receive a message from the service level task and send the rpc response.
157        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    /// Task spawned at service level that listens for messages on the rpc channel and spawns
183    /// blocking tasks using a permit pool.
184    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        // Set trace input and type
318        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        // Get ApiRef. This handle allows to keep changes between txs in an internal buffer.
405        let api = client.runtime_api();
406        // Get Blockchain backend
407        let blockchain = backend.blockchain();
408        // Get the header I want to work with.
409        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        // Get parent blockid.
418        let parent_block_hash = *header.parent_hash();
419
420        let statuses = overrides
421            .current_transaction_statuses(hash)
422            .unwrap_or_default();
423
424        // Partial ethereum transaction data to check if a trace match an ethereum transaction
425        struct EthTxPartial {
426            transaction_hash: H256,
427            from: H160,
428            to: Option<H160>,
429        }
430
431        // Known ethereum transaction hashes.
432        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 there are no ethereum transactions in the block return empty trace right away.
452        if eth_tx_hashes.is_empty() {
453            return Ok(Response::Block(vec![]));
454        }
455
456        // Get block extrinsics.
457        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        // Get DebugRuntimeApi version
463        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        // Trace the block.
474        let f = || -> RpcResult<_> {
475            let result = if trace_api_version >= 5 {
476                // The block is initialized inside "trace_block"
477                api.trace_block(parent_block_hash, exts, eth_tx_hashes, &header)
478            } else {
479                // Get core runtime api version
480                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                // Initialize block: calls the "on_initialize" hook on every pallet
491                // in AllPalletsWithSystem
492                // This was fine before pallet-message-queue because the XCM messages
493                // were processed by the "setValidationData" inherent call and not on an
494                // "on_initialize" hook, which runs before enabling XCM tracing
495                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        // Offset to account for old buggy transactions that are in trace not in the ethereum block
526        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                                        // verify that the trace matches the ethereum transaction
550                                        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                                            // if the trace does not match the ethereum transaction
582                                            // it means that the trace is about a buggy transaction that is not in the block
583                                            // we need to offset the tx_position
584                                            tx_position_offset += 1;
585                                            None
586                                        }
587                                    } else {
588                                        // If the transaction is not in the ethereum block
589                                        // it should not appear in the block trace
590                                        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    /// Replays a transaction in the Runtime at a given block height.
625    ///
626    /// In order to successfully reproduce the result of the original transaction we need a correct
627    /// state to replay over.
628    ///
629    /// Substrate allows to apply extrinsics in the Runtime and thus creating an overlaid state.
630    /// These overlaid changes will live in-memory for the lifetime of the ApiRef.
631    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        // Get ApiRef. This handle allow to keep changes between txs in an internal buffer.
665        let api = client.runtime_api();
666        // Get Blockchain backend
667        let blockchain = backend.blockchain();
668        // Get the header I want to work with.
669        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        // Get parent blockid.
677        let parent_block_hash = *header.parent_hash();
678
679        // Get block extrinsics.
680        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        // Get DebugRuntimeApi version
686        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        // Get the actual ethereum transaction.
699        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                        // The block is initialized inside "trace_transaction"
705                        api.trace_transaction(parent_block_hash, exts, &transaction, &header)
706                    } else if trace_api_version == 5 || trace_api_version == 6 {
707                        // API version 5 and 6 expect TransactionV2, so we need to convert from TransactionV3
708                        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                        // The block is initialized inside "trace_transaction"
726                        #[allow(deprecated)]
727                        api.trace_transaction_before_version_7(
728                            parent_block_hash,
729                            exts,
730                            &tx_v2,
731                            &header,
732                        )
733                    } else {
734                        // Get core runtime api version
735                        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                        // Initialize block: calls the "on_initialize" hook on every pallet
746                        // in AllPalletsWithSystem
747                        // This was fine before pallet-message-queue because the XCM messages
748                        // were processed by the "setValidationData" inherent call and not on an
749                        // "on_initialize" hook, which runs before enabling XCM tracing
750                        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                            // API version 4 expect TransactionV2, so we need to convert from TransactionV3
765                            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                            // Pre pallet-message-queue
783                            #[allow(deprecated)]
784                            api.trace_transaction_before_version_5(parent_block_hash, exts, &tx_v2)
785                        } else {
786                            // Pre-london update, legacy transactions.
787                            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        // Get ApiRef. This handle allow to keep changes between txs in an internal buffer.
915        let api = client.runtime_api();
916        // Get the header I want to work with.
917        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        // Get parent blockid.
925        let parent_block_hash = *header.parent_hash();
926
927        // Get DebugRuntimeApi version
928        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                    // Legacy request, all default to gas price.
963                    // A zero-set gas price is None.
964                    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                    // eip-1559
973                    // A zero-set max fee is None.
974                    let max_fee = if max_fee.unwrap_or_default().is_zero() {
975                        None
976                    } else {
977                        max_fee
978                    };
979                    // Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`.
980                    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}