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

            
52
pub enum RequesterInput {
53
	Call((RequestBlockId, TraceCallParams)),
54
	Transaction(H256),
55
	Block(RequestBlockId),
56
}
57

            
58
pub enum Response {
59
	Single(single::TransactionTrace),
60
	Block(Vec<block::BlockTransactionTrace>),
61
}
62

            
63
pub type Responder = oneshot::Sender<RpcResult<Response>>;
64
pub type DebugRequester =
65
	TracingUnboundedSender<((RequesterInput, Option<TraceParams>), Responder)>;
66

            
67
pub struct Debug {
68
	pub requester: DebugRequester,
69
}
70

            
71
impl Debug {
72
	pub fn new(requester: DebugRequester) -> Self {
73
		Self { requester }
74
	}
75
}
76

            
77
#[async_trait]
78
impl 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

            
166
pub struct DebugHandler<B: BlockT, C, BE>(PhantomData<(B, C, BE)>);
167

            
168
impl<B, C, BE> DebugHandler<B, C, BE>
169
where
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::Earliest) => {
383
				Ok(BlockId::Number(0u32.unique_saturated_into()))
384
			}
385
			RequestBlockId::Tag(RequestBlockTag::Pending) => {
386
				Err(internal_err("'pending' blocks are not supported"))
387
			}
388
			RequestBlockId::Hash(eth_hash) => {
389
				match futures::executor::block_on(frontier_backend_client::load_hash::<B, C>(
390
					client.as_ref(),
391
					frontier_backend.as_ref(),
392
					eth_hash,
393
				)) {
394
					Ok(Some(hash)) => Ok(BlockId::Hash(hash)),
395
					Ok(_) => Err(internal_err("Block hash not found".to_string())),
396
					Err(e) => Err(e),
397
				}
398
			}
399
		}?;
400

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

            
404
		// Enable proof recording
405
		api.record_proof();
406
		api.proof_recorder().map(|recorder| {
407
			let ext = sp_trie::proof_size_extension::ProofSizeExt::new(recorder);
408
			api.register_extension(ext);
409
		});
410

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

            
422
		// Get parent blockid.
423
		let parent_block_hash = *header.parent_hash();
424

            
425
		let statuses = overrides
426
			.current_transaction_statuses(hash)
427
			.unwrap_or_default();
428

            
429
		// Partial ethereum transaction data to check if a trace match an ethereum transaction
430
		struct EthTxPartial {
431
			transaction_hash: H256,
432
			from: H160,
433
			to: Option<H160>,
434
		}
435

            
436
		// Known ethereum transaction hashes.
437
		let eth_transactions_by_index: BTreeMap<u32, EthTxPartial> = statuses
438
			.iter()
439
			.map(|status| {
440
				(
441
					status.transaction_index,
442
					EthTxPartial {
443
						transaction_hash: status.transaction_hash,
444
						from: status.from,
445
						to: status.to,
446
					},
447
				)
448
			})
449
			.collect();
450

            
451
		let eth_tx_hashes: Vec<_> = eth_transactions_by_index
452
			.values()
453
			.map(|tx| tx.transaction_hash)
454
			.collect();
455

            
456
		// If there are no ethereum transactions in the block return empty trace right away.
457
		if eth_tx_hashes.is_empty() {
458
			return Ok(Response::Block(vec![]));
459
		}
460

            
461
		// Get block extrinsics.
462
		let exts = blockchain
463
			.body(hash)
464
			.map_err(|e| internal_err(format!("Fail to read blockchain db: {:?}", e)))?
465
			.unwrap_or_default();
466

            
467
		// Get DebugRuntimeApi version
468
		let trace_api_version = if let Ok(Some(api_version)) =
469
			api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
470
		{
471
			api_version
472
		} else {
473
			return Err(internal_err(
474
				"Runtime api version call failed (trace)".to_string(),
475
			));
476
		};
477

            
478
		// Trace the block.
479
		let f = || -> RpcResult<_> {
480
			let result = if trace_api_version >= 5 {
481
				// The block is initialized inside "trace_block"
482
				api.trace_block(parent_block_hash, exts, eth_tx_hashes, &header)
483
			} else {
484
				// Get core runtime api version
485
				let core_api_version = if let Ok(Some(api_version)) =
486
					api.api_version::<dyn Core<B>>(parent_block_hash)
487
				{
488
					api_version
489
				} else {
490
					return Err(internal_err(
491
						"Runtime api version call failed (core)".to_string(),
492
					));
493
				};
494

            
495
				// Initialize block: calls the "on_initialize" hook on every pallet
496
				// in AllPalletsWithSystem
497
				// This was fine before pallet-message-queue because the XCM messages
498
				// were processed by the "setValidationData" inherent call and not on an
499
				// "on_initialize" hook, which runs before enabling XCM tracing
500
				if core_api_version >= 5 {
501
					api.initialize_block(parent_block_hash, &header)
502
						.map_err(|e| internal_err(format!("Runtime api access error: {:?}", e)))?;
503
				} else {
504
					#[allow(deprecated)]
505
					api.initialize_block_before_version_5(parent_block_hash, &header)
506
						.map_err(|e| internal_err(format!("Runtime api access error: {:?}", e)))?;
507
				}
508

            
509
				#[allow(deprecated)]
510
				api.trace_block_before_version_5(parent_block_hash, exts, eth_tx_hashes)
511
			};
512

            
513
			result
514
				.map_err(|e| {
515
					internal_err(format!(
516
						"Blockchain error when replaying block {} : {:?}",
517
						reference_id, e
518
					))
519
				})?
520
				.map_err(|e| {
521
					internal_err(format!(
522
						"Internal runtime error when replaying block {} : {:?}",
523
						reference_id, e
524
					))
525
				})?;
526

            
527
			Ok(moonbeam_rpc_primitives_debug::Response::Block)
528
		};
529

            
530
		// Offset to account for old buggy transactions that are in trace not in the ethereum block
531
		let mut tx_position_offset = 0;
532

            
533
		return match trace_type {
534
			single::TraceType::CallList => {
535
				let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
536
				proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
537
				proxy.using(f)?;
538
				proxy.finish_transaction();
539
				let response = match tracer_input {
540
					TracerInput::CallTracer => {
541
						let result =
542
							moonbeam_client_evm_tracing::formatters::CallTracer::format(proxy)
543
								.ok_or("Trace result is empty.")
544
								.map_err(|e| internal_err(format!("{:?}", e)))?
545
								.into_iter()
546
								.filter_map(|mut trace: BlockTransactionTrace| {
547
									if let Some(EthTxPartial {
548
										transaction_hash,
549
										from,
550
										to,
551
									}) = eth_transactions_by_index
552
										.get(&(trace.tx_position - tx_position_offset))
553
									{
554
										// verify that the trace matches the ethereum transaction
555
										let (trace_from, trace_to) = match trace.result {
556
											TransactionTrace::Raw { .. } => {
557
												(Default::default(), None)
558
											}
559
											TransactionTrace::CallList(_) => {
560
												(Default::default(), None)
561
											}
562
											TransactionTrace::CallListNested(ref call) => {
563
												match call {
564
													single::Call::Blockscout(_) => {
565
														(Default::default(), None)
566
													}
567
													single::Call::CallTracer(call) => (
568
														call.from,
569
														match call.inner {
570
															CallTracerInner::Call {
571
																to, ..
572
															} => Some(to),
573
															CallTracerInner::Create { .. } => None,
574
															CallTracerInner::SelfDestruct {
575
																..
576
															} => None,
577
														},
578
													),
579
												}
580
											}
581
										};
582
										if trace_from == *from && trace_to == *to {
583
											trace.tx_hash = *transaction_hash;
584
											Some(trace)
585
										} else {
586
											// if the trace does not match the ethereum transaction
587
											// it means that the trace is about a buggy transaction that is not in the block
588
											// we need to offset the tx_position
589
											tx_position_offset += 1;
590
											None
591
										}
592
									} else {
593
										// If the transaction is not in the ethereum block
594
										// it should not appear in the block trace
595
										tx_position_offset += 1;
596
										None
597
									}
598
								})
599
								.collect::<Vec<BlockTransactionTrace>>();
600

            
601
						let n_txs = eth_transactions_by_index.len();
602
						let n_traces = result.len();
603
						if n_txs != n_traces {
604
							log::warn!(
605
								"The traces in block {:?} don't match with the number of ethereum transactions. (txs: {}, traces: {})",
606
								request_block_id,
607
								n_txs,
608
								n_traces
609
							);
610
						}
611

            
612
						Ok(result)
613
					}
614
					_ => Err(internal_err(
615
						"Bug: failed to resolve the tracer format.".to_string(),
616
					)),
617
				}?;
618

            
619
				Ok(Response::Block(response))
620
			}
621
			_ => Err(internal_err(
622
				"debug_traceBlock functions currently only support callList mode (enabled
623
				by providing `{{'tracer': 'callTracer'}}` in the request)."
624
					.to_string(),
625
			)),
626
		};
627
	}
628

            
629
	/// Replays a transaction in the Runtime at a given block height.
630
	///
631
	/// In order to successfully reproduce the result of the original transaction we need a correct
632
	/// state to replay over.
633
	///
634
	/// Substrate allows to apply extrinsics in the Runtime and thus creating an overlaid state.
635
	/// These overlaid changes will live in-memory for the lifetime of the ApiRef.
636
	fn handle_transaction_request(
637
		client: Arc<C>,
638
		backend: Arc<BE>,
639
		frontier_backend: Arc<dyn fc_api::Backend<B> + Send + Sync>,
640
		transaction_hash: H256,
641
		params: Option<TraceParams>,
642
		overrides: Arc<dyn StorageOverride<B>>,
643
		raw_max_memory_usage: usize,
644
	) -> RpcResult<Response> {
645
		let (tracer_input, trace_type, tracer_config) = Self::handle_params(params)?;
646

            
647
		let (hash, index) =
648
			match futures::executor::block_on(frontier_backend_client::load_transactions::<B, C>(
649
				client.as_ref(),
650
				frontier_backend.as_ref(),
651
				transaction_hash,
652
				false,
653
			)) {
654
				Ok(Some((hash, index))) => (hash, index as usize),
655
				Ok(None) => return Err(internal_err("Transaction hash not found".to_string())),
656
				Err(e) => return Err(e),
657
			};
658

            
659
		let reference_id =
660
			match futures::executor::block_on(frontier_backend_client::load_hash::<B, C>(
661
				client.as_ref(),
662
				frontier_backend.as_ref(),
663
				hash,
664
			)) {
665
				Ok(Some(hash)) => BlockId::Hash(hash),
666
				Ok(_) => return Err(internal_err("Block hash not found".to_string())),
667
				Err(e) => return Err(e),
668
			};
669
		// Get ApiRef. This handle allow to keep changes between txs in an internal buffer.
670
		let mut api = client.runtime_api();
671

            
672
		// Enable proof recording
673
		api.record_proof();
674
		api.proof_recorder().map(|recorder| {
675
			let ext = sp_trie::proof_size_extension::ProofSizeExt::new(recorder);
676
			api.register_extension(ext);
677
		});
678

            
679
		// Get Blockchain backend
680
		let blockchain = backend.blockchain();
681
		// Get the header I want to work with.
682
		let Ok(reference_hash) = client.expect_block_hash_from_id(&reference_id) else {
683
			return Err(internal_err("Block header not found"));
684
		};
685
		let header = match client.header(reference_hash) {
686
			Ok(Some(h)) => h,
687
			_ => return Err(internal_err("Block header not found")),
688
		};
689
		// Get parent blockid.
690
		let parent_block_hash = *header.parent_hash();
691

            
692
		// Get block extrinsics.
693
		let exts = blockchain
694
			.body(reference_hash)
695
			.map_err(|e| internal_err(format!("Fail to read blockchain db: {:?}", e)))?
696
			.unwrap_or_default();
697

            
698
		// Get DebugRuntimeApi version
699
		let trace_api_version = if let Ok(Some(api_version)) =
700
			api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
701
		{
702
			api_version
703
		} else {
704
			return Err(internal_err(
705
				"Runtime api version call failed (trace)".to_string(),
706
			));
707
		};
708

            
709
		let reference_block = overrides.current_block(reference_hash);
710

            
711
		// Get the actual ethereum transaction.
712
		if let Some(block) = reference_block {
713
			let transactions = block.transactions;
714
			if let Some(transaction) = transactions.get(index) {
715
				let f = || -> RpcResult<_> {
716
					let result = if trace_api_version >= 7 {
717
						// The block is initialized inside "trace_transaction"
718
						api.trace_transaction(parent_block_hash, exts, &transaction, &header)
719
					} else if trace_api_version == 5 || trace_api_version == 6 {
720
						// API version 5 and 6 expect TransactionV2, so we need to convert from TransactionV3
721
						let tx_v2 = match transaction {
722
							ethereum::TransactionV3::Legacy(tx) => {
723
								ethereum::TransactionV2::Legacy(tx.clone())
724
							}
725
							ethereum::TransactionV3::EIP2930(tx) => {
726
								ethereum::TransactionV2::EIP2930(tx.clone())
727
							}
728
							ethereum::TransactionV3::EIP1559(tx) => {
729
								ethereum::TransactionV2::EIP1559(tx.clone())
730
							}
731
							ethereum::TransactionV3::EIP7702(_) => return Err(internal_err(
732
								"EIP-7702 transactions are supported starting from API version 7"
733
									.to_string(),
734
							)),
735
						};
736

            
737
						// The block is initialized inside "trace_transaction"
738
						#[allow(deprecated)]
739
						api.trace_transaction_before_version_7(
740
							parent_block_hash,
741
							exts,
742
							&tx_v2,
743
							&header,
744
						)
745
					} else {
746
						// Get core runtime api version
747
						let core_api_version = if let Ok(Some(api_version)) =
748
							api.api_version::<dyn Core<B>>(parent_block_hash)
749
						{
750
							api_version
751
						} else {
752
							return Err(internal_err(
753
								"Runtime api version call failed (core)".to_string(),
754
							));
755
						};
756

            
757
						// Initialize block: calls the "on_initialize" hook on every pallet
758
						// in AllPalletsWithSystem
759
						// This was fine before pallet-message-queue because the XCM messages
760
						// were processed by the "setValidationData" inherent call and not on an
761
						// "on_initialize" hook, which runs before enabling XCM tracing
762
						if core_api_version >= 5 {
763
							api.initialize_block(parent_block_hash, &header)
764
								.map_err(|e| {
765
									internal_err(format!("Runtime api access error: {:?}", e))
766
								})?;
767
						} else {
768
							#[allow(deprecated)]
769
							api.initialize_block_before_version_5(parent_block_hash, &header)
770
								.map_err(|e| {
771
									internal_err(format!("Runtime api access error: {:?}", e))
772
								})?;
773
						}
774

            
775
						if trace_api_version == 4 {
776
							// API version 4 expect TransactionV2, so we need to convert from TransactionV3
777
							let tx_v2 = match transaction {
778
								ethereum::TransactionV3::Legacy(tx) => {
779
									ethereum::TransactionV2::Legacy(tx.clone())
780
								}
781
								ethereum::TransactionV3::EIP2930(tx) => {
782
									ethereum::TransactionV2::EIP2930(tx.clone())
783
								}
784
								ethereum::TransactionV3::EIP1559(tx) => {
785
									ethereum::TransactionV2::EIP1559(tx.clone())
786
								}
787
								ethereum::TransactionV3::EIP7702(_) => {
788
									return Err(internal_err(
789
										"EIP-7702 transactions are supported starting from API version 7"
790
											.to_string(),
791
									))
792
								}
793
							};
794

            
795
							// Pre pallet-message-queue
796
							#[allow(deprecated)]
797
							api.trace_transaction_before_version_5(parent_block_hash, exts, &tx_v2)
798
						} else {
799
							// Pre-london update, legacy transactions.
800
							match transaction {
801
								ethereum::TransactionV3::Legacy(tx) =>
802
								{
803
									#[allow(deprecated)]
804
									api.trace_transaction_before_version_4(
805
										parent_block_hash,
806
										exts,
807
										&tx,
808
									)
809
								}
810
								_ => {
811
									return Err(internal_err(
812
										"Bug: pre-london runtime expects legacy transactions"
813
											.to_string(),
814
									))
815
								}
816
							}
817
						}
818
					};
819

            
820
					result
821
						.map_err(|e| {
822
							internal_err(format!(
823
								"Runtime api access error (version {:?}): {:?}",
824
								trace_api_version, e
825
							))
826
						})?
827
						.map_err(|e| internal_err(format!("DispatchError: {:?}", e)))?;
828

            
829
					Ok(moonbeam_rpc_primitives_debug::Response::Single)
830
				};
831

            
832
				return match trace_type {
833
					single::TraceType::Raw {
834
						disable_storage,
835
						disable_memory,
836
						disable_stack,
837
					} => {
838
						let mut proxy = moonbeam_client_evm_tracing::listeners::Raw::new(
839
							disable_storage,
840
							disable_memory,
841
							disable_stack,
842
							raw_max_memory_usage,
843
						);
844
						proxy.using(f)?;
845
						Ok(Response::Single(
846
							moonbeam_client_evm_tracing::formatters::Raw::format(proxy).ok_or(
847
								internal_err(
848
									"replayed transaction generated too much data. \
849
								try disabling memory or storage?",
850
								),
851
							)?,
852
						))
853
					}
854
					single::TraceType::CallList => {
855
						let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
856
						proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
857
						proxy.using(f)?;
858
						proxy.finish_transaction();
859
						let response = match tracer_input {
860
							TracerInput::Blockscout => {
861
								moonbeam_client_evm_tracing::formatters::Blockscout::format(proxy)
862
									.ok_or("Trace result is empty.")
863
									.map_err(|e| internal_err(format!("{:?}", e)))
864
							}
865
							TracerInput::CallTracer => {
866
								let mut res =
867
									moonbeam_client_evm_tracing::formatters::CallTracer::format(
868
										proxy,
869
									)
870
									.ok_or("Trace result is empty.")
871
									.map_err(|e| internal_err(format!("{:?}", e)))?;
872
								Ok(res.pop().expect("Trace result is empty.").result)
873
							}
874
							_ => Err(internal_err(
875
								"Bug: failed to resolve the tracer format.".to_string(),
876
							)),
877
						}?;
878
						Ok(Response::Single(response))
879
					}
880
					not_supported => Err(internal_err(format!(
881
						"Bug: `handle_transaction_request` does not support {:?}.",
882
						not_supported
883
					))),
884
				};
885
			}
886
		}
887
		Err(internal_err("Runtime block call failed".to_string()))
888
	}
889

            
890
	fn handle_call_request(
891
		client: Arc<C>,
892
		frontier_backend: Arc<dyn fc_api::Backend<B> + Send + Sync>,
893
		request_block_id: RequestBlockId,
894
		call_params: TraceCallParams,
895
		trace_params: Option<TraceParams>,
896
		raw_max_memory_usage: usize,
897
	) -> RpcResult<Response> {
898
		let (tracer_input, trace_type, tracer_config) = Self::handle_params(trace_params)?;
899

            
900
		let reference_id: BlockId<B> = match request_block_id {
901
			RequestBlockId::Number(n) => Ok(BlockId::Number(n.unique_saturated_into())),
902
			RequestBlockId::Tag(RequestBlockTag::Latest) => {
903
				Ok(BlockId::Number(client.info().best_number))
904
			}
905
			RequestBlockId::Tag(RequestBlockTag::Earliest) => {
906
				Ok(BlockId::Number(0u32.unique_saturated_into()))
907
			}
908
			RequestBlockId::Tag(RequestBlockTag::Pending) => {
909
				Err(internal_err("'pending' blocks are not supported"))
910
			}
911
			RequestBlockId::Hash(eth_hash) => {
912
				match futures::executor::block_on(frontier_backend_client::load_hash::<B, C>(
913
					client.as_ref(),
914
					frontier_backend.as_ref(),
915
					eth_hash,
916
				)) {
917
					Ok(Some(hash)) => Ok(BlockId::Hash(hash)),
918
					Ok(_) => Err(internal_err("Block hash not found".to_string())),
919
					Err(e) => Err(e),
920
				}
921
			}
922
		}?;
923

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

            
927
		// Enable proof recording
928
		api.record_proof();
929
		api.proof_recorder().map(|recorder| {
930
			let ext = sp_trie::proof_size_extension::ProofSizeExt::new(recorder);
931
			api.register_extension(ext);
932
		});
933

            
934
		// Get the header I want to work with.
935
		let Ok(hash) = client.expect_block_hash_from_id(&reference_id) else {
936
			return Err(internal_err("Block header not found"));
937
		};
938
		let header = match client.header(hash) {
939
			Ok(Some(h)) => h,
940
			_ => return Err(internal_err("Block header not found")),
941
		};
942
		// Get parent blockid.
943
		let parent_block_hash = *header.parent_hash();
944

            
945
		// Get DebugRuntimeApi version
946
		let trace_api_version = if let Ok(Some(api_version)) =
947
			api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
948
		{
949
			api_version
950
		} else {
951
			return Err(internal_err(
952
				"Runtime api version call failed (trace)".to_string(),
953
			));
954
		};
955

            
956
		if trace_api_version <= 5 {
957
			return Err(internal_err(
958
				"debug_traceCall not supported with old runtimes".to_string(),
959
			));
960
		}
961

            
962
		let TraceCallParams {
963
			from,
964
			to,
965
			gas_price,
966
			max_fee_per_gas,
967
			max_priority_fee_per_gas,
968
			gas,
969
			value,
970
			data,
971
			nonce,
972
			access_list,
973
			authorization_list,
974
			..
975
		} = call_params;
976

            
977
		let (max_fee_per_gas, max_priority_fee_per_gas) =
978
			match (gas_price, max_fee_per_gas, max_priority_fee_per_gas) {
979
				(gas_price, None, None) => {
980
					// Legacy request, all default to gas price.
981
					// A zero-set gas price is None.
982
					let gas_price = if gas_price.unwrap_or_default().is_zero() {
983
						None
984
					} else {
985
						gas_price
986
					};
987
					(gas_price, gas_price)
988
				}
989
				(_, max_fee, max_priority) => {
990
					// eip-1559
991
					// A zero-set max fee is None.
992
					let max_fee = if max_fee.unwrap_or_default().is_zero() {
993
						None
994
					} else {
995
						max_fee
996
					};
997
					// Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`.
998
					if let Some(max_priority) = max_priority {
999
						let max_fee = max_fee.unwrap_or_default();
						if max_priority > max_fee {
							return Err(internal_err(
							"Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`",
						));
						}
					}
					(max_fee, max_priority)
				}
			};
		let gas_limit = match gas {
			Some(amount) => amount,
			None => {
				if let Some(block) = api
					.current_block(parent_block_hash)
					.map_err(|err| internal_err(format!("runtime error: {:?}", err)))?
				{
					block.header.gas_limit
				} else {
					return Err(internal_err(
						"block unavailable, cannot query gas limit".to_string(),
					));
				}
			}
		};
		let data = data.map(|d| d.0).unwrap_or_default();
		let access_list = access_list.unwrap_or_default();
		let f = || -> RpcResult<_> {
			let _result = api
				.trace_call(
					parent_block_hash,
					&header,
					from.unwrap_or_default(),
					to,
					data,
					value.unwrap_or_default(),
					gas_limit,
					max_fee_per_gas,
					max_priority_fee_per_gas,
					nonce,
					Some(
						access_list
							.into_iter()
							.map(|item| (item.address, item.storage_keys))
							.collect(),
					),
					authorization_list,
				)
				.map_err(|e| internal_err(format!("Runtime api access error: {:?}", e)))?
				.map_err(|e| internal_err(format!("DispatchError: {:?}", e)))?;
			Ok(moonbeam_rpc_primitives_debug::Response::Single)
		};
		return match trace_type {
			single::TraceType::Raw {
				disable_storage,
				disable_memory,
				disable_stack,
			} => {
				let mut proxy = moonbeam_client_evm_tracing::listeners::Raw::new(
					disable_storage,
					disable_memory,
					disable_stack,
					raw_max_memory_usage,
				);
				proxy.using(f)?;
				Ok(Response::Single(
					moonbeam_client_evm_tracing::formatters::Raw::format(proxy).ok_or(
						internal_err(
							"replayed transaction generated too much data. \
						try disabling memory or storage?",
						),
					)?,
				))
			}
			single::TraceType::CallList => {
				let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
				proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
				proxy.using(f)?;
				proxy.finish_transaction();
				let response = match tracer_input {
					TracerInput::Blockscout => {
						moonbeam_client_evm_tracing::formatters::Blockscout::format(proxy)
							.ok_or("Trace result is empty.")
							.map_err(|e| internal_err(format!("{:?}", e)))
					}
					TracerInput::CallTracer => {
						let mut res =
							moonbeam_client_evm_tracing::formatters::CallTracer::format(proxy)
								.ok_or("Trace result is empty.")
								.map_err(|e| internal_err(format!("{:?}", e)))?;
						Ok(res.pop().expect("Trace result is empty.").result)
					}
					_ => Err(internal_err(
						"Bug: failed to resolve the tracer format.".to_string(),
					)),
				}?;
				Ok(Response::Single(response))
			}
			not_supported => Err(internal_err(format!(
				"Bug: `handle_call_request` does not support {:?}.",
				not_supported
			))),
		};
	}
}