Skip to content

Commit

Permalink
Add xcm upm mock endpoint and tests (#792)
Browse files Browse the repository at this point in the history
* Accept tokens from other parachains

* Remove waived locations

* xcm tests constants for dancelight

* Configure dancelight xcm mocknets

* Dancelight xcm test working

* toml maid

* Extract Parse and its impl for location

* Rust fmt

* Toml fmt

* Extract NativeAssetReserve from xcm config to commons

* Add xcm upm mock endpoint

* Xcm mock working

* Remove unnecesary changes

* Add xcm upm mock endpoint

* Xcm mock working

* Remove unnecesary changes

* Remove unnecesary logs

* Revert moonwall config changes

* Prettify

* add test for receiving a ump message

* Fix tests

* Improve xcm ump dancelight test

* Clippy is not complaining anymore :D

* Fix typo

* Fix merge

* Fix merge issues

* Fix merge issues

* Fix merge issues

* Fix merge issues

* Missing fixes

* Format

* Improve the way we handle upward messages

* Remove unnecesary clone

* Improve dev_rpc_data type

---------

Co-authored-by: Agusrodri <[email protected]>
  • Loading branch information
dimartiro and Agusrodri authored Jan 2, 2025
1 parent 0b36a52 commit 3231d1f
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 21 deletions.
1 change: 0 additions & 1 deletion node/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,6 @@ 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::<Vec<u8>>(100);

let (hrmp_xcm_sender, hrmp_xcm_receiver) = flume::bounded::<(ParaId, Vec<u8>)>(100);
// Create channels for mocked parachain candidates.
let (mock_randomness_sender, mock_randomness_receiver) =
Expand Down
43 changes: 43 additions & 0 deletions solo-chains/node/tanssi-relay-service/src/dev_rpcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use jsonrpsee::{
ErrorObjectOwned,
},
};
use xcm::latest::prelude::*;

/// This RPC interface is used to provide methods in dev mode only
#[rpc(server)]
Expand All @@ -38,10 +39,15 @@ pub trait DevApi {
/// Indicate the mock parachain candidate insertion to be disabled
#[method(name = "mock_disableParaInherentCandidate")]
async fn disable_para_inherent_candidate(&self) -> RpcResult<()>;

#[method(name = "xcm_injectUpwardMessage")]
async fn inject_upward_message(&self, message: Vec<u8>) -> RpcResult<()>;
}

#[derive(Clone)]
pub struct DevRpc {
pub mock_para_inherent_channel: flume::Sender<Vec<u8>>,
pub upward_message_channel: flume::Sender<Vec<u8>>,
}

#[jsonrpsee::core::async_trait]
Expand Down Expand Up @@ -69,6 +75,43 @@ impl DevApiServer for DevRpc {

Ok(())
}

async fn inject_upward_message(&self, msg: Vec<u8>) -> 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() {
// Note: Sovereign account of the origin parachain must be funded before injecting the message.
xcm::VersionedXcm::<()>::V4(Xcm(vec![
WithdrawAsset((Here, 10000000000000u128).into()),
BuyExecution {
fees: (Here, 10000000000000u128).into(),
weight_limit: Unlimited,
},
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(())
}
}

// This bit cribbed from frontier.
Expand Down
55 changes: 37 additions & 18 deletions solo-chains/node/tanssi-relay-service/src/dev_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -107,16 +108,16 @@ struct DevDeps<C, P> {
pub pool: Arc<P>,
/// Manual seal command sink
pub command_sink: Option<futures::channel::mpsc::Sender<EngineCommand<Hash>>>,
/// Channels for dev rpcs
pub dev_rpc_data: Option<flume::Sender<Vec<u8>>>,
/// Dev rpcs
pub dev_rpc: Option<DevRpc>,
}

fn create_dev_rpc_extension<C, P>(
DevDeps {
client,
pool,
command_sink: maybe_command_sink,
dev_rpc_data: maybe_dev_rpc_data,
dev_rpc: maybe_dev_rpc,
}: DevDeps<C, P>,
) -> Result<RpcExtension, Box<dyn std::error::Error + Send + Sync>>
where
Expand Down Expand Up @@ -145,13 +146,8 @@ where
io.merge(ManualSeal::new(command_sink).into_rpc())?;
}

if let Some(mock_para_inherent_channel) = maybe_dev_rpc_data {
io.merge(
DevRpc {
mock_para_inherent_channel,
}
.into_rpc(),
)?;
if let Some(dev_rpc_data) = maybe_dev_rpc {
io.merge(dev_rpc_data.into_rpc())?;
}

Ok(io)
Expand Down Expand Up @@ -226,24 +222,33 @@ struct MockParachainsInherentDataProvider<C: HeaderBackend<Block> + ProvideRunti
pub client: Arc<C>,
pub parent: Hash,
pub keystore: KeystorePtr,
pub upward_messages_receiver: flume::Receiver<Vec<u8>>,
}

impl<C: HeaderBackend<Block> + ProvideRuntimeApi<Block>> MockParachainsInherentDataProvider<C>
where
C::Api: ParachainHost<Block>,
C: AuxStore,
{
pub fn new(client: Arc<C>, parent: Hash, keystore: KeystorePtr) -> Self {
pub fn new(
client: Arc<C>,
parent: Hash,
keystore: KeystorePtr,
upward_messages_receiver: flume::Receiver<Vec<u8>>,
) -> Self {
MockParachainsInherentDataProvider {
client,
parent,
keystore,
upward_messages_receiver,
}
}

pub async fn create(
client: Arc<C>,
parent: Hash,
keystore: KeystorePtr,
upward_messages_receiver: flume::Receiver<Vec<u8>>,
) -> Result<ParachainsInherentData, InherentError> {
let parent_header = match client.header(parent) {
Ok(Some(h)) => h,
Expand Down Expand Up @@ -388,6 +393,12 @@ where
&validation_code_hash,
);
let collator_signature = collator_pair.sign(&payload);

let upward_messages = UpwardMessages::try_from(
upward_messages_receiver.drain().collect::<Vec<_>>(),
)
.expect("create upward messages from raw messages");

// generate a candidate with most of the values mocked
let candidate = CommittedCandidateReceipt::<H256> {
descriptor: CandidateDescriptor::<H256> {
Expand All @@ -402,7 +413,7 @@ where
validation_code_hash,
},
commitments: CandidateCommitments::<u32> {
upward_messages: Default::default(),
upward_messages,
horizontal_messages: Default::default(),
new_validation_code: None,
head_data: parachain_mocked_header.clone().encode().into(),
Expand Down Expand Up @@ -481,6 +492,7 @@ where
self.client.clone(),
self.parent,
self.keystore.clone(),
self.upward_messages_receiver.clone(),
)
.await
.map_err(|e| sp_inherents::Error::Application(Box::new(e)))?
Expand Down Expand Up @@ -594,6 +606,8 @@ fn new_full<
let (downward_mock_para_inherent_sender, downward_mock_para_inherent_receiver) =
flume::bounded::<Vec<u8>>(100);

let (upward_mock_sender, upward_mock_receiver) = flume::bounded::<Vec<u8>>(100);

let (network, system_rpc_tx, tx_handler_controller, network_starter, sync_service) =
service::build_network(service::BuildNetworkParams {
config: &config,
Expand Down Expand Up @@ -705,12 +719,15 @@ 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_receiver = upward_mock_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<Vec<u8>> = downward_mock_para_inherent_receiver.drain().collect();

let upward_messages_receiver = upward_mock_receiver.clone();

// If there is a value to be updated, we update it
if let Some(value) = para_inherent_decider_messages.last() {
client_clone
Expand All @@ -719,13 +736,13 @@ fn new_full<
&[],
)
.expect("Should be able to write to aux storage; qed");

}

let parachain = MockParachainsInherentDataProvider::new(
client_clone.clone(),
parent,
keystore
keystore,
upward_messages_receiver,
);

let timestamp = get_next_timestamp(client_clone, slot_duration);
Expand All @@ -744,9 +761,11 @@ fn new_full<
);
}

// We dont need the flume receiver if we are not a validator
let dev_rpc_data = if role.clone().is_authority() {
Some(downward_mock_para_inherent_sender)
let dev_rpc = if role.clone().is_authority() {
Some(DevRpc {
mock_para_inherent_channel: downward_mock_para_inherent_sender,
upward_message_channel: upward_mock_sender,
})
} else {
None
};
Expand All @@ -761,7 +780,7 @@ fn new_full<
client: client.clone(),
pool: transaction_pool.clone(),
command_sink: command_sink.clone(),
dev_rpc_data: dev_rpc_data.clone(),
dev_rpc: dev_rpc.clone(),
};

create_dev_rpc_extension(deps).map_err(Into::into)
Expand Down
12 changes: 12 additions & 0 deletions solo-chains/runtime/dancelight/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2265,6 +2265,18 @@ sp_api::impl_runtime_apis! {
}
}

impl xcm_runtime_apis::conversions::LocationToAccountApi<Block, AccountId> for Runtime {
fn convert_location(location: VersionedLocation) -> Result<
AccountId,
xcm_runtime_apis::conversions::Error
> {
xcm_runtime_apis::conversions::LocationToAccountHelper::<
AccountId,
xcm_config::LocationConverter,
>::convert_location(location)
}
}

impl sp_api::Metadata<Block> for Runtime {
fn metadata() -> OpaqueMetadata {
OpaqueMetadata::new(Runtime::metadata().into())
Expand Down
96 changes: 96 additions & 0 deletions test/suites/dev-tanssi-relay/xcm/test-xcm-send-upward.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { beforeAll, customDevRpcRequest, describeSuite, expect } from "@moonwall/cli";
import { generateKeyringPair, KeyringPair } from "@moonwall/util";
import { ApiPromise, Keyring } from "@polkadot/api";
import { u8aToHex } from "@polkadot/util";
import { jumpToSession } from "util/block";
import { injectUmpMessageAndSeal, RawXcmMessage, XcmFragment } from "../../../util/xcm";

describeSuite({
id: "DTR1003",
title: "XCM - Succeeds sending XCM",
foundationMethods: "dev",
testCases: ({ context, it }) => {
let polkadotJs: ApiPromise;
let alice: KeyringPair;
let random: KeyringPair;
let transferredBalance;

beforeAll(async function () {
polkadotJs = context.polkadotJs();
alice = new Keyring({ type: "sr25519" }).addFromUri("//Alice", {
name: "Alice default",
});

random = generateKeyringPair("sr25519");

transferredBalance = 100_000_000_000_000_000n;

const location = {
V3: {
parents: 0,
interior: { X1: { Parachain: 2000 } },
},
};

const locationToAccountResult = await polkadotJs.call.locationToAccountApi.convertLocation(location);
expect(locationToAccountResult.isOk);

const convertedAddress = locationToAccountResult.asOk.toJSON();

let aliceNonce = (await polkadotJs.query.system.account(alice.address)).nonce.toNumber();

// Send some tokens to the sovereign account of para 2000
const txSigned = polkadotJs.tx.balances.transferAllowDeath(convertedAddress, transferredBalance);
await context.createBlock(await txSigned.signAsync(alice, { nonce: aliceNonce++ }), {
allowFailures: false,
});

const balanceSigned = (await polkadotJs.query.system.account(convertedAddress)).data.free.toBigInt();
expect(balanceSigned).to.eq(transferredBalance);
});

it({
id: "T01",
title: "Should succeed receiving tokens",
test: async function () {
const balanceRandomBefore = (
await polkadotJs.query.system.account(random.address)
).data.free.toBigInt();
expect(balanceRandomBefore).to.eq(0n);

const xcmMessage = new XcmFragment({
assets: [
{
multilocation: {
parents: 0,
interior: { Here: null },
},
fungible: transferredBalance / 10n,
},
],
beneficiary: u8aToHex(random.addressRaw),
})
.withdraw_asset()
.buy_execution()
.deposit_asset_v3()
.as_v3();

// Enable para inherent to process xcm message
await customDevRpcRequest("mock_enableParaInherentCandidate", []);

// Send ump message
await injectUmpMessageAndSeal(context, {
type: "XcmVersionedXcm",
payload: xcmMessage,
} as RawXcmMessage);

// Wait until message is processed
await jumpToSession(context, 3);
await context.createBlock();

const balanceRandomAfter = (await polkadotJs.query.system.account(random.address)).data.free.toBigInt();
expect(Number(balanceRandomAfter)).to.be.greaterThan(0);
},
});
},
});
Loading

0 comments on commit 3231d1f

Please sign in to comment.