Skip to content
1 change: 1 addition & 0 deletions integration-tests/ahm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub mod multisig_test;
pub mod proxy;
pub mod queues_priority;
pub mod tests;
pub mod xcm_route;

/// Imports for the AHM tests that can be reused for other chains.
pub mod porting_prelude {
Expand Down
224 changes: 224 additions & 0 deletions integration-tests/ahm/src/xcm_route.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.

// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.

use crate::porting_prelude::*;
use asset_hub_polkadot_runtime::{
xcm_config::XcmRouter as AhXcmRouter, BuildStorage, ParachainSystem as AhParachainSystem,
};
use codec::Encode;
use cumulus_primitives_core::send_xcm;
use pallet_ah_migrator::{
AhMigrationStage as AhMigrationStageStorage, MigrationStage as AhMigrationStage,
};
use pallet_rc_migrator::{
MigrationStage as RcMigrationStage, RcMigrationStage as RcMigrationStageStorage,
};
use polkadot_runtime::xcm_config::XcmRouter as RcXcmRouter;
use sp_runtime::traits::Dispatchable;
use xcm::prelude::*;

#[test]
fn test_send_to_rc_from_ah() {
let mut t: sp_io::TestExternalities = frame_system::GenesisConfig::<AhRuntime>::default()
.build_storage()
.unwrap()
.into();

// our universal xcm message to send to the RC
let xcm_message = Xcm(vec![
Instruction::UnpaidExecution { weight_limit: WeightLimit::Unlimited, check_origin: None },
Instruction::Transact {
origin_kind: OriginKind::Xcm,
require_weight_at_most: Weight::from_parts(1_000_000_000, 10_000),
call: AhRuntimeCall::System(frame_system::Call::remark { remark: vec![1] })
.encode()
.into(),
},
]);

// prepare the AH to send XCM messages to RC and Collectives.
t.execute_with(|| {
let now = 1;
frame_system::Pallet::<AhRuntime>::reset_events();
frame_system::Pallet::<AhRuntime>::set_block_number(now);

// setup default XCM version
let result =
AhRuntimeCall::PolkadotXcm(pallet_xcm::Call::<AhRuntime>::force_default_xcm_version {
maybe_xcm_version: Some(xcm::prelude::XCM_VERSION),
})
.dispatch(AhRuntimeOrigin::root());
assert!(result.is_ok(), "fails with error: {:?}", result.err());

// open the channel between AH and Collectives (1001)
AhParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests(1001.into());
});

// sending XCM messages via main `XcmRouter` from AH to RC and AH to Collectives succeeds
// while migration is pending.
t.execute_with(|| {
let now = 2;
frame_system::Pallet::<AhRuntime>::reset_events();
frame_system::Pallet::<AhRuntime>::set_block_number(now);

AhMigrationStageStorage::<AhRuntime>::put(AhMigrationStage::Pending);

let dest = Location::parent();
let result = send_xcm::<AhXcmRouter>(dest, xcm_message.clone());

assert!(result.is_ok());

let dest = Location::new(1, Parachain(1001));
let result = send_xcm::<AhXcmRouter>(dest, xcm_message.clone());

assert!(result.is_ok(), "fails with error: {:?}", result.err());
});

// sending XCM messages via main `XcmRouter` fails from AH to RC but succeeds from AH to
// Collectives while migration is ongoing.
t.execute_with(|| {
let now = 2;
frame_system::Pallet::<AhRuntime>::reset_events();
frame_system::Pallet::<AhRuntime>::set_block_number(now);

AhMigrationStageStorage::<AhRuntime>::put(AhMigrationStage::DataMigrationOngoing);

let dest = Location::parent();
let err = send_xcm::<AhXcmRouter>(dest, xcm_message.clone()).unwrap_err();

assert_eq!(err, SendError::Unroutable);

let dest = Location::new(1, Parachain(1001));
let result = send_xcm::<AhXcmRouter>(dest, xcm_message.clone());

assert!(result.is_ok(), "fails with error: {:?}", result.err());
});

// sending XCM messages via main `XcmRouter` from AH to RC and AH to Collectives succeeds
// while migration is done.
t.execute_with(|| {
let now = 2;
frame_system::Pallet::<AhRuntime>::reset_events();
frame_system::Pallet::<AhRuntime>::set_block_number(now);

AhMigrationStageStorage::<AhRuntime>::put(AhMigrationStage::MigrationDone);

let dest = Location::parent();
let result = send_xcm::<AhXcmRouter>(dest, xcm_message.clone());

assert!(result.is_ok(), "fails with error: {:?}", result.err());

let dest = Location::new(1, Parachain(1001));
let result = send_xcm::<AhXcmRouter>(dest, xcm_message.clone());

assert!(result.is_ok(), "fails with error: {:?}", result.err());
});
}

#[test]
fn test_send_to_ah_from_rc() {
let mut t: sp_io::TestExternalities = frame_system::GenesisConfig::<RcRuntime>::default()
.build_storage()
.unwrap()
.into();

// our universal xcm message to send to the RC
let xcm_message = Xcm(vec![
Instruction::UnpaidExecution { weight_limit: WeightLimit::Unlimited, check_origin: None },
Instruction::Transact {
origin_kind: OriginKind::Xcm,
require_weight_at_most: Weight::from_parts(1_000_000_000, 10_000),
call: RcRuntimeCall::System(frame_system::Call::remark { remark: vec![1] })
.encode()
.into(),
},
]);

// prepare the RC to send XCM messages to AH and Collectives.
t.execute_with(|| {
let now = 1;
frame_system::Pallet::<RcRuntime>::reset_events();
frame_system::Pallet::<RcRuntime>::set_block_number(now);

// setup default XCM version
let result =
RcRuntimeCall::XcmPallet(pallet_xcm::Call::<RcRuntime>::force_default_xcm_version {
maybe_xcm_version: Some(xcm::prelude::XCM_VERSION),
})
.dispatch(RcRuntimeOrigin::root());
assert!(result.is_ok(), "fails with error: {:?}", result.err());
});

// sending XCM messages via main `XcmRouter` from RC to AH and RC to Collectives succeeds
// while migration is pending.
t.execute_with(|| {
let now = 2;
frame_system::Pallet::<RcRuntime>::reset_events();
frame_system::Pallet::<RcRuntime>::set_block_number(now);

RcMigrationStageStorage::<RcRuntime>::put(RcMigrationStage::Pending);

let dest = Location::new(0, Parachain(1000));
let result = send_xcm::<RcXcmRouter>(dest, xcm_message.clone());

assert!(result.is_ok(), "fails with error: {:?}", result.err());

let dest = Location::new(0, Parachain(1001));
let result = send_xcm::<RcXcmRouter>(dest, xcm_message.clone());

assert!(result.is_ok(), "fails with error: {:?}", result.err());
});

// sending XCM messages via main `XcmRouter` fails from RC to AH but succeeds from RC to
// Collectives while migration is ongoing.
t.execute_with(|| {
let now = 2;
frame_system::Pallet::<RcRuntime>::reset_events();
frame_system::Pallet::<RcRuntime>::set_block_number(now);

RcMigrationStageStorage::<RcRuntime>::put(RcMigrationStage::AccountsMigrationInit);

let dest = Location::new(0, Parachain(1000));
let err = send_xcm::<RcXcmRouter>(dest, xcm_message.clone()).unwrap_err();

assert_eq!(err, SendError::Unroutable);

let dest = Location::new(0, Parachain(1001));
let result = send_xcm::<RcXcmRouter>(dest, xcm_message.clone());

assert!(result.is_ok(), "fails with error: {:?}", result.err());
});

// sending XCM messages via main `XcmRouter` from RC to AH and RC to Collectives succeeds
// while migration is done.
t.execute_with(|| {
let now = 2;
frame_system::Pallet::<RcRuntime>::reset_events();
frame_system::Pallet::<RcRuntime>::set_block_number(now);

RcMigrationStageStorage::<RcRuntime>::put(RcMigrationStage::MigrationDone);

let dest = Location::new(0, Parachain(1000));
let result = send_xcm::<RcXcmRouter>(dest, xcm_message.clone());

assert!(result.is_ok(), "fails with error: {:?}", result.err());

let dest = Location::new(0, Parachain(1001));
let result = send_xcm::<RcXcmRouter>(dest, xcm_message.clone());

assert!(result.is_ok(), "fails with error: {:?}", result.err());
});
}
5 changes: 4 additions & 1 deletion pallets/ah-migrator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ pub mod xcm_config;

pub use pallet::*;
pub use pallet_rc_migrator::{
types::{ForceSetHead, LeftOrRight, MaxOnIdleOrInner, QueuePriority as DmpQueuePriority},
types::{
ExceptResponseFor, ForceSetHead, LeftOrRight, MaxOnIdleOrInner,
QueuePriority as DmpQueuePriority, RouteInnerWithException,
},
weights_ah,
};
pub use weights_ah::WeightInfo;
Expand Down
67 changes: 61 additions & 6 deletions pallets/rc-migrator/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ extern crate alloc;

use super::*;
use alloc::string::String;
use frame_support::traits::ContainsPair;
use pallet_referenda::{ReferendumInfoOf, TrackIdOf};
use sp_runtime::{traits::Zero, FixedU128};
use sp_std::collections::vec_deque::VecDeque;
use xcm_builder::InspectMessageQueues;

pub trait ToPolkadotSs58 {
fn to_polkadot_ss58(&self) -> String;
Expand Down Expand Up @@ -206,13 +208,56 @@ pub trait MigrationStatus {
fn is_ongoing() -> bool;
}

/// A value that is `Left::get()` if the migration is ongoing, otherwise it is `Right::get()`.
pub struct LeftOrRight<Status, Left, Right>(PhantomData<(Status, Left, Right)>);
impl<Status: MigrationStatus, Left: TypedGet, Right: Get<Left::Type>> Get<Left::Type>
for LeftOrRight<Status, Left, Right>
/// A wrapper around `Inner` that routes messages through `Inner` unless `MigrationState` is ongoing
/// and `Exception` returns true for the given destination and message.
pub struct RouteInnerWithException<Inner, Exception, MigrationState>(
PhantomData<(Inner, Exception, MigrationState)>,
);
impl<
Inner: SendXcm,
Exception: ContainsPair<Location, Xcm<()>>,
MigrationState: MigrationStatus,
> SendXcm for RouteInnerWithException<Inner, Exception, MigrationState>
{
fn get() -> Left::Type {
Status::is_ongoing().then(|| Left::get()).unwrap_or_else(|| Right::get())
type Ticket = Inner::Ticket;
fn validate(
destination: &mut Option<Location>,
message: &mut Option<Xcm<()>>,
) -> SendResult<Self::Ticket> {
if MigrationState::is_ongoing() &&
Exception::contains(
destination.as_ref().ok_or(SendError::MissingArgument)?,
message.as_ref().ok_or(SendError::MissingArgument)?,
) {
Err(SendError::Transport("Migration ongoing - routing is temporary blocked!"))
} else {
Inner::validate(destination, message)
}
}
fn deliver(ticket: Self::Ticket) -> Result<XcmHash, SendError> {
Inner::deliver(ticket)
}
}

impl<Inner: InspectMessageQueues, Exception, MigrationState> InspectMessageQueues
for RouteInnerWithException<Inner, Exception, MigrationState>
{
fn clear_messages() {
Inner::clear_messages()
}

fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
Inner::get_messages()
}
}

pub struct ExceptResponseFor<Querier>(PhantomData<Querier>);
impl<Querier: Contains<Location>> Contains<Xcm<()>> for ExceptResponseFor<Querier> {
fn contains(l: &Xcm<()>) -> bool {
match l.first() {
Some(QueryResponse { querier: Some(querier), .. }) => !Querier::contains(querier),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have to whitelist query responses for our flow control system

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

querier here from the perspective of the destination

_ => true,
}
}
}

Expand Down Expand Up @@ -264,6 +309,16 @@ impl<BlockNumber: Copy> QueuePriority<BlockNumber> {
}
}

/// A value that is `Left::get()` if the migration is ongoing, otherwise it is `Right::get()`.
pub struct LeftOrRight<Status, Left, Right>(PhantomData<(Status, Left, Right)>);
impl<Status: MigrationStatus, Left: TypedGet, Right: Get<Left::Type>> Get<Left::Type>
for LeftOrRight<Status, Left, Right>
{
fn get() -> Left::Type {
Status::is_ongoing().then(|| Left::get()).unwrap_or_else(|| Right::get())
}
}

/// A weight that is `Weight::MAX` if the migration is ongoing, otherwise it is the [`Inner`]
/// weight of the [`pallet_fast_unstake::weights::WeightInfo`] trait.
pub struct MaxOnIdleOrInner<Status, Inner>(PhantomData<(Status, Inner)>);
Expand Down
2 changes: 1 addition & 1 deletion relay/polkadot/src/ah_migration/phase1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ pub fn call_allowed_status(call: &<Runtime as frame_system::Config>::RuntimeCall
Coretime(coretime::Call::<Runtime>::request_revenue_at { .. }) => (OFF, ON),
Coretime(..) => (ON, ON),
StateTrieMigration(..) => (OFF, OFF), // Deprecated
XcmPallet(..) => (OFF, ON), /* TODO allow para origins and root to call this during the migration, see https://github.com/polkadot-fellows/runtimes/pull/559#discussion_r1928789463 */
XcmPallet(..) => (OFF, ON),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disabled XCM pallet here should not affect the execution of messages from Coretime. messages from Coretime are first fed into the MQ, which then passes them to the XCM executor. this filtering only disables extrinsics imported to this chain that use XCM pallet dispatchables.

cc: @seadanda @ggwpez

MessageQueue(..) => (ON, ON), // TODO think about this
AssetRate(..) => (OFF, OFF),
Beefy(..) => (OFF, ON), /* TODO @claravanstaden @bkontur */
Expand Down
2 changes: 1 addition & 1 deletion relay/polkadot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1558,7 +1558,7 @@ impl pallet_rc_migrator::Config for Runtime {
>;
type Currency = Balances;
type CheckingAccount = xcm_config::CheckAccount;
type SendXcm = xcm_config::XcmRouter;
type SendXcm = xcm_config::XcmRouterWithoutException;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a special one here on the relay? I thought we want to use the one that disables during migration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bc its the only one that can send to AH and we need it for the migrator pallet to send data. the rest cannot.

type MaxRcWeight = RcMigratorMaxWeight;
type MaxAhWeight = AhMigratorMaxWeight;
type AhExistentialDeposit = AhExistentialDeposit;
Expand Down
17 changes: 13 additions & 4 deletions relay/polkadot/src/xcm_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use super::{
};
use frame_support::{
parameter_types,
traits::{Contains, Everything, Nothing},
traits::{Contains, Equals, Everything, FromContains, Nothing},
};
use frame_system::EnsureRoot;
use pallet_xcm::XcmPassthrough;
Expand Down Expand Up @@ -130,13 +130,22 @@ parameter_types! {
pub type PriceForChildParachainDelivery =
ExponentialPrice<FeeAssetId, BaseDeliveryFee, TransactionByteFee, Dmp>;

/// The XCM router. When we want to send an XCM message, we use this type. It amalgamates all of our
/// individual routers.
pub type XcmRouter = WithUniqueTopic<(
/// The XCM router. Use [`XcmRouter`] instead.
pub(crate) type XcmRouterWithoutException = WithUniqueTopic<(
// Only one router so far - use DMP to communicate with child parachains.
ChildParachainRouter<Runtime, XcmPallet, PriceForChildParachainDelivery>,
)>;

/// The XCM router. When we want to send an XCM message, we use this type. It amalgamates all of our
/// individual routers.
///
/// This router does not route to the Asset Hub if the migration is ongoing.
pub type XcmRouter = pallet_rc_migrator::types::RouteInnerWithException<
XcmRouterWithoutException,
FromContains<Equals<AssetHubLocation>, Everything>,
crate::RcMigrator,
>;

pub struct Fellows;
impl Contains<Location> for Fellows {
fn contains(loc: &Location) -> bool {
Expand Down
2 changes: 1 addition & 1 deletion system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1159,7 +1159,7 @@ impl pallet_ah_migrator::Config for Runtime {
type RcPalletsOrigin = ah_migration::RcPalletsOrigin;
type RcToAhPalletsOrigin = ah_migration::RcToAhPalletsOrigin;
type Preimage = Preimage;
type SendXcm = xcm_config::XcmRouter;
type SendXcm = xcm_config::LocalXcmRouterWithoutException;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am just thinking loud, does this AH migrator send messages just to the RC? Or possibly to other parachains?
Can we just use ParentAsUmp directly here? (I am ok, also with LocalXcmRouterWithoutException tuple - not that big deal)

Suggested change
type SendXcm = xcm_config::LocalXcmRouterWithoutException;
type SendXcm = WithUniqueTopic<
cumulus_primitives_utility::ParentAsUmp<ParachainSystem, PolkadotXcm, PriceForParentDelivery>,
>;

But the other thing - the XcmRouter was previously wrapped with WithUniqueTopic, but whatever we are adding here isn't. That means the SetTopic feature for tracking XCM messages won't apply probably.

Maybe we don’t need to track XCM messages at all. I need to go through the entire flow starting from the initial migration message (first-tracking-id). Most likely, the up-and-down migration messages won’t be tracked under the same first-tracking-id due to the query/response behavior (also need to check).

Anyway, just wanted to raise the point: without WithUniqueTopic, we most probably lose the ability to track messages using the SetTopic feature from the AssetHub migrator to the RC (maybe not that big issue also).

cc: @raymondkfcheung maybe, it could be worth to double-check all the XCM routing set it up for this migration, when everyhting will be ready (all similar PRs are merged to this dev-ahm branch).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually try to introduce as little entropy as possible. The LocalXcmRouterWithoutException type is exactly the same as the one currently used (prior to this PR); only the name has been changed. This means I do not introduce any behaviour change to the migrator pallets with this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means I do not introduce any behaviour change to the migrator pallets with this PR.

Well, using LocalXcmRouterWithoutException instead of XcmRouter avoids using SetTopic with WithUniqueTopic - which may not be relevant behaviour change for migration. As I said before, just raising the point, we can go with or without WithUniqueTopic probably.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see now. I was trying to avoid touching the ToKusamaXcmRouter and SovereignPaidRemoteExporter<...> routers, this sounds good to you? I am adding now the SetTopic for the migrator router.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bkontur I added topic for the migrator router - e4c42ef

type AhWeightInfo = weights::pallet_ah_migrator::WeightInfo<Runtime>;
type TreasuryAccounts = ah_migration::TreasuryAccounts;
type RcToAhTreasurySpend = ah_migration::RcToAhTreasurySpend;
Expand Down
Loading
Loading