1
// Copyright 2019-2022 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::ResponseFormatter, types::single};
30
use moonbeam_rpc_core_types::{RequestBlockId, RequestBlockTag};
31
use moonbeam_rpc_primitives_debug::{DebugRuntimeApi, TracerInput};
32
use sc_client_api::backend::{Backend, StateBackend, StorageProvider};
33
use sc_utils::mpsc::TracingUnboundedSender;
34
use sp_api::{ApiExt, Core, ProvideRuntimeApi};
35
use sp_block_builder::BlockBuilder;
36
use sp_blockchain::{
37
	Backend as BlockchainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata,
38
};
39
use sp_runtime::{
40
	generic::BlockId,
41
	traits::{BlakeTwo256, Block as BlockT, Header as HeaderT, UniqueSaturatedInto},
42
};
43
use std::{future::Future, marker::PhantomData, sync::Arc};
44

            
45
pub enum RequesterInput {
46
	Call((RequestBlockId, TraceCallParams)),
47
	Transaction(H256),
48
	Block(RequestBlockId),
49
}
50

            
51
pub enum Response {
52
	Single(single::TransactionTrace),
53
	Block(Vec<single::TransactionTrace>),
54
}
55

            
56
pub type Responder = oneshot::Sender<RpcResult<Response>>;
57
pub type DebugRequester =
58
	TracingUnboundedSender<((RequesterInput, Option<TraceParams>), Responder)>;
59

            
60
pub struct Debug {
61
	pub requester: DebugRequester,
62
}
63

            
64
impl Debug {
65
	pub fn new(requester: DebugRequester) -> Self {
66
		Self { requester }
67
	}
68
}
69

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

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

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

            
101
	async fn trace_block(
102
		&self,
103
		id: RequestBlockId,
104
		params: Option<TraceParams>,
105
	) -> RpcResult<Vec<single::TransactionTrace>> {
106
		let requester = self.requester.clone();
107

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

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

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

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

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

            
159
pub struct DebugHandler<B: BlockT, C, BE>(PhantomData<(B, C, BE)>);
160

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

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

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

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

            
269
						tokio::task::spawn(async move {
270
							let _ = response_tx.send(
271
								async {
272
									let _permit = permit_pool.acquire().await;
273

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

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

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

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

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

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

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

            
415
		// Get parent blockid.
416
		let parent_block_hash = *header.parent_hash();
417

            
418
		let statuses = overrides
419
			.current_transaction_statuses(hash)
420
			.unwrap_or_default();
421

            
422
		// Known ethereum transaction hashes.
423
		let eth_tx_hashes: Vec<_> = statuses.iter().map(|t| t.transaction_hash).collect();
424

            
425
		// If there are no ethereum transactions in the block return empty trace right away.
426
		if eth_tx_hashes.is_empty() {
427
			return Ok(Response::Block(vec![]));
428
		}
429

            
430
		// Get block extrinsics.
431
		let exts = blockchain
432
			.body(hash)
433
			.map_err(|e| internal_err(format!("Fail to read blockchain db: {:?}", e)))?
434
			.unwrap_or_default();
435

            
436
		// Get DebugRuntimeApi version
437
		let trace_api_version = if let Ok(Some(api_version)) =
438
			api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
439
		{
440
			api_version
441
		} else {
442
			return Err(internal_err(
443
				"Runtime api version call failed (trace)".to_string(),
444
			));
445
		};
446

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

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

            
478
				#[allow(deprecated)]
479
				api.trace_block_before_version_5(parent_block_hash, exts, eth_tx_hashes)
480
			};
481

            
482
			result
483
				.map_err(|e| {
484
					internal_err(format!(
485
						"Blockchain error when replaying block {} : {:?}",
486
						reference_id, e
487
					))
488
				})?
489
				.map_err(|e| {
490
					internal_err(format!(
491
						"Internal runtime error when replaying block {} : {:?}",
492
						reference_id, e
493
					))
494
				})?;
495

            
496
			Ok(moonbeam_rpc_primitives_debug::Response::Block)
497
		};
498

            
499
		return match trace_type {
500
			single::TraceType::CallList => {
501
				let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
502
				proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
503
				proxy.using(f)?;
504
				proxy.finish_transaction();
505
				let response = match tracer_input {
506
					TracerInput::CallTracer => {
507
						moonbeam_client_evm_tracing::formatters::CallTracer::format(proxy)
508
							.ok_or("Trace result is empty.")
509
							.map_err(|e| internal_err(format!("{:?}", e)))
510
					}
511
					_ => Err(internal_err(
512
						"Bug: failed to resolve the tracer format.".to_string(),
513
					)),
514
				}?;
515

            
516
				Ok(Response::Block(response))
517
			}
518
			_ => Err(internal_err(
519
				"debug_traceBlock functions currently only support callList mode (enabled
520
				by providing `{{'tracer': 'callTracer'}}` in the request)."
521
					.to_string(),
522
			)),
523
		};
524
	}
525

            
526
	/// Replays a transaction in the Runtime at a given block height.
527
	///
528
	/// In order to succesfully reproduce the result of the original transaction we need a correct
529
	/// state to replay over.
530
	///
531
	/// Substrate allows to apply extrinsics in the Runtime and thus creating an overlayed state.
532
	/// This overlayed changes will live in-memory for the lifetime of the ApiRef.
533
	fn handle_transaction_request(
534
		client: Arc<C>,
535
		backend: Arc<BE>,
536
		frontier_backend: Arc<dyn fc_api::Backend<B> + Send + Sync>,
537
		transaction_hash: H256,
538
		params: Option<TraceParams>,
539
		overrides: Arc<dyn StorageOverride<B>>,
540
		raw_max_memory_usage: usize,
541
	) -> RpcResult<Response> {
542
		let (tracer_input, trace_type, tracer_config) = Self::handle_params(params)?;
543

            
544
		let (hash, index) =
545
			match futures::executor::block_on(frontier_backend_client::load_transactions::<B, C>(
546
				client.as_ref(),
547
				frontier_backend.as_ref(),
548
				transaction_hash,
549
				false,
550
			)) {
551
				Ok(Some((hash, index))) => (hash, index as usize),
552
				Ok(None) => return Err(internal_err("Transaction hash not found".to_string())),
553
				Err(e) => return Err(e),
554
			};
555

            
556
		let reference_id =
557
			match futures::executor::block_on(frontier_backend_client::load_hash::<B, C>(
558
				client.as_ref(),
559
				frontier_backend.as_ref(),
560
				hash,
561
			)) {
562
				Ok(Some(hash)) => BlockId::Hash(hash),
563
				Ok(_) => return Err(internal_err("Block hash not found".to_string())),
564
				Err(e) => return Err(e),
565
			};
566
		// Get ApiRef. This handle allow to keep changes between txs in an internal buffer.
567
		let mut api = client.runtime_api();
568

            
569
		// Enable proof recording
570
		api.record_proof();
571
		api.proof_recorder().map(|recorder| {
572
			let ext = sp_trie::proof_size_extension::ProofSizeExt::new(recorder);
573
			api.register_extension(ext);
574
		});
575

            
576
		// Get Blockchain backend
577
		let blockchain = backend.blockchain();
578
		// Get the header I want to work with.
579
		let Ok(reference_hash) = client.expect_block_hash_from_id(&reference_id) else {
580
			return Err(internal_err("Block header not found"));
581
		};
582
		let header = match client.header(reference_hash) {
583
			Ok(Some(h)) => h,
584
			_ => return Err(internal_err("Block header not found")),
585
		};
586
		// Get parent blockid.
587
		let parent_block_hash = *header.parent_hash();
588

            
589
		// Get block extrinsics.
590
		let exts = blockchain
591
			.body(reference_hash)
592
			.map_err(|e| internal_err(format!("Fail to read blockchain db: {:?}", e)))?
593
			.unwrap_or_default();
594

            
595
		// Get DebugRuntimeApi version
596
		let trace_api_version = if let Ok(Some(api_version)) =
597
			api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
598
		{
599
			api_version
600
		} else {
601
			return Err(internal_err(
602
				"Runtime api version call failed (trace)".to_string(),
603
			));
604
		};
605

            
606
		let reference_block = overrides.current_block(reference_hash);
607

            
608
		// Get the actual ethereum transaction.
609
		if let Some(block) = reference_block {
610
			let transactions = block.transactions;
611
			if let Some(transaction) = transactions.get(index) {
612
				let f = || -> RpcResult<_> {
613
					let result = if trace_api_version >= 5 {
614
						// The block is initialized inside "trace_transaction"
615
						api.trace_transaction(parent_block_hash, exts, &transaction, &header)
616
					} else {
617
						// Get core runtime api version
618
						let core_api_version = if let Ok(Some(api_version)) =
619
							api.api_version::<dyn Core<B>>(parent_block_hash)
620
						{
621
							api_version
622
						} else {
623
							return Err(internal_err(
624
								"Runtime api version call failed (core)".to_string(),
625
							));
626
						};
627

            
628
						// Initialize block: calls the "on_initialize" hook on every pallet
629
						// in AllPalletsWithSystem
630
						// This was fine before pallet-message-queue because the XCM messages
631
						// were processed by the "setValidationData" inherent call and not on an
632
						// "on_initialize" hook, which runs before enabling XCM tracing
633
						if core_api_version >= 5 {
634
							api.initialize_block(parent_block_hash, &header)
635
								.map_err(|e| {
636
									internal_err(format!("Runtime api access error: {:?}", e))
637
								})?;
638
						} else {
639
							#[allow(deprecated)]
640
							api.initialize_block_before_version_5(parent_block_hash, &header)
641
								.map_err(|e| {
642
									internal_err(format!("Runtime api access error: {:?}", e))
643
								})?;
644
						}
645

            
646
						if trace_api_version == 4 {
647
							// Pre pallet-message-queue
648
							#[allow(deprecated)]
649
							api.trace_transaction_before_version_5(
650
								parent_block_hash,
651
								exts,
652
								&transaction,
653
							)
654
						} else {
655
							// Pre-london update, legacy transactions.
656
							match transaction {
657
								ethereum::TransactionV2::Legacy(tx) =>
658
								{
659
									#[allow(deprecated)]
660
									api.trace_transaction_before_version_4(
661
										parent_block_hash,
662
										exts,
663
										&tx,
664
									)
665
								}
666
								_ => {
667
									return Err(internal_err(
668
										"Bug: pre-london runtime expects legacy transactions"
669
											.to_string(),
670
									))
671
								}
672
							}
673
						}
674
					};
675

            
676
					result
677
						.map_err(|e| {
678
							internal_err(format!(
679
								"Runtime api access error (version {:?}): {:?}",
680
								trace_api_version, e
681
							))
682
						})?
683
						.map_err(|e| internal_err(format!("DispatchError: {:?}", e)))?;
684

            
685
					Ok(moonbeam_rpc_primitives_debug::Response::Single)
686
				};
687

            
688
				return match trace_type {
689
					single::TraceType::Raw {
690
						disable_storage,
691
						disable_memory,
692
						disable_stack,
693
					} => {
694
						let mut proxy = moonbeam_client_evm_tracing::listeners::Raw::new(
695
							disable_storage,
696
							disable_memory,
697
							disable_stack,
698
							raw_max_memory_usage,
699
						);
700
						proxy.using(f)?;
701
						Ok(Response::Single(
702
							moonbeam_client_evm_tracing::formatters::Raw::format(proxy).ok_or(
703
								internal_err(
704
									"replayed transaction generated too much data. \
705
								try disabling memory or storage?",
706
								),
707
							)?,
708
						))
709
					}
710
					single::TraceType::CallList => {
711
						let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
712
						proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
713
						proxy.using(f)?;
714
						proxy.finish_transaction();
715
						let response = match tracer_input {
716
							TracerInput::Blockscout => {
717
								moonbeam_client_evm_tracing::formatters::Blockscout::format(proxy)
718
									.ok_or("Trace result is empty.")
719
									.map_err(|e| internal_err(format!("{:?}", e)))
720
							}
721
							TracerInput::CallTracer => {
722
								let mut res =
723
									moonbeam_client_evm_tracing::formatters::CallTracer::format(
724
										proxy,
725
									)
726
									.ok_or("Trace result is empty.")
727
									.map_err(|e| internal_err(format!("{:?}", e)))?;
728
								Ok(res.pop().expect("Trace result is empty."))
729
							}
730
							_ => Err(internal_err(
731
								"Bug: failed to resolve the tracer format.".to_string(),
732
							)),
733
						}?;
734
						Ok(Response::Single(response))
735
					}
736
					not_supported => Err(internal_err(format!(
737
						"Bug: `handle_transaction_request` does not support {:?}.",
738
						not_supported
739
					))),
740
				};
741
			}
742
		}
743
		Err(internal_err("Runtime block call failed".to_string()))
744
	}
745

            
746
	fn handle_call_request(
747
		client: Arc<C>,
748
		frontier_backend: Arc<dyn fc_api::Backend<B> + Send + Sync>,
749
		request_block_id: RequestBlockId,
750
		call_params: TraceCallParams,
751
		trace_params: Option<TraceParams>,
752
		raw_max_memory_usage: usize,
753
	) -> RpcResult<Response> {
754
		let (tracer_input, trace_type, tracer_config) = Self::handle_params(trace_params)?;
755

            
756
		let reference_id: BlockId<B> = match request_block_id {
757
			RequestBlockId::Number(n) => Ok(BlockId::Number(n.unique_saturated_into())),
758
			RequestBlockId::Tag(RequestBlockTag::Latest) => {
759
				Ok(BlockId::Number(client.info().best_number))
760
			}
761
			RequestBlockId::Tag(RequestBlockTag::Earliest) => {
762
				Ok(BlockId::Number(0u32.unique_saturated_into()))
763
			}
764
			RequestBlockId::Tag(RequestBlockTag::Pending) => {
765
				Err(internal_err("'pending' blocks are not supported"))
766
			}
767
			RequestBlockId::Hash(eth_hash) => {
768
				match futures::executor::block_on(frontier_backend_client::load_hash::<B, C>(
769
					client.as_ref(),
770
					frontier_backend.as_ref(),
771
					eth_hash,
772
				)) {
773
					Ok(Some(hash)) => Ok(BlockId::Hash(hash)),
774
					Ok(_) => Err(internal_err("Block hash not found".to_string())),
775
					Err(e) => Err(e),
776
				}
777
			}
778
		}?;
779

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

            
783
		// Enable proof recording
784
		api.record_proof();
785
		api.proof_recorder().map(|recorder| {
786
			let ext = sp_trie::proof_size_extension::ProofSizeExt::new(recorder);
787
			api.register_extension(ext);
788
		});
789

            
790
		// Get the header I want to work with.
791
		let Ok(hash) = client.expect_block_hash_from_id(&reference_id) else {
792
			return Err(internal_err("Block header not found"));
793
		};
794
		let header = match client.header(hash) {
795
			Ok(Some(h)) => h,
796
			_ => return Err(internal_err("Block header not found")),
797
		};
798
		// Get parent blockid.
799
		let parent_block_hash = *header.parent_hash();
800

            
801
		// Get DebugRuntimeApi version
802
		let trace_api_version = if let Ok(Some(api_version)) =
803
			api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
804
		{
805
			api_version
806
		} else {
807
			return Err(internal_err(
808
				"Runtime api version call failed (trace)".to_string(),
809
			));
810
		};
811

            
812
		if trace_api_version <= 5 {
813
			return Err(internal_err(
814
				"debug_traceCall not supported with old runtimes".to_string(),
815
			));
816
		}
817

            
818
		let TraceCallParams {
819
			from,
820
			to,
821
			gas_price,
822
			max_fee_per_gas,
823
			max_priority_fee_per_gas,
824
			gas,
825
			value,
826
			data,
827
			nonce,
828
			access_list,
829
			..
830
		} = call_params;
831

            
832
		let (max_fee_per_gas, max_priority_fee_per_gas) =
833
			match (gas_price, max_fee_per_gas, max_priority_fee_per_gas) {
834
				(gas_price, None, None) => {
835
					// Legacy request, all default to gas price.
836
					// A zero-set gas price is None.
837
					let gas_price = if gas_price.unwrap_or_default().is_zero() {
838
						None
839
					} else {
840
						gas_price
841
					};
842
					(gas_price, gas_price)
843
				}
844
				(_, max_fee, max_priority) => {
845
					// eip-1559
846
					// A zero-set max fee is None.
847
					let max_fee = if max_fee.unwrap_or_default().is_zero() {
848
						None
849
					} else {
850
						max_fee
851
					};
852
					// Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`.
853
					if let Some(max_priority) = max_priority {
854
						let max_fee = max_fee.unwrap_or_default();
855
						if max_priority > max_fee {
856
							return Err(internal_err(
857
							"Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`",
858
						));
859
						}
860
					}
861
					(max_fee, max_priority)
862
				}
863
			};
864

            
865
		let gas_limit = match gas {
866
			Some(amount) => amount,
867
			None => {
868
				if let Some(block) = api
869
					.current_block(parent_block_hash)
870
					.map_err(|err| internal_err(format!("runtime error: {:?}", err)))?
871
				{
872
					block.header.gas_limit
873
				} else {
874
					return Err(internal_err(
875
						"block unavailable, cannot query gas limit".to_string(),
876
					));
877
				}
878
			}
879
		};
880
		let data = data.map(|d| d.0).unwrap_or_default();
881

            
882
		let access_list = access_list.unwrap_or_default();
883

            
884
		let f = || -> RpcResult<_> {
885
			let _result = api
886
				.trace_call(
887
					parent_block_hash,
888
					&header,
889
					from.unwrap_or_default(),
890
					to,
891
					data,
892
					value.unwrap_or_default(),
893
					gas_limit,
894
					max_fee_per_gas,
895
					max_priority_fee_per_gas,
896
					nonce,
897
					Some(
898
						access_list
899
							.into_iter()
900
							.map(|item| (item.address, item.storage_keys))
901
							.collect(),
902
					),
903
				)
904
				.map_err(|e| internal_err(format!("Runtime api access error: {:?}", e)))?
905
				.map_err(|e| internal_err(format!("DispatchError: {:?}", e)))?;
906

            
907
			Ok(moonbeam_rpc_primitives_debug::Response::Single)
908
		};
909

            
910
		return match trace_type {
911
			single::TraceType::Raw {
912
				disable_storage,
913
				disable_memory,
914
				disable_stack,
915
			} => {
916
				let mut proxy = moonbeam_client_evm_tracing::listeners::Raw::new(
917
					disable_storage,
918
					disable_memory,
919
					disable_stack,
920
					raw_max_memory_usage,
921
				);
922
				proxy.using(f)?;
923
				Ok(Response::Single(
924
					moonbeam_client_evm_tracing::formatters::Raw::format(proxy).ok_or(
925
						internal_err(
926
							"replayed transaction generated too much data. \
927
						try disabling memory or storage?",
928
						),
929
					)?,
930
				))
931
			}
932
			single::TraceType::CallList => {
933
				let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
934
				proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
935
				proxy.using(f)?;
936
				proxy.finish_transaction();
937
				let response = match tracer_input {
938
					TracerInput::Blockscout => {
939
						moonbeam_client_evm_tracing::formatters::Blockscout::format(proxy)
940
							.ok_or("Trace result is empty.")
941
							.map_err(|e| internal_err(format!("{:?}", e)))
942
					}
943
					TracerInput::CallTracer => {
944
						let mut res =
945
							moonbeam_client_evm_tracing::formatters::CallTracer::format(proxy)
946
								.ok_or("Trace result is empty.")
947
								.map_err(|e| internal_err(format!("{:?}", e)))?;
948
						Ok(res.pop().expect("Trace result is empty."))
949
					}
950
					_ => Err(internal_err(
951
						"Bug: failed to resolve the tracer format.".to_string(),
952
					)),
953
				}?;
954
				Ok(Response::Single(response))
955
			}
956
			not_supported => Err(internal_err(format!(
957
				"Bug: `handle_call_request` does not support {:?}.",
958
				not_supported
959
			))),
960
		};
961
	}
962
}