moonbeam_service/
rpc.rs

1// Copyright 2019-2025 PureStake Inc.
2// This file is part of Moonbeam.
3
4// Moonbeam is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Moonbeam is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16
17//! A collection of node-specific RPC extensions and related background tasks.
18
19pub mod tracing;
20
21use std::{sync::Arc, time::Duration};
22
23use fp_rpc::EthereumRuntimeRPCApi;
24use sp_block_builder::BlockBuilder;
25
26use crate::client::RuntimeApiCollection;
27use crate::RELAY_CHAIN_SLOT_DURATION_MILLIS;
28use cumulus_primitives_core::{ParaId, PersistedValidationData};
29use cumulus_primitives_parachain_inherent::ParachainInherentData;
30use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
31use fc_mapping_sync::{kv::MappingSyncWorker, SyncStrategy};
32use fc_rpc::{pending::ConsensusDataProvider, EthBlockDataCacheTask, EthTask, StorageOverride};
33use fc_rpc_core::types::{FeeHistoryCache, FilterPool, TransactionRequest};
34use futures::StreamExt;
35use jsonrpsee::RpcModule;
36use moonbeam_cli_opt::EthApi as EthApiCmd;
37use moonbeam_core_primitives::{Block, Hash};
38use parity_scale_codec::Encode;
39use sc_client_api::{
40	backend::{AuxStore, Backend, StateBackend, StorageProvider},
41	client::BlockchainEvents,
42	BlockOf,
43};
44use sc_consensus_manual_seal::rpc::{EngineCommand, ManualSeal, ManualSealApiServer};
45use sc_network::service::traits::NetworkService;
46use sc_network_sync::SyncingService;
47use sc_rpc::SubscriptionTaskExecutor;
48use sc_service::TaskManager;
49use sc_transaction_pool_api::TransactionPool;
50use sp_api::{CallApiAt, ProvideRuntimeApi};
51use sp_blockchain::{
52	Backend as BlockchainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata,
53};
54use sp_core::H256;
55use sp_runtime::traits::{BlakeTwo256, Block as BlockT, Header as HeaderT};
56use sp_timestamp::Timestamp;
57use std::collections::BTreeMap;
58
59pub struct MoonbeamEGA;
60
61impl fc_rpc::EstimateGasAdapter for MoonbeamEGA {
62	fn adapt_request(mut request: TransactionRequest) -> TransactionRequest {
63		// Redirect any call to batch precompile:
64		// force usage of batchAll method for estimation
65		use sp_core::H160;
66		const BATCH_PRECOMPILE_ADDRESS: H160 = H160(hex_literal::hex!(
67			"0000000000000000000000000000000000000808"
68		));
69		const BATCH_PRECOMPILE_BATCH_ALL_SELECTOR: [u8; 4] = hex_literal::hex!("96e292b8");
70		if request.to == Some(BATCH_PRECOMPILE_ADDRESS) {
71			match (&mut request.data.input, &mut request.data.data) {
72				(Some(ref mut input), _) => {
73					if input.0.len() >= 4 {
74						input.0[..4].copy_from_slice(&BATCH_PRECOMPILE_BATCH_ALL_SELECTOR);
75					}
76				}
77				(None, Some(ref mut data)) => {
78					if data.0.len() >= 4 {
79						data.0[..4].copy_from_slice(&BATCH_PRECOMPILE_BATCH_ALL_SELECTOR);
80					}
81				}
82				(_, _) => {}
83			};
84		}
85		request
86	}
87}
88
89pub struct MoonbeamEthConfig<C, BE>(std::marker::PhantomData<(C, BE)>);
90
91impl<C, BE> fc_rpc::EthConfig<Block, C> for MoonbeamEthConfig<C, BE>
92where
93	C: sc_client_api::StorageProvider<Block, BE> + Sync + Send + 'static,
94	BE: Backend<Block> + 'static,
95{
96	type EstimateGasAdapter = MoonbeamEGA;
97	type RuntimeStorageOverride =
98		fc_rpc::frontier_backend_client::SystemAccountId20StorageOverride<Block, C, BE>;
99}
100
101/// Full client dependencies.
102pub struct FullDeps<C, P, BE> {
103	/// The client instance to use.
104	pub client: Arc<C>,
105	/// Transaction pool instance.
106	pub pool: Arc<P>,
107	/// Graph pool instance.
108	pub graph: Arc<P>,
109	/// The Node authority flag
110	pub is_authority: bool,
111	/// Network service
112	pub network: Arc<dyn NetworkService>,
113	/// Chain syncing service
114	pub sync: Arc<SyncingService<Block>>,
115	/// EthFilterApi pool.
116	pub filter_pool: Option<FilterPool>,
117	/// The list of optional RPC extensions.
118	pub ethapi_cmd: Vec<EthApiCmd>,
119	/// Frontier Backend.
120	pub frontier_backend: Arc<dyn fc_api::Backend<Block>>,
121	/// Backend.
122	pub backend: Arc<BE>,
123	/// Manual seal command sink
124	pub command_sink: Option<futures::channel::mpsc::Sender<EngineCommand<Hash>>>,
125	/// Maximum number of logs in a query.
126	pub max_past_logs: u32,
127	/// Maximum block range in a query.
128	pub max_block_range: u32,
129	/// Maximum fee history cache size.
130	pub fee_history_limit: u64,
131	/// Fee history cache.
132	pub fee_history_cache: FeeHistoryCache,
133	/// Channels for manual xcm messages (downward, hrmp)
134	pub dev_rpc_data: Option<(
135		flume::Sender<Vec<u8>>,
136		flume::Sender<(ParaId, Vec<u8>)>,
137		Arc<std::sync::atomic::AtomicU32>,
138	)>,
139	/// Ethereum data access overrides.
140	pub overrides: Arc<dyn StorageOverride<Block>>,
141	/// Cache for Ethereum block data.
142	pub block_data_cache: Arc<EthBlockDataCacheTask<Block>>,
143	/// Mandated parent hashes for a given block hash.
144	pub forced_parent_hashes: Option<BTreeMap<H256, H256>>,
145}
146
147pub struct TracingConfig {
148	pub tracing_requesters: crate::rpc::tracing::RpcRequesters,
149	pub trace_filter_max_count: u32,
150}
151
152/// Instantiate all Full RPC extensions.
153pub fn create_full<C, P, BE>(
154	deps: FullDeps<C, P, BE>,
155	subscription_task_executor: SubscriptionTaskExecutor,
156	maybe_tracing_config: Option<TracingConfig>,
157	pubsub_notification_sinks: Arc<
158		fc_mapping_sync::EthereumBlockNotificationSinks<
159			fc_mapping_sync::EthereumBlockNotification<Block>,
160		>,
161	>,
162	pending_consenus_data_provider: Box<dyn ConsensusDataProvider<Block>>,
163	para_id: ParaId,
164) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>>
165where
166	BE: Backend<Block> + 'static,
167	BE::State: StateBackend<BlakeTwo256>,
168	BE::Blockchain: BlockchainBackend<Block>,
169	C: ProvideRuntimeApi<Block> + StorageProvider<Block, BE> + AuxStore,
170	C: BlockchainEvents<Block>,
171	C: HeaderBackend<Block> + HeaderMetadata<Block, Error = BlockChainError> + 'static,
172	C: CallApiAt<Block>,
173	C: Send + Sync + 'static,
174	C::Api: RuntimeApiCollection,
175	P: TransactionPool<Block = Block, Hash = <Block as BlockT>::Hash> + 'static,
176{
177	use fc_rpc::{
178		Eth, EthApiServer, EthFilter, EthFilterApiServer, EthPubSub, EthPubSubApiServer, Net,
179		NetApiServer, TxPool, TxPoolApiServer, Web3, Web3ApiServer,
180	};
181	use moonbeam_dev_rpc::{DevApiServer, DevRpc};
182	use moonbeam_finality_rpc::{MoonbeamFinality, MoonbeamFinalityApiServer};
183	use moonbeam_rpc_debug::{Debug, DebugServer};
184	use moonbeam_rpc_trace::{Trace, TraceServer};
185	use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer};
186	use substrate_frame_rpc_system::{System, SystemApiServer};
187
188	let mut io = RpcModule::new(());
189	let FullDeps {
190		client,
191		pool,
192		graph,
193		is_authority,
194		network,
195		sync,
196		filter_pool,
197		ethapi_cmd,
198		command_sink,
199		frontier_backend,
200		backend: _,
201		max_past_logs,
202		max_block_range,
203		fee_history_limit,
204		fee_history_cache,
205		dev_rpc_data,
206		overrides,
207		block_data_cache,
208		forced_parent_hashes,
209	} = deps;
210
211	io.merge(System::new(Arc::clone(&client), Arc::clone(&pool)).into_rpc())?;
212	io.merge(TransactionPayment::new(Arc::clone(&client)).into_rpc())?;
213
214	// TODO: are we supporting signing?
215	let signers = Vec::new();
216
217	enum Never {}
218	impl<T> fp_rpc::ConvertTransaction<T> for Never {
219		fn convert_transaction(&self, _transaction: pallet_ethereum::Transaction) -> T {
220			// The Never type is not instantiable, but this method requires the type to be
221			// instantiated to be called (`&self` parameter), so if the code compiles we have the
222			// guarantee that this function will never be called.
223			unreachable!()
224		}
225	}
226	let convert_transaction: Option<Never> = None;
227
228	// Need to clone it to avoid moving of `client` variable in closure below.
229	let client_for_cidp = client.clone();
230
231	let pending_create_inherent_data_providers = move |block, _| {
232		// Use timestamp in the future
233		let timestamp = sp_timestamp::InherentDataProvider::new(
234			Timestamp::current()
235				.saturating_add(RELAY_CHAIN_SLOT_DURATION_MILLIS.saturating_mul(100))
236				.into(),
237		);
238
239		let maybe_current_para_head = client_for_cidp.expect_header(block);
240		async move {
241			let current_para_block_head = Some(polkadot_primitives::HeadData(
242				maybe_current_para_head?.encode(),
243			));
244
245			let builder = RelayStateSproofBuilder {
246				para_id,
247				// Use a future relay slot (We derive one from the timestamp)
248				current_slot: polkadot_primitives::Slot::from(
249					timestamp
250						.timestamp()
251						.as_millis()
252						.saturating_div(RELAY_CHAIN_SLOT_DURATION_MILLIS),
253				),
254				included_para_head: current_para_block_head,
255				..Default::default()
256			};
257
258			// Create a dummy parachain inherent data provider which is required to pass
259			// the checks by the para chain system. We use dummy values because in the 'pending context'
260			// neither do we have access to the real values nor do we need them.
261			let (relay_parent_storage_root, relay_chain_state) =
262				builder.into_state_root_and_proof();
263
264			let vfp = PersistedValidationData {
265				// This is a hack to make `cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases`
266				// happy. Relay parent number can't be bigger than u32::MAX.
267				relay_parent_number: u32::MAX,
268				relay_parent_storage_root,
269				..Default::default()
270			};
271			let parachain_inherent_data = ParachainInherentData {
272				validation_data: vfp,
273				relay_chain_state,
274				downward_messages: Default::default(),
275				horizontal_messages: Default::default(),
276				relay_parent_descendants: Default::default(),
277				collator_peer_id: None,
278			};
279
280			Ok((timestamp, parachain_inherent_data))
281		}
282	};
283
284	io.merge(
285		Eth::<_, _, _, _, _, _, MoonbeamEthConfig<_, _>>::new(
286			Arc::clone(&client.clone()),
287			Arc::clone(&pool),
288			graph.clone(),
289			convert_transaction,
290			Arc::clone(&sync),
291			signers,
292			Arc::clone(&overrides),
293			Arc::clone(&frontier_backend),
294			is_authority,
295			Arc::clone(&block_data_cache),
296			fee_history_cache,
297			fee_history_limit,
298			10,
299			forced_parent_hashes,
300			pending_create_inherent_data_providers,
301			Some(pending_consenus_data_provider),
302		)
303		.replace_config::<MoonbeamEthConfig<C, BE>>()
304		.into_rpc(),
305	)?;
306
307	if let Some(filter_pool) = filter_pool {
308		io.merge(
309			EthFilter::new(
310				client.clone(),
311				frontier_backend.clone(),
312				graph.clone(),
313				filter_pool,
314				500_usize, // max stored filters
315				max_past_logs,
316				max_block_range,
317				block_data_cache,
318			)
319			.into_rpc(),
320		)?;
321	}
322
323	io.merge(
324		Net::new(
325			Arc::clone(&client),
326			network.clone(),
327			// Whether to format the `peer_count` response as Hex (default) or not.
328			true,
329		)
330		.into_rpc(),
331	)?;
332
333	io.merge(Web3::new(Arc::clone(&client)).into_rpc())?;
334	io.merge(
335		EthPubSub::new(
336			pool,
337			Arc::clone(&client),
338			sync.clone(),
339			subscription_task_executor,
340			overrides,
341			pubsub_notification_sinks.clone(),
342		)
343		.into_rpc(),
344	)?;
345
346	if ethapi_cmd.contains(&EthApiCmd::Txpool) {
347		io.merge(TxPool::new(Arc::clone(&client), graph).into_rpc())?;
348	}
349
350	io.merge(MoonbeamFinality::new(client.clone(), frontier_backend.clone()).into_rpc())?;
351
352	if let Some(command_sink) = command_sink {
353		io.merge(
354			// We provide the rpc handler with the sending end of the channel to allow the rpc
355			// send EngineCommands to the background block authorship task.
356			ManualSeal::new(command_sink).into_rpc(),
357		)?;
358	};
359
360	if let Some((downward_message_channel, hrmp_message_channel, additional_relay_offset)) =
361		dev_rpc_data
362	{
363		io.merge(
364			DevRpc {
365				downward_message_channel,
366				hrmp_message_channel,
367				additional_relay_offset,
368			}
369			.into_rpc(),
370		)?;
371	}
372
373	if let Some(tracing_config) = maybe_tracing_config {
374		if let Some(trace_filter_requester) = tracing_config.tracing_requesters.trace {
375			io.merge(
376				Trace::new(
377					client,
378					trace_filter_requester,
379					tracing_config.trace_filter_max_count,
380				)
381				.into_rpc(),
382			)?;
383		}
384
385		if let Some(debug_requester) = tracing_config.tracing_requesters.debug {
386			io.merge(Debug::new(debug_requester).into_rpc())?;
387		}
388	}
389
390	Ok(io)
391}
392
393pub struct SpawnTasksParams<'a, B: BlockT, C, BE> {
394	pub task_manager: &'a TaskManager,
395	pub client: Arc<C>,
396	pub substrate_backend: Arc<BE>,
397	pub frontier_backend: Arc<fc_db::Backend<B, C>>,
398	pub filter_pool: Option<FilterPool>,
399	pub overrides: Arc<dyn StorageOverride<B>>,
400	pub fee_history_limit: u64,
401	pub fee_history_cache: FeeHistoryCache,
402}
403
404/// Spawn the tasks that are required to run Moonbeam.
405pub fn spawn_essential_tasks<B, C, BE>(
406	params: SpawnTasksParams<B, C, BE>,
407	sync: Arc<SyncingService<B>>,
408	pubsub_notification_sinks: Arc<
409		fc_mapping_sync::EthereumBlockNotificationSinks<
410			fc_mapping_sync::EthereumBlockNotification<B>,
411		>,
412	>,
413) where
414	C: ProvideRuntimeApi<B> + BlockOf,
415	C: HeaderBackend<B> + HeaderMetadata<B, Error = BlockChainError> + 'static,
416	C: BlockchainEvents<B> + StorageProvider<B, BE>,
417	C: Send + Sync + 'static,
418	C::Api: EthereumRuntimeRPCApi<B>,
419	C::Api: BlockBuilder<B>,
420	B: BlockT<Hash = H256> + Send + Sync + 'static,
421	B::Header: HeaderT<Number = u32>,
422	BE: Backend<B> + 'static,
423	BE::State: StateBackend<BlakeTwo256>,
424{
425	// Frontier offchain DB task. Essential.
426	// Maps emulated ethereum data to substrate native data.
427	match *params.frontier_backend {
428		fc_db::Backend::KeyValue(ref b) => {
429			params.task_manager.spawn_essential_handle().spawn(
430				"frontier-mapping-sync-worker",
431				Some("frontier"),
432				MappingSyncWorker::new(
433					params.client.import_notification_stream(),
434					Duration::new(6, 0),
435					params.client.clone(),
436					params.substrate_backend.clone(),
437					params.overrides.clone(),
438					b.clone(),
439					3,
440					0,
441					SyncStrategy::Parachain,
442					sync.clone(),
443					pubsub_notification_sinks.clone(),
444				)
445				.for_each(|()| futures::future::ready(())),
446			);
447		}
448		fc_db::Backend::Sql(ref b) => {
449			params.task_manager.spawn_essential_handle().spawn_blocking(
450				"frontier-mapping-sync-worker",
451				Some("frontier"),
452				fc_mapping_sync::sql::SyncWorker::run(
453					params.client.clone(),
454					params.substrate_backend.clone(),
455					b.clone(),
456					params.client.import_notification_stream(),
457					fc_mapping_sync::sql::SyncWorkerConfig {
458						read_notification_timeout: Duration::from_secs(10),
459						check_indexed_blocks_interval: Duration::from_secs(60),
460					},
461					fc_mapping_sync::SyncStrategy::Parachain,
462					sync.clone(),
463					pubsub_notification_sinks.clone(),
464				),
465			);
466		}
467	}
468
469	// Frontier `EthFilterApi` maintenance.
470	// Manages the pool of user-created Filters.
471	if let Some(filter_pool) = params.filter_pool {
472		// Each filter is allowed to stay in the pool for 100 blocks.
473		const FILTER_RETAIN_THRESHOLD: u64 = 100;
474		params.task_manager.spawn_essential_handle().spawn(
475			"frontier-filter-pool",
476			Some("frontier"),
477			EthTask::filter_pool_task(
478				Arc::clone(&params.client),
479				filter_pool,
480				FILTER_RETAIN_THRESHOLD,
481			),
482		);
483	}
484
485	// Spawn Frontier FeeHistory cache maintenance task.
486	params.task_manager.spawn_essential_handle().spawn(
487		"frontier-fee-history",
488		Some("frontier"),
489		EthTask::fee_history_task(
490			Arc::clone(&params.client),
491			Arc::clone(&params.overrides),
492			params.fee_history_cache,
493			params.fee_history_limit,
494		),
495	);
496}