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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
275
						tokio::task::spawn(async move {
276
							let _ = response_tx.send(
277
								async {
278
									let _permit = permit_pool.acquire().await;
279

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
710
		// Get the actual ethereum transaction.
711
		if let Some(block) = reference_block {
712
			let transactions = block.transactions;
713
			if let Some(transaction) = transactions.get(index) {
714
				let f = || -> RpcResult<_> {
715
					let result = if trace_api_version >= 5 {
716
						// The block is initialized inside "trace_transaction"
717
						api.trace_transaction(parent_block_hash, exts, &transaction, &header)
718
					} else {
719
						// Get core runtime api version
720
						let core_api_version = if let Ok(Some(api_version)) =
721
							api.api_version::<dyn Core<B>>(parent_block_hash)
722
						{
723
							api_version
724
						} else {
725
							return Err(internal_err(
726
								"Runtime api version call failed (core)".to_string(),
727
							));
728
						};
729

            
730
						// Initialize block: calls the "on_initialize" hook on every pallet
731
						// in AllPalletsWithSystem
732
						// This was fine before pallet-message-queue because the XCM messages
733
						// were processed by the "setValidationData" inherent call and not on an
734
						// "on_initialize" hook, which runs before enabling XCM tracing
735
						if core_api_version >= 5 {
736
							api.initialize_block(parent_block_hash, &header)
737
								.map_err(|e| {
738
									internal_err(format!("Runtime api access error: {:?}", e))
739
								})?;
740
						} else {
741
							#[allow(deprecated)]
742
							api.initialize_block_before_version_5(parent_block_hash, &header)
743
								.map_err(|e| {
744
									internal_err(format!("Runtime api access error: {:?}", e))
745
								})?;
746
						}
747

            
748
						if trace_api_version == 4 {
749
							// Pre pallet-message-queue
750
							#[allow(deprecated)]
751
							api.trace_transaction_before_version_5(
752
								parent_block_hash,
753
								exts,
754
								&transaction,
755
							)
756
						} else {
757
							// Pre-london update, legacy transactions.
758
							match transaction {
759
								ethereum::TransactionV2::Legacy(tx) =>
760
								{
761
									#[allow(deprecated)]
762
									api.trace_transaction_before_version_4(
763
										parent_block_hash,
764
										exts,
765
										&tx,
766
									)
767
								}
768
								_ => {
769
									return Err(internal_err(
770
										"Bug: pre-london runtime expects legacy transactions"
771
											.to_string(),
772
									))
773
								}
774
							}
775
						}
776
					};
777

            
778
					result
779
						.map_err(|e| {
780
							internal_err(format!(
781
								"Runtime api access error (version {:?}): {:?}",
782
								trace_api_version, e
783
							))
784
						})?
785
						.map_err(|e| internal_err(format!("DispatchError: {:?}", e)))?;
786

            
787
					Ok(moonbeam_rpc_primitives_debug::Response::Single)
788
				};
789

            
790
				return match trace_type {
791
					single::TraceType::Raw {
792
						disable_storage,
793
						disable_memory,
794
						disable_stack,
795
					} => {
796
						let mut proxy = moonbeam_client_evm_tracing::listeners::Raw::new(
797
							disable_storage,
798
							disable_memory,
799
							disable_stack,
800
							raw_max_memory_usage,
801
						);
802
						proxy.using(f)?;
803
						Ok(Response::Single(
804
							moonbeam_client_evm_tracing::formatters::Raw::format(proxy).ok_or(
805
								internal_err(
806
									"replayed transaction generated too much data. \
807
								try disabling memory or storage?",
808
								),
809
							)?,
810
						))
811
					}
812
					single::TraceType::CallList => {
813
						let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
814
						proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
815
						proxy.using(f)?;
816
						proxy.finish_transaction();
817
						let response = match tracer_input {
818
							TracerInput::Blockscout => {
819
								moonbeam_client_evm_tracing::formatters::Blockscout::format(proxy)
820
									.ok_or("Trace result is empty.")
821
									.map_err(|e| internal_err(format!("{:?}", e)))
822
							}
823
							TracerInput::CallTracer => {
824
								let mut res =
825
									moonbeam_client_evm_tracing::formatters::CallTracer::format(
826
										proxy,
827
									)
828
									.ok_or("Trace result is empty.")
829
									.map_err(|e| internal_err(format!("{:?}", e)))?;
830
								Ok(res.pop().expect("Trace result is empty.").result)
831
							}
832
							_ => Err(internal_err(
833
								"Bug: failed to resolve the tracer format.".to_string(),
834
							)),
835
						}?;
836
						Ok(Response::Single(response))
837
					}
838
					not_supported => Err(internal_err(format!(
839
						"Bug: `handle_transaction_request` does not support {:?}.",
840
						not_supported
841
					))),
842
				};
843
			}
844
		}
845
		Err(internal_err("Runtime block call failed".to_string()))
846
	}
847

            
848
	fn handle_call_request(
849
		client: Arc<C>,
850
		frontier_backend: Arc<dyn fc_api::Backend<B> + Send + Sync>,
851
		request_block_id: RequestBlockId,
852
		call_params: TraceCallParams,
853
		trace_params: Option<TraceParams>,
854
		raw_max_memory_usage: usize,
855
	) -> RpcResult<Response> {
856
		let (tracer_input, trace_type, tracer_config) = Self::handle_params(trace_params)?;
857

            
858
		let reference_id: BlockId<B> = match request_block_id {
859
			RequestBlockId::Number(n) => Ok(BlockId::Number(n.unique_saturated_into())),
860
			RequestBlockId::Tag(RequestBlockTag::Latest) => {
861
				Ok(BlockId::Number(client.info().best_number))
862
			}
863
			RequestBlockId::Tag(RequestBlockTag::Earliest) => {
864
				Ok(BlockId::Number(0u32.unique_saturated_into()))
865
			}
866
			RequestBlockId::Tag(RequestBlockTag::Pending) => {
867
				Err(internal_err("'pending' blocks are not supported"))
868
			}
869
			RequestBlockId::Hash(eth_hash) => {
870
				match futures::executor::block_on(frontier_backend_client::load_hash::<B, C>(
871
					client.as_ref(),
872
					frontier_backend.as_ref(),
873
					eth_hash,
874
				)) {
875
					Ok(Some(hash)) => Ok(BlockId::Hash(hash)),
876
					Ok(_) => Err(internal_err("Block hash not found".to_string())),
877
					Err(e) => Err(e),
878
				}
879
			}
880
		}?;
881

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

            
885
		// Enable proof recording
886
		api.record_proof();
887
		api.proof_recorder().map(|recorder| {
888
			let ext = sp_trie::proof_size_extension::ProofSizeExt::new(recorder);
889
			api.register_extension(ext);
890
		});
891

            
892
		// Get the header I want to work with.
893
		let Ok(hash) = client.expect_block_hash_from_id(&reference_id) else {
894
			return Err(internal_err("Block header not found"));
895
		};
896
		let header = match client.header(hash) {
897
			Ok(Some(h)) => h,
898
			_ => return Err(internal_err("Block header not found")),
899
		};
900
		// Get parent blockid.
901
		let parent_block_hash = *header.parent_hash();
902

            
903
		// Get DebugRuntimeApi version
904
		let trace_api_version = if let Ok(Some(api_version)) =
905
			api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
906
		{
907
			api_version
908
		} else {
909
			return Err(internal_err(
910
				"Runtime api version call failed (trace)".to_string(),
911
			));
912
		};
913

            
914
		if trace_api_version <= 5 {
915
			return Err(internal_err(
916
				"debug_traceCall not supported with old runtimes".to_string(),
917
			));
918
		}
919

            
920
		let TraceCallParams {
921
			from,
922
			to,
923
			gas_price,
924
			max_fee_per_gas,
925
			max_priority_fee_per_gas,
926
			gas,
927
			value,
928
			data,
929
			nonce,
930
			access_list,
931
			..
932
		} = call_params;
933

            
934
		let (max_fee_per_gas, max_priority_fee_per_gas) =
935
			match (gas_price, max_fee_per_gas, max_priority_fee_per_gas) {
936
				(gas_price, None, None) => {
937
					// Legacy request, all default to gas price.
938
					// A zero-set gas price is None.
939
					let gas_price = if gas_price.unwrap_or_default().is_zero() {
940
						None
941
					} else {
942
						gas_price
943
					};
944
					(gas_price, gas_price)
945
				}
946
				(_, max_fee, max_priority) => {
947
					// eip-1559
948
					// A zero-set max fee is None.
949
					let max_fee = if max_fee.unwrap_or_default().is_zero() {
950
						None
951
					} else {
952
						max_fee
953
					};
954
					// Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`.
955
					if let Some(max_priority) = max_priority {
956
						let max_fee = max_fee.unwrap_or_default();
957
						if max_priority > max_fee {
958
							return Err(internal_err(
959
							"Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`",
960
						));
961
						}
962
					}
963
					(max_fee, max_priority)
964
				}
965
			};
966

            
967
		let gas_limit = match gas {
968
			Some(amount) => amount,
969
			None => {
970
				if let Some(block) = api
971
					.current_block(parent_block_hash)
972
					.map_err(|err| internal_err(format!("runtime error: {:?}", err)))?
973
				{
974
					block.header.gas_limit
975
				} else {
976
					return Err(internal_err(
977
						"block unavailable, cannot query gas limit".to_string(),
978
					));
979
				}
980
			}
981
		};
982
		let data = data.map(|d| d.0).unwrap_or_default();
983

            
984
		let access_list = access_list.unwrap_or_default();
985

            
986
		let f = || -> RpcResult<_> {
987
			let _result = api
988
				.trace_call(
989
					parent_block_hash,
990
					&header,
991
					from.unwrap_or_default(),
992
					to,
993
					data,
994
					value.unwrap_or_default(),
995
					gas_limit,
996
					max_fee_per_gas,
997
					max_priority_fee_per_gas,
998
					nonce,
999
					Some(
						access_list
							.into_iter()
							.map(|item| (item.address, item.storage_keys))
							.collect(),
					),
				)
				.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
			))),
		};
	}
}