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 mut api = client.runtime_api();
406
407		// Enable proof recording
408		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		// Get Blockchain backend
415		let blockchain = backend.blockchain();
416		// Get the header I want to work with.
417		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		// Get parent blockid.
426		let parent_block_hash = *header.parent_hash();
427
428		let statuses = overrides
429			.current_transaction_statuses(hash)
430			.unwrap_or_default();
431
432		// Partial ethereum transaction data to check if a trace match an ethereum transaction
433		struct EthTxPartial {
434			transaction_hash: H256,
435			from: H160,
436			to: Option<H160>,
437		}
438
439		// Known ethereum transaction hashes.
440		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 there are no ethereum transactions in the block return empty trace right away.
460		if eth_tx_hashes.is_empty() {
461			return Ok(Response::Block(vec![]));
462		}
463
464		// Get block extrinsics.
465		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		// Get DebugRuntimeApi version
471		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		// Trace the block.
482		let f = || -> RpcResult<_> {
483			let result = if trace_api_version >= 5 {
484				// The block is initialized inside "trace_block"
485				api.trace_block(parent_block_hash, exts, eth_tx_hashes, &header)
486			} else {
487				// Get core runtime api version
488				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				// Initialize block: calls the "on_initialize" hook on every pallet
499				// in AllPalletsWithSystem
500				// This was fine before pallet-message-queue because the XCM messages
501				// were processed by the "setValidationData" inherent call and not on an
502				// "on_initialize" hook, which runs before enabling XCM tracing
503				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		// Offset to account for old buggy transactions that are in trace not in the ethereum block
534		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										// verify that the trace matches the ethereum transaction
558										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											// if the trace does not match the ethereum transaction
590											// it means that the trace is about a buggy transaction that is not in the block
591											// we need to offset the tx_position
592											tx_position_offset += 1;
593											None
594										}
595									} else {
596										// If the transaction is not in the ethereum block
597										// it should not appear in the block trace
598										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	/// Replays a transaction in the Runtime at a given block height.
633	///
634	/// In order to successfully reproduce the result of the original transaction we need a correct
635	/// state to replay over.
636	///
637	/// Substrate allows to apply extrinsics in the Runtime and thus creating an overlaid state.
638	/// These overlaid changes will live in-memory for the lifetime of the ApiRef.
639	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		// Get ApiRef. This handle allow to keep changes between txs in an internal buffer.
673		let mut api = client.runtime_api();
674
675		// Enable proof recording
676		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		// Get Blockchain backend
683		let blockchain = backend.blockchain();
684		// Get the header I want to work with.
685		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		// Get parent blockid.
693		let parent_block_hash = *header.parent_hash();
694
695		// Get block extrinsics.
696		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		// Get DebugRuntimeApi version
702		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		// Get the actual ethereum transaction.
715		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						// The block is initialized inside "trace_transaction"
721						api.trace_transaction(parent_block_hash, exts, &transaction, &header)
722					} else if trace_api_version == 5 || trace_api_version == 6 {
723						// API version 5 and 6 expect TransactionV2, so we need to convert from TransactionV3
724						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						// The block is initialized inside "trace_transaction"
741						#[allow(deprecated)]
742						api.trace_transaction_before_version_7(
743							parent_block_hash,
744							exts,
745							&tx_v2,
746							&header,
747						)
748					} else {
749						// Get core runtime api version
750						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						// Initialize block: calls the "on_initialize" hook on every pallet
761						// in AllPalletsWithSystem
762						// This was fine before pallet-message-queue because the XCM messages
763						// were processed by the "setValidationData" inherent call and not on an
764						// "on_initialize" hook, which runs before enabling XCM tracing
765						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							// API version 4 expect TransactionV2, so we need to convert from TransactionV3
780							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							// Pre pallet-message-queue
799							#[allow(deprecated)]
800							api.trace_transaction_before_version_5(parent_block_hash, exts, &tx_v2)
801						} else {
802							// Pre-london update, legacy transactions.
803							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		// Get ApiRef. This handle allow to keep changes between txs in an internal buffer.
931		let mut api = client.runtime_api();
932
933		// Enable proof recording
934		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		// Get the header I want to work with.
941		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		// Get parent blockid.
949		let parent_block_hash = *header.parent_hash();
950
951		// Get DebugRuntimeApi version
952		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					// Legacy request, all default to gas price.
987					// A zero-set gas price is None.
988					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					// eip-1559
997					// A zero-set max fee is None.
998					let max_fee = if max_fee.unwrap_or_default().is_zero() {
999						None
1000					} else {
1001						max_fee
1002					};
1003					// Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`.
1004					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}