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::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
					} else {
						max_fee
					};
					// Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`.
					if let Some(max_priority) = max_priority {
						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
			))),
		};
	}
}