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

            
48
pub enum RequesterInput {
49
	Call((RequestBlockId, TraceCallParams)),
50
	Transaction(H256),
51
	Block(RequestBlockId),
52
}
53

            
54
pub enum Response {
55
	Single(single::TransactionTrace),
56
	Block(Vec<block::BlockTransactionTrace>),
57
}
58

            
59
pub type Responder = oneshot::Sender<RpcResult<Response>>;
60
pub type DebugRequester =
61
	TracingUnboundedSender<((RequesterInput, Option<TraceParams>), Responder)>;
62

            
63
pub struct Debug {
64
	pub requester: DebugRequester,
65
}
66

            
67
impl Debug {
68
	pub fn new(requester: DebugRequester) -> Self {
69
		Self { requester }
70
	}
71
}
72

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

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

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

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

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

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

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

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

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

            
162
pub struct DebugHandler<B: BlockT, C, BE>(PhantomData<(B, C, BE)>);
163

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

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

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

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

            
272
						tokio::task::spawn(async move {
273
							let _ = response_tx.send(
274
								async {
275
									let _permit = permit_pool.acquire().await;
276

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

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

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

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

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

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

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

            
418
		// Get parent blockid.
419
		let parent_block_hash = *header.parent_hash();
420

            
421
		let statuses = overrides
422
			.current_transaction_statuses(hash)
423
			.unwrap_or_default();
424

            
425
		// Known ethereum transaction hashes.
426
		let eth_transactions_by_index: BTreeMap<u32, H256> = statuses
427
			.iter()
428
			.map(|t| (t.transaction_index, t.transaction_hash))
429
			.collect();
430

            
431
		let eth_tx_hashes: Vec<_> = eth_transactions_by_index.values().cloned().collect();
432

            
433
		// If there are no ethereum transactions in the block return empty trace right away.
434
		if eth_tx_hashes.is_empty() {
435
			return Ok(Response::Block(vec![]));
436
		}
437

            
438
		// Get block extrinsics.
439
		let exts = blockchain
440
			.body(hash)
441
			.map_err(|e| internal_err(format!("Fail to read blockchain db: {:?}", e)))?
442
			.unwrap_or_default();
443

            
444
		// Get DebugRuntimeApi version
445
		let trace_api_version = if let Ok(Some(api_version)) =
446
			api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
447
		{
448
			api_version
449
		} else {
450
			return Err(internal_err(
451
				"Runtime api version call failed (trace)".to_string(),
452
			));
453
		};
454

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

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

            
486
				#[allow(deprecated)]
487
				api.trace_block_before_version_5(parent_block_hash, exts, eth_tx_hashes)
488
			};
489

            
490
			result
491
				.map_err(|e| {
492
					internal_err(format!(
493
						"Blockchain error when replaying block {} : {:?}",
494
						reference_id, e
495
					))
496
				})?
497
				.map_err(|e| {
498
					internal_err(format!(
499
						"Internal runtime error when replaying block {} : {:?}",
500
						reference_id, e
501
					))
502
				})?;
503

            
504
			Ok(moonbeam_rpc_primitives_debug::Response::Block)
505
		};
506

            
507
		return match trace_type {
508
			single::TraceType::CallList => {
509
				let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
510
				proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
511
				proxy.using(f)?;
512
				proxy.finish_transaction();
513
				let response = match tracer_input {
514
					TracerInput::CallTracer => {
515
						let result =
516
							moonbeam_client_evm_tracing::formatters::CallTracer::format(proxy)
517
								.ok_or("Trace result is empty.")
518
								.map_err(|e| internal_err(format!("{:?}", e)))?
519
								.into_iter()
520
								.map(|mut trace| {
521
									if let Some(transaction_hash) =
522
										eth_transactions_by_index.get(&trace.tx_position)
523
									{
524
										trace.tx_hash = *transaction_hash;
525
									}
526
									trace
527
								})
528
								.collect::<Vec<BlockTransactionTrace>>();
529

            
530
						Ok(result)
531
					}
532
					_ => Err(internal_err(
533
						"Bug: failed to resolve the tracer format.".to_string(),
534
					)),
535
				}?;
536

            
537
				Ok(Response::Block(response))
538
			}
539
			_ => Err(internal_err(
540
				"debug_traceBlock functions currently only support callList mode (enabled
541
				by providing `{{'tracer': 'callTracer'}}` in the request)."
542
					.to_string(),
543
			)),
544
		};
545
	}
546

            
547
	/// Replays a transaction in the Runtime at a given block height.
548
	///
549
	/// In order to successfully reproduce the result of the original transaction we need a correct
550
	/// state to replay over.
551
	///
552
	/// Substrate allows to apply extrinsics in the Runtime and thus creating an overlayed state.
553
	/// These overlayed changes will live in-memory for the lifetime of the ApiRef.
554
	fn handle_transaction_request(
555
		client: Arc<C>,
556
		backend: Arc<BE>,
557
		frontier_backend: Arc<dyn fc_api::Backend<B> + Send + Sync>,
558
		transaction_hash: H256,
559
		params: Option<TraceParams>,
560
		overrides: Arc<dyn StorageOverride<B>>,
561
		raw_max_memory_usage: usize,
562
	) -> RpcResult<Response> {
563
		let (tracer_input, trace_type, tracer_config) = Self::handle_params(params)?;
564

            
565
		let (hash, index) =
566
			match futures::executor::block_on(frontier_backend_client::load_transactions::<B, C>(
567
				client.as_ref(),
568
				frontier_backend.as_ref(),
569
				transaction_hash,
570
				false,
571
			)) {
572
				Ok(Some((hash, index))) => (hash, index as usize),
573
				Ok(None) => return Err(internal_err("Transaction hash not found".to_string())),
574
				Err(e) => return Err(e),
575
			};
576

            
577
		let reference_id =
578
			match futures::executor::block_on(frontier_backend_client::load_hash::<B, C>(
579
				client.as_ref(),
580
				frontier_backend.as_ref(),
581
				hash,
582
			)) {
583
				Ok(Some(hash)) => BlockId::Hash(hash),
584
				Ok(_) => return Err(internal_err("Block hash not found".to_string())),
585
				Err(e) => return Err(e),
586
			};
587
		// Get ApiRef. This handle allow to keep changes between txs in an internal buffer.
588
		let mut api = client.runtime_api();
589

            
590
		// Enable proof recording
591
		api.record_proof();
592
		api.proof_recorder().map(|recorder| {
593
			let ext = sp_trie::proof_size_extension::ProofSizeExt::new(recorder);
594
			api.register_extension(ext);
595
		});
596

            
597
		// Get Blockchain backend
598
		let blockchain = backend.blockchain();
599
		// Get the header I want to work with.
600
		let Ok(reference_hash) = client.expect_block_hash_from_id(&reference_id) else {
601
			return Err(internal_err("Block header not found"));
602
		};
603
		let header = match client.header(reference_hash) {
604
			Ok(Some(h)) => h,
605
			_ => return Err(internal_err("Block header not found")),
606
		};
607
		// Get parent blockid.
608
		let parent_block_hash = *header.parent_hash();
609

            
610
		// Get block extrinsics.
611
		let exts = blockchain
612
			.body(reference_hash)
613
			.map_err(|e| internal_err(format!("Fail to read blockchain db: {:?}", e)))?
614
			.unwrap_or_default();
615

            
616
		// Get DebugRuntimeApi version
617
		let trace_api_version = if let Ok(Some(api_version)) =
618
			api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
619
		{
620
			api_version
621
		} else {
622
			return Err(internal_err(
623
				"Runtime api version call failed (trace)".to_string(),
624
			));
625
		};
626

            
627
		let reference_block = overrides.current_block(reference_hash);
628

            
629
		// Get the actual ethereum transaction.
630
		if let Some(block) = reference_block {
631
			let transactions = block.transactions;
632
			if let Some(transaction) = transactions.get(index) {
633
				let f = || -> RpcResult<_> {
634
					let result = if trace_api_version >= 5 {
635
						// The block is initialized inside "trace_transaction"
636
						api.trace_transaction(parent_block_hash, exts, &transaction, &header)
637
					} else {
638
						// Get core runtime api version
639
						let core_api_version = if let Ok(Some(api_version)) =
640
							api.api_version::<dyn Core<B>>(parent_block_hash)
641
						{
642
							api_version
643
						} else {
644
							return Err(internal_err(
645
								"Runtime api version call failed (core)".to_string(),
646
							));
647
						};
648

            
649
						// Initialize block: calls the "on_initialize" hook on every pallet
650
						// in AllPalletsWithSystem
651
						// This was fine before pallet-message-queue because the XCM messages
652
						// were processed by the "setValidationData" inherent call and not on an
653
						// "on_initialize" hook, which runs before enabling XCM tracing
654
						if core_api_version >= 5 {
655
							api.initialize_block(parent_block_hash, &header)
656
								.map_err(|e| {
657
									internal_err(format!("Runtime api access error: {:?}", e))
658
								})?;
659
						} else {
660
							#[allow(deprecated)]
661
							api.initialize_block_before_version_5(parent_block_hash, &header)
662
								.map_err(|e| {
663
									internal_err(format!("Runtime api access error: {:?}", e))
664
								})?;
665
						}
666

            
667
						if trace_api_version == 4 {
668
							// Pre pallet-message-queue
669
							#[allow(deprecated)]
670
							api.trace_transaction_before_version_5(
671
								parent_block_hash,
672
								exts,
673
								&transaction,
674
							)
675
						} else {
676
							// Pre-london update, legacy transactions.
677
							match transaction {
678
								ethereum::TransactionV2::Legacy(tx) =>
679
								{
680
									#[allow(deprecated)]
681
									api.trace_transaction_before_version_4(
682
										parent_block_hash,
683
										exts,
684
										&tx,
685
									)
686
								}
687
								_ => {
688
									return Err(internal_err(
689
										"Bug: pre-london runtime expects legacy transactions"
690
											.to_string(),
691
									))
692
								}
693
							}
694
						}
695
					};
696

            
697
					result
698
						.map_err(|e| {
699
							internal_err(format!(
700
								"Runtime api access error (version {:?}): {:?}",
701
								trace_api_version, e
702
							))
703
						})?
704
						.map_err(|e| internal_err(format!("DispatchError: {:?}", e)))?;
705

            
706
					Ok(moonbeam_rpc_primitives_debug::Response::Single)
707
				};
708

            
709
				return match trace_type {
710
					single::TraceType::Raw {
711
						disable_storage,
712
						disable_memory,
713
						disable_stack,
714
					} => {
715
						let mut proxy = moonbeam_client_evm_tracing::listeners::Raw::new(
716
							disable_storage,
717
							disable_memory,
718
							disable_stack,
719
							raw_max_memory_usage,
720
						);
721
						proxy.using(f)?;
722
						Ok(Response::Single(
723
							moonbeam_client_evm_tracing::formatters::Raw::format(proxy).ok_or(
724
								internal_err(
725
									"replayed transaction generated too much data. \
726
								try disabling memory or storage?",
727
								),
728
							)?,
729
						))
730
					}
731
					single::TraceType::CallList => {
732
						let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
733
						proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
734
						proxy.using(f)?;
735
						proxy.finish_transaction();
736
						let response = match tracer_input {
737
							TracerInput::Blockscout => {
738
								moonbeam_client_evm_tracing::formatters::Blockscout::format(proxy)
739
									.ok_or("Trace result is empty.")
740
									.map_err(|e| internal_err(format!("{:?}", e)))
741
							}
742
							TracerInput::CallTracer => {
743
								let mut res =
744
									moonbeam_client_evm_tracing::formatters::CallTracer::format(
745
										proxy,
746
									)
747
									.ok_or("Trace result is empty.")
748
									.map_err(|e| internal_err(format!("{:?}", e)))?;
749
								Ok(res.pop().expect("Trace result is empty.").result)
750
							}
751
							_ => Err(internal_err(
752
								"Bug: failed to resolve the tracer format.".to_string(),
753
							)),
754
						}?;
755
						Ok(Response::Single(response))
756
					}
757
					not_supported => Err(internal_err(format!(
758
						"Bug: `handle_transaction_request` does not support {:?}.",
759
						not_supported
760
					))),
761
				};
762
			}
763
		}
764
		Err(internal_err("Runtime block call failed".to_string()))
765
	}
766

            
767
	fn handle_call_request(
768
		client: Arc<C>,
769
		frontier_backend: Arc<dyn fc_api::Backend<B> + Send + Sync>,
770
		request_block_id: RequestBlockId,
771
		call_params: TraceCallParams,
772
		trace_params: Option<TraceParams>,
773
		raw_max_memory_usage: usize,
774
	) -> RpcResult<Response> {
775
		let (tracer_input, trace_type, tracer_config) = Self::handle_params(trace_params)?;
776

            
777
		let reference_id: BlockId<B> = match request_block_id {
778
			RequestBlockId::Number(n) => Ok(BlockId::Number(n.unique_saturated_into())),
779
			RequestBlockId::Tag(RequestBlockTag::Latest) => {
780
				Ok(BlockId::Number(client.info().best_number))
781
			}
782
			RequestBlockId::Tag(RequestBlockTag::Earliest) => {
783
				Ok(BlockId::Number(0u32.unique_saturated_into()))
784
			}
785
			RequestBlockId::Tag(RequestBlockTag::Pending) => {
786
				Err(internal_err("'pending' blocks are not supported"))
787
			}
788
			RequestBlockId::Hash(eth_hash) => {
789
				match futures::executor::block_on(frontier_backend_client::load_hash::<B, C>(
790
					client.as_ref(),
791
					frontier_backend.as_ref(),
792
					eth_hash,
793
				)) {
794
					Ok(Some(hash)) => Ok(BlockId::Hash(hash)),
795
					Ok(_) => Err(internal_err("Block hash not found".to_string())),
796
					Err(e) => Err(e),
797
				}
798
			}
799
		}?;
800

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

            
804
		// Enable proof recording
805
		api.record_proof();
806
		api.proof_recorder().map(|recorder| {
807
			let ext = sp_trie::proof_size_extension::ProofSizeExt::new(recorder);
808
			api.register_extension(ext);
809
		});
810

            
811
		// Get the header I want to work with.
812
		let Ok(hash) = client.expect_block_hash_from_id(&reference_id) else {
813
			return Err(internal_err("Block header not found"));
814
		};
815
		let header = match client.header(hash) {
816
			Ok(Some(h)) => h,
817
			_ => return Err(internal_err("Block header not found")),
818
		};
819
		// Get parent blockid.
820
		let parent_block_hash = *header.parent_hash();
821

            
822
		// Get DebugRuntimeApi version
823
		let trace_api_version = if let Ok(Some(api_version)) =
824
			api.api_version::<dyn DebugRuntimeApi<B>>(parent_block_hash)
825
		{
826
			api_version
827
		} else {
828
			return Err(internal_err(
829
				"Runtime api version call failed (trace)".to_string(),
830
			));
831
		};
832

            
833
		if trace_api_version <= 5 {
834
			return Err(internal_err(
835
				"debug_traceCall not supported with old runtimes".to_string(),
836
			));
837
		}
838

            
839
		let TraceCallParams {
840
			from,
841
			to,
842
			gas_price,
843
			max_fee_per_gas,
844
			max_priority_fee_per_gas,
845
			gas,
846
			value,
847
			data,
848
			nonce,
849
			access_list,
850
			..
851
		} = call_params;
852

            
853
		let (max_fee_per_gas, max_priority_fee_per_gas) =
854
			match (gas_price, max_fee_per_gas, max_priority_fee_per_gas) {
855
				(gas_price, None, None) => {
856
					// Legacy request, all default to gas price.
857
					// A zero-set gas price is None.
858
					let gas_price = if gas_price.unwrap_or_default().is_zero() {
859
						None
860
					} else {
861
						gas_price
862
					};
863
					(gas_price, gas_price)
864
				}
865
				(_, max_fee, max_priority) => {
866
					// eip-1559
867
					// A zero-set max fee is None.
868
					let max_fee = if max_fee.unwrap_or_default().is_zero() {
869
						None
870
					} else {
871
						max_fee
872
					};
873
					// Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`.
874
					if let Some(max_priority) = max_priority {
875
						let max_fee = max_fee.unwrap_or_default();
876
						if max_priority > max_fee {
877
							return Err(internal_err(
878
							"Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`",
879
						));
880
						}
881
					}
882
					(max_fee, max_priority)
883
				}
884
			};
885

            
886
		let gas_limit = match gas {
887
			Some(amount) => amount,
888
			None => {
889
				if let Some(block) = api
890
					.current_block(parent_block_hash)
891
					.map_err(|err| internal_err(format!("runtime error: {:?}", err)))?
892
				{
893
					block.header.gas_limit
894
				} else {
895
					return Err(internal_err(
896
						"block unavailable, cannot query gas limit".to_string(),
897
					));
898
				}
899
			}
900
		};
901
		let data = data.map(|d| d.0).unwrap_or_default();
902

            
903
		let access_list = access_list.unwrap_or_default();
904

            
905
		let f = || -> RpcResult<_> {
906
			let _result = api
907
				.trace_call(
908
					parent_block_hash,
909
					&header,
910
					from.unwrap_or_default(),
911
					to,
912
					data,
913
					value.unwrap_or_default(),
914
					gas_limit,
915
					max_fee_per_gas,
916
					max_priority_fee_per_gas,
917
					nonce,
918
					Some(
919
						access_list
920
							.into_iter()
921
							.map(|item| (item.address, item.storage_keys))
922
							.collect(),
923
					),
924
				)
925
				.map_err(|e| internal_err(format!("Runtime api access error: {:?}", e)))?
926
				.map_err(|e| internal_err(format!("DispatchError: {:?}", e)))?;
927

            
928
			Ok(moonbeam_rpc_primitives_debug::Response::Single)
929
		};
930

            
931
		return match trace_type {
932
			single::TraceType::Raw {
933
				disable_storage,
934
				disable_memory,
935
				disable_stack,
936
			} => {
937
				let mut proxy = moonbeam_client_evm_tracing::listeners::Raw::new(
938
					disable_storage,
939
					disable_memory,
940
					disable_stack,
941
					raw_max_memory_usage,
942
				);
943
				proxy.using(f)?;
944
				Ok(Response::Single(
945
					moonbeam_client_evm_tracing::formatters::Raw::format(proxy).ok_or(
946
						internal_err(
947
							"replayed transaction generated too much data. \
948
						try disabling memory or storage?",
949
						),
950
					)?,
951
				))
952
			}
953
			single::TraceType::CallList => {
954
				let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default();
955
				proxy.with_log = tracer_config.map_or(false, |cfg| cfg.with_log);
956
				proxy.using(f)?;
957
				proxy.finish_transaction();
958
				let response = match tracer_input {
959
					TracerInput::Blockscout => {
960
						moonbeam_client_evm_tracing::formatters::Blockscout::format(proxy)
961
							.ok_or("Trace result is empty.")
962
							.map_err(|e| internal_err(format!("{:?}", e)))
963
					}
964
					TracerInput::CallTracer => {
965
						let mut res =
966
							moonbeam_client_evm_tracing::formatters::CallTracer::format(proxy)
967
								.ok_or("Trace result is empty.")
968
								.map_err(|e| internal_err(format!("{:?}", e)))?;
969
						Ok(res.pop().expect("Trace result is empty.").result)
970
					}
971
					_ => Err(internal_err(
972
						"Bug: failed to resolve the tracer format.".to_string(),
973
					)),
974
				}?;
975
				Ok(Response::Single(response))
976
			}
977
			not_supported => Err(internal_err(format!(
978
				"Bug: `handle_call_request` does not support {:?}.",
979
				not_supported
980
			))),
981
		};
982
	}
983
}