Use Message Queue pallet for UMP dispatch (#6271)

* Add ProcessXcmMessage struct

* Migrate away from weights in host config

* New well-known key to report UMPQ capacity

* Add missing file

* Fixes

* Remove original UMP files

* Docs

* Update runtime/parachains/src/inclusion/mod.rs

Co-authored-by: asynchronous rob <rphmeier@gmail.com>

* Add benchmarking

* Benchmarks

* Mock example of using the QueueChangeHandler to update the WKK

* Use master Cargo.lock

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Merge remote-tracking branch 'origin/master' into gav-message-queue

* Update Cargo.lock

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update remove-weight migration

The migration got touched on master; just resolving conflicts here.

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add message- to dispatch-origin conversion for XCM processing

Just using the `impl Into<MultiLocation>` was a bit inflexible.
Like this, the Relaychain can convert `UMP(para)` to a MultiLocation `para`.

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* DNM: Temporarily comment code since XCMv3 is not merged yet

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Use u64 for queue-wide limits on UmpAcceptanceCheckErr

Using u32 here was one audit finding for the queue pallet.

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Define one sub-queue per *MP queue

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Harden check_upward_messages

Using safe math and casts.

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add type-safe well_known_keys

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add message-queue weights

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Deploy MessageQueue to Polkadot

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update Cargo.toml

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Migrate to parachain config V5

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update UMP tests

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Cleanup

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* fmt

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Revert messed up merge 🤦

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update remove-weight migration

The migration got touched on master; just resolving conflicts here.

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add message- to dispatch-origin conversion for XCM processing

Just using the `impl Into<MultiLocation>` was a bit inflexible.
Like this, the Relaychain can convert `UMP(para)` to a MultiLocation `para`.

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* DNM: Temporarily comment code since XCMv3 is not merged yet

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Use u64 for queue-wide limits on UmpAcceptanceCheckErr

Using u32 here was one audit finding for the queue pallet.

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Define one sub-queue per *MP queue

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Harden check_upward_messages

Using safe math and casts.

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add type-safe well_known_keys

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add message-queue weights

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Deploy MessageQueue to Polkadot

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update Cargo.toml

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Migrate to parachain config V5

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update UMP tests

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Cleanup

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* fmt

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Make stuff compile

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Move DMP and HRMP messages to the MessageQueue

It currently does not compile in the CIbecause of some local
tweaks to Substrate.

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Diener for CI

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* diener update cargo.lock

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Cleanup

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Revert wrong changes

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Revert "DNM: Temporarily comment code since XCMv3 is not merged yet"

This reverts commit 820aa235cb21dd1d2621843607f7682bf035434e.

* Make compile

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fixup runtimes

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* fmt

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Define benchmarks

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Cleanup

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix migration

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Use master Cargo.lock

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Lockfile

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix test

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add AggregateMessageOrigin

This enum currently only holds one value, but having it will make
it easier in the future to extend.

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Forbid UMP for off-boarding paras

- Reject candidates with UMP messages for off-boarding paras
- Forbid scheduling off-boarding when a para has unprocess UMPs

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Delete stupid test

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Use BoundedVec for upward messages

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add weights and fix MessageProcessor

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Bound receive_upward_messages and check bound in configuration pallet

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Bound Debug impl

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* fmt

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* clippy

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix test runtime

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix xcm-simulator

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Properly fix xcm-simulator and fuzzer

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix tests

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* fmt

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* cargo update -p sp-io

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Adapt to upstream Substrate changes

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix ProcesseMessage impls

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Some tests

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Use master Cargo.lock

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* cargo update -p sp-io

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Use new MQ API

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix test

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix migration

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update Cargo.lock

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add UMP while Para offboarding tests

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* fmt

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Use Mocked message processor for benchmarking

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Use variables for constants

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add MQ pallet weights

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Use MQ pallet weights

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Configure QueueChangeHandler

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* fmt

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add config test

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix MQ serive weight

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Cleanup

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Cleanup outgoing UMP dispatch queues

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Use Master Cargo.lock

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update Cargo.lock

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Weight mul is not const

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Clippy

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Remove merge marker

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update runtime/parachains/src/inclusion/mod.rs

Co-authored-by: Gavin Wood <gavin@parity.io>

* Update runtime/kusama/src/lib.rs

Co-authored-by: Gavin Wood <gavin@parity.io>

* Use lowercase UMP

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Clarify comment

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Use Weight::from_parts

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix test

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix doc

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Emit event after the fact

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add defensive_proof to receive_upward_messages

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Reapply "Remove original UMP files"

Looks like they came back from the dead. Re-apply commit cf6d316f0

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Remove old files

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Rename MaxUmpMessageLen -> MaxUmpMessageLenOf

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Test defensive message dropping of receive_upward_messages

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fixup imports

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update implementors guide

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix tests

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Remove FAIL-CI mark

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Delete unused code

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add another test for MQ change hook

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Imports

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Keep Kusama runtime formatting

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Revert "Delete unused code"

This reverts commit dd76bca5025b7e1ef846a9539c3607eed185f16a.

* Feature gate mock functions

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Review: Use saturating_add

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Test RelayDispatchQueueSize storage key

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Move migration to own file to avoid merge conflicts

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Migration in own file

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fixup migration

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Rococo: configure MQ pallet

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fixup tests

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Revert "Rococo: configure MQ pallet"

Going to do this as follow up, since it needs Substrate changes
and i dont want to stall this MR any longer.

This reverts commit b9c15e8a8339c4e877d654ee3f09903af4210736.

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Revert "Fixup tests"

This reverts commit 88f1cbe20774d20e5e1e554e798960ae39437af1.

* Fixup migration

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix migration

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* fmt

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix CI

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix migration

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix other migration

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Bump MAX_CODE_SIZE to 10MiB

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add ForceUpdateUmpLimits migration

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* fmt

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* clippy

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* clippy

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* imports

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Use defensive instead of defensive_proof

'defensive_proof' also prints the 'self', which spams the console
too much when running the tests. Just the length is enough.

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Rename to ScheduleConfigUpdate

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fixup migration checks

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add MAX_CODE_SIZE to ScheduleConfigUpdate

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Set MAX_CODE_SIZE to 4MiB

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix benchmark

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix formatting

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Revert "Add MAX_CODE_SIZE to ScheduleConfigUpdate"

This reverts commit 7caffb09e83083b57affd548215e45b25c3d64dc.

* Revert "Set MAX_CODE_SIZE to 4MiB"

This reverts commit 103ffbaf686487d2fbe0082a16826af17cacc1a1.

* Revert "Bump MAX_CODE_SIZE to 10MiB"

This reverts commit 530734b7b0da5b7680054e0242348fcc79a666fe.

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Remove consistency check from migration

Re-addig these checks is blocked on https://github.com/paritytech/polkadot/issues/7108

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix constants

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Bump MAX_UPWARD_MESSAGE_SIZE_BOUND for Westend

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix migrations

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Use old nightly for fmt

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fixes

* cargo fmt

* Fix tests

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix tests

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fixes

* Add MQ pallet to fuzzer

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fmt

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix XMC simulator example

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Remove runtime-benchmarks from fuzzers

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Revert "Remove runtime-benchmarks from fuzzers"

This reverts commit e1f2bb01b6dea2dd465539d3658719895b58b557.

* Fix example simulator

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add V6 migration and remove old ones

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Actually make old migrations reusable

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Dont delete old migrations

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Future proof AggregateMessageOrigin and review fixes

There are indications that Loopback and Bridged will be needed soon.

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* More cleanup

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* fmt

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* fix benchmarks

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix fuzzer build

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Review

Co-authored-by: muharem <ismailov.m.h@gmail.com>

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Remove old migration

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Set MQ service weight to 20%

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix tabs in Markdown

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

---------

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: asynchronous rob <rphmeier@gmail.com>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
This commit is contained in:
Gavin Wood
2023-05-19 18:14:13 +02:00
committed by GitHub
parent 9e4bca6895
commit 400864c352
74 changed files with 4034 additions and 2536 deletions
@@ -18,8 +18,8 @@
//!
//! Configuration can change only at session boundaries and is buffered until then.
use crate::shared;
use frame_support::{pallet_prelude::*, weights::constants::WEIGHT_REF_TIME_PER_MILLIS};
use crate::{inclusion::MAX_UPWARD_MESSAGE_SIZE_BOUND, shared};
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use parity_scale_codec::{Decode, Encode};
use polkadot_parachain::primitives::{MAX_HORIZONTAL_MESSAGE_NUM, MAX_UPWARD_MESSAGE_NUM};
@@ -36,9 +36,10 @@ mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub use pallet::*;
pub mod migration;
pub mod migration_ump;
pub use pallet::*;
const LOG_TARGET: &str = "runtime::configuration";
@@ -132,11 +133,6 @@ pub struct HostConfiguration<BlockNumber> {
/// decide to do with its PoV so this value in practice will be picked as a fraction of the PoV
/// size.
pub max_downward_message_size: u32,
/// The amount of weight we wish to devote to the processing the dispatchable upward messages
/// stage.
///
/// NOTE that this is a soft limit and could be exceeded.
pub ump_service_total_weight: Weight,
/// The maximum number of outbound HRMP channels a parachain is allowed to open.
pub hrmp_max_parachain_outbound_channels: u32,
/// The maximum number of outbound HRMP channels a parathread is allowed to open.
@@ -214,9 +210,6 @@ pub struct HostConfiguration<BlockNumber> {
pub needed_approvals: u32,
/// The number of samples to do of the `RelayVRFModulo` approval assignment criterion.
pub relay_vrf_modulo_samples: u32,
/// The maximum amount of weight any individual upward message may consume. Messages above this
/// weight go into the overweight queue and may only be serviced explicitly.
pub ump_max_individual_weight: Weight,
/// This flag controls whether PVF pre-checking is enabled.
///
/// If the flag is false, the behavior should be exactly the same as prior. Specifically, the
@@ -278,7 +271,6 @@ impl<BlockNumber: Default + From<u32>> Default for HostConfiguration<BlockNumber
max_upward_queue_count: Default::default(),
max_upward_queue_size: Default::default(),
max_downward_message_size: Default::default(),
ump_service_total_weight: Default::default(),
max_upward_message_size: Default::default(),
max_upward_message_num_per_candidate: Default::default(),
hrmp_sender_deposit: Default::default(),
@@ -291,10 +283,6 @@ impl<BlockNumber: Default + From<u32>> Default for HostConfiguration<BlockNumber
hrmp_max_parachain_outbound_channels: Default::default(),
hrmp_max_parathread_outbound_channels: Default::default(),
hrmp_max_message_num_per_candidate: Default::default(),
ump_max_individual_weight: Weight::from_parts(
20u64 * WEIGHT_REF_TIME_PER_MILLIS,
MAX_POV_SIZE as u64,
),
pvf_checking_enabled: false,
pvf_voting_ttl: 2u32.into(),
minimum_validation_upgrade_delay: 2.into(),
@@ -404,7 +392,7 @@ where
})
}
if self.max_upward_message_size > crate::ump::MAX_UPWARD_MESSAGE_SIZE_BOUND {
if self.max_upward_message_size > crate::inclusion::MAX_UPWARD_MESSAGE_SIZE_BOUND {
return Err(MaxUpwardMessageSizeExceeded {
max_message_size: self.max_upward_message_size,
})
@@ -441,7 +429,7 @@ where
/// This function panics if the configuration is inconsistent.
pub fn panic_if_not_consistent(&self) {
if let Err(err) = self.check_consistency() {
panic!("Host configuration is inconsistent: {:?}", err);
panic!("Host configuration is inconsistent: {:?}\nCfg:\n{:#?}", err, self);
}
}
}
@@ -865,6 +853,8 @@ pub mod pallet {
))]
pub fn set_max_upward_queue_size(origin: OriginFor<T>, new: u32) -> DispatchResult {
ensure_root(origin)?;
ensure!(new <= MAX_UPWARD_MESSAGE_SIZE_BOUND, Error::<T>::InvalidNewValue);
Self::schedule_config_update(|config| {
config.max_upward_queue_size = new;
})
@@ -883,19 +873,6 @@ pub mod pallet {
})
}
/// Sets the soft limit for the phase of dispatching dispatchable upward messages.
#[pallet::call_index(26)]
#[pallet::weight((
T::WeightInfo::set_config_with_weight(),
DispatchClass::Operational,
))]
pub fn set_ump_service_total_weight(origin: OriginFor<T>, new: Weight) -> DispatchResult {
ensure_root(origin)?;
Self::schedule_config_update(|config| {
config.ump_service_total_weight = new;
})
}
/// Sets the maximum size of an upward message that can be sent by a candidate.
#[pallet::call_index(27)]
#[pallet::weight((
@@ -1083,19 +1060,6 @@ pub mod pallet {
})
}
/// Sets the maximum amount of weight any individual upward message may consume.
#[pallet::call_index(40)]
#[pallet::weight((
T::WeightInfo::set_config_with_weight(),
DispatchClass::Operational,
))]
pub fn set_ump_max_individual_weight(origin: OriginFor<T>, new: Weight) -> DispatchResult {
ensure_root(origin)?;
Self::schedule_config_update(|config| {
config.ump_max_individual_weight = new;
})
}
/// Enable or disable PVF pre-checking. Consult the field documentation prior executing.
#[pallet::call_index(41)]
#[pallet::weight((
@@ -1283,7 +1247,7 @@ impl<T: Config> Pallet<T> {
// duplicated code (making this function to show up in the top of heaviest functions) only for
// the sake of essentially avoiding an indirect call. Doesn't worth it.
#[inline(never)]
fn schedule_config_update(
pub(crate) fn schedule_config_update(
updater: impl FnOnce(&mut HostConfiguration<T::BlockNumber>),
) -> DispatchResult {
let mut pending_configs = <PendingConfigs<T>>::get();
@@ -27,8 +27,6 @@ benchmarks! {
set_config_with_option_u32 {}: set_max_validators(RawOrigin::Root, Some(10))
set_config_with_weight {}: set_ump_service_total_weight(RawOrigin::Root, Weight::from_parts(3_000_000, 0))
set_hrmp_open_request_ttl {}: {
Err(BenchmarkError::Override(
BenchmarkResult::from_weight(T::BlockWeights::get().max_block)
@@ -16,11 +16,8 @@
//! A module that is responsible for migration of storage.
use crate::configuration::{self, ActiveConfig, Config, Pallet, PendingConfigs, MAX_POV_SIZE};
use frame_support::{pallet_prelude::*, traits::StorageVersion, weights::Weight};
use frame_system::pallet_prelude::BlockNumberFor;
use primitives::vstaging::AsyncBackingParams;
use sp_std::vec::Vec;
use frame_support::traits::StorageVersion;
use primitives::ExecutorParams;
/// The current storage version.
///
@@ -29,376 +26,10 @@ use sp_std::vec::Vec;
/// v2-v3: <https://github.com/paritytech/polkadot/pull/6091>
/// v3-v4: <https://github.com/paritytech/polkadot/pull/6345>
/// v4-v5: <https://github.com/paritytech/polkadot/pull/6937>
/// + <https://github.com/paritytech/polkadot/pull/6961>
/// + <https://github.com/paritytech/polkadot/pull/6934>
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(5);
/// + <https://github.com/paritytech/polkadot/pull/6961>
/// + <https://github.com/paritytech/polkadot/pull/6934>
/// v5-v6: <https://github.com/paritytech/polkadot/pull/6271> (remove UMP dispatch queue)
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(6);
pub mod v5 {
use super::*;
use frame_support::{traits::OnRuntimeUpgrade, weights::constants::WEIGHT_REF_TIME_PER_MILLIS};
use primitives::{Balance, SessionIndex};
#[cfg(feature = "try-runtime")]
use sp_std::prelude::*;
// Copied over from configuration.rs @ de9e147695b9f1be8bd44e07861a31e483c8343a and removed
// all the comments, and changed the Weight struct to OldWeight
#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode, Debug, Clone)]
pub struct OldHostConfiguration<BlockNumber> {
pub max_code_size: u32,
pub max_head_data_size: u32,
pub max_upward_queue_count: u32,
pub max_upward_queue_size: u32,
pub max_upward_message_size: u32,
pub max_upward_message_num_per_candidate: u32,
pub hrmp_max_message_num_per_candidate: u32,
pub validation_upgrade_cooldown: BlockNumber,
pub validation_upgrade_delay: BlockNumber,
pub max_pov_size: u32,
pub max_downward_message_size: u32,
pub ump_service_total_weight: Weight,
pub hrmp_max_parachain_outbound_channels: u32,
pub hrmp_max_parathread_outbound_channels: u32,
pub hrmp_sender_deposit: Balance,
pub hrmp_recipient_deposit: Balance,
pub hrmp_channel_max_capacity: u32,
pub hrmp_channel_max_total_size: u32,
pub hrmp_max_parachain_inbound_channels: u32,
pub hrmp_max_parathread_inbound_channels: u32,
pub hrmp_channel_max_message_size: u32,
pub code_retention_period: BlockNumber,
pub parathread_cores: u32,
pub parathread_retries: u32,
pub group_rotation_frequency: BlockNumber,
pub chain_availability_period: BlockNumber,
pub thread_availability_period: BlockNumber,
pub scheduling_lookahead: u32,
pub max_validators_per_core: Option<u32>,
pub max_validators: Option<u32>,
pub dispute_period: SessionIndex,
pub dispute_post_conclusion_acceptance_period: BlockNumber,
pub dispute_conclusion_by_time_out_period: BlockNumber,
pub no_show_slots: u32,
pub n_delay_tranches: u32,
pub zeroth_delay_tranche_width: u32,
pub needed_approvals: u32,
pub relay_vrf_modulo_samples: u32,
pub ump_max_individual_weight: Weight,
pub pvf_checking_enabled: bool,
pub pvf_voting_ttl: SessionIndex,
pub minimum_validation_upgrade_delay: BlockNumber,
}
impl<BlockNumber: Default + From<u32>> Default for OldHostConfiguration<BlockNumber> {
fn default() -> Self {
Self {
group_rotation_frequency: 1u32.into(),
chain_availability_period: 1u32.into(),
thread_availability_period: 1u32.into(),
no_show_slots: 1u32.into(),
validation_upgrade_cooldown: Default::default(),
validation_upgrade_delay: Default::default(),
code_retention_period: Default::default(),
max_code_size: Default::default(),
max_pov_size: Default::default(),
max_head_data_size: Default::default(),
parathread_cores: Default::default(),
parathread_retries: Default::default(),
scheduling_lookahead: Default::default(),
max_validators_per_core: Default::default(),
max_validators: None,
dispute_period: 6,
dispute_post_conclusion_acceptance_period: 100.into(),
dispute_conclusion_by_time_out_period: 200.into(),
n_delay_tranches: Default::default(),
zeroth_delay_tranche_width: Default::default(),
needed_approvals: Default::default(),
relay_vrf_modulo_samples: Default::default(),
max_upward_queue_count: Default::default(),
max_upward_queue_size: Default::default(),
max_downward_message_size: Default::default(),
ump_service_total_weight: Default::default(),
max_upward_message_size: Default::default(),
max_upward_message_num_per_candidate: Default::default(),
hrmp_sender_deposit: Default::default(),
hrmp_recipient_deposit: Default::default(),
hrmp_channel_max_capacity: Default::default(),
hrmp_channel_max_total_size: Default::default(),
hrmp_max_parachain_inbound_channels: Default::default(),
hrmp_max_parathread_inbound_channels: Default::default(),
hrmp_channel_max_message_size: Default::default(),
hrmp_max_parachain_outbound_channels: Default::default(),
hrmp_max_parathread_outbound_channels: Default::default(),
hrmp_max_message_num_per_candidate: Default::default(),
ump_max_individual_weight: Weight::from_parts(
20u64 * WEIGHT_REF_TIME_PER_MILLIS,
MAX_POV_SIZE as u64,
),
pvf_checking_enabled: false,
pvf_voting_ttl: 2u32.into(),
minimum_validation_upgrade_delay: 2.into(),
}
}
}
pub struct MigrateToV5<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for MigrateToV5<T> {
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, &'static str> {
log::trace!(target: crate::configuration::LOG_TARGET, "Running pre_upgrade()");
ensure!(StorageVersion::get::<Pallet<T>>() == 4, "The migration requires version 4");
Ok(Vec::new())
}
fn on_runtime_upgrade() -> Weight {
if StorageVersion::get::<Pallet<T>>() == 4 {
let weight_consumed = migrate_to_v5::<T>();
log::info!(target: configuration::LOG_TARGET, "MigrateToV5 executed successfully");
STORAGE_VERSION.put::<Pallet<T>>();
weight_consumed
} else {
log::warn!(target: configuration::LOG_TARGET, "MigrateToV5 should be removed.");
T::DbWeight::get().reads(1)
}
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(_state: Vec<u8>) -> Result<(), &'static str> {
log::trace!(target: crate::configuration::LOG_TARGET, "Running post_upgrade()");
ensure!(
StorageVersion::get::<Pallet<T>>() == STORAGE_VERSION,
"Storage version should be 5 after the migration"
);
Ok(())
}
}
}
fn migrate_to_v5<T: Config>() -> Weight {
// Unusual formatting is justified:
// - make it easier to verify that fields assign what they supposed to assign.
// - this code is transient and will be removed after all migrations are done.
// - this code is important enough to optimize for legibility sacrificing consistency.
#[rustfmt::skip]
let translate =
|pre: v5::OldHostConfiguration<BlockNumberFor<T>>| ->
configuration::HostConfiguration<BlockNumberFor<T>>
{
super::HostConfiguration {
max_code_size : pre.max_code_size,
max_head_data_size : pre.max_head_data_size,
max_upward_queue_count : pre.max_upward_queue_count,
max_upward_queue_size : pre.max_upward_queue_size,
max_upward_message_size : pre.max_upward_message_size,
max_upward_message_num_per_candidate : pre.max_upward_message_num_per_candidate,
hrmp_max_message_num_per_candidate : pre.hrmp_max_message_num_per_candidate,
validation_upgrade_cooldown : pre.validation_upgrade_cooldown,
validation_upgrade_delay : pre.validation_upgrade_delay,
max_pov_size : pre.max_pov_size,
max_downward_message_size : pre.max_downward_message_size,
ump_service_total_weight : pre.ump_service_total_weight,
hrmp_max_parachain_outbound_channels : pre.hrmp_max_parachain_outbound_channels,
hrmp_max_parathread_outbound_channels : pre.hrmp_max_parathread_outbound_channels,
hrmp_sender_deposit : pre.hrmp_sender_deposit,
hrmp_recipient_deposit : pre.hrmp_recipient_deposit,
hrmp_channel_max_capacity : pre.hrmp_channel_max_capacity,
hrmp_channel_max_total_size : pre.hrmp_channel_max_total_size,
hrmp_max_parachain_inbound_channels : pre.hrmp_max_parachain_inbound_channels,
hrmp_max_parathread_inbound_channels : pre.hrmp_max_parathread_inbound_channels,
hrmp_channel_max_message_size : pre.hrmp_channel_max_message_size,
code_retention_period : pre.code_retention_period,
parathread_cores : pre.parathread_cores,
parathread_retries : pre.parathread_retries,
group_rotation_frequency : pre.group_rotation_frequency,
chain_availability_period : pre.chain_availability_period,
thread_availability_period : pre.thread_availability_period,
scheduling_lookahead : pre.scheduling_lookahead,
max_validators_per_core : pre.max_validators_per_core,
max_validators : pre.max_validators,
dispute_period : pre.dispute_period,
dispute_post_conclusion_acceptance_period: pre.dispute_post_conclusion_acceptance_period,
no_show_slots : pre.no_show_slots,
n_delay_tranches : pre.n_delay_tranches,
zeroth_delay_tranche_width : pre.zeroth_delay_tranche_width,
needed_approvals : pre.needed_approvals,
relay_vrf_modulo_samples : pre.relay_vrf_modulo_samples,
ump_max_individual_weight : pre.ump_max_individual_weight,
pvf_checking_enabled : pre.pvf_checking_enabled,
pvf_voting_ttl : pre.pvf_voting_ttl,
minimum_validation_upgrade_delay : pre.minimum_validation_upgrade_delay,
// Default values are zeroes, thus it's ensured allowed ancestry never crosses the upgrade block.
async_backing_params : AsyncBackingParams { max_candidate_depth: 0, allowed_ancestry_len: 0 },
// Default executor parameters set is empty
executor_params : Default::default(),
}
};
if let Err(_) = ActiveConfig::<T>::translate(|pre| pre.map(translate)) {
// `Err` is returned when the pre-migration type cannot be deserialized. This
// cannot happen if the migration runs correctly, i.e. against the expected version.
//
// This happening almost surely will lead to a panic somewhere else. Corruption seems
// to be unlikely to be caused by this. So we just log. Maybe it'll work out still?
log::error!(
target: configuration::LOG_TARGET,
"unexpected error when performing translation of the active configuration during storage upgrade to v5."
);
}
if let Err(_) = PendingConfigs::<T>::translate(|pre| {
pre.map(
|v: Vec<(primitives::SessionIndex, v5::OldHostConfiguration<BlockNumberFor<T>>)>| {
v.into_iter()
.map(|(session, config)| (session, translate(config)))
.collect::<Vec<_>>()
},
)
}) {
log::error!(
target: configuration::LOG_TARGET,
"unexpected error when performing translation of the pending configuration during storage upgrade to v5."
);
}
let num_configs = (PendingConfigs::<T>::get().len() + 1) as u64;
T::DbWeight::get().reads_writes(num_configs, num_configs)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::{new_test_ext, Test};
use primitives::ExecutorParams;
#[test]
fn v4_deserialized_from_actual_data() {
// Example how to get new `raw_config`:
// We'll obtain the raw_config at a specified a block
// Steps:
// 1. Go to Polkadot.js -> Developer -> Chain state -> Storage: https://polkadot.js.org/apps/#/chainstate
// 2. Set these parameters:
// 2.1. selected state query: configuration; activeConfig(): PolkadotRuntimeParachainsConfigurationHostConfiguration
// 2.2. blockhash to query at: 0xf89d3ab5312c5f70d396dc59612f0aa65806c798346f9db4b35278baed2e0e53 (the hash of the block)
// 2.3. Note the value of encoded storage key -> 0x06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385 for the referenced block.
// 2.4. You'll also need the decoded values to update the test.
// 3. Go to Polkadot.js -> Developer -> Chain state -> Raw storage
// 3.1 Enter the encoded storage key and you get the raw config.
// This exceeds the maximal line width length, but that's fine, since this is not code and
// doesn't need to be read and also leaving it as one line allows to easily copy it.
let raw_config = hex_literal::hex!["0000a000005000000a00000000c8000000c800000a0000000a000000100e0000580200000000500000c800000700e8764817020040011e00000000000000005039278c0400000000000000000000005039278c0400000000000000000000e8030000009001001e00000000000000009001008070000000000000000000000a0000000a0000000a00000001000000010500000001c80000000600000058020000580200000200000059000000000000001e000000280000000700c817a80402004001010200000014000000"];
let v4 = v5::OldHostConfiguration::<primitives::BlockNumber>::decode(&mut &raw_config[..])
.unwrap();
// We check only a sample of the values here. If we missed any fields or messed up data types
// that would skew all the fields coming after.
assert_eq!(v4.max_code_size, 10_485_760);
assert_eq!(v4.validation_upgrade_cooldown, 3600);
assert_eq!(v4.max_pov_size, 5_242_880);
assert_eq!(v4.hrmp_channel_max_message_size, 102_400);
assert_eq!(v4.n_delay_tranches, 89);
assert_eq!(v4.ump_max_individual_weight, Weight::from_parts(20_000_000_000, 5_242_880));
assert_eq!(v4.minimum_validation_upgrade_delay, 20);
}
#[test]
fn test_migrate_to_v5() {
// Host configuration has lots of fields. However, in this migration we only remove one field.
// The most important part to check are a couple of the last fields. We also pick
// extra fields to check arbitrarily, e.g. depending on their position (i.e. the middle) and
// also their type.
//
// We specify only the picked fields and the rest should be provided by the `Default`
// implementation. That implementation is copied over between the two types and should work
// fine.
let v4 = v5::OldHostConfiguration::<primitives::BlockNumber> {
ump_max_individual_weight: Weight::from_parts(0x71616e6f6e0au64, 0x71616e6f6e0au64),
needed_approvals: 69,
thread_availability_period: 55,
hrmp_recipient_deposit: 1337,
max_pov_size: 1111,
chain_availability_period: 33,
minimum_validation_upgrade_delay: 20,
..Default::default()
};
let mut pending_configs = Vec::new();
pending_configs.push((100, v4.clone()));
pending_configs.push((300, v4.clone()));
new_test_ext(Default::default()).execute_with(|| {
// Implant the v4 version in the state.
frame_support::storage::unhashed::put_raw(
&configuration::ActiveConfig::<Test>::hashed_key(),
&v4.encode(),
);
frame_support::storage::unhashed::put_raw(
&configuration::PendingConfigs::<Test>::hashed_key(),
&pending_configs.encode(),
);
migrate_to_v5::<Test>();
let v5 = configuration::ActiveConfig::<Test>::get();
let mut configs_to_check = configuration::PendingConfigs::<Test>::get();
configs_to_check.push((0, v5.clone()));
for (_, v4) in configs_to_check {
#[rustfmt::skip]
{
assert_eq!(v4.max_code_size , v5.max_code_size);
assert_eq!(v4.max_head_data_size , v5.max_head_data_size);
assert_eq!(v4.max_upward_queue_count , v5.max_upward_queue_count);
assert_eq!(v4.max_upward_queue_size , v5.max_upward_queue_size);
assert_eq!(v4.max_upward_message_size , v5.max_upward_message_size);
assert_eq!(v4.max_upward_message_num_per_candidate , v5.max_upward_message_num_per_candidate);
assert_eq!(v4.hrmp_max_message_num_per_candidate , v5.hrmp_max_message_num_per_candidate);
assert_eq!(v4.validation_upgrade_cooldown , v5.validation_upgrade_cooldown);
assert_eq!(v4.validation_upgrade_delay , v5.validation_upgrade_delay);
assert_eq!(v4.max_pov_size , v5.max_pov_size);
assert_eq!(v4.max_downward_message_size , v5.max_downward_message_size);
assert_eq!(v4.ump_service_total_weight , v5.ump_service_total_weight);
assert_eq!(v4.hrmp_max_parachain_outbound_channels , v5.hrmp_max_parachain_outbound_channels);
assert_eq!(v4.hrmp_max_parathread_outbound_channels , v5.hrmp_max_parathread_outbound_channels);
assert_eq!(v4.hrmp_sender_deposit , v5.hrmp_sender_deposit);
assert_eq!(v4.hrmp_recipient_deposit , v5.hrmp_recipient_deposit);
assert_eq!(v4.hrmp_channel_max_capacity , v5.hrmp_channel_max_capacity);
assert_eq!(v4.hrmp_channel_max_total_size , v5.hrmp_channel_max_total_size);
assert_eq!(v4.hrmp_max_parachain_inbound_channels , v5.hrmp_max_parachain_inbound_channels);
assert_eq!(v4.hrmp_max_parathread_inbound_channels , v5.hrmp_max_parathread_inbound_channels);
assert_eq!(v4.hrmp_channel_max_message_size , v5.hrmp_channel_max_message_size);
assert_eq!(v4.code_retention_period , v5.code_retention_period);
assert_eq!(v4.parathread_cores , v5.parathread_cores);
assert_eq!(v4.parathread_retries , v5.parathread_retries);
assert_eq!(v4.group_rotation_frequency , v5.group_rotation_frequency);
assert_eq!(v4.chain_availability_period , v5.chain_availability_period);
assert_eq!(v4.thread_availability_period , v5.thread_availability_period);
assert_eq!(v4.scheduling_lookahead , v5.scheduling_lookahead);
assert_eq!(v4.max_validators_per_core , v5.max_validators_per_core);
assert_eq!(v4.max_validators , v5.max_validators);
assert_eq!(v4.dispute_period , v5.dispute_period);
assert_eq!(v4.no_show_slots , v5.no_show_slots);
assert_eq!(v4.n_delay_tranches , v5.n_delay_tranches);
assert_eq!(v4.zeroth_delay_tranche_width , v5.zeroth_delay_tranche_width);
assert_eq!(v4.needed_approvals , v5.needed_approvals);
assert_eq!(v4.relay_vrf_modulo_samples , v5.relay_vrf_modulo_samples);
assert_eq!(v4.ump_max_individual_weight , v5.ump_max_individual_weight);
assert_eq!(v4.pvf_checking_enabled , v5.pvf_checking_enabled);
assert_eq!(v4.pvf_voting_ttl , v5.pvf_voting_ttl);
assert_eq!(v4.minimum_validation_upgrade_delay , v5.minimum_validation_upgrade_delay);
}; // ; makes this a statement. `rustfmt::skip` cannot be put on an expression.
// additional checks for async backing.
assert_eq!(v5.async_backing_params.allowed_ancestry_len, 0);
assert_eq!(v5.async_backing_params.max_candidate_depth, 0);
assert_eq!(v5.executor_params, ExecutorParams::new());
}
});
}
}
pub mod v5;
pub mod v6;
@@ -0,0 +1,504 @@
// 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/>.
//! A module that is responsible for migration of storage.
use crate::configuration::{self, Config, Pallet, MAX_POV_SIZE};
use frame_support::{
pallet_prelude::*,
traits::{Defensive, StorageVersion},
weights::Weight,
};
use frame_system::pallet_prelude::BlockNumberFor;
use primitives::vstaging::AsyncBackingParams;
use sp_std::vec::Vec;
use super::*;
use frame_support::{traits::OnRuntimeUpgrade, weights::constants::WEIGHT_REF_TIME_PER_MILLIS};
use primitives::{Balance, SessionIndex};
#[cfg(feature = "try-runtime")]
use sp_std::prelude::*;
// Copied over from configuration.rs @ de9e147695b9f1be8bd44e07861a31e483c8343a and removed
// all the comments, and changed the Weight struct to OldWeight
#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode, Debug, Clone)]
pub struct V4HostConfiguration<BlockNumber> {
pub max_code_size: u32,
pub max_head_data_size: u32,
pub max_upward_queue_count: u32,
pub max_upward_queue_size: u32,
pub max_upward_message_size: u32,
pub max_upward_message_num_per_candidate: u32,
pub hrmp_max_message_num_per_candidate: u32,
pub validation_upgrade_cooldown: BlockNumber,
pub validation_upgrade_delay: BlockNumber,
pub max_pov_size: u32,
pub max_downward_message_size: u32,
pub ump_service_total_weight: Weight,
pub hrmp_max_parachain_outbound_channels: u32,
pub hrmp_max_parathread_outbound_channels: u32,
pub hrmp_sender_deposit: Balance,
pub hrmp_recipient_deposit: Balance,
pub hrmp_channel_max_capacity: u32,
pub hrmp_channel_max_total_size: u32,
pub hrmp_max_parachain_inbound_channels: u32,
pub hrmp_max_parathread_inbound_channels: u32,
pub hrmp_channel_max_message_size: u32,
pub code_retention_period: BlockNumber,
pub parathread_cores: u32,
pub parathread_retries: u32,
pub group_rotation_frequency: BlockNumber,
pub chain_availability_period: BlockNumber,
pub thread_availability_period: BlockNumber,
pub scheduling_lookahead: u32,
pub max_validators_per_core: Option<u32>,
pub max_validators: Option<u32>,
pub dispute_period: SessionIndex,
pub dispute_post_conclusion_acceptance_period: BlockNumber,
pub dispute_conclusion_by_time_out_period: BlockNumber,
pub no_show_slots: u32,
pub n_delay_tranches: u32,
pub zeroth_delay_tranche_width: u32,
pub needed_approvals: u32,
pub relay_vrf_modulo_samples: u32,
pub ump_max_individual_weight: Weight,
pub pvf_checking_enabled: bool,
pub pvf_voting_ttl: SessionIndex,
pub minimum_validation_upgrade_delay: BlockNumber,
}
#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode, Debug, Clone)]
pub struct V5HostConfiguration<BlockNumber> {
pub max_code_size: u32,
pub max_head_data_size: u32,
pub max_upward_queue_count: u32,
pub max_upward_queue_size: u32,
pub max_upward_message_size: u32,
pub max_upward_message_num_per_candidate: u32,
pub hrmp_max_message_num_per_candidate: u32,
pub validation_upgrade_cooldown: BlockNumber,
pub validation_upgrade_delay: BlockNumber,
pub async_backing_params: AsyncBackingParams,
pub max_pov_size: u32,
pub max_downward_message_size: u32,
pub ump_service_total_weight: Weight,
pub hrmp_max_parachain_outbound_channels: u32,
pub hrmp_max_parathread_outbound_channels: u32,
pub hrmp_sender_deposit: Balance,
pub hrmp_recipient_deposit: Balance,
pub hrmp_channel_max_capacity: u32,
pub hrmp_channel_max_total_size: u32,
pub hrmp_max_parachain_inbound_channels: u32,
pub hrmp_max_parathread_inbound_channels: u32,
pub hrmp_channel_max_message_size: u32,
pub executor_params: ExecutorParams,
pub code_retention_period: BlockNumber,
pub parathread_cores: u32,
pub parathread_retries: u32,
pub group_rotation_frequency: BlockNumber,
pub chain_availability_period: BlockNumber,
pub thread_availability_period: BlockNumber,
pub scheduling_lookahead: u32,
pub max_validators_per_core: Option<u32>,
pub max_validators: Option<u32>,
pub dispute_period: SessionIndex,
pub dispute_post_conclusion_acceptance_period: BlockNumber,
pub no_show_slots: u32,
pub n_delay_tranches: u32,
pub zeroth_delay_tranche_width: u32,
pub needed_approvals: u32,
pub relay_vrf_modulo_samples: u32,
pub ump_max_individual_weight: Weight,
pub pvf_checking_enabled: bool,
pub pvf_voting_ttl: SessionIndex,
pub minimum_validation_upgrade_delay: BlockNumber,
}
#[frame_support::storage_alias]
pub(crate) type V4ActiveConfig<T: Config> =
StorageValue<Pallet<T>, V4HostConfiguration<BlockNumberFor<T>>, OptionQuery>;
#[frame_support::storage_alias]
pub(crate) type V4PendingConfigs<T: Config> = StorageValue<
Pallet<T>,
Vec<(SessionIndex, V4HostConfiguration<BlockNumberFor<T>>)>,
OptionQuery,
>;
#[frame_support::storage_alias]
pub(crate) type V5ActiveConfig<T: Config> =
StorageValue<Pallet<T>, V5HostConfiguration<BlockNumberFor<T>>, OptionQuery>;
#[frame_support::storage_alias]
pub(crate) type V5PendingConfigs<T: Config> = StorageValue<
Pallet<T>,
Vec<(SessionIndex, V5HostConfiguration<BlockNumberFor<T>>)>,
OptionQuery,
>;
impl<BlockNumber: Default + From<u32>> Default for V4HostConfiguration<BlockNumber> {
fn default() -> Self {
Self {
group_rotation_frequency: 1u32.into(),
chain_availability_period: 1u32.into(),
thread_availability_period: 1u32.into(),
no_show_slots: 1u32.into(),
validation_upgrade_cooldown: Default::default(),
validation_upgrade_delay: Default::default(),
code_retention_period: Default::default(),
max_code_size: Default::default(),
max_pov_size: Default::default(),
max_head_data_size: Default::default(),
parathread_cores: Default::default(),
parathread_retries: Default::default(),
scheduling_lookahead: Default::default(),
max_validators_per_core: Default::default(),
max_validators: None,
dispute_period: 6,
dispute_post_conclusion_acceptance_period: 100.into(),
dispute_conclusion_by_time_out_period: 200.into(),
n_delay_tranches: Default::default(),
zeroth_delay_tranche_width: Default::default(),
needed_approvals: Default::default(),
relay_vrf_modulo_samples: Default::default(),
max_upward_queue_count: Default::default(),
max_upward_queue_size: Default::default(),
max_downward_message_size: Default::default(),
ump_service_total_weight: Default::default(),
max_upward_message_size: Default::default(),
max_upward_message_num_per_candidate: Default::default(),
hrmp_sender_deposit: Default::default(),
hrmp_recipient_deposit: Default::default(),
hrmp_channel_max_capacity: Default::default(),
hrmp_channel_max_total_size: Default::default(),
hrmp_max_parachain_inbound_channels: Default::default(),
hrmp_max_parathread_inbound_channels: Default::default(),
hrmp_channel_max_message_size: Default::default(),
hrmp_max_parachain_outbound_channels: Default::default(),
hrmp_max_parathread_outbound_channels: Default::default(),
hrmp_max_message_num_per_candidate: Default::default(),
ump_max_individual_weight: Weight::from_parts(
20u64 * WEIGHT_REF_TIME_PER_MILLIS,
MAX_POV_SIZE as u64,
),
pvf_checking_enabled: false,
pvf_voting_ttl: 2u32.into(),
minimum_validation_upgrade_delay: 2.into(),
}
}
}
impl<BlockNumber: Default + From<u32>> Default for V5HostConfiguration<BlockNumber> {
fn default() -> Self {
Self {
group_rotation_frequency: 1u32.into(),
chain_availability_period: 1u32.into(),
thread_availability_period: 1u32.into(),
no_show_slots: 1u32.into(),
validation_upgrade_cooldown: Default::default(),
validation_upgrade_delay: Default::default(),
code_retention_period: Default::default(),
max_code_size: Default::default(),
max_pov_size: Default::default(),
max_head_data_size: Default::default(),
parathread_cores: Default::default(),
parathread_retries: Default::default(),
scheduling_lookahead: Default::default(),
max_validators_per_core: Default::default(),
max_validators: None,
dispute_period: 6,
dispute_post_conclusion_acceptance_period: 100.into(),
n_delay_tranches: Default::default(),
zeroth_delay_tranche_width: Default::default(),
needed_approvals: Default::default(),
relay_vrf_modulo_samples: Default::default(),
max_upward_queue_count: Default::default(),
max_upward_queue_size: Default::default(),
max_downward_message_size: Default::default(),
ump_service_total_weight: Default::default(),
max_upward_message_size: Default::default(),
max_upward_message_num_per_candidate: Default::default(),
hrmp_sender_deposit: Default::default(),
hrmp_recipient_deposit: Default::default(),
hrmp_channel_max_capacity: Default::default(),
hrmp_channel_max_total_size: Default::default(),
hrmp_max_parachain_inbound_channels: Default::default(),
hrmp_max_parathread_inbound_channels: Default::default(),
hrmp_channel_max_message_size: Default::default(),
hrmp_max_parachain_outbound_channels: Default::default(),
hrmp_max_parathread_outbound_channels: Default::default(),
hrmp_max_message_num_per_candidate: Default::default(),
ump_max_individual_weight: Weight::from_parts(
20u64 * WEIGHT_REF_TIME_PER_MILLIS,
MAX_POV_SIZE as u64,
),
pvf_checking_enabled: false,
pvf_voting_ttl: 2u32.into(),
minimum_validation_upgrade_delay: 2.into(),
async_backing_params: AsyncBackingParams {
max_candidate_depth: 0,
allowed_ancestry_len: 0,
},
executor_params: Default::default(),
}
}
}
pub struct MigrateToV5<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for MigrateToV5<T> {
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, &'static str> {
log::trace!(target: crate::configuration::LOG_TARGET, "Running pre_upgrade()");
ensure!(StorageVersion::get::<Pallet<T>>() == 4, "The migration requires version 4");
Ok(Vec::new())
}
fn on_runtime_upgrade() -> Weight {
if StorageVersion::get::<Pallet<T>>() == 4 {
let weight_consumed = migrate_to_v5::<T>();
log::info!(target: configuration::LOG_TARGET, "MigrateToV5 executed successfully");
StorageVersion::new(5).put::<Pallet<T>>();
weight_consumed
} else {
log::warn!(target: configuration::LOG_TARGET, "MigrateToV5 should be removed.");
T::DbWeight::get().reads(1)
}
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(_state: Vec<u8>) -> Result<(), &'static str> {
log::trace!(target: crate::configuration::LOG_TARGET, "Running post_upgrade()");
ensure!(
StorageVersion::get::<Pallet<T>>() == 5,
"Storage version should be 5 after the migration"
);
Ok(())
}
}
fn migrate_to_v5<T: Config>() -> Weight {
// Unusual formatting is justified:
// - make it easier to verify that fields assign what they supposed to assign.
// - this code is transient and will be removed after all migrations are done.
// - this code is important enough to optimize for legibility sacrificing consistency.
#[rustfmt::skip]
let translate =
|pre: V4HostConfiguration<BlockNumberFor<T>>| ->
V5HostConfiguration<BlockNumberFor<T>>
{
V5HostConfiguration {
max_code_size : pre.max_code_size,
max_head_data_size : pre.max_head_data_size,
max_upward_queue_count : pre.max_upward_queue_count,
max_upward_queue_size : pre.max_upward_queue_size,
max_upward_message_size : pre.max_upward_message_size,
max_upward_message_num_per_candidate : pre.max_upward_message_num_per_candidate,
hrmp_max_message_num_per_candidate : pre.hrmp_max_message_num_per_candidate,
validation_upgrade_cooldown : pre.validation_upgrade_cooldown,
validation_upgrade_delay : pre.validation_upgrade_delay,
max_pov_size : pre.max_pov_size,
max_downward_message_size : pre.max_downward_message_size,
ump_service_total_weight : pre.ump_service_total_weight,
hrmp_max_parachain_outbound_channels : pre.hrmp_max_parachain_outbound_channels,
hrmp_max_parathread_outbound_channels : pre.hrmp_max_parathread_outbound_channels,
hrmp_sender_deposit : pre.hrmp_sender_deposit,
hrmp_recipient_deposit : pre.hrmp_recipient_deposit,
hrmp_channel_max_capacity : pre.hrmp_channel_max_capacity,
hrmp_channel_max_total_size : pre.hrmp_channel_max_total_size,
hrmp_max_parachain_inbound_channels : pre.hrmp_max_parachain_inbound_channels,
hrmp_max_parathread_inbound_channels : pre.hrmp_max_parathread_inbound_channels,
hrmp_channel_max_message_size : pre.hrmp_channel_max_message_size,
code_retention_period : pre.code_retention_period,
parathread_cores : pre.parathread_cores,
parathread_retries : pre.parathread_retries,
group_rotation_frequency : pre.group_rotation_frequency,
chain_availability_period : pre.chain_availability_period,
thread_availability_period : pre.thread_availability_period,
scheduling_lookahead : pre.scheduling_lookahead,
max_validators_per_core : pre.max_validators_per_core,
max_validators : pre.max_validators,
dispute_period : pre.dispute_period,
dispute_post_conclusion_acceptance_period: pre.dispute_post_conclusion_acceptance_period,
no_show_slots : pre.no_show_slots,
n_delay_tranches : pre.n_delay_tranches,
zeroth_delay_tranche_width : pre.zeroth_delay_tranche_width,
needed_approvals : pre.needed_approvals,
relay_vrf_modulo_samples : pre.relay_vrf_modulo_samples,
ump_max_individual_weight : pre.ump_max_individual_weight,
pvf_checking_enabled : pre.pvf_checking_enabled,
pvf_voting_ttl : pre.pvf_voting_ttl,
minimum_validation_upgrade_delay : pre.minimum_validation_upgrade_delay,
// Default values are zeroes, thus it's ensured allowed ancestry never crosses the upgrade block.
async_backing_params : AsyncBackingParams { max_candidate_depth: 0, allowed_ancestry_len: 0 },
// Default executor parameters set is empty
executor_params : Default::default(),
}
};
let v4 = V4ActiveConfig::<T>::get()
.defensive_proof("Could not decode old config")
.unwrap_or_default();
let v5 = translate(v4);
V5ActiveConfig::<T>::set(Some(v5));
let pending_v4 = V4PendingConfigs::<T>::get()
.defensive_proof("Could not decode old pending")
.unwrap_or_default();
let mut pending_v5 = Vec::new();
for (session, v4) in pending_v4.into_iter() {
let v5 = translate(v4);
pending_v5.push((session, v5));
}
V5PendingConfigs::<T>::set(Some(pending_v5.clone()));
let num_configs = (pending_v5.len() + 1) as u64;
T::DbWeight::get().reads_writes(num_configs, num_configs)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::{new_test_ext, Test};
use primitives::ExecutorParams;
#[test]
fn v4_deserialized_from_actual_data() {
// Example how to get new `raw_config`:
// We'll obtain the raw_config at a specified a block
// Steps:
// 1. Go to Polkadot.js -> Developer -> Chain state -> Storage: https://polkadot.js.org/apps/#/chainstate
// 2. Set these parameters:
// 2.1. selected state query: configuration; activeConfig(): PolkadotRuntimeParachainsConfigurationHostConfiguration
// 2.2. blockhash to query at: 0xf89d3ab5312c5f70d396dc59612f0aa65806c798346f9db4b35278baed2e0e53 (the hash of the block)
// 2.3. Note the value of encoded storage key -> 0x06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385 for the referenced block.
// 2.4. You'll also need the decoded values to update the test.
// 3. Go to Polkadot.js -> Developer -> Chain state -> Raw storage
// 3.1 Enter the encoded storage key and you get the raw config.
// This exceeds the maximal line width length, but that's fine, since this is not code and
// doesn't need to be read and also leaving it as one line allows to easily copy it.
let raw_config = hex_literal::hex!["0000a000005000000a00000000c8000000c800000a0000000a000000100e0000580200000000500000c800000700e8764817020040011e00000000000000005039278c0400000000000000000000005039278c0400000000000000000000e8030000009001001e00000000000000009001008070000000000000000000000a0000000a0000000a00000001000000010500000001c80000000600000058020000580200000200000059000000000000001e000000280000000700c817a80402004001010200000014000000"];
let v4 = v5::V4HostConfiguration::<primitives::BlockNumber>::decode(&mut &raw_config[..])
.unwrap();
// We check only a sample of the values here. If we missed any fields or messed up data types
// that would skew all the fields coming after.
assert_eq!(v4.max_code_size, 10_485_760);
assert_eq!(v4.validation_upgrade_cooldown, 3600);
assert_eq!(v4.max_pov_size, 5_242_880);
assert_eq!(v4.hrmp_channel_max_message_size, 102_400);
assert_eq!(v4.n_delay_tranches, 89);
assert_eq!(v4.ump_max_individual_weight, Weight::from_parts(20_000_000_000, 5_242_880));
assert_eq!(v4.minimum_validation_upgrade_delay, 20);
}
#[test]
fn test_migrate_to_v5() {
// Host configuration has lots of fields. However, in this migration we only remove one field.
// The most important part to check are a couple of the last fields. We also pick
// extra fields to check arbitrarily, e.g. depending on their position (i.e. the middle) and
// also their type.
//
// We specify only the picked fields and the rest should be provided by the `Default`
// implementation. That implementation is copied over between the two types and should work
// fine.
let v4 = v5::V4HostConfiguration::<primitives::BlockNumber> {
ump_max_individual_weight: Weight::from_parts(0x71616e6f6e0au64, 0x71616e6f6e0au64),
needed_approvals: 69,
thread_availability_period: 55,
hrmp_recipient_deposit: 1337,
max_pov_size: 1111,
chain_availability_period: 33,
minimum_validation_upgrade_delay: 20,
..Default::default()
};
let mut pending_configs = Vec::new();
pending_configs.push((100, v4.clone()));
pending_configs.push((300, v4.clone()));
new_test_ext(Default::default()).execute_with(|| {
// Implant the v4 version in the state.
V4ActiveConfig::<Test>::set(Some(v4));
V4PendingConfigs::<Test>::set(Some(pending_configs));
migrate_to_v5::<Test>();
let v5 = V5ActiveConfig::<Test>::get().unwrap();
let mut configs_to_check = V5PendingConfigs::<Test>::get().unwrap();
configs_to_check.push((0, v5.clone()));
for (_, v4) in configs_to_check {
#[rustfmt::skip]
{
assert_eq!(v4.max_code_size , v5.max_code_size);
assert_eq!(v4.max_head_data_size , v5.max_head_data_size);
assert_eq!(v4.max_upward_queue_count , v5.max_upward_queue_count);
assert_eq!(v4.max_upward_queue_size , v5.max_upward_queue_size);
assert_eq!(v4.max_upward_message_size , v5.max_upward_message_size);
assert_eq!(v4.max_upward_message_num_per_candidate , v5.max_upward_message_num_per_candidate);
assert_eq!(v4.hrmp_max_message_num_per_candidate , v5.hrmp_max_message_num_per_candidate);
assert_eq!(v4.validation_upgrade_cooldown , v5.validation_upgrade_cooldown);
assert_eq!(v4.validation_upgrade_delay , v5.validation_upgrade_delay);
assert_eq!(v4.max_pov_size , v5.max_pov_size);
assert_eq!(v4.max_downward_message_size , v5.max_downward_message_size);
assert_eq!(v4.ump_service_total_weight , v5.ump_service_total_weight);
assert_eq!(v4.hrmp_max_parachain_outbound_channels , v5.hrmp_max_parachain_outbound_channels);
assert_eq!(v4.hrmp_max_parathread_outbound_channels , v5.hrmp_max_parathread_outbound_channels);
assert_eq!(v4.hrmp_sender_deposit , v5.hrmp_sender_deposit);
assert_eq!(v4.hrmp_recipient_deposit , v5.hrmp_recipient_deposit);
assert_eq!(v4.hrmp_channel_max_capacity , v5.hrmp_channel_max_capacity);
assert_eq!(v4.hrmp_channel_max_total_size , v5.hrmp_channel_max_total_size);
assert_eq!(v4.hrmp_max_parachain_inbound_channels , v5.hrmp_max_parachain_inbound_channels);
assert_eq!(v4.hrmp_max_parathread_inbound_channels , v5.hrmp_max_parathread_inbound_channels);
assert_eq!(v4.hrmp_channel_max_message_size , v5.hrmp_channel_max_message_size);
assert_eq!(v4.code_retention_period , v5.code_retention_period);
assert_eq!(v4.parathread_cores , v5.parathread_cores);
assert_eq!(v4.parathread_retries , v5.parathread_retries);
assert_eq!(v4.group_rotation_frequency , v5.group_rotation_frequency);
assert_eq!(v4.chain_availability_period , v5.chain_availability_period);
assert_eq!(v4.thread_availability_period , v5.thread_availability_period);
assert_eq!(v4.scheduling_lookahead , v5.scheduling_lookahead);
assert_eq!(v4.max_validators_per_core , v5.max_validators_per_core);
assert_eq!(v4.max_validators , v5.max_validators);
assert_eq!(v4.dispute_period , v5.dispute_period);
assert_eq!(v4.no_show_slots , v5.no_show_slots);
assert_eq!(v4.n_delay_tranches , v5.n_delay_tranches);
assert_eq!(v4.zeroth_delay_tranche_width , v5.zeroth_delay_tranche_width);
assert_eq!(v4.needed_approvals , v5.needed_approvals);
assert_eq!(v4.relay_vrf_modulo_samples , v5.relay_vrf_modulo_samples);
assert_eq!(v4.ump_max_individual_weight , v5.ump_max_individual_weight);
assert_eq!(v4.pvf_checking_enabled , v5.pvf_checking_enabled);
assert_eq!(v4.pvf_voting_ttl , v5.pvf_voting_ttl);
assert_eq!(v4.minimum_validation_upgrade_delay , v5.minimum_validation_upgrade_delay);
}; // ; makes this a statement. `rustfmt::skip` cannot be put on an expression.
// additional checks for async backing.
assert_eq!(v5.async_backing_params.allowed_ancestry_len, 0);
assert_eq!(v5.async_backing_params.max_candidate_depth, 0);
assert_eq!(v5.executor_params, ExecutorParams::new());
}
});
}
}
@@ -0,0 +1,292 @@
// 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/>.
//! A module that is responsible for migration of storage.
use crate::configuration::{self, Config, Pallet};
use frame_support::{
pallet_prelude::*,
traits::{Defensive, StorageVersion},
weights::Weight,
};
use frame_system::pallet_prelude::BlockNumberFor;
use sp_std::vec::Vec;
use frame_support::traits::OnRuntimeUpgrade;
use primitives::SessionIndex;
#[cfg(feature = "try-runtime")]
use sp_std::prelude::*;
use super::v5::V5HostConfiguration;
// Change this once there is V7.
type V6HostConfiguration<BlockNumber> = configuration::HostConfiguration<BlockNumber>;
#[frame_support::storage_alias]
pub(crate) type V5ActiveConfig<T: Config> =
StorageValue<Pallet<T>, V5HostConfiguration<BlockNumberFor<T>>, OptionQuery>;
#[frame_support::storage_alias]
pub(crate) type V5PendingConfigs<T: Config> = StorageValue<
Pallet<T>,
Vec<(SessionIndex, V5HostConfiguration<BlockNumberFor<T>>)>,
OptionQuery,
>;
#[frame_support::storage_alias]
pub(crate) type V6ActiveConfig<T: Config> =
StorageValue<Pallet<T>, V6HostConfiguration<BlockNumberFor<T>>, OptionQuery>;
#[frame_support::storage_alias]
pub(crate) type V6PendingConfigs<T: Config> = StorageValue<
Pallet<T>,
Vec<(SessionIndex, V6HostConfiguration<BlockNumberFor<T>>)>,
OptionQuery,
>;
pub struct MigrateToV6<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for MigrateToV6<T> {
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, &'static str> {
log::trace!(target: crate::configuration::LOG_TARGET, "Running pre_upgrade()");
ensure!(StorageVersion::get::<Pallet<T>>() == 5, "The migration requires version 4");
Ok(Vec::new())
}
fn on_runtime_upgrade() -> Weight {
if StorageVersion::get::<Pallet<T>>() == 5 {
let weight_consumed = migrate_to_v6::<T>();
log::info!(target: configuration::LOG_TARGET, "MigrateToV6 executed successfully");
StorageVersion::new(6).put::<Pallet<T>>();
weight_consumed
} else {
log::warn!(target: configuration::LOG_TARGET, "MigrateToV6 should be removed.");
T::DbWeight::get().reads(1)
}
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(_state: Vec<u8>) -> Result<(), &'static str> {
log::trace!(target: crate::configuration::LOG_TARGET, "Running post_upgrade()");
ensure!(
StorageVersion::get::<Pallet<T>>() == 6,
"Storage version should be 6 after the migration"
);
Ok(())
}
}
fn migrate_to_v6<T: Config>() -> Weight {
// Unusual formatting is justified:
// - make it easier to verify that fields assign what they supposed to assign.
// - this code is transient and will be removed after all migrations are done.
// - this code is important enough to optimize for legibility sacrificing consistency.
#[rustfmt::skip]
let translate =
|pre: V5HostConfiguration<BlockNumberFor<T>>| ->
V6HostConfiguration<BlockNumberFor<T>>
{
V6HostConfiguration {
max_code_size : pre.max_code_size,
max_head_data_size : pre.max_head_data_size,
max_upward_queue_count : pre.max_upward_queue_count,
max_upward_queue_size : pre.max_upward_queue_size,
max_upward_message_size : pre.max_upward_message_size,
max_upward_message_num_per_candidate : pre.max_upward_message_num_per_candidate,
hrmp_max_message_num_per_candidate : pre.hrmp_max_message_num_per_candidate,
validation_upgrade_cooldown : pre.validation_upgrade_cooldown,
validation_upgrade_delay : pre.validation_upgrade_delay,
max_pov_size : pre.max_pov_size,
max_downward_message_size : pre.max_downward_message_size,
hrmp_max_parachain_outbound_channels : pre.hrmp_max_parachain_outbound_channels,
hrmp_max_parathread_outbound_channels : pre.hrmp_max_parathread_outbound_channels,
hrmp_sender_deposit : pre.hrmp_sender_deposit,
hrmp_recipient_deposit : pre.hrmp_recipient_deposit,
hrmp_channel_max_capacity : pre.hrmp_channel_max_capacity,
hrmp_channel_max_total_size : pre.hrmp_channel_max_total_size,
hrmp_max_parachain_inbound_channels : pre.hrmp_max_parachain_inbound_channels,
hrmp_max_parathread_inbound_channels : pre.hrmp_max_parathread_inbound_channels,
hrmp_channel_max_message_size : pre.hrmp_channel_max_message_size,
code_retention_period : pre.code_retention_period,
parathread_cores : pre.parathread_cores,
parathread_retries : pre.parathread_retries,
group_rotation_frequency : pre.group_rotation_frequency,
chain_availability_period : pre.chain_availability_period,
thread_availability_period : pre.thread_availability_period,
scheduling_lookahead : pre.scheduling_lookahead,
max_validators_per_core : pre.max_validators_per_core,
max_validators : pre.max_validators,
dispute_period : pre.dispute_period,
dispute_post_conclusion_acceptance_period: pre.dispute_post_conclusion_acceptance_period,
no_show_slots : pre.no_show_slots,
n_delay_tranches : pre.n_delay_tranches,
zeroth_delay_tranche_width : pre.zeroth_delay_tranche_width,
needed_approvals : pre.needed_approvals,
relay_vrf_modulo_samples : pre.relay_vrf_modulo_samples,
pvf_checking_enabled : pre.pvf_checking_enabled,
pvf_voting_ttl : pre.pvf_voting_ttl,
minimum_validation_upgrade_delay : pre.minimum_validation_upgrade_delay,
async_backing_params : pre.async_backing_params,
executor_params : pre.executor_params,
}
};
let v5 = V5ActiveConfig::<T>::get()
.defensive_proof("Could not decode old config")
.unwrap_or_default();
let v6 = translate(v5);
V6ActiveConfig::<T>::set(Some(v6));
let pending_v4 = V5PendingConfigs::<T>::get()
.defensive_proof("Could not decode old pending")
.unwrap_or_default();
let mut pending_v5 = Vec::new();
for (session, v5) in pending_v4.into_iter() {
let v6 = translate(v5);
pending_v5.push((session, v6));
}
V6PendingConfigs::<T>::set(Some(pending_v5.clone()));
let num_configs = (pending_v5.len() + 1) as u64;
T::DbWeight::get().reads_writes(num_configs, num_configs)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::{new_test_ext, Test};
#[test]
fn v5_deserialized_from_actual_data() {
// Example how to get new `raw_config`:
// We'll obtain the raw_config at a specified a block
// Steps:
// 1. Go to Polkadot.js -> Developer -> Chain state -> Storage: https://polkadot.js.org/apps/#/chainstate
// 2. Set these parameters:
// 2.1. selected state query: configuration; activeConfig(): PolkadotRuntimeParachainsConfigurationHostConfiguration
// 2.2. blockhash to query at: 0xf89d3ab5312c5f70d396dc59612f0aa65806c798346f9db4b35278baed2e0e53 (the hash of the block)
// 2.3. Note the value of encoded storage key -> 0x06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385 for the referenced block.
// 2.4. You'll also need the decoded values to update the test.
// 3. Go to Polkadot.js -> Developer -> Chain state -> Raw storage
// 3.1 Enter the encoded storage key and you get the raw config.
// This exceeds the maximal line width length, but that's fine, since this is not code and
// doesn't need to be read and also leaving it as one line allows to easily copy it.
let raw_config = hex_literal::hex!["00005000005000000a00000000c8000000c800000a0000000a000000c80000006400000000000000000000000000500000c800000700e8764817020040010a0000000000000000c0220fca950300000000000000000000c0220fca9503000000000000000000e8030000009001000a0000000000000000900100008070000000000000000000000a000000050000000500000001000000010500000001c8000000060000005802000002000000280000000000000002000000010000000700c817a8040200400101020000000f000000"];
let v5 =
V5HostConfiguration::<primitives::BlockNumber>::decode(&mut &raw_config[..]).unwrap();
// We check only a sample of the values here. If we missed any fields or messed up data types
// that would skew all the fields coming after.
assert_eq!(v5.max_code_size, 5242880);
assert_eq!(v5.validation_upgrade_cooldown, 200);
assert_eq!(v5.max_pov_size, 5_242_880);
assert_eq!(v5.hrmp_channel_max_message_size, 102_400);
assert_eq!(v5.n_delay_tranches, 40);
assert_eq!(v5.ump_max_individual_weight, Weight::from_parts(20_000_000_000, 5_242_880));
assert_eq!(v5.minimum_validation_upgrade_delay, 15); // This is the last field in the struct.
}
#[test]
fn test_migrate_to_v6() {
// Host configuration has lots of fields. However, in this migration we only remove one field.
// The most important part to check are a couple of the last fields. We also pick
// extra fields to check arbitrarily, e.g. depending on their position (i.e. the middle) and
// also their type.
//
// We specify only the picked fields and the rest should be provided by the `Default`
// implementation. That implementation is copied over between the two types and should work
// fine.
let v5 = V5HostConfiguration::<primitives::BlockNumber> {
ump_max_individual_weight: Weight::from_parts(0x71616e6f6e0au64, 0x71616e6f6e0au64),
needed_approvals: 69,
thread_availability_period: 55,
hrmp_recipient_deposit: 1337,
max_pov_size: 1111,
chain_availability_period: 33,
minimum_validation_upgrade_delay: 20,
..Default::default()
};
let mut pending_configs = Vec::new();
pending_configs.push((100, v5.clone()));
pending_configs.push((300, v5.clone()));
new_test_ext(Default::default()).execute_with(|| {
// Implant the v5 version in the state.
V5ActiveConfig::<Test>::set(Some(v5));
V5PendingConfigs::<Test>::set(Some(pending_configs));
migrate_to_v6::<Test>();
let v6 = V6ActiveConfig::<Test>::get().unwrap();
let mut configs_to_check = V6PendingConfigs::<Test>::get().unwrap();
configs_to_check.push((0, v6.clone()));
for (_, v5) in configs_to_check {
#[rustfmt::skip]
{
assert_eq!(v5.max_code_size , v6.max_code_size);
assert_eq!(v5.max_head_data_size , v6.max_head_data_size);
assert_eq!(v5.max_upward_queue_count , v6.max_upward_queue_count);
assert_eq!(v5.max_upward_queue_size , v6.max_upward_queue_size);
assert_eq!(v5.max_upward_message_size , v6.max_upward_message_size);
assert_eq!(v5.max_upward_message_num_per_candidate , v6.max_upward_message_num_per_candidate);
assert_eq!(v5.hrmp_max_message_num_per_candidate , v6.hrmp_max_message_num_per_candidate);
assert_eq!(v5.validation_upgrade_cooldown , v6.validation_upgrade_cooldown);
assert_eq!(v5.validation_upgrade_delay , v6.validation_upgrade_delay);
assert_eq!(v5.max_pov_size , v6.max_pov_size);
assert_eq!(v5.max_downward_message_size , v6.max_downward_message_size);
assert_eq!(v5.hrmp_max_parachain_outbound_channels , v6.hrmp_max_parachain_outbound_channels);
assert_eq!(v5.hrmp_max_parathread_outbound_channels , v6.hrmp_max_parathread_outbound_channels);
assert_eq!(v5.hrmp_sender_deposit , v6.hrmp_sender_deposit);
assert_eq!(v5.hrmp_recipient_deposit , v6.hrmp_recipient_deposit);
assert_eq!(v5.hrmp_channel_max_capacity , v6.hrmp_channel_max_capacity);
assert_eq!(v5.hrmp_channel_max_total_size , v6.hrmp_channel_max_total_size);
assert_eq!(v5.hrmp_max_parachain_inbound_channels , v6.hrmp_max_parachain_inbound_channels);
assert_eq!(v5.hrmp_max_parathread_inbound_channels , v6.hrmp_max_parathread_inbound_channels);
assert_eq!(v5.hrmp_channel_max_message_size , v6.hrmp_channel_max_message_size);
assert_eq!(v5.code_retention_period , v6.code_retention_period);
assert_eq!(v5.parathread_cores , v6.parathread_cores);
assert_eq!(v5.parathread_retries , v6.parathread_retries);
assert_eq!(v5.group_rotation_frequency , v6.group_rotation_frequency);
assert_eq!(v5.chain_availability_period , v6.chain_availability_period);
assert_eq!(v5.thread_availability_period , v6.thread_availability_period);
assert_eq!(v5.scheduling_lookahead , v6.scheduling_lookahead);
assert_eq!(v5.max_validators_per_core , v6.max_validators_per_core);
assert_eq!(v5.max_validators , v6.max_validators);
assert_eq!(v5.dispute_period , v6.dispute_period);
assert_eq!(v5.no_show_slots , v6.no_show_slots);
assert_eq!(v5.n_delay_tranches , v6.n_delay_tranches);
assert_eq!(v5.zeroth_delay_tranche_width , v6.zeroth_delay_tranche_width);
assert_eq!(v5.needed_approvals , v6.needed_approvals);
assert_eq!(v5.relay_vrf_modulo_samples , v6.relay_vrf_modulo_samples);
assert_eq!(v5.pvf_checking_enabled , v6.pvf_checking_enabled);
assert_eq!(v5.pvf_voting_ttl , v6.pvf_voting_ttl);
assert_eq!(v5.minimum_validation_upgrade_delay , v6.minimum_validation_upgrade_delay);
assert_eq!(v5.async_backing_params.allowed_ancestry_len, v6.async_backing_params.allowed_ancestry_len);
assert_eq!(v5.async_backing_params.max_candidate_depth , v6.async_backing_params.max_candidate_depth);
assert_eq!(v5.executor_params , v6.executor_params);
}; // ; makes this a statement. `rustfmt::skip` cannot be put on an expression.
}
});
}
}
@@ -0,0 +1,111 @@
// Copyright 2021 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/>.
//! A module that is responsible for migration of UMP storage.
#![allow(unused_imports)] // Since we use features.
use crate::configuration::{self, ActiveConfig, Config, PendingConfigs, WeightInfo, LOG_TARGET};
use parity_scale_codec::{Decode, Encode};
pub mod latest {
use super::*;
use frame_support::{pallet_prelude::Weight, traits::OnRuntimeUpgrade};
/// Force update the UMP limits in the parachain host config.
// NOTE: `OnRuntimeUpgrade` does not have a `self`, so everything must be compile time.
pub struct ScheduleConfigUpdate<
T,
const MAX_UPWARD_QUEUE_SIZE: u32,
const MAX_UPWARD_QUEUE_COUNT: u32,
const MAX_UPWARD_MESSAGE_SIZE: u32,
const MAX_UPWARD_MESSAGE_NUM_PER_CANDIDATE: u32,
>(core::marker::PhantomData<T>);
impl<
T: Config,
const MAX_UPWARD_QUEUE_SIZE: u32,
const MAX_UPWARD_QUEUE_COUNT: u32,
const MAX_UPWARD_MESSAGE_SIZE: u32,
const MAX_UPWARD_MESSAGE_NUM_PER_CANDIDATE: u32,
> OnRuntimeUpgrade
for ScheduleConfigUpdate<
T,
MAX_UPWARD_QUEUE_SIZE,
MAX_UPWARD_QUEUE_COUNT,
MAX_UPWARD_MESSAGE_SIZE,
MAX_UPWARD_MESSAGE_NUM_PER_CANDIDATE,
>
{
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<sp_std::vec::Vec<u8>, &'static str> {
log::info!(target: LOG_TARGET, "pre_upgrade");
let mut pending = PendingConfigs::<T>::get();
pending.sort_by_key(|(s, _)| *s);
log::info!(
target: LOG_TARGET,
"Active HostConfig:\n\n{:#?}\n",
ActiveConfig::<T>::get()
);
log::info!(
target: LOG_TARGET,
"Last pending HostConfig upgrade:\n\n{:#?}\n",
pending.last()
);
Ok((pending.len() as u32).encode())
}
fn on_runtime_upgrade() -> Weight {
if let Err(e) = configuration::Pallet::<T>::schedule_config_update(|cfg| {
cfg.max_upward_queue_size = MAX_UPWARD_QUEUE_SIZE;
cfg.max_upward_queue_count = MAX_UPWARD_QUEUE_COUNT;
cfg.max_upward_message_size = MAX_UPWARD_MESSAGE_SIZE;
cfg.max_upward_message_num_per_candidate = MAX_UPWARD_MESSAGE_NUM_PER_CANDIDATE;
}) {
log::error!(
target: LOG_TARGET,
"\n!!!!!!!!!!!!!!!!!!!!!!!!\nFailed to schedule HostConfig upgrade: {:?}\n!!!!!!!!!!!!!!!!!!!!!!!!\n",
e,
);
}
T::WeightInfo::set_config_with_block_number()
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(state: sp_std::vec::Vec<u8>) -> Result<(), &'static str> {
log::info!(target: LOG_TARGET, "post_upgrade");
let old_pending: u32 = Decode::decode(&mut &state[..]).expect("Known good");
let mut pending = PendingConfigs::<T>::get();
pending.sort_by_key(|(s, _)| *s);
log::info!(
target: LOG_TARGET,
"Last pending HostConfig upgrade:\n\n{:#?}\n",
pending.last()
);
assert_eq!(
pending.len(),
old_pending as usize + 1,
"There must be a new pending upgrade enqueued"
);
Ok(())
}
}
}
@@ -16,7 +16,7 @@
use super::*;
use crate::mock::{new_test_ext, Configuration, ParasShared, RuntimeOrigin, Test};
use frame_support::{assert_err, assert_ok};
use frame_support::{assert_err, assert_noop, assert_ok};
fn on_new_session(session_index: SessionIndex) -> (HostConfiguration<u32>, HostConfiguration<u32>) {
ParasShared::set_session_index(session_index);
@@ -309,7 +309,6 @@ fn setting_pending_config_members() {
max_upward_queue_count: 1337,
max_upward_queue_size: 228,
max_downward_message_size: 2048,
ump_service_total_weight: Weight::from_parts(20000, 20000),
max_upward_message_size: 448,
max_upward_message_num_per_candidate: 5,
hrmp_sender_deposit: 22,
@@ -322,7 +321,6 @@ fn setting_pending_config_members() {
hrmp_max_parachain_outbound_channels: 10,
hrmp_max_parathread_outbound_channels: 20,
hrmp_max_message_num_per_candidate: 20,
ump_max_individual_weight: Weight::from_parts(909, 909),
pvf_checking_enabled: true,
pvf_voting_ttl: 3,
minimum_validation_upgrade_delay: 20,
@@ -418,16 +416,18 @@ fn setting_pending_config_members() {
new_config.max_upward_queue_size,
)
.unwrap();
assert_noop!(
Configuration::set_max_upward_queue_size(
RuntimeOrigin::root(),
MAX_UPWARD_MESSAGE_SIZE_BOUND + 1,
),
Error::<Test>::InvalidNewValue
);
Configuration::set_max_downward_message_size(
RuntimeOrigin::root(),
new_config.max_downward_message_size,
)
.unwrap();
Configuration::set_ump_service_total_weight(
RuntimeOrigin::root(),
new_config.ump_service_total_weight,
)
.unwrap();
Configuration::set_max_upward_message_size(
RuntimeOrigin::root(),
new_config.max_upward_message_size,
@@ -488,11 +488,6 @@ fn setting_pending_config_members() {
new_config.hrmp_max_message_num_per_candidate,
)
.unwrap();
Configuration::set_ump_max_individual_weight(
RuntimeOrigin::root(),
new_config.ump_max_individual_weight,
)
.unwrap();
Configuration::set_pvf_checking_enabled(
RuntimeOrigin::root(),
new_config.pvf_checking_enabled,
+7 -32
View File
@@ -15,18 +15,16 @@
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use crate::{
mock::{
new_test_ext, Configuration, Hrmp, MockGenesisConfig, Paras, ParasShared,
RuntimeEvent as MockEvent, RuntimeOrigin, System, Test,
},
paras::ParaKind,
use crate::mock::{
deregister_parachain, new_test_ext, register_parachain, register_parachain_with_balance,
Configuration, Hrmp, MockGenesisConfig, Paras, ParasShared, RuntimeEvent as MockEvent,
RuntimeOrigin, System, Test,
};
use frame_support::{assert_noop, assert_ok, traits::Currency as _};
use primitives::{BlockNumber, ValidationCode};
use frame_support::assert_noop;
use primitives::BlockNumber;
use std::collections::BTreeMap;
fn run_to_block(to: BlockNumber, new_session: Option<Vec<BlockNumber>>) {
pub(crate) fn run_to_block(to: BlockNumber, new_session: Option<Vec<BlockNumber>>) {
let config = Configuration::config();
while System::block_number() < to {
let b = System::block_number();
@@ -129,29 +127,6 @@ fn default_genesis_config() -> MockGenesisConfig {
}
}
fn register_parachain_with_balance(id: ParaId, balance: Balance) {
let validation_code: ValidationCode = vec![1].into();
assert_ok!(Paras::schedule_para_initialize(
id,
crate::paras::ParaGenesisArgs {
para_kind: ParaKind::Parachain,
genesis_head: vec![1].into(),
validation_code: validation_code.clone(),
},
));
assert_ok!(Paras::add_trusted_validation_code(RuntimeOrigin::root(), validation_code));
<Test as Config>::Currency::make_free_balance_be(&id.into_account_truncating(), balance);
}
fn register_parachain(id: ParaId) {
register_parachain_with_balance(id, 1000);
}
fn deregister_parachain(id: ParaId) {
assert_ok!(Paras::schedule_para_cleanup(id));
}
fn channel_exists(sender: ParaId, recipient: ParaId) -> bool {
HrmpChannels::<Test>::get(&HrmpChannelId { sender, recipient }).is_some()
}
@@ -0,0 +1,41 @@
// Copyright 2020 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 super::*;
use frame_benchmarking::benchmarks;
use pallet_message_queue as mq;
benchmarks! {
where_clause {
where
T: mq::Config,
}
receive_upward_messages {
let i in 1 .. 1000;
let max_len = mq::MaxMessageLenOf::<T>::get() as usize;
let para = 42u32.into(); // not especially important.
let upward_messages = vec![vec![0; max_len]; i as usize];
Pallet::<T>::receive_upward_messages(para, vec![vec![0; max_len]; 1].as_slice());
}: { Pallet::<T>::receive_upward_messages(para, upward_messages.as_slice()) }
impl_benchmark_test_suite!(
Pallet,
crate::mock::new_test_ext(Default::default()),
crate::mock::Test
);
}
+341 -53
View File
@@ -21,20 +21,32 @@
//! to included.
use crate::{
configuration, disputes, dmp, hrmp, paras, paras_inherent::DisputedBitfield,
scheduler::CoreAssignment, shared, ump,
configuration::{self, HostConfiguration},
disputes, dmp, hrmp, paras,
paras_inherent::DisputedBitfield,
scheduler::CoreAssignment,
shared,
};
use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
use frame_support::pallet_prelude::*;
use frame_support::{
defensive,
pallet_prelude::*,
traits::{Defensive, EnqueueMessage},
BoundedSlice,
};
use pallet_message_queue::OnQueueChanged;
use parity_scale_codec::{Decode, Encode};
use primitives::{
supermajority_threshold, AvailabilityBitfield, BackedCandidate, CandidateCommitments,
CandidateDescriptor, CandidateHash, CandidateReceipt, CommittedCandidateReceipt, CoreIndex,
GroupIndex, Hash, HeadData, Id as ParaId, SigningContext, UncheckedSignedAvailabilityBitfields,
ValidatorId, ValidatorIndex, ValidityAttestation,
supermajority_threshold, well_known_keys, AvailabilityBitfield, BackedCandidate,
CandidateCommitments, CandidateDescriptor, CandidateHash, CandidateReceipt,
CommittedCandidateReceipt, CoreIndex, GroupIndex, Hash, HeadData, Id as ParaId, SigningContext,
UncheckedSignedAvailabilityBitfields, UpwardMessage, ValidatorId, ValidatorIndex,
ValidityAttestation,
};
use scale_info::TypeInfo;
use sp_runtime::{traits::One, DispatchError};
use sp_runtime::{traits::One, DispatchError, SaturatedConversion, Saturating};
#[cfg(feature = "std")]
use sp_std::fmt;
use sp_std::{collections::btree_set::BTreeSet, prelude::*};
pub use pallet::*;
@@ -42,6 +54,32 @@ pub use pallet::*;
#[cfg(test)]
pub(crate) mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub trait WeightInfo {
fn receive_upward_messages(i: u32) -> Weight;
}
pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
fn receive_upward_messages(_: u32) -> Weight {
Weight::MAX
}
}
impl WeightInfo for () {
fn receive_upward_messages(_: u32) -> Weight {
Weight::zero()
}
}
/// Maximum value that `config.max_upward_message_size` can be set to.
///
/// This is used for benchmarking sanely bounding relevant storage items. It is expected from the `configuration`
/// pallet to check these values before setting.
pub const MAX_UPWARD_MESSAGE_SIZE_BOUND: u32 = 128 * 1024;
/// A bitfield signed by a validator indicating that it is keeping its piece of the erasure-coding
/// for any backed candidates referred to by a `1` bit available.
///
@@ -179,6 +217,55 @@ pub fn minimum_backing_votes(n_validators: usize) -> usize {
sp_std::cmp::min(n_validators, 2)
}
/// Reads the footprint of queues for a specific origin type.
pub trait QueueFootprinter {
type Origin;
fn message_count(origin: Self::Origin) -> u64;
}
impl QueueFootprinter for () {
type Origin = UmpQueueId;
fn message_count(_: Self::Origin) -> u64 {
0
}
}
/// Aggregate message origin for the `MessageQueue` pallet.
///
/// Can be extended to serve further use-cases besides just UMP. Is stored in storage, so any change
/// to existing values will require a migration.
#[derive(Encode, Decode, Clone, MaxEncodedLen, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub enum AggregateMessageOrigin {
/// Inbound upward message.
#[codec(index = 0)]
Ump(UmpQueueId),
}
/// Identifies a UMP queue inside the `MessageQueue` pallet.
///
/// It is written in verbose form since future variants like `Loopback` and `Bridged` are already
/// forseeable.
#[derive(Encode, Decode, Clone, MaxEncodedLen, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub enum UmpQueueId {
/// The message originated from this parachain.
#[codec(index = 0)]
Para(ParaId),
}
#[cfg(feature = "runtime-benchmarks")]
impl From<u32> for AggregateMessageOrigin {
fn from(n: u32) -> Self {
// Some dummy for the benchmarks.
Self::Ump(UmpQueueId::Para(n.into()))
}
}
/// The maximal length of a UMP message.
pub type MaxUmpMessageLenOf<T> =
<<T as Config>::MessageQueue as EnqueueMessage<AggregateMessageOrigin>>::MaxMessageLen;
#[frame_support::pallet]
pub mod pallet {
use super::*;
@@ -193,13 +280,22 @@ pub mod pallet {
+ shared::Config
+ paras::Config
+ dmp::Config
+ ump::Config
+ hrmp::Config
+ configuration::Config
{
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type DisputesHandler: disputes::DisputesHandler<Self::BlockNumber>;
type RewardValidators: RewardValidators;
/// The system message queue.
///
/// The message queue provides general queueing and processing functionality. Currently it
/// replaces the old `UMP` dispatch queue. Other use-cases can be implemented as well by
/// adding new variants to `AggregateMessageOrigin`.
type MessageQueue: EnqueueMessage<AggregateMessageOrigin>;
/// Weight info for the calls of this pallet.
type WeightInfo: WeightInfo;
}
#[pallet::event]
@@ -211,6 +307,8 @@ pub mod pallet {
CandidateIncluded(CandidateReceipt<T::Hash>, HeadData, CoreIndex, GroupIndex),
/// A candidate timed out. `[candidate, head_data]`
CandidateTimedOut(CandidateReceipt<T::Hash>, HeadData, CoreIndex),
/// Some upward messages have been received and will be processed.
UpwardMessagesReceived { from: ParaId, count: u32 },
}
#[pallet::error]
@@ -299,6 +397,71 @@ pub mod pallet {
const LOG_TARGET: &str = "runtime::inclusion";
/// The reason that a candidate's outputs were rejected for.
#[derive(derive_more::From)]
#[cfg_attr(feature = "std", derive(Debug))]
enum AcceptanceCheckErr<BlockNumber> {
HeadDataTooLarge,
/// Code upgrades are not permitted at the current time.
PrematureCodeUpgrade,
/// The new runtime blob is too large.
NewCodeTooLarge,
/// The candidate violated this DMP acceptance criteria.
ProcessedDownwardMessages(dmp::ProcessedDownwardMessagesAcceptanceErr),
/// The candidate violated this UMP acceptance criteria.
UpwardMessages(UmpAcceptanceCheckErr),
/// The candidate violated this HRMP watermark acceptance criteria.
HrmpWatermark(hrmp::HrmpWatermarkAcceptanceErr<BlockNumber>),
/// The candidate violated this outbound HRMP acceptance criteria.
OutboundHrmp(hrmp::OutboundHrmpAcceptanceErr),
}
/// An error returned by [`check_upward_messages`] that indicates a violation of one of acceptance
/// criteria rules.
#[cfg_attr(test, derive(PartialEq))]
pub enum UmpAcceptanceCheckErr {
/// The maximal number of messages that can be submitted in one batch was exceeded.
MoreMessagesThanPermitted { sent: u32, permitted: u32 },
/// The maximal size of a single message was exceeded.
MessageSize { idx: u32, msg_size: u32, max_size: u32 },
/// The allowed number of messages in the queue was exceeded.
CapacityExceeded { count: u64, limit: u64 },
/// The allowed combined message size in the queue was exceeded.
TotalSizeExceeded { total_size: u64, limit: u64 },
/// A para-chain cannot send UMP messages while it is offboarding.
IsOffboarding,
}
#[cfg(feature = "std")]
impl fmt::Debug for UmpAcceptanceCheckErr {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
UmpAcceptanceCheckErr::MoreMessagesThanPermitted { sent, permitted } => write!(
fmt,
"more upward messages than permitted by config ({} > {})",
sent, permitted,
),
UmpAcceptanceCheckErr::MessageSize { idx, msg_size, max_size } => write!(
fmt,
"upward message idx {} larger than permitted by config ({} > {})",
idx, msg_size, max_size,
),
UmpAcceptanceCheckErr::CapacityExceeded { count, limit } => write!(
fmt,
"the ump queue would have more items than permitted by config ({} > {})",
count, limit,
),
UmpAcceptanceCheckErr::TotalSizeExceeded { total_size, limit } => write!(
fmt,
"the ump queue would have grown past the max size permitted by config ({} > {})",
total_size, limit,
),
UmpAcceptanceCheckErr::IsOffboarding =>
write!(fmt, "upward message rejected because the para is off-boarding",),
}
}
}
impl<T: Config> Pallet<T> {
/// Block initialization logic, called by initializer.
pub(crate) fn initializer_initialize(_now: T::BlockNumber) -> Weight {
@@ -311,12 +474,25 @@ impl<T: Config> Pallet<T> {
/// Handle an incoming session change.
pub(crate) fn initializer_on_new_session(
_notification: &crate::initializer::SessionChangeNotification<T::BlockNumber>,
outgoing_paras: &[ParaId],
) {
// unlike most drain methods, drained elements are not cleared on `Drop` of the iterator
// and require consumption.
for _ in <PendingAvailabilityCommitments<T>>::drain() {}
for _ in <PendingAvailability<T>>::drain() {}
for _ in <AvailabilityBitfields<T>>::drain() {}
Self::cleanup_outgoing_ump_dispatch_queues(outgoing_paras);
}
pub(crate) fn cleanup_outgoing_ump_dispatch_queues(outgoing: &[ParaId]) {
for outgoing_para in outgoing {
Self::cleanup_outgoing_ump_dispatch_queue(*outgoing_para);
}
}
pub(crate) fn cleanup_outgoing_ump_dispatch_queue(para: ParaId) {
T::MessageQueue::sweep_queue(AggregateMessageOrigin::Ump(UmpQueueId::Para(para)));
}
/// Extract the freed cores based on cores that became available.
@@ -698,20 +874,22 @@ impl<T: Config> Pallet<T> {
let relay_parent_number = now;
let check_ctx = CandidateCheckContext::<T>::new(now, relay_parent_number);
if let Err(err) = check_ctx.check_validation_outputs(
para_id,
&validation_outputs.head_data,
&validation_outputs.new_validation_code,
validation_outputs.processed_downward_messages,
&validation_outputs.upward_messages,
T::BlockNumber::from(validation_outputs.hrmp_watermark),
&validation_outputs.horizontal_messages,
) {
if check_ctx
.check_validation_outputs(
para_id,
&validation_outputs.head_data,
&validation_outputs.new_validation_code,
validation_outputs.processed_downward_messages,
&validation_outputs.upward_messages,
T::BlockNumber::from(validation_outputs.hrmp_watermark),
&validation_outputs.horizontal_messages,
)
.is_err()
{
log::debug!(
target: LOG_TARGET,
"Validation outputs checking for parachain `{}` failed: {:?}",
"Validation outputs checking for parachain `{}` failed",
u32::from(para_id),
err,
);
false
} else {
@@ -750,31 +928,31 @@ impl<T: Config> Pallet<T> {
// initial weight is config read.
let mut weight = T::DbWeight::get().reads_writes(1, 0);
if let Some(new_code) = commitments.new_validation_code {
weight += <paras::Pallet<T>>::schedule_code_upgrade(
weight.saturating_add(<paras::Pallet<T>>::schedule_code_upgrade(
receipt.descriptor.para_id,
new_code,
relay_parent_number,
&config,
);
));
}
// enact the messaging facet of the candidate.
weight += <dmp::Pallet<T>>::prune_dmq(
weight.saturating_accrue(<dmp::Pallet<T>>::prune_dmq(
receipt.descriptor.para_id,
commitments.processed_downward_messages,
);
weight += <ump::Pallet<T>>::receive_upward_messages(
));
weight.saturating_accrue(Self::receive_upward_messages(
receipt.descriptor.para_id,
commitments.upward_messages,
);
weight += <hrmp::Pallet<T>>::prune_hrmp(
commitments.upward_messages.as_slice(),
));
weight.saturating_accrue(<hrmp::Pallet<T>>::prune_hrmp(
receipt.descriptor.para_id,
T::BlockNumber::from(commitments.hrmp_watermark),
);
weight += <hrmp::Pallet<T>>::queue_outbound_hrmp(
));
weight.saturating_accrue(<hrmp::Pallet<T>>::queue_outbound_hrmp(
receipt.descriptor.para_id,
commitments.horizontal_messages,
);
));
Self::deposit_event(Event::<T>::CandidateIncluded(
plain,
@@ -783,12 +961,105 @@ impl<T: Config> Pallet<T> {
backing_group,
));
weight +
<paras::Pallet<T>>::note_new_head(
receipt.descriptor.para_id,
commitments.head_data,
relay_parent_number,
)
weight.saturating_add(<paras::Pallet<T>>::note_new_head(
receipt.descriptor.para_id,
commitments.head_data,
relay_parent_number,
))
}
/// Check that all the upward messages sent by a candidate pass the acceptance criteria.
pub(crate) fn check_upward_messages(
config: &HostConfiguration<T::BlockNumber>,
para: ParaId,
upward_messages: &[UpwardMessage],
) -> Result<(), UmpAcceptanceCheckErr> {
// Cannot send UMP messages while off-boarding.
if <paras::Pallet<T>>::is_offboarding(para) {
ensure!(upward_messages.is_empty(), UmpAcceptanceCheckErr::IsOffboarding);
}
let additional_msgs = upward_messages.len();
if additional_msgs > config.max_upward_message_num_per_candidate as usize {
return Err(UmpAcceptanceCheckErr::MoreMessagesThanPermitted {
sent: additional_msgs as u32,
permitted: config.max_upward_message_num_per_candidate,
})
}
let fp = T::MessageQueue::footprint(AggregateMessageOrigin::Ump(UmpQueueId::Para(para)));
let (para_queue_count, mut para_queue_size) = (fp.count, fp.size);
if para_queue_count.saturating_add(additional_msgs as u64) >
config.max_upward_queue_count as u64
{
return Err(UmpAcceptanceCheckErr::CapacityExceeded {
count: para_queue_count.saturating_add(additional_msgs as u64),
limit: config.max_upward_queue_count as u64,
})
}
for (idx, msg) in upward_messages.into_iter().enumerate() {
let msg_size = msg.len();
if msg_size > config.max_upward_message_size as usize {
return Err(UmpAcceptanceCheckErr::MessageSize {
idx: idx as u32,
msg_size: msg_size as u32,
max_size: config.max_upward_message_size,
})
}
// make sure that the queue is not overfilled.
// we do it here only once since returning false invalidates the whole relay-chain block.
if para_queue_size.saturating_add(msg_size as u64) > config.max_upward_queue_size as u64
{
return Err(UmpAcceptanceCheckErr::TotalSizeExceeded {
total_size: para_queue_size.saturating_add(msg_size as u64),
limit: config.max_upward_queue_size as u64,
})
}
para_queue_size.saturating_accrue(msg_size as u64);
}
Ok(())
}
/// Enqueues `upward_messages` from a `para`'s accepted candidate block.
///
/// This function is infallible since the candidate was already accepted and we therefore need
/// to deal with the messages as given. Messages that are too long will be ignored since such
/// candidates should have already been rejected in [`Self::check_upward_messages`].
pub(crate) fn receive_upward_messages(para: ParaId, upward_messages: &[Vec<u8>]) -> Weight {
let bounded = upward_messages
.iter()
.filter_map(|d| {
BoundedSlice::try_from(&d[..])
.map_err(|e| {
defensive!("Accepted candidate contains too long msg, len=", d.len());
e
})
.ok()
})
.collect();
Self::receive_bounded_upward_messages(para, bounded)
}
/// Enqueues storage-bounded `upward_messages` from a `para`'s accepted candidate block.
pub(crate) fn receive_bounded_upward_messages(
para: ParaId,
messages: Vec<BoundedSlice<'_, u8, MaxUmpMessageLenOf<T>>>,
) -> Weight {
let count = messages.len() as u32;
if count == 0 {
return Weight::zero()
}
T::MessageQueue::enqueue_messages(
messages.into_iter(),
AggregateMessageOrigin::Ump(UmpQueueId::Para(para)),
);
let weight = <T as Config>::WeightInfo::receive_upward_messages(count);
Self::deposit_event(Event::UpwardMessagesReceived { from: para, count });
weight
}
/// Cleans up all paras pending availability that the predicate returns true for.
@@ -902,17 +1173,6 @@ const fn availability_threshold(n_validators: usize) -> usize {
supermajority_threshold(n_validators)
}
#[derive(derive_more::From, Debug)]
enum AcceptanceCheckErr<BlockNumber> {
HeadDataTooLarge,
PrematureCodeUpgrade,
NewCodeTooLarge,
ProcessedDownwardMessages(dmp::ProcessedDownwardMessagesAcceptanceErr),
UpwardMessages(ump::AcceptanceCheckErr),
HrmpWatermark(hrmp::HrmpWatermarkAcceptanceErr<BlockNumber>),
OutboundHrmp(hrmp::OutboundHrmpAcceptanceErr),
}
impl<BlockNumber> AcceptanceCheckErr<BlockNumber> {
/// Returns the same error so that it can be threaded through a needle of `DispatchError` and
/// ultimately returned from a `Dispatchable`.
@@ -930,6 +1190,26 @@ impl<BlockNumber> AcceptanceCheckErr<BlockNumber> {
}
}
impl<T: Config> OnQueueChanged<AggregateMessageOrigin> for Pallet<T> {
// Write back the remaining queue capacity into `relay_dispatch_queue_remaining_capacity`.
fn on_queue_changed(origin: AggregateMessageOrigin, count: u64, size: u64) {
let para = match origin {
AggregateMessageOrigin::Ump(UmpQueueId::Para(p)) => p,
};
// TODO maybe migrate this to u64
let (count, size) = (count.saturated_into(), size.saturated_into());
// TODO paritytech/polkadot#6283: Remove all usages of `relay_dispatch_queue_size`
#[allow(deprecated)]
well_known_keys::relay_dispatch_queue_size_typed(para).set((count, size));
let config = <configuration::Pallet<T>>::config();
let remaining_count = config.max_upward_queue_count.saturating_sub(count);
let remaining_size = config.max_upward_queue_size.saturating_sub(size);
well_known_keys::relay_dispatch_queue_remaining_capacity(para)
.set((remaining_count, remaining_size));
}
}
/// A collection of data required for checking a candidate.
pub(crate) struct CandidateCheckContext<T: Config> {
config: configuration::HostConfiguration<T::BlockNumber>,
@@ -965,12 +1245,13 @@ impl<T: Config> CandidateCheckContext<T> {
let relay_parent_number = now - One::one();
{
// this should never fail because the para is registered
let persisted_validation_data = match crate::util::make_persisted_validation_data::<T>(
para_id,
relay_parent_number,
parent_storage_root,
) {
)
.defensive_proof("the para is registered")
{
Some(l) => l,
None => return Ok(Err(FailedToCreatePVD)),
};
@@ -1018,10 +1299,9 @@ impl<T: Config> CandidateCheckContext<T> {
) {
log::debug!(
target: LOG_TARGET,
"Validation outputs checking during inclusion of a candidate {} for parachain `{}` failed: {:?}",
"Validation outputs checking during inclusion of a candidate {} for parachain `{}` failed",
candidate_idx,
u32::from(para_id),
err,
);
Err(err.strip_into_dispatch_err::<T>())?;
};
@@ -1059,10 +1339,18 @@ impl<T: Config> CandidateCheckContext<T> {
// check if the candidate passes the messaging acceptance criteria
<dmp::Pallet<T>>::check_processed_downward_messages(para_id, processed_downward_messages)?;
<ump::Pallet<T>>::check_upward_messages(&self.config, para_id, upward_messages)?;
Pallet::<T>::check_upward_messages(&self.config, para_id, upward_messages)?;
<hrmp::Pallet<T>>::check_hrmp_watermark(para_id, self.relay_parent_number, hrmp_watermark)?;
<hrmp::Pallet<T>>::check_outbound_hrmp(&self.config, para_id, horizontal_messages)?;
Ok(())
}
}
impl<T: Config> QueueFootprinter for Pallet<T> {
type Origin = UmpQueueId;
fn message_count(origin: Self::Origin) -> u64 {
T::MessageQueue::footprint(AggregateMessageOrigin::Ump(origin)).count
}
}
@@ -29,6 +29,7 @@ use crate::{
use assert_matches::assert_matches;
use frame_support::assert_noop;
use keyring::Sr25519Keyring;
use parity_scale_codec::DecodeAll;
use primitives::{
BlockNumber, CandidateCommitments, CandidateDescriptor, CollatorId,
CompactStatement as Statement, Hash, SignedAvailabilityBitfield, SignedStatement,
@@ -159,6 +160,17 @@ pub(crate) fn back_candidate(
backed
}
pub(crate) fn run_to_block_default_notifications(to: BlockNumber, new_session: Vec<BlockNumber>) {
run_to_block(to, |b| {
new_session.contains(&b).then_some(SessionChangeNotification {
prev_config: Configuration::config(),
new_config: Configuration::config(),
session_index: ParasShared::session_index() + 1,
..Default::default()
})
});
}
pub(crate) fn run_to_block(
to: BlockNumber,
new_session: impl Fn(BlockNumber) -> Option<SessionChangeNotification<BlockNumber>>,
@@ -177,8 +189,8 @@ pub(crate) fn run_to_block(
&notification.new_config,
notification.validators.clone(),
);
Paras::initializer_on_new_session(&notification);
ParaInclusion::initializer_on_new_session(&notification);
let outgoing = Paras::initializer_on_new_session(&notification);
ParaInclusion::initializer_on_new_session(&notification, &outgoing);
}
System::on_finalize(b);
@@ -1972,3 +1984,12 @@ fn session_change_wipes() {
assert!(<PendingAvailabilityCommitments<Test>>::iter().collect::<Vec<_>>().is_empty());
});
}
/// Assert that the encoding of a known `AggregateMessageOrigin` did not change.
#[test]
fn aggregate_origin_decode_regression_check() {
let ump = AggregateMessageOrigin::Ump(UmpQueueId::Para(u32::MAX.into()));
let raw = (0u8, 0u8, u32::MAX).encode();
let decoded = AggregateMessageOrigin::decode_all(&mut &raw[..]);
assert_eq!(decoded, Ok(ump), "Migration needed for AggregateMessageOrigin");
}
@@ -22,7 +22,7 @@
use crate::{
configuration::{self, HostConfiguration},
disputes::{self, DisputesHandler as _, SlashingHandler as _},
dmp, hrmp, inclusion, paras, scheduler, session_info, shared, ump,
dmp, hrmp, inclusion, paras, scheduler, session_info, shared,
};
use frame_support::{
traits::{OneSessionHandler, Randomness},
@@ -113,7 +113,6 @@ pub mod pallet {
+ session_info::Config
+ disputes::Config
+ dmp::Config
+ ump::Config
+ hrmp::Config
{
/// A randomness beacon.
@@ -168,7 +167,6 @@ pub mod pallet {
T::DisputesHandler::initializer_initialize(now) +
T::SlashingHandler::initializer_initialize(now) +
dmp::Pallet::<T>::initializer_initialize(now) +
ump::Pallet::<T>::initializer_initialize(now) +
hrmp::Pallet::<T>::initializer_initialize(now);
HasInitialized::<T>::set(Some(()));
@@ -179,7 +177,6 @@ pub mod pallet {
fn on_finalize(now: T::BlockNumber) {
// reverse initialization order.
hrmp::Pallet::<T>::initializer_finalize();
ump::Pallet::<T>::initializer_finalize();
dmp::Pallet::<T>::initializer_finalize();
T::SlashingHandler::initializer_finalize();
T::DisputesHandler::initializer_finalize();
@@ -263,12 +260,11 @@ impl<T: Config> Pallet<T> {
let outgoing_paras = paras::Pallet::<T>::initializer_on_new_session(&notification);
scheduler::Pallet::<T>::initializer_on_new_session(&notification);
inclusion::Pallet::<T>::initializer_on_new_session(&notification);
inclusion::Pallet::<T>::initializer_on_new_session(&notification, &outgoing_paras);
session_info::Pallet::<T>::initializer_on_new_session(&notification);
T::DisputesHandler::initializer_on_new_session(&notification);
T::SlashingHandler::initializer_on_new_session(session_index);
dmp::Pallet::<T>::initializer_on_new_session(&notification, &outgoing_paras);
ump::Pallet::<T>::initializer_on_new_session(&notification, &outgoing_paras);
hrmp::Pallet::<T>::initializer_on_new_session(&notification, &outgoing_paras);
}
+2 -1
View File
@@ -37,7 +37,6 @@ pub mod reward_points;
pub mod scheduler;
pub mod session_info;
pub mod shared;
pub mod ump;
pub mod runtime_api_impl;
@@ -47,6 +46,8 @@ mod util;
mod builder;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod ump_tests;
pub use origin::{ensure_parachain, Origin};
pub use paras::ParaLifecycle;
+139 -48
View File
@@ -17,27 +17,31 @@
//! Mocks for all the traits.
use crate::{
configuration, disputes, dmp, hrmp, inclusion, initializer, origin, paras, paras_inherent,
scheduler, session_info, shared,
ump::{self, MessageId, UmpSink},
ParaId,
configuration, disputes, dmp, hrmp,
inclusion::{self, AggregateMessageOrigin, UmpQueueId},
initializer, origin, paras,
paras::ParaKind,
paras_inherent, scheduler, session_info, shared, ParaId,
};
use frame_support::{
parameter_types,
traits::{ConstU32, GenesisBuild, ValidatorSet, ValidatorSetWithIdentification},
weights::Weight,
assert_ok, parameter_types,
traits::{
Currency, GenesisBuild, ProcessMessage, ProcessMessageError, ValidatorSet,
ValidatorSetWithIdentification,
},
weights::{Weight, WeightMeter},
};
use frame_support_test::TestRandomness;
use parity_scale_codec::Decode;
use primitives::{
AuthorityDiscoveryId, Balance, BlockNumber, CandidateHash, Header, Moment, SessionIndex,
UpwardMessage, ValidatorIndex,
UpwardMessage, ValidationCode, ValidatorIndex,
};
use sp_core::H256;
use sp_core::{ConstU32, H256};
use sp_io::TestExternalities;
use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup},
traits::{AccountIdConversion, BlakeTwo256, IdentityLookup},
transaction_validity::TransactionPriority,
Permill,
};
@@ -54,6 +58,7 @@ frame_support::construct_runtime!(
{
System: frame_system,
Balances: pallet_balances,
MessageQueue: pallet_message_queue,
Paras: paras,
Configuration: configuration,
ParasShared: shared,
@@ -62,7 +67,6 @@ frame_support::construct_runtime!(
Scheduler: scheduler,
Initializer: initializer,
Dmp: dmp,
Ump: ump,
Hrmp: hrmp,
ParachainsOrigin: origin,
SessionInfo: session_info,
@@ -149,15 +153,10 @@ impl pallet_babe::Config for Test {
// session module is the trigger
type EpochChangeTrigger = pallet_babe::ExternalTrigger;
type DisabledValidators = ();
type WeightInfo = ();
type MaxAuthorities = MaxAuthorities;
type KeyOwnerProof = sp_core::Void;
type EquivocationReportSystem = ();
}
@@ -212,6 +211,7 @@ impl crate::paras::Config for Test {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = crate::paras::TestWeightInfo;
type UnsignedPriority = ParasUnsignedPriority;
type QueueFootprinter = ParaInclusion;
type NextSessionRotation = TestNextSessionRotation;
}
@@ -221,14 +221,6 @@ parameter_types! {
pub const FirstMessageFactorPercent: u64 = 100;
}
impl crate::ump::Config for Test {
type RuntimeEvent = RuntimeEvent;
type UmpSink = TestUmpSink;
type FirstMessageFactorPercent = FirstMessageFactorPercent;
type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
type WeightInfo = crate::ump::TestWeightInfo;
}
impl crate::hrmp::Config for Test {
type RuntimeOrigin = RuntimeOrigin;
type RuntimeEvent = RuntimeEvent;
@@ -292,10 +284,62 @@ impl crate::disputes::SlashingHandler<BlockNumber> for Test {
impl crate::scheduler::Config for Test {}
pub struct TestMessageQueueWeight;
impl pallet_message_queue::WeightInfo for TestMessageQueueWeight {
fn ready_ring_knit() -> Weight {
Weight::zero()
}
fn ready_ring_unknit() -> Weight {
Weight::zero()
}
fn service_queue_base() -> Weight {
Weight::zero()
}
fn service_page_base_completion() -> Weight {
Weight::zero()
}
fn service_page_base_no_completion() -> Weight {
Weight::zero()
}
fn service_page_item() -> Weight {
Weight::zero()
}
fn bump_service_head() -> Weight {
Weight::zero()
}
fn reap_page() -> Weight {
Weight::zero()
}
fn execute_overweight_page_removed() -> Weight {
Weight::zero()
}
fn execute_overweight_page_updated() -> Weight {
Weight::zero()
}
}
parameter_types! {
pub const MessageQueueServiceWeight: Weight = Weight::from_all(500);
}
pub type MessageQueueSize = u32;
impl pallet_message_queue::Config for Test {
type Size = MessageQueueSize;
type RuntimeEvent = RuntimeEvent;
type WeightInfo = TestMessageQueueWeight;
type MessageProcessor = TestProcessMessage;
type QueueChangeHandler = ParaInclusion;
type HeapSize = ConstU32<65536>;
type MaxStale = ConstU32<8>;
type ServiceWeight = MessageQueueServiceWeight;
}
impl crate::inclusion::Config for Test {
type WeightInfo = ();
type RuntimeEvent = RuntimeEvent;
type DisputesHandler = Disputes;
type RewardValidators = TestRewardValidators;
type MessageQueue = MessageQueue;
}
impl crate::paras_inherent::Config for Test {
@@ -372,39 +416,39 @@ pub fn availability_rewards() -> HashMap<ValidatorIndex, usize> {
AVAILABILITY_REWARDS.with(|r| r.borrow().clone())
}
std::thread_local! {
static PROCESSED: RefCell<Vec<(ParaId, UpwardMessage)>> = RefCell::new(vec![]);
}
/// Return which messages have been processed by `process_upward_message` and clear the buffer.
pub fn take_processed() -> Vec<(ParaId, UpwardMessage)> {
PROCESSED.with(|opt_hook| std::mem::take(&mut *opt_hook.borrow_mut()))
parameter_types! {
pub static Processed: Vec<(ParaId, UpwardMessage)> = vec![];
}
/// An implementation of a UMP sink that just records which messages were processed.
///
/// A message's weight is defined by the first 4 bytes of its data, which we decode into a
/// `u32`.
pub struct TestUmpSink;
impl UmpSink for TestUmpSink {
fn process_upward_message(
actual_origin: ParaId,
actual_msg: &[u8],
max_weight: Weight,
) -> Result<Weight, (MessageId, Weight)> {
let weight = match u32::decode(&mut &actual_msg[..]) {
Ok(w) => Weight::from_parts(w as u64, w as u64),
Err(_) => return Ok(Weight::zero()), // same as the real `UmpSink`
pub struct TestProcessMessage;
impl ProcessMessage for TestProcessMessage {
type Origin = AggregateMessageOrigin;
fn process_message(
message: &[u8],
origin: AggregateMessageOrigin,
meter: &mut WeightMeter,
) -> Result<bool, ProcessMessageError> {
let para = match origin {
AggregateMessageOrigin::Ump(UmpQueueId::Para(p)) => p,
};
if weight.any_gt(max_weight) {
let id = sp_io::hashing::blake2_256(actual_msg);
return Err((id, weight))
let required = match u32::decode(&mut &message[..]) {
Ok(w) => Weight::from_parts(w as u64, w as u64),
Err(_) => return Err(ProcessMessageError::Corrupt), // same as the real `ProcessMessage`
};
if !meter.check_accrue(required) {
return Err(ProcessMessageError::Overweight(required))
}
PROCESSED.with(|opt_hook| {
opt_hook.borrow_mut().push((actual_origin, actual_msg.to_owned()));
});
Ok(weight)
let mut processed = Processed::get();
processed.push((para, message.to_vec()));
Processed::set(processed);
Ok(true)
}
}
@@ -463,3 +507,50 @@ pub fn assert_last_event(generic_event: RuntimeEvent) {
let frame_system::EventRecord { event, .. } = &events[events.len() - 1];
assert_eq!(event, &system_event);
}
pub fn assert_last_events<E>(generic_events: E)
where
E: DoubleEndedIterator<Item = RuntimeEvent> + ExactSizeIterator,
{
for (i, (got, want)) in frame_system::Pallet::<Test>::events()
.into_iter()
.rev()
.map(|e| e.event)
.zip(generic_events.rev().map(<Test as frame_system::Config>::RuntimeEvent::from))
.rev()
.enumerate()
{
assert_eq!((i, got), (i, want));
}
}
pub(crate) fn register_parachain_with_balance(id: ParaId, balance: Balance) {
let validation_code: ValidationCode = vec![1].into();
assert_ok!(Paras::schedule_para_initialize(
id,
crate::paras::ParaGenesisArgs {
para_kind: ParaKind::Parachain,
genesis_head: vec![1].into(),
validation_code: validation_code.clone(),
},
));
assert_ok!(Paras::add_trusted_validation_code(RuntimeOrigin::root(), validation_code));
<Test as crate::hrmp::Config>::Currency::make_free_balance_be(
&id.into_account_truncating(),
balance,
);
}
pub(crate) fn register_parachain(id: ParaId) {
register_parachain_with_balance(id, 1000);
}
pub(crate) fn deregister_parachain(id: ParaId) {
assert_ok!(Paras::schedule_para_cleanup(id));
}
/// Calls `schedule_para_cleanup` in a new storage transactions, since it assumes rollback on error.
pub(crate) fn try_deregister_parachain(id: ParaId) -> crate::DispatchResult {
frame_support::storage::transactional::with_storage_layer(|| Paras::schedule_para_cleanup(id))
}
+26 -3
View File
@@ -107,7 +107,12 @@
//! ```
//!
use crate::{configuration, initializer::SessionChangeNotification, shared};
use crate::{
configuration,
inclusion::{QueueFootprinter, UmpQueueId},
initializer::SessionChangeNotification,
shared,
};
use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
use frame_support::{pallet_prelude::*, traits::EstimateNextSessionRotation};
use frame_system::pallet_prelude::*;
@@ -551,6 +556,12 @@ pub mod pallet {
type NextSessionRotation: EstimateNextSessionRotation<Self::BlockNumber>;
/// Retrieve how many UMP messages are enqueued for this para-chain.
///
/// This is used to judge whether or not a para-chain can offboard. Per default this should
/// be set to the `ParaInclusion` pallet.
type QueueFootprinter: QueueFootprinter<Origin = UmpQueueId>;
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
}
@@ -1653,12 +1664,13 @@ impl<T: Config> Pallet<T> {
///
/// - para is not a stable parachain or parathread (i.e. [`ParaLifecycle::is_stable`] is `false`)
/// - para has a pending upgrade.
/// - para has unprocessed messages in its UMP queue.
///
/// No-op if para is not registered at all.
pub(crate) fn schedule_para_cleanup(id: ParaId) -> DispatchResult {
// Disallow offboarding in case there is a PVF pre-checking in progress.
//
// This is not a fundamential limitation but rather simplification: it allows us to get
// This is not a fundamental limitation but rather simplification: it allows us to get
// away without introducing additional logic for pruning and, more importantly, enacting
// ongoing PVF pre-checking votes. It also removes some nasty edge cases.
//
@@ -1683,7 +1695,7 @@ impl<T: Config> Pallet<T> {
Some(ParaLifecycle::Parachain) => {
ParaLifecycles::<T>::insert(&id, ParaLifecycle::OffboardingParachain);
},
_ => return Err(Error::<T>::CannotOffboard)?,
_ => return Err(Error::<T>::CannotOffboard.into()),
}
let scheduled_session = Self::scheduled_session();
@@ -1693,6 +1705,10 @@ impl<T: Config> Pallet<T> {
}
});
if <T as Config>::QueueFootprinter::message_count(UmpQueueId::Para(id)) != 0 {
return Err(Error::<T>::CannotOffboard.into())
}
Ok(())
}
@@ -1986,6 +2002,13 @@ impl<T: Config> Pallet<T> {
}
}
/// Returns whether the given ID refers to a para that is offboarding.
///
/// An invalid or non-offboarding para ID will return `false`.
pub fn is_offboarding(id: ParaId) -> bool {
ParaLifecycles::<T>::get(&id).map_or(false, |state| state.is_offboarding())
}
/// Whether a para ID corresponds to any live parachain.
///
/// Includes parachains which will downgrade to a parathread in the future.
@@ -29,7 +29,7 @@ use crate::{
initializer,
metrics::METRICS,
scheduler::{self, CoreAssignment, FreedReason},
shared, ump, ParaId,
shared, ParaId,
};
use bitvec::prelude::BitVec;
use frame_support::{
@@ -531,10 +531,6 @@ impl<T: Config> Pallet<T> {
// Note which of the scheduled cores were actually occupied by a backed candidate.
<scheduler::Pallet<T>>::occupied(&occupied);
// Give some time slice to dispatch pending upward messages.
// this is max config.ump_service_total_weight
let _ump_weight = <ump::Pallet<T>>::process_pending_upward_messages();
METRICS.on_after_filter(total_consumed_weight.ref_time());
Ok(Some(total_consumed_weight).into())
@@ -331,14 +331,18 @@ where
<frame_system::Pallet<T>>::read_events_no_consensus()
.into_iter()
.filter_map(|record| extract_event(record.event))
.map(|event| match event {
RawEvent::<T>::CandidateBacked(c, h, core, group) =>
CandidateEvent::CandidateBacked(c, h, core, group),
RawEvent::<T>::CandidateIncluded(c, h, core, group) =>
CandidateEvent::CandidateIncluded(c, h, core, group),
RawEvent::<T>::CandidateTimedOut(c, h, core) =>
CandidateEvent::CandidateTimedOut(c, h, core),
RawEvent::<T>::__Ignore(_, _) => unreachable!("__Ignore cannot be used"),
.filter_map(|event| {
Some(match event {
RawEvent::<T>::CandidateBacked(c, h, core, group) =>
CandidateEvent::CandidateBacked(c, h, core, group),
RawEvent::<T>::CandidateIncluded(c, h, core, group) =>
CandidateEvent::CandidateIncluded(c, h, core, group),
RawEvent::<T>::CandidateTimedOut(c, h, core) =>
CandidateEvent::CandidateTimedOut(c, h, core),
// Not needed for candidate events.
RawEvent::<T>::UpwardMessagesReceived { .. } => return None,
RawEvent::<T>::__Ignore(_, _) => unreachable!("__Ignore cannot be used"),
})
})
.collect()
}
-765
View File
@@ -1,765 +0,0 @@
// 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::{
configuration::{self, HostConfiguration},
initializer,
};
use frame_support::{pallet_prelude::*, traits::EnsureOrigin};
use frame_system::pallet_prelude::*;
use polkadot_parachain::primitives::UpwardMessages;
use primitives::{Id as ParaId, UpwardMessage};
use sp_std::{collections::btree_map::BTreeMap, fmt, marker::PhantomData, mem, prelude::*};
use xcm::latest::Outcome;
pub use pallet::*;
/// Maximum value that `config.max_upward_message_size` can be set to
///
/// This is used for benchmarking sanely bounding relevant storage items. It is expected from the `configurations`
/// pallet to check these values before setting.
pub const MAX_UPWARD_MESSAGE_SIZE_BOUND: u32 = 50 * 1024;
/// Maximum amount of overweight messages that can exist in the queue at any given time.
pub const MAX_OVERWEIGHT_MESSAGES: u32 = 1000;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod migration;
#[cfg(test)]
pub(crate) mod tests;
/// All upward messages coming from parachains will be funneled into an implementation of this trait.
///
/// The message is opaque from the perspective of UMP. The message size can range from 0 to
/// `config.max_upward_message_size`.
///
/// It's up to the implementation of this trait to decide what to do with a message as long as it
/// returns the amount of weight consumed in the process of handling. Ignoring a message is a valid
/// strategy.
///
/// There are no guarantees on how much time it takes for the message sent by a candidate to end up
/// in the sink after the candidate was enacted. That typically depends on the UMP traffic, the sizes
/// of upward messages and the configuration of UMP.
///
/// It is possible that by the time the message is sank the origin parachain was offboarded. It is
/// up to the implementer to check that if it cares.
pub trait UmpSink {
/// Process an incoming upward message and return the amount of weight it consumed, or `None` if
/// it did not begin processing a message since it would otherwise exceed `max_weight`.
///
/// See the trait docs for more details.
fn process_upward_message(
origin: ParaId,
msg: &[u8],
max_weight: Weight,
) -> Result<Weight, (MessageId, Weight)>;
}
/// An implementation of a sink that just swallows the message without consuming any weight. Returns
/// `Some(0)` indicating that no messages existed for it to process.
impl UmpSink for () {
fn process_upward_message(
_: ParaId,
_: &[u8],
_: Weight,
) -> Result<Weight, (MessageId, Weight)> {
Ok(Weight::zero())
}
}
/// Simple type used to identify messages for the purpose of reporting events. Secure if and only
/// if the message content is unique.
pub type MessageId = [u8; 32];
/// Index used to identify overweight messages.
pub type OverweightIndex = u64;
/// A specific implementation of a `UmpSink` where messages are in the XCM format
/// and will be forwarded to the XCM Executor.
pub struct XcmSink<XcmExecutor, Config>(PhantomData<(XcmExecutor, Config)>);
/// Returns a [`MessageId`] for the given upward message payload.
fn upward_message_id(data: &[u8]) -> MessageId {
sp_io::hashing::blake2_256(data)
}
impl<XcmExecutor: xcm::latest::ExecuteXcm<C::RuntimeCall>, C: Config> UmpSink
for XcmSink<XcmExecutor, C>
{
fn process_upward_message(
origin: ParaId,
mut data: &[u8],
max_weight: Weight,
) -> Result<Weight, (MessageId, Weight)> {
use parity_scale_codec::DecodeLimit;
use xcm::{
latest::{Error as XcmError, Junction, Xcm},
VersionedXcm,
};
let id = upward_message_id(data);
let maybe_msg_and_weight = VersionedXcm::<C::RuntimeCall>::decode_all_with_depth_limit(
xcm::MAX_XCM_DECODE_DEPTH,
&mut data,
)
.map(|xcm| {
(
Xcm::<C::RuntimeCall>::try_from(xcm),
// NOTE: We are overestimating slightly here.
// The benchmark is timing this whole function with different message sizes and a NOOP extrinsic to
// measure the size-dependent weight. But as we use the weight funtion **in** the benchmarked funtion we
// are taking call and control-flow overhead into account twice.
<C as Config>::WeightInfo::process_upward_message(data.len() as u32),
)
});
match maybe_msg_and_weight {
Err(_) => {
Pallet::<C>::deposit_event(Event::InvalidFormat(id));
Ok(Weight::zero())
},
Ok((Err(()), weight_used)) => {
Pallet::<C>::deposit_event(Event::UnsupportedVersion(id));
Ok(weight_used)
},
Ok((Ok(xcm_message), weight_used)) => {
let xcm_junction = Junction::Parachain(origin.into());
let outcome = XcmExecutor::execute_xcm(xcm_junction, xcm_message, id, max_weight);
match outcome {
Outcome::Error(XcmError::WeightLimitReached(required)) => Err((id, required)),
outcome => {
let outcome_weight = outcome.weight_used();
Pallet::<C>::deposit_event(Event::ExecutedUpward(id, outcome));
Ok(weight_used.saturating_add(outcome_weight))
},
}
},
}
}
}
/// An error returned by [`check_upward_messages`] that indicates a violation of one of acceptance
/// criteria rules.
pub enum AcceptanceCheckErr {
MoreMessagesThanPermitted { sent: u32, permitted: u32 },
MessageSize { idx: u32, msg_size: u32, max_size: u32 },
CapacityExceeded { count: u32, limit: u32 },
TotalSizeExceeded { total_size: u32, limit: u32 },
}
impl fmt::Debug for AcceptanceCheckErr {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
AcceptanceCheckErr::MoreMessagesThanPermitted { sent, permitted } => write!(
fmt,
"more upward messages than permitted by config ({} > {})",
sent, permitted,
),
AcceptanceCheckErr::MessageSize { idx, msg_size, max_size } => write!(
fmt,
"upward message idx {} larger than permitted by config ({} > {})",
idx, msg_size, max_size,
),
AcceptanceCheckErr::CapacityExceeded { count, limit } => write!(
fmt,
"the ump queue would have more items than permitted by config ({} > {})",
count, limit,
),
AcceptanceCheckErr::TotalSizeExceeded { total_size, limit } => write!(
fmt,
"the ump queue would have grown past the max size permitted by config ({} > {})",
total_size, limit,
),
}
}
}
/// Weight information of this pallet.
pub trait WeightInfo {
fn service_overweight() -> Weight;
fn process_upward_message(s: u32) -> Weight;
fn clean_ump_after_outgoing() -> Weight;
}
/// fallback implementation
pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
fn service_overweight() -> Weight {
Weight::MAX
}
fn process_upward_message(_msg_size: u32) -> Weight {
Weight::MAX
}
fn clean_ump_after_outgoing() -> Weight {
Weight::MAX
}
}
#[frame_support::pallet]
pub mod pallet {
use super::*;
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::without_storage_info]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config + configuration::Config {
/// The aggregate event.
type RuntimeEvent: From<Event> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// A place where all received upward messages are funneled.
type UmpSink: UmpSink;
/// The factor by which the weight limit it multiplied for the first UMP message to execute with.
///
/// An amount less than 100 keeps more available weight in the queue for messages after the first, and potentially
/// stalls the queue in doing so. More than 100 will provide additional weight for the first message only.
///
/// Generally you'll want this to be a bit more - 150 or 200 would be good values.
type FirstMessageFactorPercent: Get<u64>;
/// Origin which is allowed to execute overweight messages.
type ExecuteOverweightOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event {
/// Upward message is invalid XCM.
/// \[ id \]
InvalidFormat(MessageId),
/// Upward message is unsupported version of XCM.
/// \[ id \]
UnsupportedVersion(MessageId),
/// Upward message executed with the given outcome.
/// \[ id, outcome \]
ExecutedUpward(MessageId, Outcome),
/// The weight limit for handling upward messages was reached.
/// \[ id, remaining, required \]
WeightExhausted(MessageId, Weight, Weight),
/// Some upward messages have been received and will be processed.
/// \[ para, count, size \]
UpwardMessagesReceived(ParaId, u32, u32),
/// The weight budget was exceeded for an individual upward message.
///
/// This message can be later dispatched manually using `service_overweight` dispatchable
/// using the assigned `overweight_index`.
///
/// \[ para, id, overweight_index, required \]
OverweightEnqueued(ParaId, MessageId, OverweightIndex, Weight),
/// Upward message from the overweight queue was executed with the given actual weight
/// used.
///
/// \[ overweight_index, used \]
OverweightServiced(OverweightIndex, Weight),
}
#[pallet::error]
pub enum Error<T> {
/// The message index given is unknown.
UnknownMessageIndex,
/// The amount of weight given is possibly not enough for executing the message.
WeightOverLimit,
}
/// The messages waiting to be handled by the relay-chain originating from a certain parachain.
///
/// Note that some upward messages might have been already processed by the inclusion logic. E.g.
/// channel management messages.
///
/// The messages are processed in FIFO order.
#[pallet::storage]
pub type RelayDispatchQueues<T: Config> =
StorageMap<_, Twox64Concat, ParaId, Vec<UpwardMessage>, ValueQuery>;
/// Size of the dispatch queues. Caches sizes of the queues in `RelayDispatchQueue`.
///
/// First item in the tuple is the count of messages and second
/// is the total length (in bytes) of the message payloads.
///
/// Note that this is an auxiliary mapping: it's possible to tell the byte size and the number of
/// messages only looking at `RelayDispatchQueues`. This mapping is separate to avoid the cost of
/// loading the whole message queue if only the total size and count are required.
///
/// Invariant:
/// - The set of keys should exactly match the set of keys of `RelayDispatchQueues`.
// NOTE that this field is used by parachains via merkle storage proofs, therefore changing
// the format will require migration of parachains.
#[pallet::storage]
pub type RelayDispatchQueueSize<T: Config> =
StorageMap<_, Twox64Concat, ParaId, (u32, u32), ValueQuery>;
/// The ordered list of `ParaId`s that have a `RelayDispatchQueue` entry.
///
/// Invariant:
/// - The set of items from this vector should be exactly the set of the keys in
/// `RelayDispatchQueues` and `RelayDispatchQueueSize`.
#[pallet::storage]
pub type NeedsDispatch<T: Config> = StorageValue<_, Vec<ParaId>, ValueQuery>;
/// This is the para that gets will get dispatched first during the next upward dispatchable queue
/// execution round.
///
/// Invariant:
/// - If `Some(para)`, then `para` must be present in `NeedsDispatch`.
#[pallet::storage]
pub type NextDispatchRoundStartWith<T: Config> = StorageValue<_, ParaId>;
/// The messages that exceeded max individual message weight budget.
///
/// These messages stay there until manually dispatched.
#[pallet::storage]
pub type Overweight<T: Config> =
CountedStorageMap<_, Twox64Concat, OverweightIndex, (ParaId, Vec<u8>), OptionQuery>;
/// The number of overweight messages ever recorded in `Overweight` (and thus the lowest free
/// index).
#[pallet::storage]
pub type OverweightCount<T: Config> = StorageValue<_, OverweightIndex, ValueQuery>;
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Service a single overweight upward message.
///
/// - `origin`: Must pass `ExecuteOverweightOrigin`.
/// - `index`: The index of the overweight message to service.
/// - `weight_limit`: The amount of weight that message execution may take.
///
/// Errors:
/// - `UnknownMessageIndex`: Message of `index` is unknown.
/// - `WeightOverLimit`: Message execution may use greater than `weight_limit`.
///
/// Events:
/// - `OverweightServiced`: On success.
#[pallet::call_index(0)]
#[pallet::weight(weight_limit.saturating_add(<T as Config>::WeightInfo::service_overweight()))]
pub fn service_overweight(
origin: OriginFor<T>,
index: OverweightIndex,
weight_limit: Weight,
) -> DispatchResultWithPostInfo {
T::ExecuteOverweightOrigin::ensure_origin(origin)?;
let (sender, data) =
Overweight::<T>::get(index).ok_or(Error::<T>::UnknownMessageIndex)?;
let used = T::UmpSink::process_upward_message(sender, &data[..], weight_limit)
.map_err(|_| Error::<T>::WeightOverLimit)?;
Overweight::<T>::remove(index);
Self::deposit_event(Event::OverweightServiced(index, used));
Ok(Some(used.saturating_add(<T as Config>::WeightInfo::service_overweight())).into())
}
}
}
/// Routines related to the upward message passing.
impl<T: Config> Pallet<T> {
/// Block initialization logic, called by initializer.
pub(crate) fn initializer_initialize(_now: T::BlockNumber) -> Weight {
Weight::zero()
}
/// Block finalization logic, called by initializer.
pub(crate) fn initializer_finalize() {}
/// Called by the initializer to note that a new session has started.
pub(crate) fn initializer_on_new_session(
_notification: &initializer::SessionChangeNotification<T::BlockNumber>,
outgoing_paras: &[ParaId],
) -> Weight {
Self::perform_outgoing_para_cleanup(outgoing_paras)
}
/// Iterate over all paras that were noted for offboarding and remove all the data
/// associated with them.
fn perform_outgoing_para_cleanup(outgoing: &[ParaId]) -> Weight {
let mut weight: Weight = Weight::zero();
for outgoing_para in outgoing {
weight = weight.saturating_add(Self::clean_ump_after_outgoing(outgoing_para));
}
weight
}
/// Remove all relevant storage items for an outgoing parachain.
pub(crate) fn clean_ump_after_outgoing(outgoing_para: &ParaId) -> Weight {
RelayDispatchQueueSize::<T>::remove(outgoing_para);
RelayDispatchQueues::<T>::remove(outgoing_para);
// Remove the outgoing para from the `NeedsDispatch` list and from
// `NextDispatchRoundStartWith`.
//
// That's needed for maintaining invariant that `NextDispatchRoundStartWith` points to an
// existing item in `NeedsDispatch`.
NeedsDispatch::<T>::mutate(|v| {
if let Ok(i) = v.binary_search(outgoing_para) {
v.remove(i);
}
});
NextDispatchRoundStartWith::<T>::mutate(|v| *v = v.filter(|p| p == outgoing_para));
<T as Config>::WeightInfo::clean_ump_after_outgoing()
}
/// Check that all the upward messages sent by a candidate pass the acceptance criteria. Returns
/// false, if any of the messages doesn't pass.
pub(crate) fn check_upward_messages(
config: &HostConfiguration<T::BlockNumber>,
para: ParaId,
upward_messages: &[UpwardMessage],
) -> Result<(), AcceptanceCheckErr> {
if upward_messages.len() as u32 > config.max_upward_message_num_per_candidate {
return Err(AcceptanceCheckErr::MoreMessagesThanPermitted {
sent: upward_messages.len() as u32,
permitted: config.max_upward_message_num_per_candidate,
})
}
let (mut para_queue_count, mut para_queue_size) = RelayDispatchQueueSize::<T>::get(&para);
for (idx, msg) in upward_messages.into_iter().enumerate() {
let msg_size = msg.len() as u32;
if msg_size > config.max_upward_message_size {
return Err(AcceptanceCheckErr::MessageSize {
idx: idx as u32,
msg_size,
max_size: config.max_upward_message_size,
})
}
para_queue_count += 1;
para_queue_size += msg_size;
}
// make sure that the queue is not overfilled.
// we do it here only once since returning false invalidates the whole relay-chain block.
if para_queue_count > config.max_upward_queue_count {
return Err(AcceptanceCheckErr::CapacityExceeded {
count: para_queue_count,
limit: config.max_upward_queue_count,
})
}
if para_queue_size > config.max_upward_queue_size {
return Err(AcceptanceCheckErr::TotalSizeExceeded {
total_size: para_queue_size,
limit: config.max_upward_queue_size,
})
}
Ok(())
}
/// Enqueues `upward_messages` from a `para`'s accepted candidate block.
pub(crate) fn receive_upward_messages(para: ParaId, upward_messages: UpwardMessages) -> Weight {
let mut weight = Weight::zero();
if !upward_messages.is_empty() {
let (extra_count, extra_size) = upward_messages
.iter()
.fold((0, 0), |(cnt, size), d| (cnt + 1, size + d.len() as u32));
RelayDispatchQueues::<T>::mutate(&para, |v| v.extend(upward_messages.into_iter()));
RelayDispatchQueueSize::<T>::mutate(&para, |(ref mut cnt, ref mut size)| {
*cnt += extra_count;
*size += extra_size;
});
NeedsDispatch::<T>::mutate(|v| {
if let Err(i) = v.binary_search(&para) {
v.insert(i, para);
}
});
// NOTE: The actual computation is not accounted for. It should be benchmarked.
weight += T::DbWeight::get().reads_writes(3, 3);
Self::deposit_event(Event::UpwardMessagesReceived(para, extra_count, extra_size));
}
weight
}
/// Devote some time into dispatching pending upward messages.
pub(crate) fn process_pending_upward_messages() -> Weight {
const MAX_MESSAGES_PER_BLOCK: u8 = 10;
let mut messages_processed = 0;
let mut weight_used = Weight::zero();
let config = <configuration::Pallet<T>>::config();
let mut cursor = NeedsDispatchCursor::new::<T>();
let mut queue_cache = QueueCache::new();
while let Some(dispatchee) = cursor.peek() {
if weight_used.any_gte(config.ump_service_total_weight) ||
messages_processed >= MAX_MESSAGES_PER_BLOCK
{
// Temporarily allow for processing of a max of 10 messages per block, until we
// properly account for proof size weights.
//
// Then check whether we've reached or overshoot the
// preferred weight for the dispatching stage.
//
// if so - bail.
break
}
let max_weight = if weight_used == Weight::zero() {
// we increase the amount of weight that we're allowed to use on the first message to try to prevent
// the possibility of blockage of the queue.
config
.ump_service_total_weight
.saturating_mul(T::FirstMessageFactorPercent::get()) /
100
} else {
config.ump_service_total_weight - weight_used
};
// attempt to process the next message from the queue of the dispatchee; if not beyond
// our remaining weight limit, then consume it.
let maybe_next = queue_cache.peek_front::<T>(dispatchee);
if let Some(upward_message) = maybe_next {
messages_processed += 1;
match T::UmpSink::process_upward_message(dispatchee, upward_message, max_weight) {
Ok(used) => {
weight_used += used;
let _ = queue_cache.consume_front::<T>(dispatchee);
},
Err((id, required)) => {
let is_under_limit = Overweight::<T>::count() < MAX_OVERWEIGHT_MESSAGES;
weight_used.saturating_accrue(T::DbWeight::get().reads(1));
if required.any_gt(config.ump_max_individual_weight) && is_under_limit {
// overweight - add to overweight queue and continue with message
// execution consuming the message.
let upward_message = queue_cache.consume_front::<T>(dispatchee).expect(
"`consume_front` should return the same msg as `peek_front`;\
if we get into this branch then `peek_front` returned `Some`;\
thus `upward_message` cannot be `None`; qed",
);
let index = Self::stash_overweight(dispatchee, upward_message);
Self::deposit_event(Event::OverweightEnqueued(
dispatchee, id, index, required,
));
} else {
// we process messages in order and don't drop them if we run out of weight,
// so need to break here without calling `consume_front`.
Self::deposit_event(Event::WeightExhausted(id, max_weight, required));
break
}
},
}
}
if queue_cache.is_empty::<T>(dispatchee) {
// the queue is empty now - this para doesn't need attention anymore.
cursor.remove();
} else {
cursor.advance();
}
}
cursor.flush::<T>();
queue_cache.flush::<T>();
weight_used
}
/// Puts a given upward message into the list of overweight messages allowing it to be executed
/// later.
fn stash_overweight(sender: ParaId, upward_message: Vec<u8>) -> OverweightIndex {
let index = OverweightCount::<T>::mutate(|count| {
let index = *count;
*count += 1;
index
});
Overweight::<T>::insert(index, (sender, upward_message));
index
}
}
/// To avoid constant fetching, deserializing and serialization the queues are cached.
///
/// After an item dequeued from a queue for the first time, the queue is stored in this struct
/// rather than being serialized and persisted.
///
/// This implementation works best when:
///
/// 1. when the queues are shallow
/// 2. the dispatcher makes more than one cycle
///
/// if the queues are deep and there are many we would load and keep the queues for a long time,
/// thus increasing the peak memory consumption of the wasm runtime. Under such conditions persisting
/// queues might play better since it's unlikely that they are going to be requested once more.
///
/// On the other hand, the situation when deep queues exist and it takes more than one dispatcher
/// cycle to traverse the queues is already sub-optimal and better be avoided.
///
/// This struct is not supposed to be dropped but rather to be consumed by [`flush`].
struct QueueCache(BTreeMap<ParaId, QueueCacheEntry>);
struct QueueCacheEntry {
queue: Vec<UpwardMessage>,
total_size: u32,
consumed_count: usize,
consumed_size: usize,
}
impl QueueCache {
fn new() -> Self {
Self(BTreeMap::new())
}
fn ensure_cached<T: Config>(&mut self, para: ParaId) -> &mut QueueCacheEntry {
self.0.entry(para).or_insert_with(|| {
let queue = RelayDispatchQueues::<T>::get(&para);
let (_, total_size) = RelayDispatchQueueSize::<T>::get(&para);
QueueCacheEntry { queue, total_size, consumed_count: 0, consumed_size: 0 }
})
}
/// Returns the message at the front of `para`'s queue, or `None` if the queue is empty.
///
/// Does not mutate the queue.
fn peek_front<T: Config>(&mut self, para: ParaId) -> Option<&UpwardMessage> {
let entry = self.ensure_cached::<T>(para);
entry.queue.get(entry.consumed_count)
}
/// Attempts to remove one message from the front of `para`'s queue. If the queue is empty, then
/// does nothing.
fn consume_front<T: Config>(&mut self, para: ParaId) -> Option<UpwardMessage> {
let cache_entry = self.ensure_cached::<T>(para);
match cache_entry.queue.get_mut(cache_entry.consumed_count) {
Some(msg) => {
cache_entry.consumed_count += 1;
cache_entry.consumed_size += msg.len();
Some(mem::take(msg))
},
None => None,
}
}
/// Returns if the queue for the given para is empty.
///
/// That is, if this returns `true` then the next call to [`peek_front`] will return `None`.
///
/// Does not mutate the queue.
fn is_empty<T: Config>(&mut self, para: ParaId) -> bool {
let cache_entry = self.ensure_cached::<T>(para);
cache_entry.consumed_count >= cache_entry.queue.len()
}
/// Flushes the updated queues into the storage.
fn flush<T: Config>(self) {
// NOTE we use an explicit method here instead of Drop impl because it has unwanted semantics
// within runtime. It is dangerous to use because of double-panics and flushing on a panic
// is not necessary as well.
for (para, entry) in self.0 {
if entry.consumed_count >= entry.queue.len() {
// remove the entries altogether.
RelayDispatchQueues::<T>::remove(&para);
RelayDispatchQueueSize::<T>::remove(&para);
} else if entry.consumed_count > 0 {
RelayDispatchQueues::<T>::insert(&para, &entry.queue[entry.consumed_count..]);
let count = (entry.queue.len() - entry.consumed_count) as u32;
let size = entry.total_size.saturating_sub(entry.consumed_size as u32);
RelayDispatchQueueSize::<T>::insert(&para, (count, size));
}
}
}
}
/// A cursor that iterates over all entries in `NeedsDispatch`.
///
/// This cursor will start with the para indicated by `NextDispatchRoundStartWith` storage entry.
/// This cursor is cyclic meaning that after reaching the end it will jump to the beginning. Unlike
/// an iterator, this cursor allows removing items during the iteration.
///
/// Each iteration cycle *must be* concluded with a call to either `advance` or `remove`.
///
/// This struct is not supposed to be dropped but rather to be consumed by [`flush`].
#[derive(Debug)]
struct NeedsDispatchCursor {
needs_dispatch: Vec<ParaId>,
index: usize,
}
impl NeedsDispatchCursor {
fn new<T: Config>() -> Self {
let needs_dispatch: Vec<ParaId> = NeedsDispatch::<T>::get();
let start_with = NextDispatchRoundStartWith::<T>::get();
let initial_index = match start_with {
Some(para) => match needs_dispatch.binary_search(&para) {
Ok(found_index) => found_index,
Err(_supposed_index) => {
// well that's weird because we maintain an invariant that
// `NextDispatchRoundStartWith` must point into one of the items in
// `NeedsDispatch`.
//
// let's select 0 as the starting index as a safe bet.
debug_assert!(false);
0
},
},
None => 0,
};
Self { needs_dispatch, index: initial_index }
}
/// Returns the item the cursor points to.
fn peek(&self) -> Option<ParaId> {
self.needs_dispatch.get(self.index).cloned()
}
/// Moves the cursor to the next item.
fn advance(&mut self) {
if self.needs_dispatch.is_empty() {
return
}
self.index = (self.index + 1) % self.needs_dispatch.len();
}
/// Removes the item under the cursor.
fn remove(&mut self) {
if self.needs_dispatch.is_empty() {
return
}
let _ = self.needs_dispatch.remove(self.index);
// we might've removed the last element and that doesn't necessarily mean that `needs_dispatch`
// became empty. Reposition the cursor in this case to the beginning.
if self.needs_dispatch.get(self.index).is_none() {
self.index = 0;
}
}
/// Flushes the dispatcher state into the persistent storage.
fn flush<T: Config>(self) {
let next_one = self.peek();
NextDispatchRoundStartWith::<T>::set(next_one);
NeedsDispatch::<T>::put(self.needs_dispatch);
}
}
@@ -1,137 +0,0 @@
// 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 super::{Pallet as Ump, *};
use frame_system::RawOrigin;
use xcm::prelude::*;
fn assert_last_event_type<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
let events = frame_system::Pallet::<T>::events();
let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into();
// compare to the last event record
let frame_system::EventRecord { event, .. } = &events[events.len() - 1];
assert_eq!(sp_std::mem::discriminant(event), sp_std::mem::discriminant(&system_event));
}
fn queue_upward_msg<T: Config>(
host_conf: &HostConfiguration<T::BlockNumber>,
para: ParaId,
msg: UpwardMessage,
) {
let len = msg.len() as u32;
let msgs: UpwardMessages = vec![msg].try_into().unwrap();
Ump::<T>::check_upward_messages(host_conf, para, &msgs).unwrap();
let _ = Ump::<T>::receive_upward_messages(para, msgs);
assert_last_event_type::<T>(Event::UpwardMessagesReceived(para, 1, len).into());
}
// Create a message with at least `size` bytes encoded length
fn create_message_min_size<T: Config>(size: u32) -> Vec<u8> {
// Create a message with an empty remark call to determine the encoding overhead
let msg_size_empty_transact = VersionedXcm::<T>::from(Xcm::<T>(vec![Transact {
origin_kind: OriginKind::SovereignAccount,
require_weight_at_most: Weight::MAX,
call: frame_system::Call::<T>::remark_with_event { remark: vec![] }.encode().into(),
}]))
.encode()
.len();
// Create a message with a remark call of just the size required to make the whole encoded message the requested size
let size = size.saturating_sub(msg_size_empty_transact as u32) as usize;
let mut remark = Vec::new();
remark.resize(size, 0u8);
let msg = VersionedXcm::<T>::from(Xcm::<T>(vec![Transact {
origin_kind: OriginKind::SovereignAccount,
require_weight_at_most: Weight::MAX,
call: frame_system::Call::<T>::remark_with_event { remark }.encode().into(),
}]))
.encode();
assert!(msg.len() >= size);
msg
}
fn create_message_overweight<T: Config>() -> Vec<u8> {
// We use a `set_code` Call because it
let call = frame_system::Call::<T>::set_code { code: vec![] };
VersionedXcm::<T>::from(Xcm::<T>(vec![Transact {
origin_kind: OriginKind::Superuser,
require_weight_at_most: Weight::MAX / 2,
call: call.encode().into(),
}]))
.encode()
}
frame_benchmarking::benchmarks! {
// NOTE: We are overestimating slightly here.
// The benchmark is timing this whole function with different message sizes and a NOOP extrinsic to
// measure the size-dependent weight. But as we use the weight function **in** the benchmarked function we
// are taking call and control-flow overhead into account twice.
process_upward_message {
let s in 0..MAX_UPWARD_MESSAGE_SIZE_BOUND;
let para = ParaId::from(1978);
let data = create_message_min_size::<T>(s);
}: {
assert!(T::UmpSink::process_upward_message(para, &data[..], Weight::MAX).is_ok());
}
clean_ump_after_outgoing {
// max number of queued messages.
let count = configuration::ActiveConfig::<T>::get().max_upward_queue_count;
let host_conf = configuration::ActiveConfig::<T>::get();
let msg = create_message_min_size::<T>(0);
// Start with the block number 1. This is needed because should an event be
// emitted during the genesis block they will be implicitly wiped.
frame_system::Pallet::<T>::set_block_number(1u32.into());
// fill the queue, each message has it's own para-id.
for id in 0..count {
queue_upward_msg::<T>(&host_conf, ParaId::from(id), msg.clone());
}
}: {
Ump::<T>::clean_ump_after_outgoing(&ParaId::from(0));
}
service_overweight {
let host_conf = configuration::ActiveConfig::<T>::get();
let para = ParaId::from(1978);
// The message's weight does not really matter here, as we add service_overweight's
// max_weight parameter to the extrinsic's weight in the weight calculation.
// The size of the message influences decoding time, so we create a min-sized message here
// and take the decoding weight into account by adding it to the extrinsic execution weight
// in the process_upward_message function.
let msg = create_message_overweight::<T>();
// This just makes sure that 0 is not a valid index and we can use it later on.
let _ = Ump::<T>::service_overweight(RawOrigin::Root.into(), 0, Weight::from_parts(1000, 1000));
// Start with the block number 1. This is needed because should an event be
// emitted during the genesis block they will be implicitly wiped.
frame_system::Pallet::<T>::set_block_number(1u32.into());
queue_upward_msg::<T>(&host_conf, para, msg.clone());
Ump::<T>::process_pending_upward_messages();
assert_last_event_type::<T>(
Event::OverweightEnqueued(para, upward_message_id(&msg), 0, Weight::zero()).into()
);
}: _(RawOrigin::Root, 0, Weight::MAX)
verify {
assert_last_event_type::<T>(Event::OverweightServiced(0, Weight::zero()).into());
}
}
frame_benchmarking::impl_benchmark_test_suite!(
Ump,
crate::mock::new_test_ext(crate::ump::tests::GenesisConfigBuilder::default().build()),
crate::mock::Test
);
@@ -1,47 +0,0 @@
// 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::ump::{Config, Overweight, Pallet};
use frame_support::{
pallet_prelude::*,
traits::{OnRuntimeUpgrade, StorageVersion},
weights::Weight,
};
pub mod v1 {
use super::*;
pub struct MigrateToV1<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for MigrateToV1<T> {
fn on_runtime_upgrade() -> Weight {
if StorageVersion::get::<Pallet<T>>() == 0 {
let mut weight = T::DbWeight::get().reads(1);
let overweight_messages = Overweight::<T>::initialize_counter() as u64;
log::info!("Initialized Overweight to {}", overweight_messages);
weight.saturating_accrue(T::DbWeight::get().reads_writes(overweight_messages, 1));
StorageVersion::new(1).put::<Pallet<T>>();
weight.saturating_add(T::DbWeight::get().writes(1))
} else {
log::warn!("skipping v1, should be removed");
T::DbWeight::get().reads(1)
}
}
}
}
@@ -1,362 +0,0 @@
// 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 super::*;
use crate::mock::{
assert_last_event, new_test_ext, take_processed, Configuration, MockGenesisConfig,
RuntimeOrigin, System, Test, Ump,
};
use frame_support::{assert_noop, assert_ok, weights::Weight};
use std::collections::HashSet;
pub(super) struct GenesisConfigBuilder {
max_upward_message_size: u32,
max_upward_message_num_per_candidate: u32,
max_upward_queue_count: u32,
max_upward_queue_size: u32,
ump_service_total_weight: Weight,
ump_max_individual_weight: Weight,
}
impl Default for GenesisConfigBuilder {
fn default() -> Self {
Self {
max_upward_message_size: 32,
max_upward_message_num_per_candidate: 2,
max_upward_queue_count: 4,
max_upward_queue_size: 64,
ump_service_total_weight: Weight::from_parts(1000, 1000),
ump_max_individual_weight: Weight::from_parts(100, 100),
}
}
}
impl GenesisConfigBuilder {
pub(super) fn build(self) -> crate::mock::MockGenesisConfig {
let mut genesis = default_genesis_config();
let config = &mut genesis.configuration.config;
config.max_upward_message_size = self.max_upward_message_size;
config.max_upward_message_num_per_candidate = self.max_upward_message_num_per_candidate;
config.max_upward_queue_count = self.max_upward_queue_count;
config.max_upward_queue_size = self.max_upward_queue_size;
config.ump_service_total_weight = self.ump_service_total_weight;
config.ump_max_individual_weight = self.ump_max_individual_weight;
genesis
}
}
fn default_genesis_config() -> MockGenesisConfig {
MockGenesisConfig {
configuration: crate::configuration::GenesisConfig {
config: crate::configuration::HostConfiguration {
max_downward_message_size: 1024,
..Default::default()
},
},
..Default::default()
}
}
fn queue_upward_msg(para: ParaId, msg: UpwardMessage) {
let msgs: UpwardMessages = vec![msg].try_into().unwrap();
assert!(Ump::check_upward_messages(&Configuration::config(), para, &msgs).is_ok());
let _ = Ump::receive_upward_messages(para, msgs);
}
fn assert_storage_consistency_exhaustive() {
// check that empty queues don't clutter the storage.
for (_para, queue) in RelayDispatchQueues::<Test>::iter() {
assert!(!queue.is_empty());
}
// actually count the counts and sizes in queues and compare them to the bookkept version.
for (para, queue) in RelayDispatchQueues::<Test>::iter() {
let (expected_count, expected_size) = RelayDispatchQueueSize::<Test>::get(para);
let (actual_count, actual_size) = queue
.into_iter()
.fold((0, 0), |(acc_count, acc_size), x| (acc_count + 1, acc_size + x.len() as u32));
assert_eq!(expected_count, actual_count);
assert_eq!(expected_size, actual_size);
}
// since we wipe the empty queues the sets of paras in queue contents, queue sizes and
// need dispatch set should all be equal.
let queue_contents_set =
RelayDispatchQueues::<Test>::iter().map(|(k, _)| k).collect::<HashSet<ParaId>>();
let queue_sizes_set = RelayDispatchQueueSize::<Test>::iter()
.map(|(k, _)| k)
.collect::<HashSet<ParaId>>();
let needs_dispatch_set = NeedsDispatch::<Test>::get().into_iter().collect::<HashSet<ParaId>>();
assert_eq!(queue_contents_set, queue_sizes_set);
assert_eq!(queue_contents_set, needs_dispatch_set);
// `NextDispatchRoundStartWith` should point into a para that is tracked.
if let Some(para) = NextDispatchRoundStartWith::<Test>::get() {
assert!(queue_contents_set.contains(&para));
}
// `NeedsDispatch` is always sorted.
assert!(NeedsDispatch::<Test>::get().windows(2).all(|xs| xs[0] <= xs[1]));
}
#[test]
fn dispatch_empty() {
new_test_ext(default_genesis_config()).execute_with(|| {
assert_storage_consistency_exhaustive();
// make sure that the case with empty queues is handled properly
Ump::process_pending_upward_messages();
assert_storage_consistency_exhaustive();
});
}
#[test]
fn dispatch_single_message() {
let a = ParaId::from(228);
let msg = 1000u32.encode();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
queue_upward_msg(a, msg.clone());
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(a, msg)]);
assert_storage_consistency_exhaustive();
});
}
#[test]
fn dispatch_resume_after_exceeding_dispatch_stage_weight() {
let a = ParaId::from(128);
let c = ParaId::from(228);
let q = ParaId::from(911);
let a_msg_1 = (200u32, "a_msg_1").encode();
let a_msg_2 = (100u32, "a_msg_2").encode();
let c_msg_1 = (300u32, "c_msg_1").encode();
let c_msg_2 = (100u32, "c_msg_2").encode();
let q_msg = (500u32, "q_msg").encode();
new_test_ext(
GenesisConfigBuilder {
ump_service_total_weight: Weight::from_parts(500, 500),
..Default::default()
}
.build(),
)
.execute_with(|| {
queue_upward_msg(q, q_msg.clone());
queue_upward_msg(c, c_msg_1.clone());
queue_upward_msg(a, a_msg_1.clone());
queue_upward_msg(a, a_msg_2.clone());
assert_storage_consistency_exhaustive();
// we expect only two first messages to fit in the first iteration.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(a, a_msg_1), (c, c_msg_1)]);
assert_storage_consistency_exhaustive();
queue_upward_msg(c, c_msg_2.clone());
assert_storage_consistency_exhaustive();
// second iteration should process the second message.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(q, q_msg)]);
assert_storage_consistency_exhaustive();
// 3rd iteration.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(a, a_msg_2), (c, c_msg_2)]);
assert_storage_consistency_exhaustive();
// finally, make sure that the queue is empty.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![]);
assert_storage_consistency_exhaustive();
});
}
#[test]
fn dispatch_keeps_message_after_weight_exhausted() {
let a = ParaId::from(128);
let a_msg_1 = (300u32, "a_msg_1").encode();
let a_msg_2 = (300u32, "a_msg_2").encode();
new_test_ext(
GenesisConfigBuilder {
ump_service_total_weight: Weight::from_parts(500, 500),
ump_max_individual_weight: Weight::from_parts(300, 300),
..Default::default()
}
.build(),
)
.execute_with(|| {
queue_upward_msg(a, a_msg_1.clone());
queue_upward_msg(a, a_msg_2.clone());
assert_storage_consistency_exhaustive();
// we expect only one message to fit in the first iteration.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(a, a_msg_1)]);
assert_storage_consistency_exhaustive();
// second iteration should process the remaining message.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(a, a_msg_2)]);
assert_storage_consistency_exhaustive();
// finally, make sure that the queue is empty.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![]);
assert_storage_consistency_exhaustive();
});
}
#[test]
fn dispatch_correctly_handle_remove_of_latest() {
let a = ParaId::from(1991);
let b = ParaId::from(1999);
let a_msg_1 = (300u32, "a_msg_1").encode();
let a_msg_2 = (300u32, "a_msg_2").encode();
let b_msg_1 = (300u32, "b_msg_1").encode();
new_test_ext(
GenesisConfigBuilder {
ump_service_total_weight: Weight::from_parts(900, 900),
..Default::default()
}
.build(),
)
.execute_with(|| {
// We want to test here an edge case, where we remove the queue with the highest
// para id (i.e. last in the `needs_dispatch` order).
//
// If the last entry was removed we should proceed execution, assuming we still have
// weight available.
queue_upward_msg(a, a_msg_1.clone());
queue_upward_msg(a, a_msg_2.clone());
queue_upward_msg(b, b_msg_1.clone());
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(a, a_msg_1), (b, b_msg_1), (a, a_msg_2)]);
});
}
#[test]
fn verify_relay_dispatch_queue_size_is_externally_accessible() {
// Make sure that the relay dispatch queue size storage entry is accessible via well known
// keys and is decodable into a (u32, u32).
use parity_scale_codec::Decode as _;
use primitives::well_known_keys;
let a = ParaId::from(228);
let msg = vec![1, 2, 3];
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
queue_upward_msg(a, msg);
let raw_queue_size = sp_io::storage::get(&well_known_keys::relay_dispatch_queue_size(a))
.expect(
"enqueing a message should create the dispatch queue\
and it should be accessible via the well known keys",
);
let (cnt, size) = <(u32, u32)>::decode(&mut &raw_queue_size[..])
.expect("the dispatch queue size should be decodable into (u32, u32)");
assert_eq!(cnt, 1);
assert_eq!(size, 3);
});
}
#[test]
fn service_overweight_unknown() {
// This test just makes sure that 0 is not a valid index and we can use it not worrying in
// the next test.
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
assert_noop!(
Ump::service_overweight(RuntimeOrigin::root(), 0, Weight::from_parts(1000, 1000)),
Error::<Test>::UnknownMessageIndex
);
});
}
#[test]
fn overweight_queue_works() {
let para_a = ParaId::from(2021);
let a_msg_1 = (301u32, "a_msg_1").encode();
let a_msg_2 = (500u32, "a_msg_2").encode();
let a_msg_3 = (500u32, "a_msg_3").encode();
new_test_ext(
GenesisConfigBuilder {
ump_service_total_weight: Weight::from_parts(900, 900),
ump_max_individual_weight: Weight::from_parts(300, 300),
..Default::default()
}
.build(),
)
.execute_with(|| {
// HACK: Start with the block number 1. This is needed because should an event be
// emitted during the genesis block they will be implicitly wiped.
System::set_block_number(1);
// This one is overweight. However, the weight is plenty and we can afford to execute
// this message, thus expect it.
queue_upward_msg(para_a, a_msg_1.clone());
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(para_a, a_msg_1)]);
// This is overweight and this message cannot fit into the total weight budget.
queue_upward_msg(para_a, a_msg_2.clone());
queue_upward_msg(para_a, a_msg_3.clone());
Ump::process_pending_upward_messages();
assert_last_event(
Event::OverweightEnqueued(
para_a,
upward_message_id(&a_msg_3[..]),
0,
Weight::from_parts(500, 500),
)
.into(),
);
// Now verify that if we wanted to service this overweight message with less than enough
// weight it will fail.
assert_noop!(
Ump::service_overweight(RuntimeOrigin::root(), 0, Weight::from_parts(499, 499)),
Error::<Test>::WeightOverLimit
);
// ... and if we try to service it with just enough weight it will succeed as well.
assert_ok!(Ump::service_overweight(RuntimeOrigin::root(), 0, Weight::from_parts(500, 500)));
assert_last_event(Event::OverweightServiced(0, Weight::from_parts(500, 500)).into());
// ... and if we try to service a message with index that doesn't exist it will error
// out.
assert_noop!(
Ump::service_overweight(RuntimeOrigin::root(), 1, Weight::from_parts(1000, 1000)),
Error::<Test>::UnknownMessageIndex
);
});
}
@@ -0,0 +1,652 @@
// Copyright 2020 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::{
inclusion::{
tests::run_to_block_default_notifications as run_to_block, AggregateMessageOrigin,
AggregateMessageOrigin::Ump, UmpAcceptanceCheckErr, UmpQueueId,
},
mock::{
assert_last_event, assert_last_events, new_test_ext, Configuration, MessageQueue,
MessageQueueSize, MockGenesisConfig, ParaInclusion, Processed, System, Test, *,
},
};
use frame_support::{
assert_noop, assert_ok,
pallet_prelude::*,
traits::{EnqueueMessage, ExecuteOverweightError, ServiceQueues},
weights::Weight,
};
use primitives::v4::{well_known_keys, Id as ParaId, UpwardMessage};
use sp_core::twox_64;
use sp_runtime::traits::{Bounded, Hash};
use sp_std::prelude::*;
pub(super) struct GenesisConfigBuilder {
max_upward_message_size: u32,
max_upward_message_num_per_candidate: u32,
max_upward_queue_count: u32,
max_upward_queue_size: u32,
}
impl Default for GenesisConfigBuilder {
fn default() -> Self {
Self {
max_upward_message_size: 16,
max_upward_message_num_per_candidate: 2,
max_upward_queue_count: 4,
max_upward_queue_size: 64,
}
}
}
impl GenesisConfigBuilder {
pub(super) fn large_queue_count() -> Self {
Self { max_upward_queue_count: 128, ..Default::default() }
}
pub(super) fn build(self) -> crate::mock::MockGenesisConfig {
let mut genesis = default_genesis_config();
let config = &mut genesis.configuration.config;
config.max_upward_message_size = self.max_upward_message_size;
config.max_upward_message_num_per_candidate = self.max_upward_message_num_per_candidate;
config.max_upward_queue_count = self.max_upward_queue_count;
config.max_upward_queue_size = self.max_upward_queue_size;
genesis
}
}
fn default_genesis_config() -> MockGenesisConfig {
MockGenesisConfig {
configuration: crate::configuration::GenesisConfig {
config: crate::configuration::HostConfiguration {
max_downward_message_size: 1024,
..Default::default()
},
},
..Default::default()
}
}
fn queue_upward_msg(para: ParaId, msg: UpwardMessage) {
try_queue_upward_msg(para, msg).unwrap();
}
fn try_queue_upward_msg(para: ParaId, msg: UpwardMessage) -> Result<(), UmpAcceptanceCheckErr> {
let msgs = vec![msg];
ParaInclusion::check_upward_messages(&Configuration::config(), para, &msgs)?;
ParaInclusion::receive_upward_messages(para, msgs.as_slice());
Ok(())
}
mod check_upward_messages {
use super::*;
const P_0: ParaId = ParaId::new(0u32);
const P_1: ParaId = ParaId::new(1u32);
// Currently its trivial since unbounded, but this function will be handy when we bound it.
fn msg(data: &str) -> UpwardMessage {
data.as_bytes().to_vec()
}
/// Check that these messages *could* be queued.
fn check(para: ParaId, msgs: Vec<UpwardMessage>, err: Option<UmpAcceptanceCheckErr>) {
assert_eq!(
ParaInclusion::check_upward_messages(&Configuration::config(), para, &msgs[..]).err(),
err
);
}
/// Enqueue these upward messages.
fn queue(para: ParaId, msgs: Vec<UpwardMessage>) {
msgs.into_iter().for_each(|msg| super::queue_upward_msg(para, msg));
}
#[test]
fn basic_works() {
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
let _g = frame_support::StorageNoopGuard::default();
check(P_0, vec![msg("p0m0")], None);
check(P_1, vec![msg("p1m0")], None);
check(P_0, vec![msg("p0m1")], None);
check(P_1, vec![msg("p1m1")], None);
});
}
#[test]
fn num_per_candidate_exceeded_error() {
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
let _g = frame_support::StorageNoopGuard::default();
let permitted = Configuration::config().max_upward_message_num_per_candidate;
for sent in 0..permitted + 1 {
check(P_0, vec![msg(""); sent as usize], None);
}
for sent in permitted + 1..permitted + 10 {
check(
P_0,
vec![msg(""); sent as usize],
Some(UmpAcceptanceCheckErr::MoreMessagesThanPermitted { sent, permitted }),
);
}
});
}
#[test]
fn size_per_message_exceeded_error() {
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
let _g = frame_support::StorageNoopGuard::default();
let max_size = Configuration::config().max_upward_message_size;
let max_per_candidate = Configuration::config().max_upward_message_num_per_candidate;
for msg_size in 0..=max_size {
check(P_0, vec![vec![0; msg_size as usize]], None);
}
for msg_size in max_size + 1..max_size + 10 {
for goods in 0..max_per_candidate {
let mut msgs = vec![vec![0; max_size as usize]; goods as usize];
msgs.push(vec![0; msg_size as usize]);
check(
P_0,
msgs,
Some(UmpAcceptanceCheckErr::MessageSize { idx: goods, msg_size, max_size }),
);
}
}
});
}
#[test]
fn queue_count_exceeded_error() {
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
let limit = Configuration::config().max_upward_queue_count as u64;
for _ in 0..limit {
check(P_0, vec![msg("")], None);
queue(P_0, vec![msg("")]);
}
check(
P_0,
vec![msg("")],
Some(UmpAcceptanceCheckErr::CapacityExceeded { count: limit + 1, limit }),
);
check(
P_0,
vec![msg(""); 2],
Some(UmpAcceptanceCheckErr::CapacityExceeded { count: limit + 2, limit }),
);
});
}
#[test]
fn queue_size_exceeded_error() {
new_test_ext(GenesisConfigBuilder::large_queue_count().build()).execute_with(|| {
let limit = Configuration::config().max_upward_queue_size as u64;
assert_eq!(pallet_message_queue::ItemHeader::<MessageQueueSize>::max_encoded_len(), 5);
assert!(
Configuration::config().max_upward_queue_size <
crate::inclusion::MaxUmpMessageLenOf::<Test>::get(),
"Test will not work"
);
for _ in 0..limit {
check(P_0, vec![msg("1")], None);
queue(P_0, vec![msg("1")]);
}
check(
P_0,
vec![msg("1")],
Some(UmpAcceptanceCheckErr::TotalSizeExceeded { total_size: limit + 1, limit }),
);
check(
P_0,
vec![msg("123456")],
Some(UmpAcceptanceCheckErr::TotalSizeExceeded { total_size: limit + 6, limit }),
);
});
}
}
#[test]
fn dispatch_empty() {
new_test_ext(default_genesis_config()).execute_with(|| {
// make sure that the case with empty queues is handled properly
MessageQueue::service_queues(Weight::max_value());
});
}
#[test]
fn dispatch_single_message() {
let a = ParaId::from(228);
let msg = 1000u32.encode();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
queue_upward_msg(a, msg.clone());
MessageQueue::service_queues(Weight::max_value());
assert_eq!(Processed::take(), vec![(a, msg)]);
});
}
#[test]
fn dispatch_resume_after_exceeding_dispatch_stage_weight() {
let a = ParaId::from(128);
let c = ParaId::from(228);
let q = ParaId::from(911);
let a_msg_1 = (200u32, "a_msg_1").encode();
let a_msg_2 = (100u32, "a_msg_2").encode();
let c_msg_1 = (300u32, "c_msg_1").encode();
let c_msg_2 = (100u32, "c_msg_2").encode();
let q_msg = (500u32, "q_msg").encode();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
queue_upward_msg(q, q_msg.clone());
queue_upward_msg(c, c_msg_1.clone());
queue_upward_msg(a, a_msg_1.clone());
queue_upward_msg(a, a_msg_2.clone());
// we expect only two first messages to fit in the first iteration.
MessageQueue::service_queues(Weight::from_parts(500, 500));
assert_eq!(Processed::take(), vec![(q, q_msg)]);
queue_upward_msg(c, c_msg_2.clone());
// second iteration should process the second message.
MessageQueue::service_queues(Weight::from_parts(500, 500));
assert_eq!(Processed::take(), vec![(c, c_msg_1), (c, c_msg_2)]);
// 3rd iteration.
MessageQueue::service_queues(Weight::from_parts(500, 500));
assert_eq!(Processed::take(), vec![(a, a_msg_1), (a, a_msg_2)]);
// finally, make sure that the queue is empty.
MessageQueue::service_queues(Weight::from_parts(500, 500));
assert_eq!(Processed::take(), vec![]);
});
}
#[test]
fn dispatch_keeps_message_after_weight_exhausted() {
let a = ParaId::from(128);
let a_msg_1 = (300u32, "a_msg_1").encode();
let a_msg_2 = (300u32, "a_msg_2").encode();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
queue_upward_msg(a, a_msg_1.clone());
queue_upward_msg(a, a_msg_2.clone());
// we expect only one message to fit in the first iteration.
MessageQueue::service_queues(Weight::from_parts(500, 500));
assert_eq!(Processed::take(), vec![(a, a_msg_1)]);
// second iteration should process the remaining message.
MessageQueue::service_queues(Weight::from_parts(500, 500));
assert_eq!(Processed::take(), vec![(a, a_msg_2)]);
// finally, make sure that the queue is empty.
MessageQueue::service_queues(Weight::from_parts(500, 500));
assert_eq!(Processed::take(), vec![]);
});
}
#[test]
fn dispatch_correctly_handle_remove_of_latest() {
let a = ParaId::from(1991);
let b = ParaId::from(1999);
let a_msg_1 = (300u32, "a_msg_1").encode();
let a_msg_2 = (300u32, "a_msg_2").encode();
let b_msg_1 = (300u32, "b_msg_1").encode();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
// We want to test here an edge case, where we remove the queue with the highest
// para id (i.e. last in the `needs_dispatch` order).
//
// If the last entry was removed we should proceed execution, assuming we still have
// weight available.
queue_upward_msg(a, a_msg_1.clone());
queue_upward_msg(a, a_msg_2.clone());
queue_upward_msg(b, b_msg_1.clone());
MessageQueue::service_queues(Weight::from_parts(900, 900));
assert_eq!(Processed::take(), vec![(a, a_msg_1), (a, a_msg_2), (b, b_msg_1)]);
});
}
#[test]
#[cfg_attr(debug_assertions, should_panic = "Defensive failure has been triggered")]
fn queue_enact_too_long_ignored() {
const P_0: ParaId = ParaId::new(0u32);
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
let max_enact = crate::inclusion::MaxUmpMessageLenOf::<Test>::get() as usize;
let m1 = (300u32, "a_msg_1").encode();
let m2 = vec![0u8; max_enact + 1];
let m3 = (300u32, "a_msg_3").encode();
// .. but the enact defensively ignores.
ParaInclusion::receive_upward_messages(P_0, &[m1.clone(), m2.clone(), m3.clone()]);
// There is one message in the queue now:
MessageQueue::service_queues(Weight::from_parts(900, 900));
assert_eq!(Processed::take(), vec![(P_0, m1), (P_0, m3)]);
});
}
/// Check that the Inclusion pallet correctly updates the well known keys in the MQ handler.
///
/// Also checks that it works in the presence of overweight messages.
#[test]
fn relay_dispatch_queue_size_is_updated() {
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
let cfg = Configuration::config();
for p in 0..100 {
let para = p.into();
// Do some tricks with the weight such that the MQ pallet will process in order:
// Q0:0, Q1:0 … Q0:1, Q1:1 …
let m1 = (300u32 * (100 - p), "m1").encode();
let m2 = (300u32 * (100 - p), "m11").encode();
queue_upward_msg(para, m1);
queue_upward_msg(para, m2);
assert_queue_size(para, 2, 15);
assert_queue_remaining(
para,
cfg.max_upward_queue_count - 2,
cfg.max_upward_queue_size - 15,
);
// Now processing one message should also update the queue size.
MessageQueue::service_queues(Weight::from_all(300u64 * (100 - p) as u64));
assert_queue_remaining(
para,
cfg.max_upward_queue_count - 1,
cfg.max_upward_queue_size - 8,
);
}
// The messages of Q0…Q98 are overweight, so `service_queues` wont help.
for p in 0..98 {
let para = UmpQueueId::Para(p.into());
MessageQueue::service_queues(Weight::from_all(u64::MAX));
let fp = MessageQueue::footprint(AggregateMessageOrigin::Ump(para));
let (para_queue_count, para_queue_size) = (fp.count, fp.size);
assert_eq!(para_queue_count, 1, "count wrong for para: {}", p);
assert_eq!(para_queue_size, 8, "size wrong for para: {}", p);
}
// All queues are empty after processing overweight messages.
for p in 0..100 {
let para = UmpQueueId::Para(p.into());
let _ = <MessageQueue as ServiceQueues>::execute_overweight(
Weight::from_all(u64::MAX),
(AggregateMessageOrigin::Ump(para.clone()), 0, 1),
);
assert_queue_remaining(p.into(), cfg.max_upward_queue_count, cfg.max_upward_queue_size);
let fp = MessageQueue::footprint(AggregateMessageOrigin::Ump(para));
let (para_queue_count, para_queue_size) = (fp.count, fp.size);
assert_eq!(para_queue_count, 0, "count wrong for para: {}", p);
assert_eq!(para_queue_size, 0, "size wrong for para: {}", p);
}
});
}
/// Assert that the old and the new way of accessing `relay_dispatch_queue_size` is the same.
#[test]
fn relay_dispatch_queue_size_key_is_correct() {
#![allow(deprecated)]
// Storage alias to the old way of accessing the queue size.
#[frame_support::storage_alias]
type RelayDispatchQueueSize = StorageMap<Ump, Twox64Concat, ParaId, (u32, u32), ValueQuery>;
for i in 0..1024 {
// A "random" para id.
let para: ParaId = u32::from_ne_bytes(twox_64(&i.encode())[..4].try_into().unwrap()).into();
let well_known = primitives::well_known_keys::relay_dispatch_queue_size(para);
let aliased = RelayDispatchQueueSize::hashed_key_for(para);
assert_eq!(well_known, aliased, "Old and new key must match");
}
}
#[test]
fn verify_relay_dispatch_queue_size_is_externally_accessible() {
// Make sure that the relay dispatch queue size storage entry is accessible via well known
// keys and is decodable into a (u32, u32).
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
let cfg = Configuration::config();
for para in 0..10 {
let para = para.into();
queue_upward_msg(para, vec![0u8; 3]);
assert_queue_size(para, 1, 3);
assert_queue_remaining(
para,
cfg.max_upward_queue_count - 1,
cfg.max_upward_queue_size - 3,
);
queue_upward_msg(para, vec![0u8; 3]);
assert_queue_size(para, 2, 6);
assert_queue_remaining(
para,
cfg.max_upward_queue_count - 2,
cfg.max_upward_queue_size - 6,
);
}
});
}
fn assert_queue_size(para: ParaId, count: u32, size: u32) {
#[allow(deprecated)]
let raw_queue_size = sp_io::storage::get(&well_known_keys::relay_dispatch_queue_size(para)).expect(
"enqueing a message should create the dispatch queue\
and it should be accessible via the well known keys",
);
let (c, s) = <(u32, u32)>::decode(&mut &raw_queue_size[..])
.expect("the dispatch queue size should be decodable into (u32, u32)");
assert_eq!((c, s), (count, size));
// Test the deprecated but at least type-safe `relay_dispatch_queue_size_typed`:
#[allow(deprecated)]
let (c, s) = well_known_keys::relay_dispatch_queue_size_typed(para).get().expect(
"enqueing a message should create the dispatch queue\
and it should be accessible via the well known keys",
);
assert_eq!((c, s), (count, size));
}
fn assert_queue_remaining(para: ParaId, count: u32, size: u32) {
let (remaining_cnt, remaining_size) =
well_known_keys::relay_dispatch_queue_remaining_capacity(para)
.get()
.expect("No storage value");
assert_eq!(remaining_cnt, count, "Wrong number of remaining messages in Q{}", para);
assert_eq!(remaining_size, size, "Wrong remaining size in Q{}", para);
}
#[test]
fn service_overweight_unknown() {
// This test just makes sure that 0 is not a valid index and we can use it not worrying in
// the next test.
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
assert_noop!(
<MessageQueue as ServiceQueues>::execute_overweight(
Weight::MAX,
(Ump(UmpQueueId::Para(0u32.into())), 0, 0)
),
ExecuteOverweightError::NotFound,
);
});
}
#[test]
fn overweight_queue_works() {
let para_a = ParaId::from(2021);
let a_msg_1 = (301u32, "a_msg_1").encode();
let a_msg_2 = (501u32, "a_msg_2").encode();
let a_msg_3 = (501u32, "a_msg_3").encode();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
// HACK: Start with the block number 1. This is needed because should an event be
// emitted during the genesis block they will be implicitly wiped.
System::set_block_number(1);
// This one is overweight. However, the weight is plenty and we can afford to execute
// this message, thus expect it.
queue_upward_msg(para_a, a_msg_1.clone());
queue_upward_msg(para_a, a_msg_2.clone());
queue_upward_msg(para_a, a_msg_3.clone());
MessageQueue::service_queues(Weight::from_parts(500, 500));
let hash_1 = <<Test as frame_system::Config>::Hashing as Hash>::hash(&a_msg_1[..]);
let hash_2 = <<Test as frame_system::Config>::Hashing as Hash>::hash(&a_msg_2[..]);
let hash_3 = <<Test as frame_system::Config>::Hashing as Hash>::hash(&a_msg_3[..]);
assert_last_events(
[
pallet_message_queue::Event::<Test>::Processed {
hash: hash_1.clone(),
origin: Ump(UmpQueueId::Para(para_a)),
weight_used: Weight::from_parts(301, 301),
success: true,
}
.into(),
pallet_message_queue::Event::<Test>::OverweightEnqueued {
hash: hash_2.clone(),
origin: Ump(UmpQueueId::Para(para_a)),
page_index: 0,
message_index: 1,
}
.into(),
pallet_message_queue::Event::<Test>::OverweightEnqueued {
hash: hash_3.clone(),
origin: Ump(UmpQueueId::Para(para_a)),
page_index: 0,
message_index: 2,
}
.into(),
]
.into_iter(),
);
assert_eq!(Processed::take(), vec![(para_a, a_msg_1)]);
// Now verify that if we wanted to service this overweight message with less than enough
// weight it will fail.
assert_noop!(
<MessageQueue as ServiceQueues>::execute_overweight(
Weight::from_parts(500, 500),
(Ump(UmpQueueId::Para(para_a)), 0, 2)
),
ExecuteOverweightError::InsufficientWeight,
);
// ... and if we try to service it with just enough weight it will succeed as well.
assert_ok!(<MessageQueue as ServiceQueues>::execute_overweight(
Weight::from_parts(501, 501),
(Ump(UmpQueueId::Para(para_a)), 0, 2)
));
assert_last_event(
pallet_message_queue::Event::<Test>::Processed {
hash: hash_3,
origin: Ump(UmpQueueId::Para(para_a)),
weight_used: Weight::from_parts(501, 501),
success: true,
}
.into(),
);
// ... and if we try to service a message with index that doesn't exist it will error
// out.
assert_noop!(
<MessageQueue as ServiceQueues>::execute_overweight(
Weight::from_parts(501, 501),
(Ump(UmpQueueId::Para(para_a)), 0, 2)
),
ExecuteOverweightError::NotFound,
);
});
}
/// Tests that UMP messages in the dispatch queue of the relay prevents the parachain from being
/// scheduled for offboarding.
#[test]
fn cannot_offboard_while_ump_dispatch_queued() {
let para = 32.into();
let msg = (300u32, "something").encode();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
register_parachain(para);
run_to_block(5, vec![4, 5]);
queue_upward_msg(para, msg.clone());
queue_upward_msg(para, msg.clone());
// Cannot offboard since there are two UMP messages in the queue.
for i in 6..10 {
assert!(try_deregister_parachain(para).is_err());
run_to_block(i, vec![i]);
assert!(Paras::is_valid_para(para));
}
// Now let's process the first message.
MessageQueue::on_initialize(System::block_number());
assert_eq!(Processed::take().len(), 1);
// Cannot offboard since there is another one in the queue.
assert!(try_deregister_parachain(para).is_err());
// Now also process the second message ...
MessageQueue::on_initialize(System::block_number());
assert_eq!(Processed::take().len(), 1);
// ... and offboard.
run_to_block(10, vec![10]);
assert!(Paras::is_valid_para(para));
assert_ok!(try_deregister_parachain(para));
assert!(Paras::is_offboarding(para));
// Offboarding completed.
run_to_block(11, vec![11]);
assert!(!Paras::is_valid_para(para));
});
}
/// A para-chain cannot send an UMP to the relay chain while it is offboarding.
#[test]
fn cannot_enqueue_ump_while_offboarding() {
let para = 32.into();
let msg = (300u32, "something").encode();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
register_parachain(para);
run_to_block(5, vec![4, 5]);
// Start with an offboarding para.
assert_ok!(try_deregister_parachain(para));
assert!(Paras::is_offboarding(para));
// Cannot enqueue a message.
assert!(try_queue_upward_msg(para, msg.clone()).is_err());
run_to_block(6, vec![6]);
// Para is still there and still cannot enqueue a message.
assert!(Paras::is_offboarding(para));
assert!(try_queue_upward_msg(para, msg.clone()).is_err());
// Now offboarding is completed.
run_to_block(7, vec![7]);
assert!(!Paras::is_valid_para(para));
});
}