moonbeam_dev_rpc/
lib.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
17use cumulus_primitives_core::ParaId;
18use cumulus_primitives_core::XcmpMessageFormat;
19use jsonrpsee::{
20	core::RpcResult,
21	proc_macros::rpc,
22	types::{
23		error::{INTERNAL_ERROR_CODE, INTERNAL_ERROR_MSG},
24		ErrorObjectOwned,
25	},
26};
27use parity_scale_codec::Encode;
28use xcm::opaque::lts::Weight;
29use xcm::v5::prelude::*;
30use xcm_primitives::DEFAULT_PROOF_SIZE;
31
32/// This RPC interface is used to provide methods in dev mode only
33#[rpc(server)]
34#[jsonrpsee::core::async_trait]
35pub trait DevApi {
36	/// Inject a downward xcm message - A message that comes from the relay chain.
37	/// You may provide an arbitrary message, or if you provide an empty byte array,
38	/// Then a default message (DOT transfer down to ALITH) will be injected
39	#[method(name = "xcm_injectDownwardMessage")]
40	async fn inject_downward_message(&self, message: Vec<u8>) -> RpcResult<()>;
41
42	/// Inject an HRMP message - A message that comes from a dedicated channel to a sibling
43	/// parachain.
44	///
45	/// Cumulus Parachain System seems to have a constraint that at most one hrmp message will be
46	/// sent on a channel per block. At least that's what this comment implies:
47	/// https://github.com/paritytech/cumulus/blob/c308c01b/pallets/parachain-system/src/lib.rs#L204
48	/// Neither this RPC, nor the mock inherent data provider make any attempt to enforce this
49	/// constraint. In fact, violating it may be useful for testing.
50	/// The method accepts a sending paraId and a bytearray representing an arbitrary message as
51	/// parameters. If you provide an empty byte array, then a default message representing a
52	/// transfer of the sending paraId's native token will be injected.
53	#[method(name = "xcm_injectHrmpMessage")]
54	async fn inject_hrmp_message(&self, sender: ParaId, message: Vec<u8>) -> RpcResult<()>;
55
56	/// Skip N relay blocks, for testing purposes
57	#[method(name = "test_skipRelayBlocks")]
58	async fn skip_relay_blocks(&self, n: u32) -> RpcResult<()>;
59}
60
61pub struct DevRpc {
62	pub downward_message_channel: flume::Sender<Vec<u8>>,
63	pub hrmp_message_channel: flume::Sender<(ParaId, Vec<u8>)>,
64	pub additional_relay_offset: std::sync::Arc<std::sync::atomic::AtomicU32>,
65}
66
67#[jsonrpsee::core::async_trait]
68impl DevApiServer for DevRpc {
69	async fn inject_downward_message(&self, msg: Vec<u8>) -> RpcResult<()> {
70		let downward_message_channel = self.downward_message_channel.clone();
71		// If no message is supplied, inject a default one.
72		let msg = if msg.is_empty() {
73			xcm::VersionedXcm::<()>::V5(Xcm(vec![
74				ReserveAssetDeposited((Parent, 10000000000000u128).into()),
75				ClearOrigin,
76				BuyExecution {
77					fees: (Parent, 10000000000000u128).into(),
78					weight_limit: Limited(Weight::from_parts(
79						10_000_000_000u64,
80						DEFAULT_PROOF_SIZE,
81					)),
82				},
83				DepositAsset {
84					assets: AllCounted(1).into(),
85					beneficiary: Location::new(
86						0,
87						[AccountKey20 {
88							network: None,
89							key: hex_literal::hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"),
90						}],
91					),
92				},
93			]))
94			.encode()
95		} else {
96			msg
97		};
98
99		// Push the message to the shared channel where it will be queued up
100		// to be injected into an upcoming block.
101		downward_message_channel
102			.send_async(msg)
103			.await
104			.map_err(|err| internal_err(err.to_string()))?;
105
106		Ok(())
107	}
108
109	async fn inject_hrmp_message(&self, sender: ParaId, msg: Vec<u8>) -> RpcResult<()> {
110		let hrmp_message_channel = self.hrmp_message_channel.clone();
111
112		// If no message is supplied, inject a default one.
113		let msg = if msg.is_empty() {
114			let mut mes = XcmpMessageFormat::ConcatenatedVersionedXcm.encode();
115			mes.append(
116				&mut (xcm::VersionedXcm::<()>::V5(Xcm(vec![
117					ReserveAssetDeposited(
118						((Parent, Parachain(sender.into())), 10000000000000u128).into(),
119					),
120					ClearOrigin,
121					BuyExecution {
122						fees: ((Parent, Parachain(sender.into())), 10000000000000u128).into(),
123						weight_limit: Limited(Weight::from_parts(
124							10_000_000_000u64,
125							DEFAULT_PROOF_SIZE,
126						)),
127					},
128					DepositAsset {
129						assets: AllCounted(1).into(),
130						beneficiary: Location::new(
131							0,
132							[AccountKey20 {
133								network: None,
134								key: hex_literal::hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"),
135							}],
136						),
137					},
138				]))
139				.encode()),
140			);
141			mes
142		} else {
143			msg
144		};
145
146		// Push the message to the shared channel where it will be queued up
147		// to be injected into an upcoming block.
148		hrmp_message_channel
149			.send_async((sender, msg))
150			.await
151			.map_err(|err| internal_err(err.to_string()))?;
152
153		Ok(())
154	}
155
156	async fn skip_relay_blocks(&self, n: u32) -> RpcResult<()> {
157		self.additional_relay_offset
158			.fetch_add(n, std::sync::atomic::Ordering::SeqCst);
159		Ok(())
160	}
161}
162
163// This bit cribbed from frontier.
164pub fn internal_err<T: ToString>(message: T) -> ErrorObjectOwned {
165	ErrorObjectOwned::owned(
166		INTERNAL_ERROR_CODE,
167		INTERNAL_ERROR_MSG,
168		Some(message.to_string()),
169	)
170}