Files
pezkuwi-subxt/cumulus/pallets/dmp-queue/src/tests.rs
T
Oliver Tale-Yazdi e1c033ebe1 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 <>
2023-11-02 15:31:38 +01:00

235 lines
7.3 KiB
Rust

// Copyright Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// Substrate 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.
// Substrate 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 Cumulus. If not, see <http://www.gnu.org/licenses/>.
//! Test the lazy migration.
#![cfg(test)]
use super::{migration::*, mock::*};
use crate::*;
use frame_support::{
pallet_prelude::*,
traits::{OnFinalize, OnIdle, OnInitialize},
StorageNoopGuard,
};
#[test]
fn migration_works() {
let mut ext = new_test_ext();
ext.execute_with(|| {
sp_tracing::try_init_simple();
// Insert some storage:
PageIndex::<Runtime>::set(PageIndexData {
begin_used: 10,
end_used: 20,
overweight_count: 5,
});
for p in 10..20 {
let msgs = (0..16).map(|i| (p, vec![i as u8; 1])).collect::<Vec<_>>();
Pages::<Runtime>::insert(p, msgs);
}
for i in 0..5 {
Overweight::<Runtime>::insert(i, (0, vec![i as u8; 1]));
}
testing_only::Configuration::<Runtime>::put(123);
});
// We need to commit, otherwise the keys are removed from the overlay; not the backend.
ext.commit_all().unwrap();
ext.execute_with(|| {
// Run one step of the migration:
pre_upgrade_checks::<Runtime>();
run_to_block(1);
// First we expect a StartedExport event:
assert_only_event(Event::StartedExport);
// Then we expect 10 Exported events:
for page in 0..10 {
run_to_block(2 + page);
assert_only_event(Event::Exported { page: page as u32 + 10 });
assert!(!Pages::<Runtime>::contains_key(page as u32), "Page is gone");
assert_eq!(
MigrationStatus::<Runtime>::get(),
MigrationState::StartedExport { next_begin_used: page as u32 + 11 }
);
}
// Then we expect a CompletedExport event:
run_to_block(12);
assert_only_event(Event::CompletedExport);
assert_eq!(MigrationStatus::<Runtime>::get(), MigrationState::CompletedExport);
// Then we expect a StartedOverweightExport event:
run_to_block(13);
assert_only_event(Event::StartedOverweightExport);
assert_eq!(
MigrationStatus::<Runtime>::get(),
MigrationState::StartedOverweightExport { next_overweight_index: 0 }
);
// Then we expect 5 ExportedOverweight events:
for index in 0..5 {
run_to_block(14 + index);
assert_only_event(Event::ExportedOverweight { index });
assert!(!Overweight::<Runtime>::contains_key(index), "Overweight msg is gone");
assert_eq!(
MigrationStatus::<Runtime>::get(),
MigrationState::StartedOverweightExport { next_overweight_index: index + 1 }
);
}
// Then we expect a CompletedOverweightExport event:
run_to_block(19);
assert_only_event(Event::CompletedOverweightExport);
assert_eq!(MigrationStatus::<Runtime>::get(), MigrationState::CompletedOverweightExport);
// Then we expect a StartedCleanup event:
run_to_block(20);
assert_only_event(Event::StartedCleanup);
assert_eq!(
MigrationStatus::<Runtime>::get(),
MigrationState::StartedCleanup { cursor: None }
);
});
ext.commit_all().unwrap();
// Then it cleans up the remaining storage items:
ext.execute_with(|| {
run_to_block(21);
assert_only_event(Event::CleanedSome { keys_removed: 2 });
});
ext.commit_all().unwrap();
ext.execute_with(|| {
run_to_block(22);
assert_only_event(Event::CleanedSome { keys_removed: 2 });
});
ext.commit_all().unwrap();
ext.execute_with(|| {
run_to_block(24);
assert_eq!(
System::events().into_iter().map(|e| e.event).collect::<Vec<_>>(),
vec![
Event::CleanedSome { keys_removed: 2 }.into(),
Event::Completed { error: false }.into()
]
);
System::reset_events();
assert_eq!(MigrationStatus::<Runtime>::get(), MigrationState::Completed);
post_upgrade_checks::<Runtime>();
assert_eq!(RecordedMessages::take().len(), 10 * 16 + 5);
// Test the storage removal:
assert!(!PageIndex::<Runtime>::exists());
assert!(!testing_only::Configuration::<Runtime>::exists());
assert_eq!(Pages::<Runtime>::iter_keys().count(), 0);
assert_eq!(Overweight::<Runtime>::iter_keys().count(), 0);
// The `MigrationStatus` never disappears and there are no more storage changes:
{
let _g = StorageNoopGuard::default();
run_to_block(100);
assert_eq!(MigrationStatus::<Runtime>::get(), MigrationState::Completed);
assert!(System::events().is_empty());
// ... besides the block number
System::set_block_number(24);
}
});
}
/// Too long messages are dropped by the migration.
#[test]
fn migration_too_long_ignored() {
new_test_ext().execute_with(|| {
// Setup the storage:
PageIndex::<Runtime>::set(PageIndexData {
begin_used: 10,
end_used: 11,
overweight_count: 2,
});
let short = vec![1; 16];
let long = vec![0; 17];
Pages::<Runtime>::insert(10, vec![(10, short.clone()), (10, long.clone())]);
// Insert one good and one bad overweight msg:
Overweight::<Runtime>::insert(0, (0, short.clone()));
Overweight::<Runtime>::insert(1, (0, long.clone()));
// Run the migration:
pre_upgrade_checks::<Runtime>();
run_to_block(100);
post_upgrade_checks::<Runtime>();
assert_eq!(RecordedMessages::take(), vec![short.clone(), short]);
// Test the storage removal:
assert!(!PageIndex::<Runtime>::exists());
assert_eq!(Pages::<Runtime>::iter_keys().count(), 0);
assert_eq!(Overweight::<Runtime>::iter_keys().count(), 0);
});
}
fn run_to_block(n: u64) {
assert!(n > System::block_number(), "Cannot go back in time");
while System::block_number() < n {
AllPalletsWithSystem::on_finalize(System::block_number());
System::set_block_number(System::block_number() + 1);
AllPalletsWithSystem::on_initialize(System::block_number());
AllPalletsWithSystem::on_idle(System::block_number(), Weight::MAX);
}
}
fn assert_only_event(e: Event<Runtime>) {
assert_eq!(System::events().pop().expect("Event expected").event, e.clone().into());
assert_eq!(System::events().len(), 1, "Got events: {:?} but wanted {:?}", System::events(), e);
System::reset_events();
}
/// TESTING ONLY
fn pre_upgrade_checks<T: crate::Config>() {
let index = PageIndex::<T>::get();
// Check that all pages are present.
assert!(index.begin_used <= index.end_used, "Invalid page index");
for p in index.begin_used..index.end_used {
assert!(Pages::<T>::contains_key(p), "Missing page");
assert!(Pages::<T>::get(p).len() > 0, "Empty page");
}
// Check that all overweight messages are present.
for i in 0..index.overweight_count {
assert!(Overweight::<T>::contains_key(i), "Missing overweight message");
}
}
/// TESTING ONLY
fn post_upgrade_checks<T: crate::Config>() {
let index = PageIndex::<T>::get();
// Check that all pages are removed.
for p in index.begin_used..index.end_used {
assert!(!Pages::<T>::contains_key(p), "Page should be gone");
}
assert!(Pages::<T>::iter_keys().next().is_none(), "Un-indexed pages");
// Check that all overweight messages are removed.
for i in 0..index.overweight_count {
assert!(!Overweight::<T>::contains_key(i), "Overweight message should be gone");
}
assert!(Overweight::<T>::iter_keys().next().is_none(), "Un-indexed overweight messages");
}