From 7369f6b266d9ded2fb1555493210805d382e8296 Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 23 Dec 2024 09:56:12 -0300 Subject: [PATCH] Add xcm upm mock endpoint --- client/manual-xcm/src/lib.rs | 49 ++++++++++++- client/service-container-chain/src/rpc.rs | 17 ++++- .../nodes/frontier/src/rpc/mod.rs | 11 ++- .../nodes/frontier/src/service.rs | 3 +- container-chains/nodes/simple/src/rpc.rs | 11 ++- container-chains/nodes/simple/src/service.rs | 3 +- node/src/rpc.rs | 11 ++- node/src/service.rs | 3 +- .../tanssi-relay-service/src/dev_service.rs | 28 +++++++- .../xcm/test-xcm-send-upward.ts | 68 +++++++++++++++++++ test/util/xcm.ts | 14 +++- 11 files changed, 203 insertions(+), 15 deletions(-) create mode 100644 test/suites/dev-tanssi-relay/xcm/test-xcm-send-upward.ts diff --git a/client/manual-xcm/src/lib.rs b/client/manual-xcm/src/lib.rs index c01979049..fc19a257d 100644 --- a/client/manual-xcm/src/lib.rs +++ b/client/manual-xcm/src/lib.rs @@ -29,11 +29,17 @@ const DEFAULT_PROOF_SIZE: u64 = 64 * 1024; #[jsonrpsee::core::async_trait] pub trait ManualXcmApi { /// Inject a downward xcm message - A message that comes from the relay chain. - /// You may provide an arbitrary message, or if you provide an emtpy byte array, + /// You may provide an arbitrary message, or if you provide an empty byte array, /// Then a default message (DOT transfer down to ALITH) will be injected #[method(name = "xcm_injectDownwardMessage")] async fn inject_downward_message(&self, message: Vec) -> RpcResult<()>; + /// Inject a upward xcm message - A message that comes from the a parachain. + /// You may provide an arbitrary message, or if you provide an empty byte array, + /// Then a default message (DOT transfer up to ALITH) will be injected + #[method(name = "xcm_injectUpwardMessage")] + async fn inject_upward_message(&self, message: Vec) -> RpcResult<()>; + /// Inject an HRMP message - A message that comes from a dedicated channel to a sibling /// parachain. /// @@ -51,6 +57,7 @@ pub trait ManualXcmApi { pub struct ManualXcm { pub downward_message_channel: flume::Sender>, + pub upward_message_channel: flume::Sender>, pub hrmp_message_channel: flume::Sender<(ParaId, Vec)>, } @@ -96,6 +103,46 @@ impl ManualXcmApiServer for ManualXcm { Ok(()) } + async fn inject_upward_message(&self, msg: Vec) -> RpcResult<()> { + let upward_message_channel = self.upward_message_channel.clone(); + // If no message is supplied, inject a default one. + let msg = if msg.is_empty() { + staging_xcm::VersionedXcm::<()>::V4(Xcm(vec![ + ReserveAssetDeposited((Here, 10000000000000u128).into()), + ClearOrigin, + BuyExecution { + fees: (Here, 10000000000000u128).into(), + weight_limit: Limited(Weight::from_parts( + 4_000_000_000u64, + DEFAULT_PROOF_SIZE * 2, + )), + }, + DepositAsset { + assets: AllCounted(1).into(), + beneficiary: Location::new( + 0, + [AccountKey20 { + network: None, + key: hex_literal::hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"), + }], + ), + }, + ])) + .encode() + } else { + msg + }; + + // Push the message to the shared channel where it will be queued up + // to be injected in to an upcoming block. + upward_message_channel + .send_async(msg) + .await + .map_err(|err| internal_err(err.to_string()))?; + + Ok(()) + } + async fn inject_hrmp_message(&self, sender: ParaId, msg: Vec) -> RpcResult<()> { let hrmp_message_channel = self.hrmp_message_channel.clone(); diff --git a/client/service-container-chain/src/rpc.rs b/client/service-container-chain/src/rpc.rs index fec3bb165..f2c53a23f 100644 --- a/client/service-container-chain/src/rpc.rs +++ b/client/service-container-chain/src/rpc.rs @@ -51,7 +51,11 @@ pub struct FullDeps { /// Manual seal command sink pub command_sink: Option>>, /// Channels for manual xcm messages (downward, hrmp) - pub xcm_senders: Option<(flume::Sender>, flume::Sender<(ParaId, Vec)>)>, + pub xcm_senders: Option<( + flume::Sender>, + flume::Sender>, + flume::Sender<(ParaId, Vec)>, + )>, } tp_traits::alias!( @@ -103,10 +107,13 @@ where )?; }; - if let Some((downward_message_channel, hrmp_message_channel)) = xcm_senders { + if let Some((downward_message_channel, upward_message_channel, hrmp_message_channel)) = + xcm_senders + { module.merge( ManualXcm { downward_message_channel, + upward_message_channel, hrmp_message_channel, } .into_rpc(), @@ -137,7 +144,11 @@ pub mod generate_rpc_builder { sc_transaction_pool::TransactionPoolHandle>; pub type CommandSink = futures::channel::mpsc::Sender>; - pub type XcmSenders = (flume::Sender>, flume::Sender<(ParaId, Vec)>); + pub type XcmSenders = ( + flume::Sender>, + flume::Sender>, + flume::Sender<(ParaId, Vec)>, + ); pub type Network = dyn sc_network::service::traits::NetworkService; pub type CompleteRpcBuilder = Box< dyn Fn(sc_rpc::SubscriptionTaskExecutor) -> Result, ServiceError>, diff --git a/container-chains/nodes/frontier/src/rpc/mod.rs b/container-chains/nodes/frontier/src/rpc/mod.rs index a62299808..b76b0747c 100644 --- a/container-chains/nodes/frontier/src/rpc/mod.rs +++ b/container-chains/nodes/frontier/src/rpc/mod.rs @@ -124,7 +124,11 @@ pub struct FullDeps { /// Manual seal command sink pub command_sink: Option>>, /// Channels for manual xcm messages (downward, hrmp) - pub xcm_senders: Option<(flume::Sender>, flume::Sender<(ParaId, Vec)>)>, + pub xcm_senders: Option<( + flume::Sender>, + flume::Sender>, + flume::Sender<(ParaId, Vec)>, + )>, } /// Instantiate all Full RPC extensions. @@ -320,10 +324,13 @@ where )?; io.merge(tx_pool.into_rpc())?; - if let Some((downward_message_channel, hrmp_message_channel)) = xcm_senders { + if let Some((downward_message_channel, upward_message_channel, hrmp_message_channel)) = + xcm_senders + { io.merge( ManualXcm { downward_message_channel, + upward_message_channel, hrmp_message_channel, } .into_rpc(), diff --git a/container-chains/nodes/frontier/src/service.rs b/container-chains/nodes/frontier/src/service.rs index 1f19048c5..996113bc5 100644 --- a/container-chains/nodes/frontier/src/service.rs +++ b/container-chains/nodes/frontier/src/service.rs @@ -369,8 +369,9 @@ pub async fn start_dev_node( if parachain_config.role.is_authority() { let client = node_builder.client.clone(); let (downward_xcm_sender, downward_xcm_receiver) = flume::bounded::>(100); + let (upward_xcm_sender, _) = flume::bounded::>(100); let (hrmp_xcm_sender, hrmp_xcm_receiver) = flume::bounded::<(ParaId, Vec)>(100); - xcm_senders = Some((downward_xcm_sender, hrmp_xcm_sender)); + xcm_senders = Some((downward_xcm_sender, upward_xcm_sender, hrmp_xcm_sender)); let authorities = vec![get_aura_id_from_seed("alice")]; diff --git a/container-chains/nodes/simple/src/rpc.rs b/container-chains/nodes/simple/src/rpc.rs index 7c3bf2fb0..7bee53dcd 100644 --- a/container-chains/nodes/simple/src/rpc.rs +++ b/container-chains/nodes/simple/src/rpc.rs @@ -49,7 +49,11 @@ pub struct FullDeps { /// Manual seal command sink pub command_sink: Option>>, /// Channels for manual xcm messages (downward, hrmp) - pub xcm_senders: Option<(flume::Sender>, flume::Sender<(ParaId, Vec)>)>, + pub xcm_senders: Option<( + flume::Sender>, + flume::Sender>, + flume::Sender<(ParaId, Vec)>, + )>, } /// Instantiate all RPC extensions. @@ -89,10 +93,13 @@ where )?; }; - if let Some((downward_message_channel, hrmp_message_channel)) = xcm_senders { + if let Some((downward_message_channel, upward_message_channel, hrmp_message_channel)) = + xcm_senders + { module.merge( ManualXcm { downward_message_channel, + upward_message_channel, hrmp_message_channel, } .into_rpc(), diff --git a/container-chains/nodes/simple/src/service.rs b/container-chains/nodes/simple/src/service.rs index cc4052743..1eadfc4a6 100644 --- a/container-chains/nodes/simple/src/service.rs +++ b/container-chains/nodes/simple/src/service.rs @@ -208,8 +208,9 @@ pub async fn start_dev_node( if parachain_config.role.is_authority() { let client = node_builder.client.clone(); let (downward_xcm_sender, downward_xcm_receiver) = flume::bounded::>(100); + let (upward_xcm_sender, _) = flume::bounded::>(100); let (hrmp_xcm_sender, hrmp_xcm_receiver) = flume::bounded::<(ParaId, Vec)>(100); - xcm_senders = Some((downward_xcm_sender, hrmp_xcm_sender)); + xcm_senders = Some((downward_xcm_sender, upward_xcm_sender, hrmp_xcm_sender)); let authorities = vec![get_aura_id_from_seed("alice")]; diff --git a/node/src/rpc.rs b/node/src/rpc.rs index 26317acdd..60e06d798 100644 --- a/node/src/rpc.rs +++ b/node/src/rpc.rs @@ -54,7 +54,11 @@ pub struct FullDeps { /// Manual seal command sink pub command_sink: Option>>, /// Channels for manual xcm messages (downward, hrmp) - pub xcm_senders: Option<(flume::Sender>, flume::Sender<(ParaId, Vec)>)>, + pub xcm_senders: Option<( + flume::Sender>, + flume::Sender>, + flume::Sender<(ParaId, Vec)>, + )>, } /// Instantiate all RPC extensions. @@ -98,10 +102,13 @@ where )?; }; - if let Some((downward_message_channel, hrmp_message_channel)) = xcm_senders { + if let Some((downward_message_channel, upward_message_channel, hrmp_message_channel)) = + xcm_senders + { module.merge( ManualXcm { downward_message_channel, + upward_message_channel, hrmp_message_channel, } .into_rpc(), diff --git a/node/src/service.rs b/node/src/service.rs index 68780f9c8..73a82007f 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -896,8 +896,9 @@ pub fn start_dev_node( if parachain_config.role.is_authority() { let client = node_builder.client.clone(); let (downward_xcm_sender, downward_xcm_receiver) = flume::bounded::>(100); + let (upward_xcm_sender, _) = flume::bounded::>(100); let (hrmp_xcm_sender, hrmp_xcm_receiver) = flume::bounded::<(ParaId, Vec)>(100); - xcm_senders = Some((downward_xcm_sender, hrmp_xcm_sender)); + xcm_senders = Some((downward_xcm_sender, upward_xcm_sender, hrmp_xcm_sender)); command_sink = node_builder.install_manual_seal(ManualSealConfiguration { block_import, diff --git a/solo-chains/node/tanssi-relay-service/src/dev_service.rs b/solo-chains/node/tanssi-relay-service/src/dev_service.rs index 9638e61e8..762ab520c 100644 --- a/solo-chains/node/tanssi-relay-service/src/dev_service.rs +++ b/solo-chains/node/tanssi-relay-service/src/dev_service.rs @@ -43,6 +43,7 @@ use { polkadot_core_primitives::{AccountId, Balance, Block, Hash, Nonce}, polkadot_node_core_parachains_inherent::Error as InherentError, polkadot_overseer::Handle, + polkadot_parachain_primitives::primitives::UpwardMessages, polkadot_primitives::{ runtime_api::ParachainHost, BackedCandidate, CandidateCommitments, CandidateDescriptor, CollatorPair, CommittedCandidateReceipt, CompactStatement, EncodeAs, @@ -77,6 +78,7 @@ use { // We use this key to store whether we want the para inherent mocker to be active const PARA_INHERENT_SELECTOR_AUX_KEY: &[u8] = b"__DEV_PARA_INHERENT_SELECTOR"; +const XMC_UPM_SELECTOR_AUX_KEY: &[u8] = b"__DEV_XMC_UMP_SELECTOR"; pub type FullBackend = service::TFullBackend; @@ -231,6 +233,7 @@ struct MockParachainsInherentDataProvider + ProvideRunti impl + ProvideRuntimeApi> MockParachainsInherentDataProvider where C::Api: ParachainHost, + C: AuxStore, { pub fn new(client: Arc, parent: Hash, keystore: KeystorePtr) -> Self { MockParachainsInherentDataProvider { @@ -388,6 +391,14 @@ where &validation_code_hash, ); let collator_signature = collator_pair.sign(&payload); + + let mut upm_messages = UpwardMessages::new(); + + client + .get_aux(XMC_UPM_SELECTOR_AUX_KEY) + .expect("Should be able to query aux storage; qed") + .map(|upm_message| upm_messages.force_push(upm_message)); + // generate a candidate with most of the values mocked let candidate = CommittedCandidateReceipt:: { descriptor: CandidateDescriptor:: { @@ -402,7 +413,7 @@ where validation_code_hash, }, commitments: CandidateCommitments:: { - upward_messages: Default::default(), + upward_messages: upm_messages, horizontal_messages: Default::default(), new_validation_code: None, head_data: parachain_mocked_header.clone().encode().into(), @@ -594,6 +605,9 @@ fn new_full< let (downward_mock_para_inherent_sender, downward_mock_para_inherent_receiver) = flume::bounded::>(100); + let (_, upward_mock_para_inherent_receiver) = + flume::bounded::>(100); + let (network, system_rpc_tx, tx_handler_controller, network_starter, sync_service) = service::build_network(service::BuildNetworkParams { config: &config, @@ -705,12 +719,16 @@ fn new_full< let client_clone = client_clone.clone(); let keystore = keystore_clone.clone(); let downward_mock_para_inherent_receiver = downward_mock_para_inherent_receiver.clone(); + let upward_mock_para_inherent_receiver = upward_mock_para_inherent_receiver.clone(); async move { let downward_mock_para_inherent_receiver = downward_mock_para_inherent_receiver.clone(); // here we only take the last one let para_inherent_decider_messages: Vec> = downward_mock_para_inherent_receiver.drain().collect(); + let upward_mock_para_inherent_receiver = upward_mock_para_inherent_receiver.clone(); + let para_inherent_upward_messages: Vec> = upward_mock_para_inherent_receiver.drain().collect(); + // If there is a value to be updated, we update it if let Some(value) = para_inherent_decider_messages.last() { client_clone @@ -719,7 +737,15 @@ fn new_full< &[], ) .expect("Should be able to write to aux storage; qed"); + } + if let Some(value) = para_inherent_upward_messages.last() { + client_clone + .insert_aux( + &[(XMC_UPM_SELECTOR_AUX_KEY, value.as_slice())], + &[], + ) + .expect("Should be able to write to aux storage; qed"); } let parachain = MockParachainsInherentDataProvider::new( diff --git a/test/suites/dev-tanssi-relay/xcm/test-xcm-send-upward.ts b/test/suites/dev-tanssi-relay/xcm/test-xcm-send-upward.ts new file mode 100644 index 000000000..1cd12d057 --- /dev/null +++ b/test/suites/dev-tanssi-relay/xcm/test-xcm-send-upward.ts @@ -0,0 +1,68 @@ +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; +import { KeyringPair } from "@moonwall/util"; +import { ApiPromise, Keyring } from "@polkadot/api"; +import { u8aToHex } from "@polkadot/util"; +import { RawXcmMessage, XcmFragment, injectUmpMessageAndSeal } from "../../../util/xcm"; + + +describeSuite({ + id: "DTR1003", + title: "XCM - Succeeds sending XCM", + foundationMethods: "dev", + testCases: ({ context, it }) => { + let polkadotJs: ApiPromise; + let alice: KeyringPair; + let transferredBalance; + + beforeAll(async function () { + polkadotJs = context.polkadotJs(); + alice = new Keyring({ type: "sr25519" }).addFromUri("//Alice", { + name: "Alice default", + }); + + transferredBalance = 10_000_000_000_000n; + }); + + it({ + id: "T01", + title: "Should succeed receiving tokens", + test: async function () { + // XCM message sending reserved assets to alice + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { Here: null }, + }, + fungible: transferredBalance, + }, + ], + beneficiary: u8aToHex(alice.addressRaw), + }) + .reserve_asset_deposited() + .clear_origin() + .buy_execution() + .deposit_asset() + .as_v3(); + + // Send an XCM and create block to execute it + await injectUmpMessageAndSeal(context, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Create a block in which the XCM will be executed + await context.createBlock(); + + // Make sure the state has alice's to DOT tokens + const alice_dot_balance = (await context.polkadotJs().query.foreignAssets.account(1, alice.address)) + .unwrap() + .balance.toBigInt(); + expect(alice_dot_balance > 0n).to.be.true; + // we should expect to have received less than the amount transferred + expect(alice_dot_balance < transferredBalance).to.be.true; + }, + }); + }, +}); diff --git a/test/util/xcm.ts b/test/util/xcm.ts index 2f49fe47c..ab5c2281d 100644 --- a/test/util/xcm.ts +++ b/test/util/xcm.ts @@ -1,4 +1,5 @@ import { DevModeContext, customDevRpcRequest, expect } from "@moonwall/cli"; +import { ApiPromise } from "@polkadot/api"; import { XcmpMessageFormat } from "@polkadot/types/interfaces"; import { CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, @@ -7,7 +8,6 @@ import { } from "@polkadot/types/lookup"; import { BN, hexToU8a, stringToU8a, u8aToHex } from "@polkadot/util"; import { xxhashAsU8a } from "@polkadot/util-crypto"; -import { ApiPromise } from "@polkadot/api"; // Creates and returns the tx that overrides the paraHRMP existence // This needs to be inserted at every block in which you are willing to test @@ -204,6 +204,12 @@ export async function injectDmpMessage(context: DevModeContext, message?: RawXcm await customDevRpcRequest("xcm_injectDownwardMessage", [totalMessage]); } +export async function injectUmpMessage(context: DevModeContext, message?: RawXcmMessage) { + const totalMessage = message != null ? buildDmpMessage(context, message) : []; + // Send RPC call to inject XCM message + await customDevRpcRequest("xcm_injectUpwardMessage", [totalMessage]); +} + // Weight a particular message using the xcm utils precompile export async function weightMessage(context: DevModeContext, message: XcmVersionedXcm) { return (await context.readPrecompile!({ @@ -225,6 +231,12 @@ export async function injectDmpMessageAndSeal(context: DevModeContext, message?: await context.createBlock(); } +export async function injectUmpMessageAndSeal(context: DevModeContext, message?: RawXcmMessage) { + await injectUmpMessage(context, message); + // Create a block in which the XCM will be executed + await context.createBlock(); +} + interface Junction { Parachain?: number; AccountId32?: {