diff --git a/pallets/xcmp-queue/src/lib.rs b/pallets/xcmp-queue/src/lib.rs index 2444aaabe4..cab3a18e42 100644 --- a/pallets/xcmp-queue/src/lib.rs +++ b/pallets/xcmp-queue/src/lib.rs @@ -25,6 +25,8 @@ #![cfg_attr(not(feature = "std"), no_std)] +pub mod migration; + #[cfg(test)] mod mock; @@ -36,7 +38,7 @@ use cumulus_primitives_core::{ relay_chain::BlockNumber as RelayBlockNumber, ChannelStatus, GetChannelInfo, MessageSendError, ParaId, XcmpMessageFormat, XcmpMessageHandler, XcmpMessageSource, }; -use frame_support::weights::Weight; +use frame_support::weights::{constants::WEIGHT_PER_MILLIS, Weight}; use rand_chacha::{ rand_core::{RngCore, SeedableRng}, ChaChaRng, @@ -48,6 +50,11 @@ use xcm::{latest::prelude::*, VersionedXcm, WrapVersion, MAX_XCM_DECODE_DEPTH}; pub use pallet::*; +/// Index used to identify overweight XCMs. +pub type OverweightIndex = u64; + +const LOG_TARGET: &str = "xcmp_queue"; + #[frame_support::pallet] pub mod pallet { use super::*; @@ -56,6 +63,7 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(migration::STORAGE_VERSION)] pub struct Pallet(_); #[pallet::config] @@ -70,22 +78,17 @@ pub mod pallet { /// Means of converting an `Xcm` into a `VersionedXcm`. type VersionWrapper: WrapVersion; - } - impl Default for QueueConfigData { - fn default() -> Self { - Self { - suspend_threshold: 2, - drop_threshold: 5, - resume_threshold: 1, - threshold_weight: 100_000, - weight_restrict_decay: 2, - } - } + /// The origin that is allowed to execute overweight messages. + type ExecuteOverweightOrigin: EnsureOrigin; } #[pallet::hooks] impl Hooks> for Pallet { + fn on_runtime_upgrade() -> Weight { + migration::migrate_to_latest::() + } + fn on_idle(_now: T::BlockNumber, max_weight: Weight) -> Weight { // on_idle processes additional messages with any remaining block weight. Self::service_xcmp_queue(max_weight) @@ -93,7 +96,40 @@ pub mod pallet { } #[pallet::call] - impl Pallet {} + impl Pallet { + /// Services a single overweight XCM. + /// + /// - `origin`: Must pass `ExecuteOverweightOrigin`. + /// - `index`: The index of the overweight XCM to service + /// - `weight_limit`: The amount of weight that XCM execution may take. + /// + /// Errors: + /// - `BadOverweightIndex`: XCM under `index` is not found in the `Overweight` storage map. + /// - `BadXcm`: XCM under `index` cannot be properly decoded into a valid XCM format. + /// - `WeightOverLimit`: XCM execution may use greater `weight_limit`. + /// + /// Events: + /// - `OverweightServiced`: On success. + #[pallet::weight(weight_limit.saturating_add(1_000_000))] + pub fn service_overweight( + origin: OriginFor, + index: OverweightIndex, + weight_limit: Weight, + ) -> DispatchResultWithPostInfo { + T::ExecuteOverweightOrigin::ensure_origin(origin)?; + + let (sender, sent_at, data) = + Overweight::::get(index).ok_or(Error::::BadOverweightIndex)?; + let xcm = + VersionedXcm::::decode_all_with_depth_limit(MAX_XCM_DECODE_DEPTH, &data) + .map_err(|_| Error::::BadXcm)?; + let used = Self::handle_xcm_message(sender, sent_at, xcm, weight_limit) + .map_err(|_| Error::::WeightOverLimit)?; + Overweight::::remove(index); + Self::deposit_event(Event::OverweightServiced(index, used)); + Ok(Some(used.saturating_add(1_000_000)).into()) + } + } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -110,6 +146,10 @@ pub mod pallet { UpwardMessageSent(Option), /// An HRMP message was sent to a sibling parachain. XcmpMessageSent(Option), + /// An XCM exceeded the individual message weight budget. + OverweightEnqueued(ParaId, RelayBlockNumber, OverweightIndex, Weight), + /// An XCM from the overweight queue was executed with the given actual weight used. + OverweightServiced(OverweightIndex, Weight), } #[pallet::error] @@ -120,6 +160,10 @@ pub mod pallet { BadXcmOrigin, /// Bad XCM data. BadXcm, + /// Bad overweight index. + BadOverweightIndex, + /// Provided weight is possibly not enough to execute the message. + WeightOverLimit, } /// Status of the inbound XCMP channels. @@ -163,6 +207,19 @@ pub mod pallet { /// The configuration which controls the dynamics of the outbound queue. #[pallet::storage] pub(super) type QueueConfig = StorageValue<_, QueueConfigData, ValueQuery>; + + /// The messages that exceeded max individual message weight budget. + /// + /// These message stay in this storage map until they are manually dispatched via + /// `service_overweight`. + #[pallet::storage] + pub(super) type Overweight = + StorageMap<_, Twox64Concat, OverweightIndex, (ParaId, RelayBlockNumber, Vec)>; + + /// The number of overweight messages ever recorded in `Overweight`. Also doubles as the next + /// available free overweight index. + #[pallet::storage] + pub(super) type OverweightCount = StorageValue<_, OverweightIndex, ValueQuery>; } #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug, TypeInfo)] @@ -244,6 +301,22 @@ pub struct QueueConfigData { /// The speed to which the available weight approaches the maximum weight. A lower number /// results in a faster progression. A value of 1 makes the entire weight available initially. weight_restrict_decay: Weight, + /// The maximum amount of weight any individual message may consume. Messages above this weight + /// go into the overweight queue and may only be serviced explicitly. + xcmp_max_individual_weight: Weight, +} + +impl Default for QueueConfigData { + fn default() -> Self { + Self { + suspend_threshold: 2, + drop_threshold: 5, + resume_threshold: 1, + threshold_weight: 100_000, + weight_restrict_decay: 2, + xcmp_max_individual_weight: 20 * WEIGHT_PER_MILLIS, + } + } } #[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, TypeInfo)] @@ -414,6 +487,7 @@ impl Pallet { sender: ParaId, (sent_at, format): (RelayBlockNumber, XcmpMessageFormat), max_weight: Weight, + max_individual_weight: Weight, ) -> (Weight, bool) { let data = >::get(sender, sent_at); let mut last_remaining_fragments; @@ -430,6 +504,19 @@ impl Pallet { let weight = max_weight - weight_used; match Self::handle_xcm_message(sender, sent_at, xcm, weight) { Ok(used) => weight_used = weight_used.saturating_add(used), + Err(XcmError::WeightLimitReached(required)) + if required > max_individual_weight => + { + // overweight - add to overweight queue and continue with message + // execution consuming the message. + let msg_len = last_remaining_fragments + .len() + .saturating_sub(remaining_fragments.len()); + let overweight_xcm = last_remaining_fragments[..msg_len].to_vec(); + let index = Self::stash_overweight(sender, sent_at, overweight_xcm); + let e = Event::OverweightEnqueued(sender, sent_at, index, required); + Self::deposit_event(e); + }, Err(XcmError::WeightLimitReached(required)) if required <= max_weight => { @@ -488,6 +575,22 @@ impl Pallet { (weight_used, is_empty) } + /// Puts a given XCM into the list of overweight messages, allowing it to be executed later. + fn stash_overweight( + sender: ParaId, + sent_at: RelayBlockNumber, + xcm: Vec, + ) -> OverweightIndex { + let index = ::OverweightCount::mutate(|count| { + let index = *count; + *count += 1; + index + }); + + ::Overweight::insert(index, (sender, sent_at, xcm)); + index + } + /// Service the incoming XCMP message queue attempting to execute up to `max_weight` execution /// weight of messages. /// @@ -521,8 +624,13 @@ impl Pallet { return 0 } - let QueueConfigData { resume_threshold, threshold_weight, weight_restrict_decay, .. } = - >::get(); + let QueueConfigData { + resume_threshold, + threshold_weight, + weight_restrict_decay, + xcmp_max_individual_weight, + .. + } = >::get(); let mut shuffled = Self::create_shuffle(status.len()); let mut weight_used = 0; @@ -568,6 +676,7 @@ impl Pallet { sender, status[index].message_metadata[0], weight_remaining, + xcmp_max_individual_weight, ); if is_empty { status[index].message_metadata.remove(0); diff --git a/pallets/xcmp-queue/src/migration.rs b/pallets/xcmp-queue/src/migration.rs new file mode 100644 index 0000000000..db5833e2df --- /dev/null +++ b/pallets/xcmp-queue/src/migration.rs @@ -0,0 +1,128 @@ +// 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 . + +//! A module that is responsible for migration of storage. + +use crate::{Config, Pallet, Store}; +use frame_support::{pallet_prelude::*, traits::StorageVersion, weights::Weight}; + +/// The current storage version. +pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + +/// Migrates the pallet storage to the most recent version, checking and setting the +/// `StorageVersion`. +pub fn migrate_to_latest() -> Weight { + let mut weight = 0; + + if StorageVersion::get::>() == 0 { + weight += migrate_to_v1::(); + StorageVersion::new(1).put::>(); + } + + weight +} + +mod v0 { + use super::*; + use codec::{Decode, Encode}; + + #[derive(Encode, Decode, Debug)] + pub struct QueueConfigData { + pub suspend_threshold: u32, + pub drop_threshold: u32, + pub resume_threshold: u32, + pub threshold_weight: Weight, + pub weight_restrict_decay: Weight, + } + + impl Default for QueueConfigData { + fn default() -> Self { + QueueConfigData { + suspend_threshold: 2, + drop_threshold: 5, + resume_threshold: 1, + threshold_weight: 100_000, + weight_restrict_decay: 2, + } + } + } +} + +/// Migrates `QueueConfigData` from v0 (without the `xcmp_max_individual_weight` field) to v1 (with +/// max individual weight). +/// Uses the `Default` implementation of `QueueConfigData` to choose a value for +/// `xcmp_max_individual_weight`. +/// +/// NOTE: Only use this function if you know what you're doing. Default to using +/// `migrate_to_latest`. +pub fn migrate_to_v1() -> Weight { + let translate = |pre: v0::QueueConfigData| -> super::QueueConfigData { + super::QueueConfigData { + suspend_threshold: pre.suspend_threshold, + drop_threshold: pre.drop_threshold, + resume_threshold: pre.resume_threshold, + threshold_weight: pre.threshold_weight, + weight_restrict_decay: pre.weight_restrict_decay, + xcmp_max_individual_weight: super::QueueConfigData::default() + .xcmp_max_individual_weight, + } + }; + + if let Err(_) = as Store>::QueueConfig::translate(|pre| pre.map(translate)) { + log::error!( + target: super::LOG_TARGET, + "unexpected error when performing translation of the QueueConfig type during storage upgrade to v1" + ); + } + + T::DbWeight::get().reads_writes(1, 1) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::{new_test_ext, Test}; + + #[test] + fn test_migration_to_v1() { + let v0 = v0::QueueConfigData { + suspend_threshold: 5, + drop_threshold: 12, + resume_threshold: 3, + threshold_weight: 333_333, + weight_restrict_decay: 1, + }; + + new_test_ext().execute_with(|| { + // Put the v0 version in the state + frame_support::storage::unhashed::put_raw( + &crate::QueueConfig::::hashed_key(), + &v0.encode(), + ); + + migrate_to_v1::(); + + let v1 = crate::QueueConfig::::get(); + + assert_eq!(v0.suspend_threshold, v1.suspend_threshold); + assert_eq!(v0.drop_threshold, v1.drop_threshold); + assert_eq!(v0.resume_threshold, v1.resume_threshold); + assert_eq!(v0.threshold_weight, v1.threshold_weight); + assert_eq!(v0.weight_restrict_decay, v1.weight_restrict_decay); + assert_eq!(v1.xcmp_max_individual_weight, 20_000_000_000); + }); + } +} diff --git a/pallets/xcmp-queue/src/mock.rs b/pallets/xcmp-queue/src/mock.rs index 3496db5aa3..0519f20f59 100644 --- a/pallets/xcmp-queue/src/mock.rs +++ b/pallets/xcmp-queue/src/mock.rs @@ -16,6 +16,7 @@ use super::*; use crate as xcmp_queue; use frame_support::parameter_types; +use frame_system::EnsureRoot; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -157,6 +158,7 @@ impl Config for Test { type XcmExecutor = xcm_executor::XcmExecutor; type ChannelInfo = ParachainSystem; type VersionWrapper = (); + type ExecuteOverweightOrigin = EnsureRoot; } pub fn new_test_ext() -> sp_io::TestExternalities { diff --git a/pallets/xcmp-queue/src/tests.rs b/pallets/xcmp-queue/src/tests.rs index 117933d8f5..aa9e489c0a 100644 --- a/pallets/xcmp-queue/src/tests.rs +++ b/pallets/xcmp-queue/src/tests.rs @@ -15,9 +15,10 @@ use super::*; use cumulus_primitives_core::XcmpMessageHandler; +use frame_support::assert_noop; #[cfg(debug_assertions)] use mock::Test; -use mock::{new_test_ext, XcmpQueue}; +use mock::{new_test_ext, Origin, XcmpQueue}; #[test] fn one_message_does_not_panic() { @@ -43,7 +44,7 @@ fn bad_message_is_handled() { InboundXcmpMessages::::insert(ParaId::from(1000), 1, bad_data); let format = XcmpMessageFormat::ConcatenatedEncodedBlob; // This should exit with an error. - XcmpQueue::process_xcmp_message(1000.into(), (1, format), 10_000_000_000); + XcmpQueue::process_xcmp_message(1000.into(), (1, format), 10_000_000_000, 10_000_000_000); }); } @@ -60,6 +61,26 @@ fn other_bad_message_is_handled() { InboundXcmpMessages::::insert(ParaId::from(1000), 1, bad_data); let format = XcmpMessageFormat::ConcatenatedEncodedBlob; // This should exit with an error. - XcmpQueue::process_xcmp_message(1000.into(), (1, format), 10_000_000_000); + XcmpQueue::process_xcmp_message(1000.into(), (1, format), 10_000_000_000, 10_000_000_000); + }); +} + +#[test] +fn service_overweight_unknown() { + new_test_ext().execute_with(|| { + assert_noop!( + XcmpQueue::service_overweight(Origin::root(), 0, 1000), + Error::::BadOverweightIndex, + ); + }); +} + +#[test] +fn service_overweight_bad_xcm_format() { + new_test_ext().execute_with(|| { + let bad_xcm = vec![255]; + Overweight::::insert(0, (ParaId::from(1000), 0, bad_xcm)); + + assert_noop!(XcmpQueue::service_overweight(Origin::root(), 0, 1000), Error::::BadXcm); }); } diff --git a/parachain-template/runtime/src/lib.rs b/parachain-template/runtime/src/lib.rs index d2459f17d8..79a4d2a178 100644 --- a/parachain-template/runtime/src/lib.rs +++ b/parachain-template/runtime/src/lib.rs @@ -521,6 +521,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type XcmExecutor = XcmExecutor; type ChannelInfo = ParachainSystem; type VersionWrapper = (); + type ExecuteOverweightOrigin = EnsureRoot; } impl cumulus_pallet_dmp_queue::Config for Runtime { diff --git a/polkadot-parachains/rococo-parachain/src/lib.rs b/polkadot-parachains/rococo-parachain/src/lib.rs index a60929658e..46465bb2f5 100644 --- a/polkadot-parachains/rococo-parachain/src/lib.rs +++ b/polkadot-parachains/rococo-parachain/src/lib.rs @@ -447,6 +447,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type XcmExecutor = XcmExecutor; type ChannelInfo = ParachainSystem; type VersionWrapper = (); + type ExecuteOverweightOrigin = EnsureRoot; } impl cumulus_pallet_dmp_queue::Config for Runtime { diff --git a/polkadot-parachains/statemine/src/lib.rs b/polkadot-parachains/statemine/src/lib.rs index 24acd5d1af..e06f20d358 100644 --- a/polkadot-parachains/statemine/src/lib.rs +++ b/polkadot-parachains/statemine/src/lib.rs @@ -597,6 +597,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type XcmExecutor = XcmExecutor; type ChannelInfo = ParachainSystem; type VersionWrapper = PolkadotXcm; + type ExecuteOverweightOrigin = EnsureRoot; } impl cumulus_pallet_dmp_queue::Config for Runtime { diff --git a/polkadot-parachains/statemint/src/lib.rs b/polkadot-parachains/statemint/src/lib.rs index cb716dc3ce..748ff853cb 100644 --- a/polkadot-parachains/statemint/src/lib.rs +++ b/polkadot-parachains/statemint/src/lib.rs @@ -609,6 +609,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type XcmExecutor = XcmExecutor; type ChannelInfo = ParachainSystem; type VersionWrapper = PolkadotXcm; + type ExecuteOverweightOrigin = EnsureRoot; } impl cumulus_pallet_dmp_queue::Config for Runtime { diff --git a/polkadot-parachains/westmint/src/lib.rs b/polkadot-parachains/westmint/src/lib.rs index 27766c69a5..2566f10ed0 100644 --- a/polkadot-parachains/westmint/src/lib.rs +++ b/polkadot-parachains/westmint/src/lib.rs @@ -589,6 +589,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type XcmExecutor = XcmExecutor; type ChannelInfo = ParachainSystem; type VersionWrapper = PolkadotXcm; + type ExecuteOverweightOrigin = EnsureRoot; } impl cumulus_pallet_dmp_queue::Config for Runtime {