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
+84 -94
View File
@@ -14,110 +14,100 @@
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! A module that is responsible for migration of storage.
//! Migrates the storage from the previously deleted DMP pallet.
use crate::{Config, Configuration, Overweight, Pallet, DEFAULT_POV_SIZE};
use frame_support::{
pallet_prelude::*,
traits::{OnRuntimeUpgrade, StorageVersion},
weights::{constants::WEIGHT_REF_TIME_PER_MILLIS, Weight},
};
use crate::*;
use cumulus_primitives_core::relay_chain::BlockNumber as RelayBlockNumber;
use frame_support::{pallet_prelude::*, storage_alias, traits::HandleMessage};
use sp_std::vec::Vec;
/// The current storage version.
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
pub(crate) const LOG: &str = "runtime::dmp-queue-export-xcms";
/// Migrates the pallet storage to the most recent version.
pub struct Migration<T: Config>(PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for Migration<T> {
fn on_runtime_upgrade() -> Weight {
let mut weight = T::DbWeight::get().reads(1);
if StorageVersion::get::<Pallet<T>>() == 0 {
weight.saturating_accrue(migrate_to_v1::<T>());
StorageVersion::new(1).put::<Pallet<T>>();
weight.saturating_accrue(T::DbWeight::get().writes(1));
}
if StorageVersion::get::<Pallet<T>>() == 1 {
weight.saturating_accrue(migrate_to_v2::<T>());
StorageVersion::new(2).put::<Pallet<T>>();
weight.saturating_accrue(T::DbWeight::get().writes(1));
}
weight
}
/// The old `PageIndexData` struct.
#[derive(Copy, Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct PageIndexData {
/// The lowest used page index.
pub begin_used: PageCounter,
/// The lowest unused page index.
pub end_used: PageCounter,
/// The number of overweight messages ever recorded (and thus the lowest free index).
pub overweight_count: OverweightIndex,
}
mod v0 {
/// The old `MigrationState` type.
pub type OverweightIndex = u64;
/// The old `MigrationState` type.
pub type PageCounter = u32;
/// The old `PageIndex` storage item.
#[storage_alias]
pub type PageIndex<T: Config> = StorageValue<Pallet<T>, PageIndexData, ValueQuery>;
/// The old `Pages` storage item.
#[storage_alias]
pub type Pages<T: Config> = StorageMap<
Pallet<T>,
Blake2_128Concat,
PageCounter,
Vec<(RelayBlockNumber, Vec<u8>)>,
ValueQuery,
>;
/// The old `Overweight` storage item.
#[storage_alias]
pub type Overweight<T: Config> = CountedStorageMap<
Pallet<T>,
Blake2_128Concat,
OverweightIndex,
(RelayBlockNumber, Vec<u8>),
OptionQuery,
>;
pub(crate) mod testing_only {
use super::*;
use codec::{Decode, Encode};
#[derive(Decode, Encode, Debug)]
pub struct ConfigData {
pub max_individual: u64,
}
impl Default for ConfigData {
fn default() -> Self {
ConfigData { max_individual: 10u64 * WEIGHT_REF_TIME_PER_MILLIS }
}
}
/// This alias is not used by the migration but only for testing.
///
/// Note that the alias type is wrong on purpose.
#[storage_alias]
pub type Configuration<T: Config> = StorageValue<Pallet<T>, u32>;
}
/// 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`.
pub fn migrate_to_v1<T: Config>() -> Weight {
let translate = |pre: v0::ConfigData| -> super::ConfigData {
super::ConfigData {
max_individual: Weight::from_parts(pre.max_individual, DEFAULT_POV_SIZE),
}
/// Migrates a single page to the `DmpSink`.
pub(crate) fn migrate_page<T: crate::Config>(p: PageCounter) -> Result<(), ()> {
let page = Pages::<T>::take(p);
log::debug!(target: LOG, "Migrating page #{p} with {} messages ...", page.len());
if page.is_empty() {
log::error!(target: LOG, "Page #{p}: EMPTY - storage corrupted?");
return Err(())
}
for (m, (block, msg)) in page.iter().enumerate() {
let Ok(bound) = BoundedVec::<u8, _>::try_from(msg.clone()) else {
log::error!(target: LOG, "[Page {p}] Message #{m}: TOO LONG - dropping");
continue
};
T::DmpSink::handle_message(bound.as_bounded_slice());
log::debug!(target: LOG, "[Page {p}] Migrated message #{m} from block {block}");
}
Ok(())
}
/// Migrates a single overweight message to the `DmpSink`.
pub(crate) fn migrate_overweight<T: crate::Config>(i: OverweightIndex) -> Result<(), ()> {
let Some((block, msg)) = Overweight::<T>::take(i) else {
log::error!(target: LOG, "[Overweight {i}] Message: EMPTY - storage corrupted?");
return Err(())
};
let Ok(bound) = BoundedVec::<u8, _>::try_from(msg) else {
log::error!(target: LOG, "[Overweight {i}] Message: TOO LONG - dropping");
return Err(())
};
if Configuration::<T>::translate(|pre| pre.map(translate)).is_err() {
log::error!(
target: "dmp_queue",
"unexpected error when performing translation of the QueueConfig type during storage upgrade to v2"
);
}
T::DmpSink::handle_message(bound.as_bounded_slice());
log::debug!(target: LOG, "[Overweight {i}] Migrated message from block {block}");
T::DbWeight::get().reads_writes(1, 1)
}
/// Migrates `Overweight` so that it initializes the storage map's counter.
///
/// NOTE: Only use this function if you know what you're doing. Default to using
/// `migrate_to_latest`.
pub fn migrate_to_v2<T: Config>() -> Weight {
let overweight_messages = Overweight::<T>::initialize_counter() as u64;
T::DbWeight::get().reads_writes(overweight_messages, 1)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::{new_test_ext, Test};
#[test]
fn test_migration_to_v1() {
let v0 = v0::ConfigData { max_individual: 30_000_000_000 };
new_test_ext().execute_with(|| {
frame_support::storage::unhashed::put_raw(
&crate::Configuration::<Test>::hashed_key(),
&v0.encode(),
);
migrate_to_v1::<Test>();
let v1 = crate::Configuration::<Test>::get();
assert_eq!(v0.max_individual, v1.max_individual.ref_time());
assert_eq!(v1.max_individual.proof_size(), DEFAULT_POV_SIZE);
});
}
Ok(())
}