Merge branch 'master' into gav-xcm-v3

This commit is contained in:
Keith Yeung
2022-03-08 22:58:14 -08:00
174 changed files with 11477 additions and 5059 deletions
+28
View File
@@ -0,0 +1,28 @@
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Benchmarking setup for cumulus-pallet-xcmp-queue
use crate::*;
use frame_benchmarking::{benchmarks, impl_benchmark_test_suite};
use frame_system::RawOrigin;
benchmarks! {
set_config_with_u32 {}: update_resume_threshold(RawOrigin::Root, 100)
set_config_with_weight {}: update_weight_restrict_decay(RawOrigin::Root, 3_000_000 as Weight)
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
+173 -32
View File
@@ -33,12 +33,20 @@ mod mock;
#[cfg(test)]
mod tests;
use codec::{Decode, DecodeAll, DecodeLimit, Encode};
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
pub use weights::WeightInfo;
use codec::{Decode, DecodeLimit, Encode};
use cumulus_primitives_core::{
relay_chain::BlockNumber as RelayBlockNumber, ChannelStatus, GetChannelInfo, MessageSendError,
ParaId, XcmpMessageFormat, XcmpMessageHandler, XcmpMessageSource,
};
use frame_support::weights::{constants::WEIGHT_PER_MILLIS, Weight};
use frame_support::{
traits::EnsureOrigin,
weights::{constants::WEIGHT_PER_MILLIS, Weight},
};
use rand_chacha::{
rand_core::{RngCore, SeedableRng},
ChaChaRng,
@@ -47,6 +55,7 @@ use scale_info::TypeInfo;
use sp_runtime::{traits::Hash, RuntimeDebug};
use sp_std::{convert::TryFrom, prelude::*};
use xcm::{latest::prelude::*, VersionedXcm, WrapVersion, MAX_XCM_DECODE_DEPTH};
use xcm_executor::traits::ConvertOrigin;
pub use pallet::*;
@@ -64,6 +73,7 @@ pub mod pallet {
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::storage_version(migration::STORAGE_VERSION)]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
#[pallet::config]
@@ -81,6 +91,16 @@ pub mod pallet {
/// The origin that is allowed to execute overweight messages.
type ExecuteOverweightOrigin: EnsureOrigin<Self::Origin>;
/// The origin that is allowed to resume or suspend the XCMP queue.
type ControllerOrigin: EnsureOrigin<Self::Origin>;
/// The conversion function used to attempt to convert an XCM `MultiLocation` origin to a
/// superuser origin.
type ControllerOriginConverter: ConvertOrigin<Self::Origin>;
/// The weight information of this pallet.
type WeightInfo: WeightInfo;
}
#[pallet::hooks]
@@ -110,7 +130,7 @@ pub mod pallet {
///
/// Events:
/// - `OverweightServiced`: On success.
#[pallet::weight(weight_limit.saturating_add(1_000_000))]
#[pallet::weight((weight_limit.saturating_add(1_000_000), DispatchClass::Operational,))]
pub fn service_overweight(
origin: OriginFor<T>,
index: OverweightIndex,
@@ -120,15 +140,123 @@ pub mod pallet {
let (sender, sent_at, data) =
Overweight::<T>::get(index).ok_or(Error::<T>::BadOverweightIndex)?;
let xcm =
VersionedXcm::<T::Call>::decode_all_with_depth_limit(MAX_XCM_DECODE_DEPTH, &data)
.map_err(|_| Error::<T>::BadXcm)?;
let xcm = VersionedXcm::<T::Call>::decode_all_with_depth_limit(
MAX_XCM_DECODE_DEPTH,
&mut data.as_slice(),
)
.map_err(|_| Error::<T>::BadXcm)?;
let used = Self::handle_xcm_message(sender, sent_at, xcm, weight_limit)
.map_err(|_| Error::<T>::WeightOverLimit)?;
Overweight::<T>::remove(index);
Self::deposit_event(Event::OverweightServiced(index, used));
Ok(Some(used.saturating_add(1_000_000)).into())
}
/// Suspends all XCM executions for the XCMP queue, regardless of the sender's origin.
///
/// - `origin`: Must pass `ControllerOrigin`.
#[pallet::weight((T::DbWeight::get().writes(1), DispatchClass::Operational,))]
pub fn suspend_xcm_execution(origin: OriginFor<T>) -> DispatchResult {
T::ControllerOrigin::ensure_origin(origin)?;
QueueSuspended::<T>::put(true);
Ok(())
}
/// Resumes all XCM executions for the XCMP queue.
///
/// Note that this function doesn't change the status of the in/out bound channels.
///
/// - `origin`: Must pass `ControllerOrigin`.
#[pallet::weight((T::DbWeight::get().writes(1), DispatchClass::Operational,))]
pub fn resume_xcm_execution(origin: OriginFor<T>) -> DispatchResult {
T::ControllerOrigin::ensure_origin(origin)?;
QueueSuspended::<T>::put(false);
Ok(())
}
/// Overwrites the number of pages of messages which must be in the queue for the other side to be told to
/// suspend their sending.
///
/// - `origin`: Must pass `Root`.
/// - `new`: Desired value for `QueueConfigData.suspend_value`
#[pallet::weight((T::WeightInfo::set_config_with_u32(), DispatchClass::Operational,))]
pub fn update_suspend_threshold(origin: OriginFor<T>, new: u32) -> DispatchResult {
ensure_root(origin)?;
QueueConfig::<T>::mutate(|data| data.suspend_threshold = new);
Ok(())
}
/// Overwrites the number of pages of messages which must be in the queue after which we drop any further
/// messages from the channel.
///
/// - `origin`: Must pass `Root`.
/// - `new`: Desired value for `QueueConfigData.drop_threshold`
#[pallet::weight((T::WeightInfo::set_config_with_u32(),DispatchClass::Operational,))]
pub fn update_drop_threshold(origin: OriginFor<T>, new: u32) -> DispatchResult {
ensure_root(origin)?;
QueueConfig::<T>::mutate(|data| data.drop_threshold = new);
Ok(())
}
/// Overwrites the number of pages of messages which the queue must be reduced to before it signals that
/// message sending may recommence after it has been suspended.
///
/// - `origin`: Must pass `Root`.
/// - `new`: Desired value for `QueueConfigData.resume_threshold`
#[pallet::weight((T::WeightInfo::set_config_with_u32(), DispatchClass::Operational,))]
pub fn update_resume_threshold(origin: OriginFor<T>, new: u32) -> DispatchResult {
ensure_root(origin)?;
QueueConfig::<T>::mutate(|data| data.resume_threshold = new);
Ok(())
}
/// Overwrites the amount of remaining weight under which we stop processing messages.
///
/// - `origin`: Must pass `Root`.
/// - `new`: Desired value for `QueueConfigData.threshold_weight`
#[pallet::weight((T::WeightInfo::set_config_with_weight(), DispatchClass::Operational,))]
pub fn update_threshold_weight(origin: OriginFor<T>, new: Weight) -> DispatchResult {
ensure_root(origin)?;
QueueConfig::<T>::mutate(|data| data.threshold_weight = new);
Ok(())
}
/// Overwrites 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.
///
/// - `origin`: Must pass `Root`.
/// - `new`: Desired value for `QueueConfigData.weight_restrict_decay`.
#[pallet::weight((T::WeightInfo::set_config_with_weight(), DispatchClass::Operational,))]
pub fn update_weight_restrict_decay(origin: OriginFor<T>, new: Weight) -> DispatchResult {
ensure_root(origin)?;
QueueConfig::<T>::mutate(|data| data.weight_restrict_decay = new);
Ok(())
}
/// Overwrite 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.
///
/// - `origin`: Must pass `Root`.
/// - `new`: Desired value for `QueueConfigData.xcmp_max_individual_weight`.
#[pallet::weight((T::WeightInfo::set_config_with_weight(), DispatchClass::Operational,))]
pub fn update_xcmp_max_individual_weight(
origin: OriginFor<T>,
new: Weight,
) -> DispatchResult {
ensure_root(origin)?;
QueueConfig::<T>::mutate(|data| data.xcmp_max_individual_weight = new);
Ok(())
}
}
#[pallet::event]
@@ -220,6 +348,10 @@ pub mod pallet {
/// available free overweight index.
#[pallet::storage]
pub(super) type OverweightCount<T: Config> = StorageValue<_, OverweightIndex, ValueQuery>;
/// Whether or not the XCMP queue is suspended from executing incoming XCMs or not.
#[pallet::storage]
pub(super) type QueueSuspended<T: Config> = StorageValue<_, bool, ValueQuery>;
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug, TypeInfo)]
@@ -370,10 +502,8 @@ impl<T: Config> Pallet<T> {
let have_active = s[index].last_index > s[index].first_index;
let appended = have_active &&
<OutboundXcmpMessages<T>>::mutate(recipient, s[index].last_index - 1, |s| {
if XcmpMessageFormat::decode_and_advance_with_depth_limit(
MAX_XCM_DECODE_DEPTH,
&mut &s[..],
) != Ok(format)
if XcmpMessageFormat::decode_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut &s[..]) !=
Ok(format)
{
return false
}
@@ -497,7 +627,7 @@ impl<T: Config> Pallet<T> {
XcmpMessageFormat::ConcatenatedVersionedXcm => {
while !remaining_fragments.is_empty() {
last_remaining_fragments = remaining_fragments;
if let Ok(xcm) = VersionedXcm::<T::Call>::decode_and_advance_with_depth_limit(
if let Ok(xcm) = VersionedXcm::<T::Call>::decode_with_depth_limit(
MAX_XCM_DECODE_DEPTH,
&mut remaining_fragments,
) {
@@ -538,26 +668,24 @@ impl<T: Config> Pallet<T> {
XcmpMessageFormat::ConcatenatedEncodedBlob => {
while !remaining_fragments.is_empty() {
last_remaining_fragments = remaining_fragments;
match <Vec<u8>>::decode_all(&mut remaining_fragments) {
Ok(blob) if remaining_fragments.len() < last_remaining_fragments.len() => {
let weight = max_weight - weight_used;
match Self::handle_blob_message(sender, sent_at, blob, weight) {
Ok(used) => weight_used = weight_used.saturating_add(used),
Err(true) => {
// That message didn't get processed this time because of being
// too heavy. We leave it around for next time and bail.
remaining_fragments = last_remaining_fragments;
break
},
Err(false) => {
// Message invalid; don't attempt to retry
},
}
},
_ => {
debug_assert!(false, "Invalid incoming blob message data");
remaining_fragments = &b""[..];
},
if let Ok(blob) = <Vec<u8>>::decode(&mut remaining_fragments) {
let weight = max_weight - weight_used;
match Self::handle_blob_message(sender, sent_at, blob, weight) {
Ok(used) => weight_used = weight_used.saturating_add(used),
Err(true) => {
// That message didn't get processed this time because of being
// too heavy. We leave it around for next time and bail.
remaining_fragments = last_remaining_fragments;
break
},
Err(false) => {
// Message invalid; don't attempt to retry
},
}
} else {
debug_assert!(false, "Invalid incoming blob message data");
remaining_fragments = &b""[..];
}
}
},
@@ -619,6 +747,8 @@ impl<T: Config> Pallet<T> {
/// for the second &c. though empirical and or practical factors may give rise to adjusting it
/// further.
fn service_xcmp_queue(max_weight: Weight) -> Weight {
let suspended = QueueSuspended::<T>::get();
let mut status = <InboundXcmpStatus<T>>::get(); // <- sorted.
if status.len() == 0 {
return 0
@@ -650,6 +780,17 @@ impl<T: Config> Pallet<T> {
{
let index = shuffled[shuffle_index];
let sender = status[index].sender;
let sender_origin = T::ControllerOriginConverter::convert_origin(
(1, Parachain(sender.into())),
OriginKind::Superuser,
);
let is_controller = sender_origin
.map_or(false, |origin| T::ControllerOrigin::try_origin(origin).is_ok());
if suspended && !is_controller {
shuffle_index += 1;
continue
}
if weight_available != max_weight {
// Get incrementally closer to freeing up max_weight for message execution over the
@@ -760,7 +901,7 @@ impl<T: Config> XcmpMessageHandler for Pallet<T> {
for (sender, sent_at, data) in iter {
// Figure out the message format.
let mut data_ref = data;
let format = match XcmpMessageFormat::decode_and_advance_with_depth_limit(
let format = match XcmpMessageFormat::decode_with_depth_limit(
MAX_XCM_DECODE_DEPTH,
&mut data_ref,
) {
+33 -4
View File
@@ -15,16 +15,20 @@
use super::*;
use crate as xcmp_queue;
use frame_support::parameter_types;
use core::marker::PhantomData;
use cumulus_primitives_core::{IsSystem, ParaId};
use frame_support::{parameter_types, traits::OriginTrait};
use frame_system::EnsureRoot;
use sp_core::H256;
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup},
};
use xcm::prelude::*;
use xcm_builder::{
CurrencyAdapter, FixedWeightBounds, IsConcrete, LocationInverter, NativeAsset, ParentIsDefault,
CurrencyAdapter, FixedWeightBounds, IsConcrete, LocationInverter, NativeAsset, ParentIsPreset,
};
use xcm_executor::traits::ConvertOrigin;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
@@ -98,7 +102,7 @@ impl pallet_balances::Config for Test {
impl cumulus_pallet_parachain_system::Config for Test {
type Event = Event;
type OnValidationData = ();
type OnSystemEvent = ();
type SelfParaId = ();
type OutboundXcmpMessageSource = XcmpQueue;
type DmpMessageHandler = ();
@@ -129,7 +133,7 @@ pub type LocalAssetTransactor = CurrencyAdapter<
(),
>;
pub type LocationToAccountId = (ParentIsDefault<AccountId>,);
pub type LocationToAccountId = (ParentIsPreset<AccountId>,);
pub struct XcmConfig;
impl xcm_executor::Config for XcmConfig {
@@ -157,12 +161,37 @@ pub type XcmRouter = (
XcmpQueue,
);
pub struct SystemParachainAsSuperuser<Origin>(PhantomData<Origin>);
impl<Origin: OriginTrait> ConvertOrigin<Origin> for SystemParachainAsSuperuser<Origin> {
fn convert_origin(
origin: impl Into<MultiLocation>,
kind: OriginKind,
) -> Result<Origin, MultiLocation> {
let origin = origin.into();
if kind == OriginKind::Superuser &&
matches!(
origin,
MultiLocation {
parents: 1,
interior: X1(Parachain(id)),
} if ParaId::from(id).is_system(),
) {
Ok(Origin::root())
} else {
Err(origin)
}
}
}
impl Config for Test {
type Event = Event;
type XcmExecutor = xcm_executor::XcmExecutor<XcmConfig>;
type ChannelInfo = ParachainSystem;
type VersionWrapper = ();
type ExecuteOverweightOrigin = EnsureRoot<AccountId>;
type ControllerOrigin = EnsureRoot<AccountId>;
type ControllerOriginConverter = SystemParachainAsSuperuser<Origin>;
type WeightInfo = ();
}
pub fn new_test_ext() -> sp_io::TestExternalities {
+117 -5
View File
@@ -15,8 +15,9 @@
use super::*;
use cumulus_primitives_core::XcmpMessageHandler;
use frame_support::assert_noop;
use mock::{new_test_ext, Origin, Test, XcmpQueue};
use frame_support::{assert_noop, assert_ok};
use mock::{new_test_ext, Call, Origin, Test, XcmpQueue};
use sp_runtime::traits::BadOrigin;
#[test]
fn one_message_does_not_panic() {
@@ -46,10 +47,12 @@ fn bad_message_is_handled() {
});
}
/// Tests that a blob message is handled. Currently this isn't implemented and panics when debug assertions
/// are enabled. When this feature is enabled, this test should be rewritten properly.
#[test]
#[should_panic = "Invalid incoming blob message data"]
#[should_panic = "Blob messages not handled."]
#[cfg(debug_assertions)]
fn other_bad_message_is_handled() {
fn handle_blob_message() {
new_test_ext().execute_with(|| {
let bad_data = vec![
1, 1, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 64, 239,
@@ -58,7 +61,6 @@ fn other_bad_message_is_handled() {
];
InboundXcmpMessages::<Test>::insert(ParaId::from(1000), 1, bad_data);
let format = XcmpMessageFormat::ConcatenatedEncodedBlob;
// This should exit with an error.
XcmpQueue::process_xcmp_message(1000.into(), (1, format), 10_000_000_000, 10_000_000_000);
});
}
@@ -82,3 +84,113 @@ fn service_overweight_bad_xcm_format() {
assert_noop!(XcmpQueue::service_overweight(Origin::root(), 0, 1000), Error::<Test>::BadXcm);
});
}
#[test]
fn suspend_xcm_execution_works() {
new_test_ext().execute_with(|| {
QueueSuspended::<Test>::put(true);
let xcm = VersionedXcm::from(Xcm::<Call>(vec![Instruction::<Call>::ClearOrigin])).encode();
let mut message_format = XcmpMessageFormat::ConcatenatedVersionedXcm.encode();
message_format.extend(xcm.clone());
let messages = vec![(ParaId::from(999), 1u32.into(), message_format.as_slice())];
// This should have executed the incoming XCM, because it came from a system parachain
XcmpQueue::handle_xcmp_messages(messages.into_iter(), Weight::max_value());
let queued_xcm = InboundXcmpMessages::<Test>::get(ParaId::from(999), 1u32);
assert!(queued_xcm.is_empty());
let messages = vec![(ParaId::from(2000), 1u32.into(), message_format.as_slice())];
// This shouldn't have executed the incoming XCM
XcmpQueue::handle_xcmp_messages(messages.into_iter(), Weight::max_value());
let queued_xcm = InboundXcmpMessages::<Test>::get(ParaId::from(2000), 1u32);
assert_eq!(queued_xcm, xcm);
});
}
#[test]
fn update_suspend_threshold_works() {
new_test_ext().execute_with(|| {
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.suspend_threshold, 2);
assert_ok!(XcmpQueue::update_suspend_threshold(Origin::root(), 3));
assert_noop!(XcmpQueue::update_suspend_threshold(Origin::signed(2), 5), BadOrigin);
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.suspend_threshold, 3);
});
}
#[test]
fn update_drop_threshold_works() {
new_test_ext().execute_with(|| {
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.drop_threshold, 5);
assert_ok!(XcmpQueue::update_drop_threshold(Origin::root(), 6));
assert_noop!(XcmpQueue::update_drop_threshold(Origin::signed(2), 7), BadOrigin);
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.drop_threshold, 6);
});
}
#[test]
fn update_resume_threshold_works() {
new_test_ext().execute_with(|| {
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.resume_threshold, 1);
assert_ok!(XcmpQueue::update_resume_threshold(Origin::root(), 2));
assert_noop!(XcmpQueue::update_resume_threshold(Origin::signed(7), 3), BadOrigin);
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.resume_threshold, 2);
});
}
#[test]
fn update_threshold_weight_works() {
new_test_ext().execute_with(|| {
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.threshold_weight, 100_000);
assert_ok!(XcmpQueue::update_threshold_weight(Origin::root(), 10_000));
assert_noop!(XcmpQueue::update_threshold_weight(Origin::signed(5), 10_000_000), BadOrigin);
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.threshold_weight, 10_000);
});
}
#[test]
fn update_weight_restrict_decay_works() {
new_test_ext().execute_with(|| {
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.weight_restrict_decay, 2);
assert_ok!(XcmpQueue::update_weight_restrict_decay(Origin::root(), 5));
assert_noop!(XcmpQueue::update_weight_restrict_decay(Origin::signed(6), 4), BadOrigin);
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.weight_restrict_decay, 5);
});
}
#[test]
fn update_xcmp_max_individual_weight() {
new_test_ext().execute_with(|| {
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.xcmp_max_individual_weight, 20 * WEIGHT_PER_MILLIS);
assert_ok!(XcmpQueue::update_xcmp_max_individual_weight(
Origin::root(),
30 * WEIGHT_PER_MILLIS
));
assert_noop!(
XcmpQueue::update_xcmp_max_individual_weight(Origin::signed(3), 10 * WEIGHT_PER_MILLIS),
BadOrigin
);
let data: QueueConfigData = <QueueConfig<Test>>::get();
assert_eq!(data.xcmp_max_individual_weight, 30 * WEIGHT_PER_MILLIS);
});
}
+48
View File
@@ -0,0 +1,48 @@
#![allow(unused_parens)]
#![allow(unused_imports)]
use frame_support::{
traits::Get,
weights::{constants::RocksDbWeight, Weight},
};
use sp_std::marker::PhantomData;
// Implemented by autogenerated benchmarking code.
pub trait WeightInfo {
fn set_config_with_u32() -> Weight;
fn set_config_with_weight() -> Weight;
}
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: XcmpQueue QueueConfig (r:1 w:1)
fn set_config_with_u32() -> Weight {
(2_717_000 as Weight)
.saturating_add(T::DbWeight::get().reads(1 as Weight))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
}
// Storage: XcmpQueue QueueConfig (r:1 w:1)
fn set_config_with_weight() -> Weight {
(2_717_000 as Weight)
.saturating_add(T::DbWeight::get().reads(1 as Weight))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
}
}
impl WeightInfo for () {
// Storage: XcmpQueue QueueConfig (r:1 w:1)
fn set_config_with_u32() -> Weight {
(2_717_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
}
// Storage: XcmpQueue QueueConfig (r:1 w:1)
fn set_config_with_weight() -> Weight {
(2_717_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
}
}