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 api = client.runtime_api();
396
		// Get Blockchain backend
397
		let blockchain = backend.blockchain();
398
		// Get the header I want to work with.
399
		let Ok(hash) = client.expect_block_hash_from_id(&reference_id) else {
400
			return Err(internal_err("Block header not found"));
401
		};
402
		let header = match client.header(hash) {
403
			Ok(Some(h)) => h,
404
			_ => return Err(internal_err("Block header not found")),
405
		};
406

            
407
		// Get parent blockid.
408
		let parent_block_hash = *header.parent_hash();
409

            
410
		let statuses = overrides
411
			.current_transaction_statuses(hash)
412
			.unwrap_or_default();
413

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

            
417
		// If there are no ethereum transactions in the block return empty trace right away.
418
		if eth_tx_hashes.is_empty() {
419
			return Ok(Response::Block(vec![]));
420
		}
421

            
422
		// Get block extrinsics.
423
		let exts = blockchain
424
			.body(hash)
425
			.map_err(|e| internal_err(format!("Fail to read blockchain db: {:?}", e)))?
426
			.unwrap_or_default();
427

            
428
		// Get DebugRuntimeApi version
429
		let trace_api_version = if let Ok(Some(api_version)) =
430
			api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
431
		{
432
			api_version
433
		} else {
434
			return Err(internal_err(
435
				"Runtime api version call failed (trace)".to_string(),
436
			));
437
		};
438

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

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

            
470
				#[allow(deprecated)]
471
				api.trace_block_before_version_5(parent_block_hash, exts, eth_tx_hashes)
472
			};
473

            
474
			result
475
				.map_err(|e| {
476
					internal_err(format!(
477
						"Blockchain error when replaying block {} : {:?}",
478
						reference_id, e
479
					))
480
				})?
481
				.map_err(|e| {
482
					internal_err(format!(
483
						"Internal runtime error when replaying block {} : {:?}",
484
						reference_id, e
485
					))
486
				})?;
487

            
488
			Ok(moonbeam_rpc_primitives_debug::Response::Block)
489
		};
490

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

            
508
				Ok(Response::Block(response))
509
			}
510
			_ => Err(internal_err(
511
				"debug_traceBlock functions currently only support callList mode (enabled
512
				by providing `{{'tracer': 'callTracer'}}` in the request)."
513
					.to_string(),
514
			)),
515
		};
516
	}
517

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

            
536
		let (hash, index) =
537
			match futures::executor::block_on(frontier_backend_client::load_transactions::<B, C>(
538
				client.as_ref(),
539
				frontier_backend.as_ref(),
540
				transaction_hash,
541
				false,
542
			)) {
543
				Ok(Some((hash, index))) => (hash, index as usize),
544
				Ok(None) => return Err(internal_err("Transaction hash not found".to_string())),
545
				Err(e) => return Err(e),
546
			};
547

            
548
		let reference_id =
549
			match futures::executor::block_on(frontier_backend_client::load_hash::<B, C>(
550
				client.as_ref(),
551
				frontier_backend.as_ref(),
552
				hash,
553
			)) {
554
				Ok(Some(hash)) => BlockId::Hash(hash),
555
				Ok(_) => return Err(internal_err("Block hash not found".to_string())),
556
				Err(e) => return Err(e),
557
			};
558
		// Get ApiRef. This handle allow to keep changes between txs in an internal buffer.
559
		let api = client.runtime_api();
560
		// Get Blockchain backend
561
		let blockchain = backend.blockchain();
562
		// Get the header I want to work with.
563
		let Ok(reference_hash) = client.expect_block_hash_from_id(&reference_id) else {
564
			return Err(internal_err("Block header not found"));
565
		};
566
		let header = match client.header(reference_hash) {
567
			Ok(Some(h)) => h,
568
			_ => return Err(internal_err("Block header not found")),
569
		};
570
		// Get parent blockid.
571
		let parent_block_hash = *header.parent_hash();
572

            
573
		// Get block extrinsics.
574
		let exts = blockchain
575
			.body(reference_hash)
576
			.map_err(|e| internal_err(format!("Fail to read blockchain db: {:?}", e)))?
577
			.unwrap_or_default();
578

            
579
		// Get DebugRuntimeApi version
580
		let trace_api_version = if let Ok(Some(api_version)) =
581
			api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
582
		{
583
			api_version
584
		} else {
585
			return Err(internal_err(
586
				"Runtime api version call failed (trace)".to_string(),
587
			));
588
		};
589

            
590
		let reference_block = overrides.current_block(reference_hash);
591

            
592
		// Get the actual ethereum transaction.
593
		if let Some(block) = reference_block {
594
			let transactions = block.transactions;
595
			if let Some(transaction) = transactions.get(index) {
596
				let f = || -> RpcResult<_> {
597
					let result = if trace_api_version >= 5 {
598
						// The block is initialized inside "trace_transaction"
599
						api.trace_transaction(parent_block_hash, exts, &transaction, &header)
600
					} else {
601
						// Get core runtime api version
602
						let core_api_version = if let Ok(Some(api_version)) =
603
							api.api_version::<dyn Core<B>>(parent_block_hash)
604
						{
605
							api_version
606
						} else {
607
							return Err(internal_err(
608
								"Runtime api version call failed (core)".to_string(),
609
							));
610
						};
611

            
612
						// Initialize block: calls the "on_initialize" hook on every pallet
613
						// in AllPalletsWithSystem
614
						// This was fine before pallet-message-queue because the XCM messages
615
						// were processed by the "setValidationData" inherent call and not on an
616
						// "on_initialize" hook, which runs before enabling XCM tracing
617
						if core_api_version >= 5 {
618
							api.initialize_block(parent_block_hash, &header)
619
								.map_err(|e| {
620
									internal_err(format!("Runtime api access error: {:?}", e))
621
								})?;
622
						} else {
623
							#[allow(deprecated)]
624
							api.initialize_block_before_version_5(parent_block_hash, &header)
625
								.map_err(|e| {
626
									internal_err(format!("Runtime api access error: {:?}", e))
627
								})?;
628
						}
629

            
630
						if trace_api_version == 4 {
631
							// Pre pallet-message-queue
632
							#[allow(deprecated)]
633
							api.trace_transaction_before_version_5(
634
								parent_block_hash,
635
								exts,
636
								&transaction,
637
							)
638
						} else {
639
							// Pre-london update, legacy transactions.
640
							match transaction {
641
								ethereum::TransactionV2::Legacy(tx) =>
642
								{
643
									#[allow(deprecated)]
644
									api.trace_transaction_before_version_4(
645
										parent_block_hash,
646
										exts,
647
										&tx,
648
									)
649
								}
650
								_ => {
651
									return Err(internal_err(
652
										"Bug: pre-london runtime expects legacy transactions"
653
											.to_string(),
654
									))
655
								}
656
							}
657
						}
658
					};
659

            
660
					result
661
						.map_err(|e| {
662
							internal_err(format!(
663
								"Runtime api access error (version {:?}): {:?}",
664
								trace_api_version, e
665
							))
666
						})?
667
						.map_err(|e| internal_err(format!("DispatchError: {:?}", e)))?;
668

            
669
					Ok(moonbeam_rpc_primitives_debug::Response::Single)
670
				};
671

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

            
730
	fn handle_call_request(
731
		client: Arc<C>,
732
		frontier_backend: Arc<dyn fc_api::Backend<B> + Send + Sync>,
733
		request_block_id: RequestBlockId,
734
		call_params: TraceCallParams,
735
		trace_params: Option<TraceParams>,
736
		raw_max_memory_usage: usize,
737
	) -> RpcResult<Response> {
738
		let (tracer_input, trace_type, tracer_config) = Self::handle_params(trace_params)?;
739

            
740
		let reference_id: BlockId<B> = match request_block_id {
741
			RequestBlockId::Number(n) => Ok(BlockId::Number(n.unique_saturated_into())),
742
			RequestBlockId::Tag(RequestBlockTag::Latest) => {
743
				Ok(BlockId::Number(client.info().best_number))
744
			}
745
			RequestBlockId::Tag(RequestBlockTag::Earliest) => {
746
				Ok(BlockId::Number(0u32.unique_saturated_into()))
747
			}
748
			RequestBlockId::Tag(RequestBlockTag::Pending) => {
749
				Err(internal_err("'pending' blocks are not supported"))
750
			}
751
			RequestBlockId::Hash(eth_hash) => {
752
				match futures::executor::block_on(frontier_backend_client::load_hash::<B, C>(
753
					client.as_ref(),
754
					frontier_backend.as_ref(),
755
					eth_hash,
756
				)) {
757
					Ok(Some(hash)) => Ok(BlockId::Hash(hash)),
758
					Ok(_) => Err(internal_err("Block hash not found".to_string())),
759
					Err(e) => Err(e),
760
				}
761
			}
762
		}?;
763

            
764
		// Get ApiRef. This handle allow to keep changes between txs in an internal buffer.
765
		let api = client.runtime_api();
766
		// Get the header I want to work with.
767
		let Ok(hash) = client.expect_block_hash_from_id(&reference_id) else {
768
			return Err(internal_err("Block header not found"));
769
		};
770
		let header = match client.header(hash) {
771
			Ok(Some(h)) => h,
772
			_ => return Err(internal_err("Block header not found")),
773
		};
774
		// Get parent blockid.
775
		let parent_block_hash = *header.parent_hash();
776

            
777
		// Get DebugRuntimeApi version
778
		let trace_api_version = if let Ok(Some(api_version)) =
779
			api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
780
		{
781
			api_version
782
		} else {
783
			return Err(internal_err(
784
				"Runtime api version call failed (trace)".to_string(),
785
			));
786
		};
787

            
788
		if trace_api_version <= 5 {
789
			return Err(internal_err(
790
				"debug_traceCall not supported with old runtimes".to_string(),
791
			));
792
		}
793

            
794
		let TraceCallParams {
795
			from,
796
			to,
797
			gas_price,
798
			max_fee_per_gas,
799
			max_priority_fee_per_gas,
800
			gas,
801
			value,
802
			data,
803
			nonce,
804
			access_list,
805
			..
806
		} = call_params;
807

            
808
		let (max_fee_per_gas, max_priority_fee_per_gas) =
809
			match (gas_price, max_fee_per_gas, max_priority_fee_per_gas) {
810
				(gas_price, None, None) => {
811
					// Legacy request, all default to gas price.
812
					// A zero-set gas price is None.
813
					let gas_price = if gas_price.unwrap_or_default().is_zero() {
814
						None
815
					} else {
816
						gas_price
817
					};
818
					(gas_price, gas_price)
819
				}
820
				(_, max_fee, max_priority) => {
821
					// eip-1559
822
					// A zero-set max fee is None.
823
					let max_fee = if max_fee.unwrap_or_default().is_zero() {
824
						None
825
					} else {
826
						max_fee
827
					};
828
					// Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`.
829
					if let Some(max_priority) = max_priority {
830
						let max_fee = max_fee.unwrap_or_default();
831
						if max_priority > max_fee {
832
							return Err(internal_err(
833
							"Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`",
834
						));
835
						}
836
					}
837
					(max_fee, max_priority)
838
				}
839
			};
840

            
841
		let gas_limit = match gas {
842
			Some(amount) => amount,
843
			None => {
844
				if let Some(block) = api
845
					.current_block(parent_block_hash)
846
					.map_err(|err| internal_err(format!("runtime error: {:?}", err)))?
847
				{
848
					block.header.gas_limit
849
				} else {
850
					return Err(internal_err(
851
						"block unavailable, cannot query gas limit".to_string(),
852
					));
853
				}
854
			}
855
		};
856
		let data = data.map(|d| d.0).unwrap_or_default();
857

            
858
		let access_list = access_list.unwrap_or_default();
859

            
860
		let f = || -> RpcResult<_> {
861
			let _result = api
862
				.trace_call(
863
					parent_block_hash,
864
					&header,
865
					from.unwrap_or_default(),
866
					to,
867
					data,
868
					value.unwrap_or_default(),
869
					gas_limit,
870
					max_fee_per_gas,
871
					max_priority_fee_per_gas,
872
					nonce,
873
					Some(
874
						access_list
875
							.into_iter()
876
							.map(|item| (item.address, item.storage_keys))
877
							.collect(),
878
					),
879
				)
880
				.map_err(|e| internal_err(format!("Runtime api access error: {:?}", e)))?
881
				.map_err(|e| internal_err(format!("DispatchError: {:?}", e)))?;
882

            
883
			Ok(moonbeam_rpc_primitives_debug::Response::Single)
884
		};
885

            
886
		return match trace_type {
887
			single::TraceType::Raw {
888
				disable_storage,
889
				disable_memory,
890
				disable_stack,
891
			} => {
892
				let mut proxy = moonbeam_client_evm_tracing::listeners::Raw::new(
893
					disable_storage,
894
					disable_memory,
895
					disable_stack,
896
					raw_max_memory_usage,
897
				);
898
				proxy.using(f)?;
899
				Ok(Response::Single(
900
					moonbeam_client_evm_tracing::formatters::Raw::format(proxy).ok_or(
901
						internal_err(
902
							"replayed transaction generated too much data. \
903
						try disabling memory or storage?",
904
						),
905
					)?,
906
				))
907
			}
908
			single::TraceType::CallList => {
909
				let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
910
				proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
911
				proxy.using(f)?;
912
				proxy.finish_transaction();
913
				let response = match tracer_input {
914
					TracerInput::Blockscout => {
915
						moonbeam_client_evm_tracing::formatters::Blockscout::format(proxy)
916
							.ok_or("Trace result is empty.")
917
							.map_err(|e| internal_err(format!("{:?}", e)))
918
					}
919
					TracerInput::CallTracer => {
920
						let mut res =
921
							moonbeam_client_evm_tracing::formatters::CallTracer::format(proxy)
922
								.ok_or("Trace result is empty.")
923
								.map_err(|e| internal_err(format!("{:?}", e)))?;
924
						Ok(res.pop().expect("Trace result is empty."))
925
					}
926
					_ => Err(internal_err(
927
						"Bug: failed to resolve the tracer format.".to_string(),
928
					)),
929
				}?;
930
				Ok(Response::Single(response))
931
			}
932
			not_supported => Err(internal_err(format!(
933
				"Bug: `handle_call_request` does not support {:?}.",
934
				not_supported
935
			))),
936
		};
937
	}
938
}