Use Message Queue as DMP and XCMP dispatch queue (#1246)

(imported from https://github.com/paritytech/cumulus/pull/2157)

## Changes

This MR refactores the XCMP, Parachains System and DMP pallets to use
the [MessageQueue](https://github.com/paritytech/substrate/pull/12485)
for delayed execution of incoming messages. The DMP pallet is entirely
replaced by the MQ and thereby removed. This allows for PoV-bounded
execution and resolves a number of issues that stem from the current
work-around.

All System Parachains adopt this change.  
The most important changes are in `primitives/core/src/lib.rs`,
`parachains/common/src/process_xcm_message.rs`,
`pallets/parachain-system/src/lib.rs`, `pallets/xcmp-queue/src/lib.rs`
and the runtime configs.

### DMP Queue Pallet

The pallet got removed and its logic refactored into parachain-system.
Overweight message management can be done directly through the MQ
pallet.

Final undeployment migrations are provided by
`cumulus_pallet_dmp_queue::UndeployDmpQueue` and `DeleteDmpQueue` that
can be configured with an aux config trait like:

```rust
parameter_types! {
	pub const DmpQueuePalletName: &'static str = \"DmpQueue\" < CHANGE ME;
	pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent;
}

impl cumulus_pallet_dmp_queue::MigrationConfig for Runtime {
	type PalletName = DmpQueuePalletName;
	type DmpHandler = frame_support::traits::EnqueueWithOrigin<MessageQueue, RelayOrigin>;
	type DbWeight = <Runtime as frame_system::Config>::DbWeight;
}

// And adding them to your Migrations tuple:
pub type Migrations = (
	...
	cumulus_pallet_dmp_queue::UndeployDmpQueue<Runtime>,
	cumulus_pallet_dmp_queue::DeleteDmpQueue<Runtime>,
);
```

### XCMP Queue pallet

Removed all dispatch queue functionality. Incoming XCMP messages are now
either: Immediately handled if they are Signals, enqueued into the MQ
pallet otherwise.

New config items for the XCMP queue pallet:
```rust
/// The actual queue implementation that retains the messages for later processing.
type XcmpQueue: EnqueueMessage<ParaId>;

/// How a XCM over HRMP from a sibling parachain should be processed.
type XcmpProcessor: ProcessMessage<Origin = ParaId>;

/// The maximal number of suspended XCMP channels at the same time.
#[pallet::constant]
type MaxInboundSuspended: Get<u32>;
```

How to configure those:

```rust
// Use the MessageQueue pallet to store messages for later processing. The `TransformOrigin` is needed since
// the MQ pallet itself operators on `AggregateMessageOrigin` but we want to enqueue `ParaId`s.
type XcmpQueue = TransformOrigin<MessageQueue, AggregateMessageOrigin, ParaId, ParaIdToSibling>;

// Process XCMP messages from siblings. This is type-safe to only accept `ParaId`s. They will be dispatched
// with origin `Junction::Sibling(…)`.
type XcmpProcessor = ProcessFromSibling<
	ProcessXcmMessage<
		AggregateMessageOrigin,
		xcm_executor::XcmExecutor<xcm_config::XcmConfig>,
		RuntimeCall,
	>,
>;

// Not really important what to choose here. Just something larger than the maximal number of channels.
type MaxInboundSuspended = sp_core::ConstU32<1_000>;
```

The `InboundXcmpStatus` storage item was replaced by
`InboundXcmpSuspended` since it now only tracks inbound queue suspension
and no message indices anymore.

Now only sends the most recent channel `Signals`, as all prio ones are
out-dated anyway.

### Parachain System pallet

For `DMP` messages instead of forwarding them to the `DMP` pallet, it
now pushes them to the configured `DmpQueue`. The message processing
which was triggered in `set_validation_data` is now being done by the MQ
pallet `on_initialize`.

XCMP messages are still handed off to the `XcmpMessageHandler`
(XCMP-Queue pallet) - no change here.

New config items for the parachain system pallet:
```rust
/// Queues inbound downward messages for delayed processing. 
///
/// Analogous to the `XcmpQueue` of the XCMP queue pallet.
type DmpQueue: EnqueueMessage<AggregateMessageOrigin>;
``` 

How to configure:
```rust
/// Use the MQ pallet to store DMP messages for delayed processing.
type DmpQueue = MessageQueue;
``` 

## Message Flow

The flow of messages on the parachain side. Messages come in from the
left via the `Validation Data` and finally end up at the `Xcm Executor`
on the right.

![Untitled
(1)](https://github.com/paritytech/cumulus/assets/10380170/6cf8b377-88c9-4aed-96df-baace266e04d)

## Further changes

- Bumped the default suspension, drop and resume thresholds in
`QueueConfigData::default()`.
- `XcmpQueue::{suspend_xcm_execution, resume_xcm_execution}` errors when
they would be a noop.
- Properly validate the `QueueConfigData` before setting it.
- Marked weight files as auto-generated so they wont auto-expand in the
MR files view.
- Move the `hypothetical` asserts to `frame_support` under the name
`experimental_hypothetically`

Questions:
- [ ] What about the ugly `#[cfg(feature = \"runtime-benchmarks\")]` in
the runtimes? Not sure how to best fix. Just having them like this makes
tests fail that rely on the real message processor when the feature is
enabled.
- [ ] Need a good weight for `MessageQueueServiceWeight`. The scheduler
already takes 80% so I put it to 10% but that is quite low.

TODO:
- [x] Remove c&p code after
https://github.com/paritytech/polkadot/pull/6271
- [x] Use `HandleMessage` once it is public in Substrate
- [x] fix `runtime-benchmarks` feature
https://github.com/paritytech/polkadot/pull/6966
- [x] Benchmarks
- [x] Tests
- [ ] Migrate `InboundXcmpStatus` to `InboundXcmpSuspended`
- [x] Possibly cleanup Migrations (DMP+XCMP)
- [x] optional: create `TransformProcessMessageOrigin` in Substrate and
replace `ProcessFromSibling`
- [ ] Rerun weights on ref HW

---------

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>
Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: command-bot <>
This commit is contained in:
Oliver Tale-Yazdi
2023-11-02 15:31:38 +01:00
committed by GitHub
parent 7df0417bcd
commit e1c033ebe1
277 changed files with 11604 additions and 4733 deletions
+152 -6
View File
@@ -17,12 +17,158 @@
use crate::*;
use frame_benchmarking::{benchmarks, impl_benchmark_test_suite};
use codec::DecodeAll;
use frame_benchmarking::v2::*;
use frame_support::traits::Hooks;
use frame_system::RawOrigin;
use xcm::v3::MAX_INSTRUCTIONS_TO_DECODE;
benchmarks! {
set_config_with_u32 {}: update_resume_threshold(RawOrigin::Root, 100)
set_config_with_weight {}: update_weight_restrict_decay(RawOrigin::Root, Weight::from_parts(3_000_000, 0))
#[benchmarks]
mod benchmarks {
use super::*;
/// Modify any of the `QueueConfig` fields with a new `u32` value.
///
/// Used as weight for:
/// - update_suspend_threshold
/// - update_drop_threshold
/// - update_resume_threshold
#[benchmark]
fn set_config_with_u32() {
#[extrinsic_call]
Pallet::<T>::update_resume_threshold(RawOrigin::Root, 1);
}
#[benchmark]
fn enqueue_xcmp_message() {
assert!(QueueConfig::<T>::get().drop_threshold * MaxXcmpMessageLenOf::<T>::get() > 1000);
let msg = BoundedVec::<u8, MaxXcmpMessageLenOf<T>>::default();
#[block]
{
Pallet::<T>::enqueue_xcmp_message(0.into(), msg, &mut WeightMeter::new()).unwrap();
}
}
#[benchmark]
fn suspend_channel() {
let para = 123.into();
let data = ChannelSignal::Suspend.encode();
#[block]
{
ChannelSignal::decode_all(&mut &data[..]).unwrap();
Pallet::<T>::suspend_channel(para);
}
assert_eq!(
OutboundXcmpStatus::<T>::get()
.iter()
.find(|p| p.recipient == para)
.unwrap()
.state,
OutboundState::Suspended
);
}
#[benchmark]
fn resume_channel() {
let para = 123.into();
let data = ChannelSignal::Resume.encode();
Pallet::<T>::suspend_channel(para);
#[block]
{
ChannelSignal::decode_all(&mut &data[..]).unwrap();
Pallet::<T>::resume_channel(para);
}
assert!(
OutboundXcmpStatus::<T>::get().iter().find(|p| p.recipient == para).is_none(),
"No messages in the channel; therefore removed."
);
}
/// Split a singular XCM.
#[benchmark]
fn take_first_concatenated_xcm() {
let max_downward_message_size = MaxXcmpMessageLenOf::<T>::get() as usize;
assert!(MAX_INSTRUCTIONS_TO_DECODE as u32 > MAX_XCM_DECODE_DEPTH, "Preconditon failed");
let max_instrs = MAX_INSTRUCTIONS_TO_DECODE as u32 - MAX_XCM_DECODE_DEPTH;
let mut xcm = Xcm::<T>(vec![ClearOrigin; max_instrs as usize]);
for _ in 0..MAX_XCM_DECODE_DEPTH - 1 {
xcm = Xcm::<T>(vec![Instruction::SetAppendix(xcm)]);
}
let data = VersionedXcm::<T>::from(xcm).encode();
assert!(data.len() < max_downward_message_size, "Page size is too small");
// Verify that decoding works with the exact recursion limit:
VersionedXcm::<T::RuntimeCall>::decode_with_depth_limit(
MAX_XCM_DECODE_DEPTH,
&mut &data[..],
)
.unwrap();
VersionedXcm::<T::RuntimeCall>::decode_with_depth_limit(
MAX_XCM_DECODE_DEPTH - 1,
&mut &data[..],
)
.unwrap_err();
#[block]
{
Pallet::<T>::take_first_concatenated_xcm(&mut &data[..], &mut WeightMeter::new())
.unwrap();
}
}
/// Benchmark the migration for a maximal sized message.
#[benchmark]
fn on_idle_good_msg() {
use migration::v3;
let block = 5;
let para = ParaId::from(4);
let message = vec![123u8; MaxXcmpMessageLenOf::<T>::get() as usize];
let message_metadata = vec![(block, XcmpMessageFormat::ConcatenatedVersionedXcm)];
v3::InboundXcmpMessages::<T>::insert(para, block, message);
v3::InboundXcmpStatus::<T>::set(Some(vec![v3::InboundChannelDetails {
sender: para,
state: v3::InboundState::Ok,
message_metadata,
}]));
#[block]
{
Pallet::<T>::on_idle(0u32.into(), Weight::MAX);
}
}
/// Benchmark the migration with a 64 KiB message that will not be possible to enqueue.
#[benchmark]
fn on_idle_large_msg() {
use migration::v3;
let block = 5;
let para = ParaId::from(4);
let message = vec![123u8; 1 << 16]; // 64 KiB message
let message_metadata = vec![(block, XcmpMessageFormat::ConcatenatedVersionedXcm)];
v3::InboundXcmpMessages::<T>::insert(para, block, message);
v3::InboundXcmpStatus::<T>::set(Some(vec![v3::InboundChannelDetails {
sender: para,
state: v3::InboundState::Ok,
message_metadata,
}]));
#[block]
{
Pallet::<T>::on_idle(0u32.into(), Weight::MAX);
}
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
File diff suppressed because it is too large Load Diff
+105 -5
View File
@@ -16,20 +16,23 @@
//! A module that is responsible for migration of storage.
use crate::{Config, Overweight, Pallet, QueueConfig, DEFAULT_POV_SIZE};
use crate::{Config, OverweightIndex, Pallet, ParaId, QueueConfig, DEFAULT_POV_SIZE};
use cumulus_primitives_core::XcmpMessageFormat;
use frame_support::{
pallet_prelude::*,
traits::{OnRuntimeUpgrade, StorageVersion},
traits::{EnqueueMessage, OnRuntimeUpgrade, StorageVersion},
weights::{constants::WEIGHT_REF_TIME_PER_MILLIS, Weight},
};
/// The current storage version.
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(3);
/// Migrates the pallet storage to the most recent version.
pub struct Migration<T: Config>(PhantomData<T>);
pub const LOG: &str = "runtime::xcmp-queue-migration";
impl<T: Config> OnRuntimeUpgrade for Migration<T> {
/// Migrates the pallet storage to the most recent version.
pub struct MigrationToV3<T: Config>(PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for MigrationToV3<T> {
fn on_runtime_upgrade() -> Weight {
let mut weight = T::DbWeight::get().reads(1);
@@ -77,11 +80,55 @@ mod v1 {
}
}
pub mod v3 {
use super::*;
use crate::*;
/// Status of the inbound XCMP channels.
#[frame_support::storage_alias]
pub(crate) type InboundXcmpStatus<T: Config> =
StorageValue<Pallet<T>, Vec<InboundChannelDetails>, OptionQuery>;
/// Inbound aggregate XCMP messages. It can only be one per ParaId/block.
#[frame_support::storage_alias]
pub(crate) type InboundXcmpMessages<T: Config> = StorageDoubleMap<
Pallet<T>,
Blake2_128Concat,
ParaId,
Twox64Concat,
RelayBlockNumber,
Vec<u8>,
OptionQuery,
>;
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
pub struct InboundChannelDetails {
/// The `ParaId` of the parachain that this channel is connected with.
pub sender: ParaId,
/// The state of the channel.
pub state: InboundState,
/// The ordered metadata of each inbound message.
///
/// Contains info about the relay block number that the message was sent at, and the format
/// of the incoming message.
pub message_metadata: Vec<(RelayBlockNumber, XcmpMessageFormat)>,
}
#[derive(
Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug, TypeInfo,
)]
pub enum InboundState {
Ok,
Suspended,
}
}
/// Migrates `QueueConfigData` from v1 (using only reference time weights) to v2 (with
/// 2D weights).
///
/// NOTE: Only use this function if you know what you're doing. Default to using
/// `migrate_to_latest`.
#[allow(deprecated)]
pub fn migrate_to_v2<T: Config>() -> Weight {
let translate = |pre: v1::QueueConfigData| -> super::QueueConfigData {
super::QueueConfigData {
@@ -108,17 +155,70 @@ pub fn migrate_to_v2<T: Config>() -> Weight {
}
pub fn migrate_to_v3<T: Config>() -> Weight {
#[frame_support::storage_alias]
type Overweight<T: Config> =
CountedStorageMap<Pallet<T>, Twox64Concat, OverweightIndex, ParaId>;
let overweight_messages = Overweight::<T>::initialize_counter() as u64;
T::DbWeight::get().reads_writes(overweight_messages, 1)
}
pub fn lazy_migrate_inbound_queue<T: Config>() {
let Some(mut states) = v3::InboundXcmpStatus::<T>::get() else {
log::debug!(target: LOG, "Lazy migration finished: item gone");
return
};
let Some(ref mut next) = states.first_mut() else {
log::debug!(target: LOG, "Lazy migration finished: item empty");
v3::InboundXcmpStatus::<T>::kill();
return
};
log::debug!(
"Migrating inbound HRMP channel with sibling {:?}, msgs left {}.",
next.sender,
next.message_metadata.len()
);
// We take the last element since the MQ is a FIFO and we want to keep the order.
let Some((block_number, format)) = next.message_metadata.pop() else {
states.remove(0);
v3::InboundXcmpStatus::<T>::put(states);
return
};
if format != XcmpMessageFormat::ConcatenatedVersionedXcm {
log::warn!(target: LOG,
"Dropping message with format {:?} (not ConcatenatedVersionedXcm)",
format
);
v3::InboundXcmpMessages::<T>::remove(&next.sender, &block_number);
v3::InboundXcmpStatus::<T>::put(states);
return
}
let Some(msg) = v3::InboundXcmpMessages::<T>::take(&next.sender, &block_number) else {
defensive!("Storage corrupted: HRMP message missing:", (next.sender, block_number));
v3::InboundXcmpStatus::<T>::put(states);
return
};
let Ok(msg): Result<BoundedVec<_, _>, _> = msg.try_into() else {
log::error!(target: LOG, "Message dropped: too big");
v3::InboundXcmpStatus::<T>::put(states);
return
};
// Finally! We have a proper message.
T::XcmpQueue::enqueue_message(msg.as_bounded_slice(), next.sender);
log::debug!(target: LOG, "Migrated HRMP message to MQ: {:?}", (next.sender, block_number));
v3::InboundXcmpStatus::<T>::put(states);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::{new_test_ext, Test};
#[test]
#[allow(deprecated)]
fn test_migration_to_v2() {
let v1 = v1::QueueConfigData {
suspend_threshold: 5,
+112 -7
View File
@@ -17,10 +17,11 @@ use super::*;
use crate as xcmp_queue;
use core::marker::PhantomData;
use cumulus_pallet_parachain_system::AnyRelayNumber;
use cumulus_primitives_core::{IsSystem, ParaId};
use cumulus_primitives_core::{ChannelInfo, IsSystem, ParaId};
use frame_support::{
parameter_types,
traits::{ConstU32, Everything, Nothing, OriginTrait},
BoundedSlice,
};
use frame_system::EnsureRoot;
use sp_core::H256;
@@ -105,11 +106,13 @@ impl pallet_balances::Config for Test {
}
impl cumulus_pallet_parachain_system::Config for Test {
type WeightInfo = ();
type RuntimeEvent = RuntimeEvent;
type OnSystemEvent = ();
type SelfParaId = ();
type OutboundXcmpMessageSource = XcmpQueue;
type DmpMessageHandler = ();
// Ignore all DMP messages by enqueueing them into `()`:
type DmpQueue = frame_support::traits::EnqueueWithOrigin<(), sp_core::ConstU8<0>>;
type ReservedDmpWeight = ();
type XcmpMessageHandler = XcmpQueue;
type ReservedXcmpWeight = ();
@@ -199,6 +202,54 @@ impl<RuntimeOrigin: OriginTrait> ConvertOrigin<RuntimeOrigin>
}
}
parameter_types! {
pub static EnqueuedMessages: Vec<(ParaId, Vec<u8>)> = Default::default();
}
/// An `EnqueueMessage` implementation that puts all messages in thread-local storage.
pub struct EnqueueToLocalStorage<T>(PhantomData<T>);
impl<T: OnQueueChanged<ParaId>> EnqueueMessage<ParaId> for EnqueueToLocalStorage<T> {
type MaxMessageLen = sp_core::ConstU32<65_536>;
fn enqueue_message(message: BoundedSlice<u8, Self::MaxMessageLen>, origin: ParaId) {
let mut msgs = EnqueuedMessages::get();
msgs.push((origin, message.to_vec()));
EnqueuedMessages::set(msgs);
T::on_queue_changed(origin, Self::footprint(origin));
}
fn enqueue_messages<'a>(
iter: impl Iterator<Item = BoundedSlice<'a, u8, Self::MaxMessageLen>>,
origin: ParaId,
) {
let mut msgs = EnqueuedMessages::get();
msgs.extend(iter.map(|m| (origin, m.to_vec())));
EnqueuedMessages::set(msgs);
T::on_queue_changed(origin, Self::footprint(origin));
}
fn sweep_queue(origin: ParaId) {
let mut msgs = EnqueuedMessages::get();
msgs.retain(|(o, _)| o != &origin);
EnqueuedMessages::set(msgs);
T::on_queue_changed(origin, Self::footprint(origin));
}
fn footprint(origin: ParaId) -> QueueFootprint {
let msgs = EnqueuedMessages::get();
let mut footprint = QueueFootprint::default();
for (o, m) in msgs {
if o == origin {
footprint.storage.count += 1;
footprint.storage.size += m.len() as u64;
}
}
footprint.pages = footprint.storage.size as u32 / 16; // Number does not matter
footprint
}
}
parameter_types! {
/// The asset ID for the asset that we use to pay for message delivery fees.
pub FeeAssetId: AssetId = Concrete(RelayChain::get());
@@ -217,10 +268,10 @@ pub type PriceForSiblingParachainDelivery = polkadot_runtime_common::xcm_sender:
impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type XcmExecutor = xcm_executor::XcmExecutor<XcmConfig>;
type ChannelInfo = ParachainSystem;
type ChannelInfo = MockedChannelInfo;
type VersionWrapper = ();
type ExecuteOverweightOrigin = EnsureRoot<AccountId>;
type XcmpQueue = EnqueueToLocalStorage<Pallet<Test>>;
type MaxInboundSuspended = sp_core::ConstU32<1_000>;
type ControllerOrigin = EnsureRoot<AccountId>;
type ControllerOriginConverter = SystemParachainAsSuperuser<RuntimeOrigin>;
type WeightInfo = ();
@@ -228,6 +279,60 @@ impl Config for Test {
}
pub fn new_test_ext() -> sp_io::TestExternalities {
let t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
t.into()
frame_system::GenesisConfig::<Test>::default().build_storage().unwrap().into()
}
/// A para that we have an HRMP channel with.
pub const HRMP_PARA_ID: u32 = 7777;
pub struct MockedChannelInfo;
impl GetChannelInfo for MockedChannelInfo {
fn get_channel_status(id: ParaId) -> ChannelStatus {
if id == HRMP_PARA_ID.into() {
return ChannelStatus::Ready(usize::MAX, usize::MAX)
}
ParachainSystem::get_channel_status(id)
}
fn get_channel_info(id: ParaId) -> Option<ChannelInfo> {
if id == HRMP_PARA_ID.into() {
return Some(ChannelInfo {
max_capacity: u32::MAX,
max_total_size: u32::MAX,
max_message_size: u32::MAX,
msg_count: 0,
total_size: 0,
})
}
ParachainSystem::get_channel_info(id)
}
}
pub(crate) fn mk_page() -> Vec<u8> {
let mut page = Vec::<u8>::new();
for i in 0..100 {
page.extend(match i % 2 {
0 => v2_xcm().encode(),
1 => v3_xcm().encode(),
// We cannot push an undecodable XCM here since it would break the decode stream.
// This is expected and the whole reason to introduce `MaybeDoubleEncodedVersionedXcm`
// instead.
_ => unreachable!(),
});
}
page
}
pub(crate) fn v2_xcm() -> VersionedXcm<()> {
let instr = xcm::v2::Instruction::<()>::ClearOrigin;
VersionedXcm::V2(xcm::v2::Xcm::<()>(vec![instr; 3]))
}
pub(crate) fn v3_xcm() -> VersionedXcm<()> {
let instr = xcm::v3::Instruction::<()>::Trap(1);
VersionedXcm::V3(xcm::v3::Xcm::<()>(vec![instr; 3]))
}
+539 -180
View File
@@ -13,247 +13,325 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::*;
use super::{
mock::{mk_page, v2_xcm, v3_xcm, EnqueuedMessages, HRMP_PARA_ID},
*,
};
use XcmpMessageFormat::*;
use codec::Input;
use cumulus_primitives_core::{ParaId, XcmpMessageHandler};
use frame_support::{assert_noop, assert_ok};
use mock::{new_test_ext, ParachainSystem, RuntimeCall, RuntimeOrigin, Test, XcmpQueue};
use sp_runtime::traits::BadOrigin;
use frame_support::{
assert_err, assert_noop, assert_ok, assert_storage_noop, hypothetically,
traits::{Footprint, Hooks},
StorageNoopGuard,
};
use mock::{new_test_ext, ParachainSystem, RuntimeOrigin as Origin, Test, XcmpQueue};
use sp_runtime::traits::{BadOrigin, Zero};
use std::iter::{once, repeat};
#[test]
fn one_message_does_not_panic() {
fn empty_concatenated_works() {
new_test_ext().execute_with(|| {
let message_format = XcmpMessageFormat::ConcatenatedVersionedXcm.encode();
let messages = vec![(Default::default(), 1u32, message_format.as_slice())];
let data = ConcatenatedVersionedXcm.encode();
// This shouldn't cause a panic
XcmpQueue::handle_xcmp_messages(messages.into_iter(), Weight::MAX);
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX);
})
}
#[test]
#[should_panic = "Invalid incoming blob message data"]
fn xcm_enqueueing_basic_works() {
new_test_ext().execute_with(|| {
let xcm = VersionedXcm::<Test>::from(Xcm::<Test>(vec![ClearOrigin])).encode();
let data = [ConcatenatedVersionedXcm.encode(), xcm.clone()].concat();
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX);
assert_eq!(EnqueuedMessages::get(), vec![(1000.into(), xcm)]);
})
}
#[test]
fn xcm_enqueueing_many_works() {
new_test_ext().execute_with(|| {
let mut encoded_xcms = vec![];
for i in 0..10 {
let xcm = VersionedXcm::<Test>::from(Xcm::<Test>(vec![ClearOrigin; i as usize]));
encoded_xcms.push(xcm.encode());
}
let mut data = ConcatenatedVersionedXcm.encode();
data.extend(encoded_xcms.iter().flatten());
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX);
assert_eq!(
EnqueuedMessages::get(),
encoded_xcms.into_iter().map(|xcm| (1000.into(), xcm)).collect::<Vec<_>>(),
);
})
}
#[test]
fn xcm_enqueueing_multiple_times_works() {
new_test_ext().execute_with(|| {
let mut encoded_xcms = vec![];
for _ in 0..10 {
let xcm = VersionedXcm::<Test>::from(Xcm::<Test>(vec![ClearOrigin]));
encoded_xcms.push(xcm.encode());
}
let mut data = ConcatenatedVersionedXcm.encode();
data.extend(encoded_xcms.iter().flatten());
for i in 0..10 {
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX);
assert_eq!((i + 1) * 10, EnqueuedMessages::get().len());
}
assert_eq!(
EnqueuedMessages::get(),
encoded_xcms
.into_iter()
.map(|xcm| (1000.into(), xcm))
.cycle()
.take(100)
.collect::<Vec<_>>(),
);
})
}
#[test]
#[cfg_attr(debug_assertions, should_panic = "Defensive failure")]
fn xcm_enqueueing_starts_dropping_on_overflow() {
new_test_ext().execute_with(|| {
let xcm = VersionedXcm::<Test>::from(Xcm::<Test>(vec![ClearOrigin]));
let data = (ConcatenatedVersionedXcm, xcm).encode();
// Its possible to enqueue 256 messages at most:
let limit = 256;
XcmpQueue::handle_xcmp_messages(
repeat((1000.into(), 1, data.as_slice())).take(limit * 2),
Weight::MAX,
);
assert_eq!(EnqueuedMessages::get().len(), limit);
// The drop threshold for pages is 48, the others numbers dont really matter:
assert_eq!(
<Test as Config>::XcmpQueue::footprint(1000.into()),
QueueFootprint { storage: Footprint { count: 256, size: 768 }, pages: 48 }
);
})
}
/// First enqueue 10 good, 1 bad and then 10 good XCMs.
///
/// It should only process the first 10 good though.
#[test]
#[cfg(not(debug_assertions))]
fn xcm_enqueueing_broken_xcm_works() {
new_test_ext().execute_with(|| {
let mut encoded_xcms = vec![];
for _ in 0..10 {
let xcm = VersionedXcm::<Test>::from(Xcm::<Test>(vec![ClearOrigin]));
encoded_xcms.push(xcm.encode());
}
let mut good = ConcatenatedVersionedXcm.encode();
good.extend(encoded_xcms.iter().flatten());
let mut bad = ConcatenatedVersionedXcm.encode();
bad.extend(vec![0u8].into_iter());
// Of we enqueue them in multiple pages, then its fine.
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, good.as_slice())), Weight::MAX);
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, bad.as_slice())), Weight::MAX);
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, good.as_slice())), Weight::MAX);
assert_eq!(20, EnqueuedMessages::get().len());
assert_eq!(
EnqueuedMessages::get(),
encoded_xcms
.clone()
.into_iter()
.map(|xcm| (1000.into(), xcm))
.cycle()
.take(20)
.collect::<Vec<_>>(),
);
EnqueuedMessages::set(&vec![]);
// But if we do it all in one page, then it only uses the first 10:
XcmpQueue::handle_xcmp_messages(
vec![(1000.into(), 1, good.as_slice()), (1000.into(), 1, bad.as_slice())].into_iter(),
Weight::MAX,
);
assert_eq!(10, EnqueuedMessages::get().len());
assert_eq!(
EnqueuedMessages::get(),
encoded_xcms
.into_iter()
.map(|xcm| (1000.into(), xcm))
.cycle()
.take(10)
.collect::<Vec<_>>(),
);
})
}
/// Message blobs are not supported and panic in debug mode.
#[test]
#[should_panic = "Blob messages are unhandled"]
#[cfg(debug_assertions)]
fn bad_message_is_handled() {
fn bad_blob_message_panics() {
new_test_ext().execute_with(|| {
let bad_data = vec![
1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 64, 239, 139, 0,
0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 37, 0,
0, 0, 0, 0, 0, 0, 16, 0, 127, 147,
];
InboundXcmpMessages::<Test>::insert(ParaId::from(1000), 1, bad_data);
let format = XcmpMessageFormat::ConcatenatedEncodedBlob;
// This should exit with an error.
XcmpQueue::process_xcmp_message(
1000.into(),
(1, format),
&mut 0,
Weight::from_parts(10_000_000_000, 0),
Weight::from_parts(10_000_000_000, 0),
);
let data = [ConcatenatedEncodedBlob.encode(), vec![1].encode()].concat();
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX);
});
}
/// Tests that a blob message is handled. Currently this isn't implemented and panics when debug
/// assertions are enabled. When this feature is enabled, this test should be rewritten properly.
/// Message blobs do not panic in release mode but are just a No-OP.
#[test]
#[should_panic = "Blob messages not handled."]
#[cfg(not(debug_assertions))]
fn bad_blob_message_no_panic() {
new_test_ext().execute_with(|| {
let data = [ConcatenatedEncodedBlob.encode(), vec![1].encode()].concat();
frame_support::assert_storage_noop!(XcmpQueue::handle_xcmp_messages(
once((1000.into(), 1, data.as_slice())),
Weight::MAX,
));
});
}
/// Invalid concatenated XCMs panic in debug mode.
#[test]
#[should_panic = "HRMP inbound decode stream broke; page will be dropped."]
#[cfg(debug_assertions)]
fn handle_blob_message() {
fn handle_invalid_data_panics() {
new_test_ext().execute_with(|| {
let bad_data = vec![
1, 1, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 64, 239,
139, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0,
37, 0, 0, 0, 0, 0, 0, 0, 16, 0, 127, 147,
];
InboundXcmpMessages::<Test>::insert(ParaId::from(1000), 1, bad_data);
let format = XcmpMessageFormat::ConcatenatedEncodedBlob;
XcmpQueue::process_xcmp_message(
1000.into(),
(1, format),
&mut 0,
Weight::from_parts(10_000_000_000, 0),
Weight::from_parts(10_000_000_000, 0),
);
let data = [ConcatenatedVersionedXcm.encode(), Xcm::<Test>(vec![]).encode()].concat();
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX);
});
}
/// Invalid concatenated XCMs do not panic in release mode but are just a No-OP.
#[test]
#[should_panic = "Invalid incoming XCMP message data"]
#[cfg(debug_assertions)]
fn handle_invalid_data() {
#[cfg(not(debug_assertions))]
fn handle_invalid_data_no_panic() {
new_test_ext().execute_with(|| {
let data = Xcm::<Test>(vec![]).encode();
InboundXcmpMessages::<Test>::insert(ParaId::from(1000), 1, data);
let format = XcmpMessageFormat::ConcatenatedVersionedXcm;
XcmpQueue::process_xcmp_message(
1000.into(),
(1, format),
&mut 0,
Weight::from_parts(10_000_000_000, 0),
Weight::from_parts(10_000_000_000, 0),
);
});
}
let data = [ConcatenatedVersionedXcm.encode(), Xcm::<Test>(vec![]).encode()].concat();
#[test]
fn service_overweight_unknown() {
new_test_ext().execute_with(|| {
assert_noop!(
XcmpQueue::service_overweight(RuntimeOrigin::root(), 0, Weight::from_parts(1000, 1000)),
Error::<Test>::BadOverweightIndex,
);
});
}
#[test]
fn service_overweight_bad_xcm_format() {
new_test_ext().execute_with(|| {
let bad_xcm = vec![255];
Overweight::<Test>::insert(0, (ParaId::from(1000), 0, bad_xcm));
assert_noop!(
XcmpQueue::service_overweight(RuntimeOrigin::root(), 0, Weight::from_parts(1000, 1000)),
Error::<Test>::BadXcm
);
frame_support::assert_storage_noop!(XcmpQueue::handle_xcmp_messages(
once((1000.into(), 1, data.as_slice())),
Weight::MAX,
));
});
}
#[test]
fn suspend_xcm_execution_works() {
new_test_ext().execute_with(|| {
assert!(!XcmpQueue::is_paused(&2000.into()));
QueueSuspended::<Test>::put(true);
assert!(XcmpQueue::is_paused(&2000.into()));
// System parachains can bypass suspension:
assert!(!XcmpQueue::is_paused(&999.into()));
});
}
let xcm =
VersionedXcm::from(Xcm::<RuntimeCall>(vec![Instruction::<RuntimeCall>::ClearOrigin]))
.encode();
let mut message_format = XcmpMessageFormat::ConcatenatedVersionedXcm.encode();
message_format.extend(xcm.clone());
let messages = vec![(ParaId::from(999), 1u32, message_format.as_slice())];
#[test]
fn suspend_and_resume_xcm_execution_work() {
new_test_ext().execute_with(|| {
assert_noop!(XcmpQueue::suspend_xcm_execution(Origin::signed(1)), BadOrigin);
assert_ok!(XcmpQueue::suspend_xcm_execution(Origin::root()));
assert_noop!(
XcmpQueue::suspend_xcm_execution(Origin::root()),
Error::<Test>::AlreadySuspended
);
assert!(QueueSuspended::<Test>::get());
// This should have executed the incoming XCM, because it came from a system parachain
XcmpQueue::handle_xcmp_messages(messages.into_iter(), Weight::MAX);
assert_noop!(XcmpQueue::resume_xcm_execution(Origin::signed(1)), BadOrigin);
assert_ok!(XcmpQueue::resume_xcm_execution(Origin::root()));
assert_noop!(
XcmpQueue::resume_xcm_execution(Origin::root()),
Error::<Test>::AlreadyResumed
);
assert!(!QueueSuspended::<Test>::get());
});
}
let queued_xcm = InboundXcmpMessages::<Test>::get(ParaId::from(999), 1u32);
assert!(queued_xcm.is_empty());
#[test]
#[cfg(not(debug_assertions))]
fn xcm_enqueueing_backpressure_works() {
let para: ParaId = 1000.into();
new_test_ext().execute_with(|| {
let xcm = VersionedXcm::<Test>::from(Xcm::<Test>(vec![ClearOrigin]));
let data = (ConcatenatedVersionedXcm, xcm).encode();
let messages = vec![(ParaId::from(2000), 1u32, message_format.as_slice())];
XcmpQueue::handle_xcmp_messages(repeat((para, 1, data.as_slice())).take(170), Weight::MAX);
// This shouldn't have executed the incoming XCM
XcmpQueue::handle_xcmp_messages(messages.into_iter(), Weight::MAX);
assert_eq!(EnqueuedMessages::get().len(), 170,);
// Not yet suspended:
assert!(InboundXcmpSuspended::<Test>::get().is_empty());
// Enqueueing one more will suspend it:
let xcm = VersionedXcm::<Test>::from(Xcm::<Test>(vec![ClearOrigin])).encode();
let small = [ConcatenatedVersionedXcm.encode(), xcm].concat();
let queued_xcm = InboundXcmpMessages::<Test>::get(ParaId::from(2000), 1u32);
assert_eq!(queued_xcm, xcm);
XcmpQueue::handle_xcmp_messages(once((para, 1, small.as_slice())), Weight::MAX);
// Suspended:
assert_eq!(InboundXcmpSuspended::<Test>::get().iter().collect::<Vec<_>>(), vec![&para]);
// Now enqueueing many more will only work until the drop threshold:
XcmpQueue::handle_xcmp_messages(repeat((para, 1, data.as_slice())).take(100), Weight::MAX);
assert_eq!(mock::EnqueuedMessages::get().len(), 256);
crate::mock::EnqueueToLocalStorage::<Pallet<Test>>::sweep_queue(para);
XcmpQueue::handle_xcmp_messages(once((para, 1, small.as_slice())), Weight::MAX);
// Got resumed:
assert!(InboundXcmpSuspended::<Test>::get().is_empty());
// Still resumed:
XcmpQueue::handle_xcmp_messages(once((para, 1, small.as_slice())), Weight::MAX);
assert!(InboundXcmpSuspended::<Test>::get().is_empty());
});
}
#[test]
fn update_suspend_threshold_works() {
new_test_ext().execute_with(|| {
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.suspend_threshold, 2);
assert_ok!(XcmpQueue::update_suspend_threshold(RuntimeOrigin::root(), 3));
assert_noop!(XcmpQueue::update_suspend_threshold(RuntimeOrigin::signed(2), 5), BadOrigin);
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(<QueueConfig<Test>>::get().suspend_threshold, 32);
assert_noop!(XcmpQueue::update_suspend_threshold(Origin::signed(2), 49), BadOrigin);
assert_eq!(data.suspend_threshold, 3);
assert_ok!(XcmpQueue::update_suspend_threshold(Origin::root(), 33));
assert_eq!(<QueueConfig<Test>>::get().suspend_threshold, 33);
});
}
#[test]
fn update_drop_threshold_works() {
new_test_ext().execute_with(|| {
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.drop_threshold, 5);
assert_ok!(XcmpQueue::update_drop_threshold(RuntimeOrigin::root(), 6));
assert_noop!(XcmpQueue::update_drop_threshold(RuntimeOrigin::signed(2), 7), BadOrigin);
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(<QueueConfig<Test>>::get().drop_threshold, 48);
assert_ok!(XcmpQueue::update_drop_threshold(Origin::root(), 4000));
assert_noop!(XcmpQueue::update_drop_threshold(Origin::signed(2), 7), BadOrigin);
assert_eq!(data.drop_threshold, 6);
assert_eq!(<QueueConfig<Test>>::get().drop_threshold, 4000);
});
}
#[test]
fn update_resume_threshold_works() {
new_test_ext().execute_with(|| {
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.resume_threshold, 1);
assert_ok!(XcmpQueue::update_resume_threshold(RuntimeOrigin::root(), 2));
assert_noop!(XcmpQueue::update_resume_threshold(RuntimeOrigin::signed(7), 3), BadOrigin);
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.resume_threshold, 2);
});
}
#[test]
fn update_threshold_weight_works() {
new_test_ext().execute_with(|| {
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.threshold_weight, Weight::from_parts(100_000, 0));
assert_ok!(XcmpQueue::update_threshold_weight(
RuntimeOrigin::root(),
Weight::from_parts(10_000, 0)
));
assert_eq!(<QueueConfig<Test>>::get().resume_threshold, 8);
assert_noop!(
XcmpQueue::update_threshold_weight(
RuntimeOrigin::signed(5),
Weight::from_parts(10_000_000, 0),
),
BadOrigin
XcmpQueue::update_resume_threshold(Origin::root(), 0),
Error::<Test>::BadQueueConfig
);
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.threshold_weight, Weight::from_parts(10_000, 0));
});
}
#[test]
fn update_weight_restrict_decay_works() {
new_test_ext().execute_with(|| {
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.weight_restrict_decay, Weight::from_parts(2, 0));
assert_ok!(XcmpQueue::update_weight_restrict_decay(
RuntimeOrigin::root(),
Weight::from_parts(5, 0)
));
assert_noop!(
XcmpQueue::update_weight_restrict_decay(
RuntimeOrigin::signed(6),
Weight::from_parts(4, 0),
),
BadOrigin
XcmpQueue::update_resume_threshold(Origin::root(), 33),
Error::<Test>::BadQueueConfig
);
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_ok!(XcmpQueue::update_resume_threshold(Origin::root(), 16));
assert_noop!(XcmpQueue::update_resume_threshold(Origin::signed(7), 3), BadOrigin);
assert_eq!(data.weight_restrict_decay, Weight::from_parts(5, 0));
});
}
#[test]
fn update_xcmp_max_individual_weight() {
new_test_ext().execute_with(|| {
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(
data.xcmp_max_individual_weight,
Weight::from_parts(20u64 * WEIGHT_REF_TIME_PER_MILLIS, DEFAULT_POV_SIZE),
);
assert_ok!(XcmpQueue::update_xcmp_max_individual_weight(
RuntimeOrigin::root(),
Weight::from_parts(30u64 * WEIGHT_REF_TIME_PER_MILLIS, 0)
));
assert_noop!(
XcmpQueue::update_xcmp_max_individual_weight(
RuntimeOrigin::signed(3),
Weight::from_parts(10u64 * WEIGHT_REF_TIME_PER_MILLIS, 0)
),
BadOrigin
);
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(
data.xcmp_max_individual_weight,
Weight::from_parts(30u64 * WEIGHT_REF_TIME_PER_MILLIS, 0)
);
assert_eq!(<QueueConfig<Test>>::get().resume_threshold, 16);
});
}
@@ -343,6 +421,287 @@ fn xcmp_queue_consumes_dest_and_msg_on_ok_validate() {
});
}
#[test]
fn xcmp_queue_validate_nested_xcm_works() {
let dest = (Parent, X1(Parachain(5555)));
// Message that is not too deeply nested:
let mut good = Xcm(vec![ClearOrigin]);
for _ in 0..MAX_XCM_DECODE_DEPTH - 1 {
good = Xcm(vec![SetAppendix(good)]);
}
new_test_ext().execute_with(|| {
// Check that the good message is validated:
assert_ok!(<XcmpQueue as SendXcm>::validate(
&mut Some(dest.into()),
&mut Some(good.clone())
));
// Nesting the message one more time should reject it:
let bad = Xcm(vec![SetAppendix(good)]);
assert_eq!(
Err(SendError::ExceedsMaxMessageSize),
<XcmpQueue as SendXcm>::validate(&mut Some(dest.into()), &mut Some(bad))
);
});
}
#[test]
fn send_xcm_nested_works() {
let dest = (Parent, X1(Parachain(HRMP_PARA_ID)));
// Message that is not too deeply nested:
let mut good = Xcm(vec![ClearOrigin]);
for _ in 0..MAX_XCM_DECODE_DEPTH - 1 {
good = Xcm(vec![SetAppendix(good)]);
}
// Check that the good message is sent:
new_test_ext().execute_with(|| {
assert_ok!(send_xcm::<XcmpQueue>(dest.into(), good.clone()));
assert_eq!(
XcmpQueue::take_outbound_messages(usize::MAX),
vec![(
HRMP_PARA_ID.into(),
(XcmpMessageFormat::ConcatenatedVersionedXcm, VersionedXcm::V3(good.clone()))
.encode(),
)]
);
});
// Nesting the message one more time should not send it:
let bad = Xcm(vec![SetAppendix(good)]);
new_test_ext().execute_with(|| {
assert_err!(send_xcm::<XcmpQueue>(dest.into(), bad), SendError::ExceedsMaxMessageSize);
assert!(XcmpQueue::take_outbound_messages(usize::MAX).is_empty());
});
}
#[test]
fn hrmp_signals_are_prioritized() {
let message = Xcm(vec![Trap(5)]);
let sibling_para_id = ParaId::from(12345);
let dest = (Parent, X1(Parachain(sibling_para_id.into())));
let mut dest_wrapper = Some(dest.into());
let mut msg_wrapper = Some(message.clone());
new_test_ext().execute_with(|| {
frame_system::Pallet::<Test>::set_block_number(1);
<XcmpQueue as SendXcm>::validate(&mut dest_wrapper, &mut msg_wrapper).unwrap();
// check wrapper were consumed
assert_eq!(None, dest_wrapper.take());
assert_eq!(None, msg_wrapper.take());
ParachainSystem::open_custom_outbound_hrmp_channel_for_benchmarks_or_tests(
sibling_para_id,
cumulus_primitives_core::AbridgedHrmpChannel {
max_capacity: 128,
max_total_size: 1 << 16,
max_message_size: 128,
msg_count: 0,
total_size: 0,
mqc_head: None,
},
);
let taken = XcmpQueue::take_outbound_messages(130);
assert_eq!(taken, vec![]);
// Enqueue some messages
let num_events = frame_system::Pallet::<Test>::events().len();
for _ in 0..256 {
assert_ok!(send_xcm::<XcmpQueue>(dest.into(), message.clone()));
}
assert_eq!(num_events + 256, frame_system::Pallet::<Test>::events().len());
// Without a signal we get the messages in order:
let mut expected_msg = XcmpMessageFormat::ConcatenatedVersionedXcm.encode();
for _ in 0..31 {
expected_msg.extend(VersionedXcm::V3(message.clone()).encode());
}
hypothetically!({
let taken = XcmpQueue::take_outbound_messages(usize::MAX);
assert_eq!(taken, vec![(sibling_para_id.into(), expected_msg,)]);
});
// But a signal gets prioritized instead of the messages:
XcmpQueue::send_signal(sibling_para_id.into(), ChannelSignal::Suspend);
let taken = XcmpQueue::take_outbound_messages(130);
assert_eq!(
taken,
vec![(
sibling_para_id.into(),
(XcmpMessageFormat::Signals, ChannelSignal::Suspend).encode()
)]
);
});
}
#[test]
fn maybe_double_encoded_versioned_xcm_works() {
// pre conditions
assert_eq!(VersionedXcm::<()>::V2(Default::default()).encode(), &[2, 0]);
assert_eq!(VersionedXcm::<()>::V3(Default::default()).encode(), &[3, 0]);
}
// Now also testing a page instead of just concat messages.
#[test]
fn maybe_double_encoded_versioned_xcm_decode_page_works() {
let page = mk_page();
// Now try to decode the page.
let input = &mut &page[..];
for i in 0..100 {
match (i % 2, VersionedXcm::<()>::decode(input)) {
(0, Ok(xcm)) => {
assert_eq!(xcm, v2_xcm());
},
(1, Ok(xcm)) => {
assert_eq!(xcm, v3_xcm());
},
unexpected => unreachable!("{:?}", unexpected),
}
}
assert_eq!(input.remaining_len(), Ok(Some(0)), "All data consumed");
}
/// Check that `take_first_concatenated_xcm` correctly splits a page into its XCMs.
#[test]
fn take_first_concatenated_xcm_works() {
let page = mk_page();
let input = &mut &page[..];
for i in 0..100 {
let xcm = XcmpQueue::take_first_concatenated_xcm(input, &mut WeightMeter::new()).unwrap();
match (i % 2, xcm) {
(0, data) | (2, data) => {
assert_eq!(data, v2_xcm().encode());
},
(1, data) | (3, data) => {
assert_eq!(data, v3_xcm().encode());
},
unexpected => unreachable!("{:?}", unexpected),
}
}
assert_eq!(input.remaining_len(), Ok(Some(0)), "All data consumed");
}
/// A message that is not too deeply nested will be accepted by `take_first_concatenated_xcm`.
#[test]
fn take_first_concatenated_xcm_good_recursion_depth_works() {
let mut good = Xcm::<()>(vec![ClearOrigin]);
for _ in 0..MAX_XCM_DECODE_DEPTH - 1 {
good = Xcm(vec![SetAppendix(good)]);
}
let good = VersionedXcm::V3(good);
let page = good.encode();
assert_ok!(XcmpQueue::take_first_concatenated_xcm(&mut &page[..], &mut WeightMeter::new()));
}
/// A message that is too deeply nested will be rejected by `take_first_concatenated_xcm`.
#[test]
fn take_first_concatenated_xcm_good_bad_depth_errors() {
let mut bad = Xcm::<()>(vec![ClearOrigin]);
for _ in 0..MAX_XCM_DECODE_DEPTH {
bad = Xcm(vec![SetAppendix(bad)]);
}
let bad = VersionedXcm::V3(bad);
let page = bad.encode();
assert_err!(
XcmpQueue::take_first_concatenated_xcm(&mut &page[..], &mut WeightMeter::new()),
()
);
}
#[test]
fn lazy_migration_works() {
use crate::migration::v3::*;
new_test_ext().execute_with(|| {
EnqueuedMessages::set(vec![]);
let _g = StorageNoopGuard::default(); // No storage is leaked.
let mut channels = vec![];
for i in 0..20 {
let para = ParaId::from(i);
let mut message_metadata = vec![];
for block in 0..30 {
message_metadata.push((block, XcmpMessageFormat::ConcatenatedVersionedXcm));
InboundXcmpMessages::<Test>::insert(para, block, vec![(i + block) as u8]);
}
channels.push(InboundChannelDetails {
sender: para,
state: InboundState::Ok,
message_metadata,
});
}
InboundXcmpStatus::<Test>::set(Some(channels));
for para in 0..20u32 {
assert_eq!(InboundXcmpStatus::<Test>::get().unwrap().len() as u32, 20 - para);
assert_eq!(InboundXcmpMessages::<Test>::iter_prefix(ParaId::from(para)).count(), 30);
for block in 0..30 {
XcmpQueue::on_idle(0u32.into(), Weight::MAX);
assert_eq!(
EnqueuedMessages::get(),
vec![(para.into(), vec![(29 - block + para) as u8])]
);
EnqueuedMessages::set(vec![]);
}
// One more to jump to the next channel:
XcmpQueue::on_idle(0u32.into(), Weight::MAX);
assert_eq!(InboundXcmpStatus::<Test>::get().unwrap().len() as u32, 20 - para - 1);
assert_eq!(InboundXcmpMessages::<Test>::iter_prefix(ParaId::from(para)).count(), 0);
}
// One more for the cleanup:
XcmpQueue::on_idle(0u32.into(), Weight::MAX);
assert!(!InboundXcmpStatus::<Test>::exists());
assert_eq!(InboundXcmpMessages::<Test>::iter().count(), 0);
EnqueuedMessages::set(vec![]);
});
}
#[test]
fn lazy_migration_noop_when_out_of_weight() {
use crate::migration::v3::*;
assert!(!XcmpQueue::on_idle_weight().is_zero(), "pre condition");
new_test_ext().execute_with(|| {
let _g = StorageNoopGuard::default(); // No storage is leaked.
let block = 5;
let para = ParaId::from(4);
let message_metadata = vec![(block, XcmpMessageFormat::ConcatenatedVersionedXcm)];
InboundXcmpMessages::<Test>::insert(para, block, vec![123u8]);
InboundXcmpStatus::<Test>::set(Some(vec![InboundChannelDetails {
sender: para,
state: InboundState::Ok,
message_metadata,
}]));
// Hypothetically, it would do something with enough weight limit:
hypothetically!({
XcmpQueue::on_idle(0u32.into(), Weight::MAX);
assert_eq!(EnqueuedMessages::get(), vec![(para, vec![123u8])]);
});
// But does not, since the limit is zero:
assert_storage_noop!({ XcmpQueue::on_idle(0u32.into(), Weight::zero()) });
InboundXcmpMessages::<Test>::remove(para, block);
InboundXcmpStatus::<Test>::kill();
});
}
#[test]
fn xcmp_queue_send_xcm_works() {
new_test_ext().execute_with(|| {
+208 -20
View File
@@ -13,51 +13,239 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! Autogenerated weights for `cumulus_pallet_xcmp_queue`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2023-09-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `Olivers-MacBook-Pro.local`, CPU: `<UNKNOWN>`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-kusama-dev")`, DB CACHE: `1024`
// Executed Command:
// ./target/release/polkadot-parachain
// benchmark
// pallet
// --pallet
// cumulus-pallet-xcmp-queue
// --chain
// asset-hub-kusama-dev
// --output
// cumulus/pallets/xcmp-queue/src/weights.rs
// --template
// substrate/.maintain/frame-weight-template.hbs
// --extrinsic
//
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{
traits::Get,
weights::{constants::RocksDbWeight, Weight},
};
use sp_std::marker::PhantomData;
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use core::marker::PhantomData;
// Implemented by autogenerated benchmarking code.
/// Weight functions needed for `cumulus_pallet_xcmp_queue`.
pub trait WeightInfo {
fn set_config_with_u32() -> Weight;
fn set_config_with_weight() -> Weight;
fn enqueue_xcmp_message() -> Weight;
fn suspend_channel() -> Weight;
fn resume_channel() -> Weight;
fn take_first_concatenated_xcm() -> Weight;
fn on_idle_good_msg() -> Weight;
fn on_idle_large_msg() -> Weight;
}
/// Weights for `cumulus_pallet_xcmp_queue` using the Substrate node and recommended hardware.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: XcmpQueue QueueConfig (r:1 w:1)
/// Storage: `XcmpQueue::QueueConfig` (r:1 w:1)
/// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn set_config_with_u32() -> Weight {
Weight::from_parts(2_717_000_u64, 0)
// Proof Size summary in bytes:
// Measured: `76`
// Estimated: `1561`
// Minimum execution time: 5_000_000 picoseconds.
Weight::from_parts(6_000_000, 1561)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
// Storage: XcmpQueue QueueConfig (r:1 w:1)
fn set_config_with_weight() -> Weight {
Weight::from_parts(2_717_000_u64, 0)
/// Storage: `XcmpQueue::QueueConfig` (r:1 w:0)
/// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `MessageQueue::BookStateFor` (r:1 w:1)
/// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
/// Storage: `MessageQueue::ServiceHead` (r:1 w:1)
/// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`)
/// Storage: `XcmpQueue::InboundXcmpSuspended` (r:1 w:0)
/// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `MessageQueue::Pages` (r:0 w:1)
/// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`)
fn enqueue_xcmp_message() -> Weight {
// Proof Size summary in bytes:
// Measured: `118`
// Estimated: `3517`
// Minimum execution time: 15_000_000 picoseconds.
Weight::from_parts(16_000_000, 3517)
.saturating_add(T::DbWeight::get().reads(4_u64))
.saturating_add(T::DbWeight::get().writes(3_u64))
}
/// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1)
/// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn suspend_channel() -> Weight {
// Proof Size summary in bytes:
// Measured: `76`
// Estimated: `1561`
// Minimum execution time: 3_000_000 picoseconds.
Weight::from_parts(4_000_000, 1561)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1)
/// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn resume_channel() -> Weight {
// Proof Size summary in bytes:
// Measured: `111`
// Estimated: `1596`
// Minimum execution time: 4_000_000 picoseconds.
Weight::from_parts(4_000_000, 1596)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
fn take_first_concatenated_xcm() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 44_000_000 picoseconds.
Weight::from_parts(45_000_000, 0)
}
/// Storage: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1)
/// Proof: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1)
/// Storage: `XcmpQueue::InboundXcmpMessages` (r:1 w:1)
/// Proof: `XcmpQueue::InboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `MessageQueue::BookStateFor` (r:1 w:1)
/// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
/// Storage: `MessageQueue::ServiceHead` (r:1 w:1)
/// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`)
/// Storage: `XcmpQueue::QueueConfig` (r:1 w:0)
/// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `XcmpQueue::InboundXcmpSuspended` (r:1 w:0)
/// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `MessageQueue::Pages` (r:0 w:1)
/// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`)
fn on_idle_good_msg() -> Weight {
// Proof Size summary in bytes:
// Measured: `65747`
// Estimated: `69212`
// Minimum execution time: 63_000_000 picoseconds.
Weight::from_parts(66_000_000, 69212)
.saturating_add(T::DbWeight::get().reads(6_u64))
.saturating_add(T::DbWeight::get().writes(5_u64))
}
/// Storage: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1)
/// Proof: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1)
fn on_idle_large_msg() -> Weight {
// Proof Size summary in bytes:
// Measured: `65710`
// Estimated: `69175`
// Minimum execution time: 42_000_000 picoseconds.
Weight::from_parts(52_000_000, 69175)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(2_u64))
}
}
// For backwards compatibility and tests.
impl WeightInfo for () {
// Storage: XcmpQueue QueueConfig (r:1 w:1)
/// Storage: `XcmpQueue::QueueConfig` (r:1 w:1)
/// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn set_config_with_u32() -> Weight {
Weight::from_parts(2_717_000_u64, 0)
// Proof Size summary in bytes:
// Measured: `76`
// Estimated: `1561`
// Minimum execution time: 5_000_000 picoseconds.
Weight::from_parts(6_000_000, 1561)
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
// Storage: XcmpQueue QueueConfig (r:1 w:1)
fn set_config_with_weight() -> Weight {
Weight::from_parts(2_717_000_u64, 0)
/// Storage: `XcmpQueue::QueueConfig` (r:1 w:0)
/// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `MessageQueue::BookStateFor` (r:1 w:1)
/// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
/// Storage: `MessageQueue::ServiceHead` (r:1 w:1)
/// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`)
/// Storage: `XcmpQueue::InboundXcmpSuspended` (r:1 w:0)
/// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `MessageQueue::Pages` (r:0 w:1)
/// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`)
fn enqueue_xcmp_message() -> Weight {
// Proof Size summary in bytes:
// Measured: `118`
// Estimated: `3517`
// Minimum execution time: 15_000_000 picoseconds.
Weight::from_parts(16_000_000, 3517)
.saturating_add(RocksDbWeight::get().reads(4_u64))
.saturating_add(RocksDbWeight::get().writes(3_u64))
}
/// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1)
/// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn suspend_channel() -> Weight {
// Proof Size summary in bytes:
// Measured: `76`
// Estimated: `1561`
// Minimum execution time: 3_000_000 picoseconds.
Weight::from_parts(4_000_000, 1561)
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1)
/// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn resume_channel() -> Weight {
// Proof Size summary in bytes:
// Measured: `111`
// Estimated: `1596`
// Minimum execution time: 4_000_000 picoseconds.
Weight::from_parts(4_000_000, 1596)
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
fn take_first_concatenated_xcm() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 44_000_000 picoseconds.
Weight::from_parts(45_000_000, 0)
}
/// Storage: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1)
/// Proof: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1)
/// Storage: `XcmpQueue::InboundXcmpMessages` (r:1 w:1)
/// Proof: `XcmpQueue::InboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `MessageQueue::BookStateFor` (r:1 w:1)
/// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
/// Storage: `MessageQueue::ServiceHead` (r:1 w:1)
/// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`)
/// Storage: `XcmpQueue::QueueConfig` (r:1 w:0)
/// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `XcmpQueue::InboundXcmpSuspended` (r:1 w:0)
/// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `MessageQueue::Pages` (r:0 w:1)
/// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`)
fn on_idle_good_msg() -> Weight {
// Proof Size summary in bytes:
// Measured: `65747`
// Estimated: `69212`
// Minimum execution time: 63_000_000 picoseconds.
Weight::from_parts(66_000_000, 69212)
.saturating_add(RocksDbWeight::get().reads(6_u64))
.saturating_add(RocksDbWeight::get().writes(5_u64))
}
/// Storage: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1)
/// Proof: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1)
fn on_idle_large_msg() -> Weight {
// Proof Size summary in bytes:
// Measured: `65710`
// Estimated: `69175`
// Minimum execution time: 42_000_000 picoseconds.
Weight::from_parts(52_000_000, 69175)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(2_u64))
}
}