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/>.
16
use futures::StreamExt;
17
use jsonrpsee::core::{async_trait, RpcResult};
18
pub use moonbeam_rpc_core_debug::{DebugServer, TraceCallParams, TraceParams};
19

            
20
use tokio::{
21
	self,
22
	sync::{oneshot, Semaphore},
23
};
24

            
25
use ethereum_types::H256;
26
use fc_rpc::{frontier_backend_client, internal_err};
27
use fc_storage::StorageOverride;
28
use fp_rpc::EthereumRuntimeRPCApi;
29
use moonbeam_client_evm_tracing::types::block;
30
use moonbeam_client_evm_tracing::types::block::BlockTransactionTrace;
31
use moonbeam_client_evm_tracing::{formatters::ResponseFormatter, types::single};
32
use moonbeam_rpc_core_types::{RequestBlockId, RequestBlockTag};
33
use moonbeam_rpc_primitives_debug::{DebugRuntimeApi, TracerInput};
34
use sc_client_api::backend::{Backend, StateBackend, StorageProvider};
35
use sc_utils::mpsc::TracingUnboundedSender;
36
use sp_api::{ApiExt, Core, ProvideRuntimeApi};
37
use sp_block_builder::BlockBuilder;
38
use sp_blockchain::{
39
	Backend as BlockchainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata,
40
};
41
use sp_runtime::{
42
	generic::BlockId,
43
	traits::{BlakeTwo256, Block as BlockT, Header as HeaderT, UniqueSaturatedInto},
44
};
45
use std::collections::BTreeMap;
46
use std::{future::Future, marker::PhantomData, sync::Arc};
47

            
48
pub enum RequesterInput {
49
	Call((RequestBlockId, TraceCallParams)),
50
	Transaction(H256),
51
	Block(RequestBlockId),
52
}
53

            
54
pub enum Response {
55
	Single(single::TransactionTrace),
56
	Block(Vec<block::BlockTransactionTrace>),
57
}
58

            
59
pub type Responder = oneshot::Sender<RpcResult<Response>>;
60
pub type DebugRequester =
61
	TracingUnboundedSender<((RequesterInput, Option<TraceParams>), Responder)>;
62

            
63
pub struct Debug {
64
	pub requester: DebugRequester,
65
}
66

            
67
impl Debug {
68
	pub fn new(requester: DebugRequester) -> Self {
69
		Self { requester }
70
	}
71
}
72

            
73
#[async_trait]
74
impl DebugServer for Debug {
75
	/// Handler for `debug_traceTransaction` request. Communicates with the service-defined task
76
	/// using channels.
77
	async fn trace_transaction(
78
		&self,
79
		transaction_hash: H256,
80
		params: Option<TraceParams>,
81
	) -> RpcResult<single::TransactionTrace> {
82
		let requester = self.requester.clone();
83

            
84
		let (tx, rx) = oneshot::channel();
85
		// Send a message from the rpc handler to the service level task.
86
		requester
87
			.unbounded_send(((RequesterInput::Transaction(transaction_hash), params), tx))
88
			.map_err(|err| {
89
				internal_err(format!(
90
					"failed to send request to debug service : {:?}",
91
					err
92
				))
93
			})?;
94

            
95
		// Receive a message from the service level task and send the rpc response.
96
		rx.await
97
			.map_err(|err| internal_err(format!("debug service dropped the channel : {:?}", err)))?
98
			.map(|res| match res {
99
				Response::Single(res) => res,
100
				_ => unreachable!(),
101
			})
102
	}
103

            
104
	async fn trace_block(
105
		&self,
106
		id: RequestBlockId,
107
		params: Option<TraceParams>,
108
	) -> RpcResult<Vec<BlockTransactionTrace>> {
109
		let requester = self.requester.clone();
110

            
111
		let (tx, rx) = oneshot::channel();
112
		// Send a message from the rpc handler to the service level task.
113
		requester
114
			.unbounded_send(((RequesterInput::Block(id), params), tx))
115
			.map_err(|err| {
116
				internal_err(format!(
117
					"failed to send request to debug service : {:?}",
118
					err
119
				))
120
			})?;
121

            
122
		// Receive a message from the service level task and send the rpc response.
123
		rx.await
124
			.map_err(|err| internal_err(format!("debug service dropped the channel : {:?}", err)))?
125
			.map(|res| match res {
126
				Response::Block(res) => res,
127
				_ => unreachable!(),
128
			})
129
	}
130

            
131
	/// Handler for `debug_traceCall` request. Communicates with the service-defined task
132
	/// using channels.
133
	async fn trace_call(
134
		&self,
135
		call_params: TraceCallParams,
136
		id: RequestBlockId,
137
		params: Option<TraceParams>,
138
	) -> RpcResult<single::TransactionTrace> {
139
		let requester = self.requester.clone();
140

            
141
		let (tx, rx) = oneshot::channel();
142
		// Send a message from the rpc handler to the service level task.
143
		requester
144
			.unbounded_send(((RequesterInput::Call((id, call_params)), params), tx))
145
			.map_err(|err| {
146
				internal_err(format!(
147
					"failed to send request to debug service : {:?}",
148
					err
149
				))
150
			})?;
151

            
152
		// Receive a message from the service level task and send the rpc response.
153
		rx.await
154
			.map_err(|err| internal_err(format!("debug service dropped the channel : {:?}", err)))?
155
			.map(|res| match res {
156
				Response::Single(res) => res,
157
				_ => unreachable!(),
158
			})
159
	}
160
}
161

            
162
pub struct DebugHandler<B: BlockT, C, BE>(PhantomData<(B, C, BE)>);
163

            
164
impl<B, C, BE> DebugHandler<B, C, BE>
165
where
166
	BE: Backend<B> + 'static,
167
	BE::State: StateBackend<BlakeTwo256>,
168
	C: ProvideRuntimeApi<B>,
169
	C: StorageProvider<B, BE>,
170
	C: HeaderMetadata<B, Error = BlockChainError> + HeaderBackend<B>,
171
	C: Send + Sync + 'static,
172
	B: BlockT<Hash = H256> + Send + Sync + 'static,
173
	C::Api: BlockBuilder<B>,
174
	C::Api: DebugRuntimeApi<B>,
175
	C::Api: EthereumRuntimeRPCApi<B>,
176
	C::Api: ApiExt<B>,
177
{
178
	/// Task spawned at service level that listens for messages on the rpc channel and spawns
179
	/// blocking tasks using a permit pool.
180
	pub fn task(
181
		client: Arc<C>,
182
		backend: Arc<BE>,
183
		frontier_backend: Arc<dyn fc_api::Backend<B> + Send + Sync>,
184
		permit_pool: Arc<Semaphore>,
185
		overrides: Arc<dyn StorageOverride<B>>,
186
		raw_max_memory_usage: usize,
187
	) -> (impl Future<Output = ()>, DebugRequester) {
188
		let (tx, mut rx): (DebugRequester, _) =
189
			sc_utils::mpsc::tracing_unbounded("debug-requester", 100_000);
190

            
191
		let fut = async move {
192
			loop {
193
				match rx.next().await {
194
					Some((
195
						(RequesterInput::Transaction(transaction_hash), params),
196
						response_tx,
197
					)) => {
198
						let client = client.clone();
199
						let backend = backend.clone();
200
						let frontier_backend = frontier_backend.clone();
201
						let permit_pool = permit_pool.clone();
202
						let overrides = overrides.clone();
203

            
204
						tokio::task::spawn(async move {
205
							let _ = response_tx.send(
206
								async {
207
									let _permit = permit_pool.acquire().await;
208
									tokio::task::spawn_blocking(move || {
209
										Self::handle_transaction_request(
210
											client.clone(),
211
											backend.clone(),
212
											frontier_backend.clone(),
213
											transaction_hash,
214
											params,
215
											overrides.clone(),
216
											raw_max_memory_usage,
217
										)
218
									})
219
									.await
220
									.map_err(|e| {
221
										internal_err(format!(
222
											"Internal error on spawned task : {:?}",
223
											e
224
										))
225
									})?
226
								}
227
								.await,
228
							);
229
						});
230
					}
231
					Some((
232
						(RequesterInput::Call((request_block_id, call_params)), params),
233
						response_tx,
234
					)) => {
235
						let client = client.clone();
236
						let frontier_backend = frontier_backend.clone();
237
						let permit_pool = permit_pool.clone();
238

            
239
						tokio::task::spawn(async move {
240
							let _ = response_tx.send(
241
								async {
242
									let _permit = permit_pool.acquire().await;
243
									tokio::task::spawn_blocking(move || {
244
										Self::handle_call_request(
245
											client.clone(),
246
											frontier_backend.clone(),
247
											request_block_id,
248
											call_params,
249
											params,
250
											raw_max_memory_usage,
251
										)
252
									})
253
									.await
254
									.map_err(|e| {
255
										internal_err(format!(
256
											"Internal error on spawned task : {:?}",
257
											e
258
										))
259
									})?
260
								}
261
								.await,
262
							);
263
						});
264
					}
265
					Some(((RequesterInput::Block(request_block_id), params), response_tx)) => {
266
						let client = client.clone();
267
						let backend = backend.clone();
268
						let frontier_backend = frontier_backend.clone();
269
						let permit_pool = permit_pool.clone();
270
						let overrides = overrides.clone();
271

            
272
						tokio::task::spawn(async move {
273
							let _ = response_tx.send(
274
								async {
275
									let _permit = permit_pool.acquire().await;
276

            
277
									tokio::task::spawn_blocking(move || {
278
										Self::handle_block_request(
279
											client.clone(),
280
											backend.clone(),
281
											frontier_backend.clone(),
282
											request_block_id,
283
											params,
284
											overrides.clone(),
285
										)
286
									})
287
									.await
288
									.map_err(|e| {
289
										internal_err(format!(
290
											"Internal error on spawned task : {:?}",
291
											e
292
										))
293
									})?
294
								}
295
								.await,
296
							);
297
						});
298
					}
299
					_ => {}
300
				}
301
			}
302
		};
303
		(fut, tx)
304
	}
305

            
306
	fn handle_params(
307
		params: Option<TraceParams>,
308
	) -> RpcResult<(
309
		TracerInput,
310
		single::TraceType,
311
		Option<single::TraceCallConfig>,
312
	)> {
313
		// Set trace input and type
314
		match params {
315
			Some(TraceParams {
316
				tracer: Some(tracer),
317
				tracer_config,
318
				..
319
			}) => {
320
				const BLOCKSCOUT_JS_CODE_HASH: [u8; 16] =
321
					hex_literal::hex!("94d9f08796f91eb13a2e82a6066882f7");
322
				const BLOCKSCOUT_JS_CODE_HASH_V2: [u8; 16] =
323
					hex_literal::hex!("89db13694675692951673a1e6e18ff02");
324
				let hash = sp_io::hashing::twox_128(&tracer.as_bytes());
325
				let tracer =
326
					if hash == BLOCKSCOUT_JS_CODE_HASH || hash == BLOCKSCOUT_JS_CODE_HASH_V2 {
327
						Some(TracerInput::Blockscout)
328
					} else if tracer == "callTracer" {
329
						Some(TracerInput::CallTracer)
330
					} else {
331
						None
332
					};
333
				if let Some(tracer) = tracer {
334
					Ok((tracer, single::TraceType::CallList, tracer_config))
335
				} else {
336
					return Err(internal_err(format!(
337
						"javascript based tracing is not available (hash :{:?})",
338
						hash
339
					)));
340
				}
341
			}
342
			Some(params) => Ok((
343
				TracerInput::None,
344
				single::TraceType::Raw {
345
					disable_storage: params.disable_storage.unwrap_or(false),
346
					disable_memory: params.disable_memory.unwrap_or(false),
347
					disable_stack: params.disable_stack.unwrap_or(false),
348
				},
349
				params.tracer_config,
350
			)),
351
			_ => Ok((
352
				TracerInput::None,
353
				single::TraceType::Raw {
354
					disable_storage: false,
355
					disable_memory: false,
356
					disable_stack: false,
357
				},
358
				None,
359
			)),
360
		}
361
	}
362

            
363
	fn handle_block_request(
364
		client: Arc<C>,
365
		backend: Arc<BE>,
366
		frontier_backend: Arc<dyn fc_api::Backend<B> + Send + Sync>,
367
		request_block_id: RequestBlockId,
368
		params: Option<TraceParams>,
369
		overrides: Arc<dyn StorageOverride<B>>,
370
	) -> RpcResult<Response> {
371
		let (tracer_input, trace_type, tracer_config) = Self::handle_params(params)?;
372

            
373
		let reference_id: BlockId<B> = match request_block_id {
374
			RequestBlockId::Number(n) => Ok(BlockId::Number(n.unique_saturated_into())),
375
			RequestBlockId::Tag(RequestBlockTag::Latest) => {
376
				Ok(BlockId::Number(client.info().best_number))
377
			}
378
			RequestBlockId::Tag(RequestBlockTag::Earliest) => {
379
				Ok(BlockId::Number(0u32.unique_saturated_into()))
380
			}
381
			RequestBlockId::Tag(RequestBlockTag::Pending) => {
382
				Err(internal_err("'pending' blocks are not supported"))
383
			}
384
			RequestBlockId::Hash(eth_hash) => {
385
				match futures::executor::block_on(frontier_backend_client::load_hash::<B, C>(
386
					client.as_ref(),
387
					frontier_backend.as_ref(),
388
					eth_hash,
389
				)) {
390
					Ok(Some(hash)) => Ok(BlockId::Hash(hash)),
391
					Ok(_) => Err(internal_err("Block hash not found".to_string())),
392
					Err(e) => Err(e),
393
				}
394
			}
395
		}?;
396

            
397
		// Get ApiRef. This handle allows to keep changes between txs in an internal buffer.
398
		let mut api = client.runtime_api();
399

            
400
		// Enable proof recording
401
		api.record_proof();
402
		api.proof_recorder().map(|recorder| {
403
			let ext = sp_trie::proof_size_extension::ProofSizeExt::new(recorder);
404
			api.register_extension(ext);
405
		});
406

            
407
		// Get Blockchain backend
408
		let blockchain = backend.blockchain();
409
		// Get the header I want to work with.
410
		let Ok(hash) = client.expect_block_hash_from_id(&reference_id) else {
411
			return Err(internal_err("Block header not found"));
412
		};
413
		let header = match client.header(hash) {
414
			Ok(Some(h)) => h,
415
			_ => return Err(internal_err("Block header not found")),
416
		};
417

            
418
		// Get parent blockid.
419
		let parent_block_hash = *header.parent_hash();
420

            
421
		let statuses = overrides
422
			.current_transaction_statuses(hash)
423
			.unwrap_or_default();
424

            
425
		// Known ethereum transaction hashes.
426
		let eth_transactions_by_index: BTreeMap<u32, H256> = statuses
427
			.iter()
428
			.map(|t| (t.transaction_index, t.transaction_hash))
429
			.collect();
430

            
431
		let eth_tx_hashes: Vec<_> = eth_transactions_by_index.values().cloned().collect();
432

            
433
		// If there are no ethereum transactions in the block return empty trace right away.
434
		if eth_tx_hashes.is_empty() {
435
			return Ok(Response::Block(vec![]));
436
		}
437

            
438
		// Get block extrinsics.
439
		let exts = blockchain
440
			.body(hash)
441
			.map_err(|e| internal_err(format!("Fail to read blockchain db: {:?}", e)))?
442
			.unwrap_or_default();
443

            
444
		// Get DebugRuntimeApi version
445
		let trace_api_version = if let Ok(Some(api_version)) =
446
			api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
447
		{
448
			api_version
449
		} else {
450
			return Err(internal_err(
451
				"Runtime api version call failed (trace)".to_string(),
452
			));
453
		};
454

            
455
		// Trace the block.
456
		let f = || -> RpcResult<_> {
457
			let result = if trace_api_version >= 5 {
458
				// The block is initialized inside "trace_block"
459
				api.trace_block(parent_block_hash, exts, eth_tx_hashes, &header)
460
			} else {
461
				// Get core runtime api version
462
				let core_api_version = if let Ok(Some(api_version)) =
463
					api.api_version::<dyn Core<B>>(parent_block_hash)
464
				{
465
					api_version
466
				} else {
467
					return Err(internal_err(
468
						"Runtime api version call failed (core)".to_string(),
469
					));
470
				};
471

            
472
				// Initialize block: calls the "on_initialize" hook on every pallet
473
				// in AllPalletsWithSystem
474
				// This was fine before pallet-message-queue because the XCM messages
475
				// were processed by the "setValidationData" inherent call and not on an
476
				// "on_initialize" hook, which runs before enabling XCM tracing
477
				if core_api_version >= 5 {
478
					api.initialize_block(parent_block_hash, &header)
479
						.map_err(|e| internal_err(format!("Runtime api access error: {:?}", e)))?;
480
				} else {
481
					#[allow(deprecated)]
482
					api.initialize_block_before_version_5(parent_block_hash, &header)
483
						.map_err(|e| internal_err(format!("Runtime api access error: {:?}", e)))?;
484
				}
485

            
486
				#[allow(deprecated)]
487
				api.trace_block_before_version_5(parent_block_hash, exts, eth_tx_hashes)
488
			};
489

            
490
			result
491
				.map_err(|e| {
492
					internal_err(format!(
493
						"Blockchain error when replaying block {} : {:?}",
494
						reference_id, e
495
					))
496
				})?
497
				.map_err(|e| {
498
					internal_err(format!(
499
						"Internal runtime error when replaying block {} : {:?}",
500
						reference_id, e
501
					))
502
				})?;
503

            
504
			Ok(moonbeam_rpc_primitives_debug::Response::Block)
505
		};
506

            
507
		return match trace_type {
508
			single::TraceType::CallList => {
509
				let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
510
				proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
511
				proxy.using(f)?;
512
				proxy.finish_transaction();
513
				let response = match tracer_input {
514
					TracerInput::CallTracer => {
515
						let result =
516
							moonbeam_client_evm_tracing::formatters::CallTracer::format(proxy)
517
								.ok_or("Trace result is empty.")
518
								.map_err(|e| internal_err(format!("{:?}", e)))?
519
								.into_iter()
520
								.map(|mut trace| {
521
									if let Some(transaction_hash) =
522
										eth_transactions_by_index.get(&trace.tx_position)
523
									{
524
										trace.tx_hash = *transaction_hash;
525
									}
526
									trace
527
								})
528
								.collect::<Vec<BlockTransactionTrace>>();
529

            
530
						let n_txs = eth_transactions_by_index.len();
531
						let n_traces = result.len();
532
						if n_txs != n_traces {
533
							log::warn!(
534
								"The traces in block {:?} don't match with the number of ethereum transactions. (txs: {}, traces: {})",
535
								request_block_id,
536
								n_txs,
537
								n_traces
538
							);
539
						}
540

            
541
						Ok(result)
542
					}
543
					_ => Err(internal_err(
544
						"Bug: failed to resolve the tracer format.".to_string(),
545
					)),
546
				}?;
547

            
548
				Ok(Response::Block(response))
549
			}
550
			_ => Err(internal_err(
551
				"debug_traceBlock functions currently only support callList mode (enabled
552
				by providing `{{'tracer': 'callTracer'}}` in the request)."
553
					.to_string(),
554
			)),
555
		};
556
	}
557

            
558
	/// Replays a transaction in the Runtime at a given block height.
559
	///
560
	/// In order to successfully reproduce the result of the original transaction we need a correct
561
	/// state to replay over.
562
	///
563
	/// Substrate allows to apply extrinsics in the Runtime and thus creating an overlayed state.
564
	/// These overlayed changes will live in-memory for the lifetime of the ApiRef.
565
	fn handle_transaction_request(
566
		client: Arc<C>,
567
		backend: Arc<BE>,
568
		frontier_backend: Arc<dyn fc_api::Backend<B> + Send + Sync>,
569
		transaction_hash: H256,
570
		params: Option<TraceParams>,
571
		overrides: Arc<dyn StorageOverride<B>>,
572
		raw_max_memory_usage: usize,
573
	) -> RpcResult<Response> {
574
		let (tracer_input, trace_type, tracer_config) = Self::handle_params(params)?;
575

            
576
		let (hash, index) =
577
			match futures::executor::block_on(frontier_backend_client::load_transactions::<B, C>(
578
				client.as_ref(),
579
				frontier_backend.as_ref(),
580
				transaction_hash,
581
				false,
582
			)) {
583
				Ok(Some((hash, index))) => (hash, index as usize),
584
				Ok(None) => return Err(internal_err("Transaction hash not found".to_string())),
585
				Err(e) => return Err(e),
586
			};
587

            
588
		let reference_id =
589
			match futures::executor::block_on(frontier_backend_client::load_hash::<B, C>(
590
				client.as_ref(),
591
				frontier_backend.as_ref(),
592
				hash,
593
			)) {
594
				Ok(Some(hash)) => BlockId::Hash(hash),
595
				Ok(_) => return Err(internal_err("Block hash not found".to_string())),
596
				Err(e) => return Err(e),
597
			};
598
		// Get ApiRef. This handle allow to keep changes between txs in an internal buffer.
599
		let mut api = client.runtime_api();
600

            
601
		// Enable proof recording
602
		api.record_proof();
603
		api.proof_recorder().map(|recorder| {
604
			let ext = sp_trie::proof_size_extension::ProofSizeExt::new(recorder);
605
			api.register_extension(ext);
606
		});
607

            
608
		// Get Blockchain backend
609
		let blockchain = backend.blockchain();
610
		// Get the header I want to work with.
611
		let Ok(reference_hash) = client.expect_block_hash_from_id(&reference_id) else {
612
			return Err(internal_err("Block header not found"));
613
		};
614
		let header = match client.header(reference_hash) {
615
			Ok(Some(h)) => h,
616
			_ => return Err(internal_err("Block header not found")),
617
		};
618
		// Get parent blockid.
619
		let parent_block_hash = *header.parent_hash();
620

            
621
		// Get block extrinsics.
622
		let exts = blockchain
623
			.body(reference_hash)
624
			.map_err(|e| internal_err(format!("Fail to read blockchain db: {:?}", e)))?
625
			.unwrap_or_default();
626

            
627
		// Get DebugRuntimeApi version
628
		let trace_api_version = if let Ok(Some(api_version)) =
629
			api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
630
		{
631
			api_version
632
		} else {
633
			return Err(internal_err(
634
				"Runtime api version call failed (trace)".to_string(),
635
			));
636
		};
637

            
638
		let reference_block = overrides.current_block(reference_hash);
639

            
640
		// Get the actual ethereum transaction.
641
		if let Some(block) = reference_block {
642
			let transactions = block.transactions;
643
			if let Some(transaction) = transactions.get(index) {
644
				let f = || -> RpcResult<_> {
645
					let result = if trace_api_version >= 5 {
646
						// The block is initialized inside "trace_transaction"
647
						api.trace_transaction(parent_block_hash, exts, &transaction, &header)
648
					} else {
649
						// Get core runtime api version
650
						let core_api_version = if let Ok(Some(api_version)) =
651
							api.api_version::<dyn Core<B>>(parent_block_hash)
652
						{
653
							api_version
654
						} else {
655
							return Err(internal_err(
656
								"Runtime api version call failed (core)".to_string(),
657
							));
658
						};
659

            
660
						// Initialize block: calls the "on_initialize" hook on every pallet
661
						// in AllPalletsWithSystem
662
						// This was fine before pallet-message-queue because the XCM messages
663
						// were processed by the "setValidationData" inherent call and not on an
664
						// "on_initialize" hook, which runs before enabling XCM tracing
665
						if core_api_version >= 5 {
666
							api.initialize_block(parent_block_hash, &header)
667
								.map_err(|e| {
668
									internal_err(format!("Runtime api access error: {:?}", e))
669
								})?;
670
						} else {
671
							#[allow(deprecated)]
672
							api.initialize_block_before_version_5(parent_block_hash, &header)
673
								.map_err(|e| {
674
									internal_err(format!("Runtime api access error: {:?}", e))
675
								})?;
676
						}
677

            
678
						if trace_api_version == 4 {
679
							// Pre pallet-message-queue
680
							#[allow(deprecated)]
681
							api.trace_transaction_before_version_5(
682
								parent_block_hash,
683
								exts,
684
								&transaction,
685
							)
686
						} else {
687
							// Pre-london update, legacy transactions.
688
							match transaction {
689
								ethereum::TransactionV2::Legacy(tx) =>
690
								{
691
									#[allow(deprecated)]
692
									api.trace_transaction_before_version_4(
693
										parent_block_hash,
694
										exts,
695
										&tx,
696
									)
697
								}
698
								_ => {
699
									return Err(internal_err(
700
										"Bug: pre-london runtime expects legacy transactions"
701
											.to_string(),
702
									))
703
								}
704
							}
705
						}
706
					};
707

            
708
					result
709
						.map_err(|e| {
710
							internal_err(format!(
711
								"Runtime api access error (version {:?}): {:?}",
712
								trace_api_version, e
713
							))
714
						})?
715
						.map_err(|e| internal_err(format!("DispatchError: {:?}", e)))?;
716

            
717
					Ok(moonbeam_rpc_primitives_debug::Response::Single)
718
				};
719

            
720
				return match trace_type {
721
					single::TraceType::Raw {
722
						disable_storage,
723
						disable_memory,
724
						disable_stack,
725
					} => {
726
						let mut proxy = moonbeam_client_evm_tracing::listeners::Raw::new(
727
							disable_storage,
728
							disable_memory,
729
							disable_stack,
730
							raw_max_memory_usage,
731
						);
732
						proxy.using(f)?;
733
						Ok(Response::Single(
734
							moonbeam_client_evm_tracing::formatters::Raw::format(proxy).ok_or(
735
								internal_err(
736
									"replayed transaction generated too much data. \
737
								try disabling memory or storage?",
738
								),
739
							)?,
740
						))
741
					}
742
					single::TraceType::CallList => {
743
						let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
744
						proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
745
						proxy.using(f)?;
746
						proxy.finish_transaction();
747
						let response = match tracer_input {
748
							TracerInput::Blockscout => {
749
								moonbeam_client_evm_tracing::formatters::Blockscout::format(proxy)
750
									.ok_or("Trace result is empty.")
751
									.map_err(|e| internal_err(format!("{:?}", e)))
752
							}
753
							TracerInput::CallTracer => {
754
								let mut res =
755
									moonbeam_client_evm_tracing::formatters::CallTracer::format(
756
										proxy,
757
									)
758
									.ok_or("Trace result is empty.")
759
									.map_err(|e| internal_err(format!("{:?}", e)))?;
760
								Ok(res.pop().expect("Trace result is empty.").result)
761
							}
762
							_ => Err(internal_err(
763
								"Bug: failed to resolve the tracer format.".to_string(),
764
							)),
765
						}?;
766
						Ok(Response::Single(response))
767
					}
768
					not_supported => Err(internal_err(format!(
769
						"Bug: `handle_transaction_request` does not support {:?}.",
770
						not_supported
771
					))),
772
				};
773
			}
774
		}
775
		Err(internal_err("Runtime block call failed".to_string()))
776
	}
777

            
778
	fn handle_call_request(
779
		client: Arc<C>,
780
		frontier_backend: Arc<dyn fc_api::Backend<B> + Send + Sync>,
781
		request_block_id: RequestBlockId,
782
		call_params: TraceCallParams,
783
		trace_params: Option<TraceParams>,
784
		raw_max_memory_usage: usize,
785
	) -> RpcResult<Response> {
786
		let (tracer_input, trace_type, tracer_config) = Self::handle_params(trace_params)?;
787

            
788
		let reference_id: BlockId<B> = match request_block_id {
789
			RequestBlockId::Number(n) => Ok(BlockId::Number(n.unique_saturated_into())),
790
			RequestBlockId::Tag(RequestBlockTag::Latest) => {
791
				Ok(BlockId::Number(client.info().best_number))
792
			}
793
			RequestBlockId::Tag(RequestBlockTag::Earliest) => {
794
				Ok(BlockId::Number(0u32.unique_saturated_into()))
795
			}
796
			RequestBlockId::Tag(RequestBlockTag::Pending) => {
797
				Err(internal_err("'pending' blocks are not supported"))
798
			}
799
			RequestBlockId::Hash(eth_hash) => {
800
				match futures::executor::block_on(frontier_backend_client::load_hash::<B, C>(
801
					client.as_ref(),
802
					frontier_backend.as_ref(),
803
					eth_hash,
804
				)) {
805
					Ok(Some(hash)) => Ok(BlockId::Hash(hash)),
806
					Ok(_) => Err(internal_err("Block hash not found".to_string())),
807
					Err(e) => Err(e),
808
				}
809
			}
810
		}?;
811

            
812
		// Get ApiRef. This handle allow to keep changes between txs in an internal buffer.
813
		let mut api = client.runtime_api();
814

            
815
		// Enable proof recording
816
		api.record_proof();
817
		api.proof_recorder().map(|recorder| {
818
			let ext = sp_trie::proof_size_extension::ProofSizeExt::new(recorder);
819
			api.register_extension(ext);
820
		});
821

            
822
		// Get the header I want to work with.
823
		let Ok(hash) = client.expect_block_hash_from_id(&reference_id) else {
824
			return Err(internal_err("Block header not found"));
825
		};
826
		let header = match client.header(hash) {
827
			Ok(Some(h)) => h,
828
			_ => return Err(internal_err("Block header not found")),
829
		};
830
		// Get parent blockid.
831
		let parent_block_hash = *header.parent_hash();
832

            
833
		// Get DebugRuntimeApi version
834
		let trace_api_version = if let Ok(Some(api_version)) =
835
			api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
836
		{
837
			api_version
838
		} else {
839
			return Err(internal_err(
840
				"Runtime api version call failed (trace)".to_string(),
841
			));
842
		};
843

            
844
		if trace_api_version <= 5 {
845
			return Err(internal_err(
846
				"debug_traceCall not supported with old runtimes".to_string(),
847
			));
848
		}
849

            
850
		let TraceCallParams {
851
			from,
852
			to,
853
			gas_price,
854
			max_fee_per_gas,
855
			max_priority_fee_per_gas,
856
			gas,
857
			value,
858
			data,
859
			nonce,
860
			access_list,
861
			..
862
		} = call_params;
863

            
864
		let (max_fee_per_gas, max_priority_fee_per_gas) =
865
			match (gas_price, max_fee_per_gas, max_priority_fee_per_gas) {
866
				(gas_price, None, None) => {
867
					// Legacy request, all default to gas price.
868
					// A zero-set gas price is None.
869
					let gas_price = if gas_price.unwrap_or_default().is_zero() {
870
						None
871
					} else {
872
						gas_price
873
					};
874
					(gas_price, gas_price)
875
				}
876
				(_, max_fee, max_priority) => {
877
					// eip-1559
878
					// A zero-set max fee is None.
879
					let max_fee = if max_fee.unwrap_or_default().is_zero() {
880
						None
881
					} else {
882
						max_fee
883
					};
884
					// Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`.
885
					if let Some(max_priority) = max_priority {
886
						let max_fee = max_fee.unwrap_or_default();
887
						if max_priority > max_fee {
888
							return Err(internal_err(
889
							"Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`",
890
						));
891
						}
892
					}
893
					(max_fee, max_priority)
894
				}
895
			};
896

            
897
		let gas_limit = match gas {
898
			Some(amount) => amount,
899
			None => {
900
				if let Some(block) = api
901
					.current_block(parent_block_hash)
902
					.map_err(|err| internal_err(format!("runtime error: {:?}", err)))?
903
				{
904
					block.header.gas_limit
905
				} else {
906
					return Err(internal_err(
907
						"block unavailable, cannot query gas limit".to_string(),
908
					));
909
				}
910
			}
911
		};
912
		let data = data.map(|d| d.0).unwrap_or_default();
913

            
914
		let access_list = access_list.unwrap_or_default();
915

            
916
		let f = || -> RpcResult<_> {
917
			let _result = api
918
				.trace_call(
919
					parent_block_hash,
920
					&header,
921
					from.unwrap_or_default(),
922
					to,
923
					data,
924
					value.unwrap_or_default(),
925
					gas_limit,
926
					max_fee_per_gas,
927
					max_priority_fee_per_gas,
928
					nonce,
929
					Some(
930
						access_list
931
							.into_iter()
932
							.map(|item| (item.address, item.storage_keys))
933
							.collect(),
934
					),
935
				)
936
				.map_err(|e| internal_err(format!("Runtime api access error: {:?}", e)))?
937
				.map_err(|e| internal_err(format!("DispatchError: {:?}", e)))?;
938

            
939
			Ok(moonbeam_rpc_primitives_debug::Response::Single)
940
		};
941

            
942
		return match trace_type {
943
			single::TraceType::Raw {
944
				disable_storage,
945
				disable_memory,
946
				disable_stack,
947
			} => {
948
				let mut proxy = moonbeam_client_evm_tracing::listeners::Raw::new(
949
					disable_storage,
950
					disable_memory,
951
					disable_stack,
952
					raw_max_memory_usage,
953
				);
954
				proxy.using(f)?;
955
				Ok(Response::Single(
956
					moonbeam_client_evm_tracing::formatters::Raw::format(proxy).ok_or(
957
						internal_err(
958
							"replayed transaction generated too much data. \
959
						try disabling memory or storage?",
960
						),
961
					)?,
962
				))
963
			}
964
			single::TraceType::CallList => {
965
				let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
966
				proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
967
				proxy.using(f)?;
968
				proxy.finish_transaction();
969
				let response = match tracer_input {
970
					TracerInput::Blockscout => {
971
						moonbeam_client_evm_tracing::formatters::Blockscout::format(proxy)
972
							.ok_or("Trace result is empty.")
973
							.map_err(|e| internal_err(format!("{:?}", e)))
974
					}
975
					TracerInput::CallTracer => {
976
						let mut res =
977
							moonbeam_client_evm_tracing::formatters::CallTracer::format(proxy)
978
								.ok_or("Trace result is empty.")
979
								.map_err(|e| internal_err(format!("{:?}", e)))?;
980
						Ok(res.pop().expect("Trace result is empty.").result)
981
					}
982
					_ => Err(internal_err(
983
						"Bug: failed to resolve the tracer format.".to_string(),
984
					)),
985
				}?;
986
				Ok(Response::Single(response))
987
			}
988
			not_supported => Err(internal_err(format!(
989
				"Bug: `handle_call_request` does not support {:?}.",
990
				not_supported
991
			))),
992
		};
993
	}
994
}