diff --git a/pallets/xcmp-queue/src/lib.rs b/pallets/xcmp-queue/src/lib.rs index c7bddaa82dd..39e972d6ebe 100644 --- a/pallets/xcmp-queue/src/lib.rs +++ b/pallets/xcmp-queue/src/lib.rs @@ -38,7 +38,10 @@ 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 +50,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::*; @@ -82,6 +86,13 @@ pub mod pallet { /// The origin that is allowed to execute overweight messages. type ExecuteOverweightOrigin: EnsureOrigin; + + /// The origin that is allowed to resume or suspend the XCMP queue. + type ControllerOrigin: EnsureOrigin; + + /// The conversion function used to attempt to convert an XCM `MultiLocation` origin to a + /// superuser origin. + type ControllerOriginConverter: ConvertOrigin; } #[pallet::hooks] @@ -130,6 +141,32 @@ pub mod pallet { 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))] + pub fn suspend_xcm_execution(origin: OriginFor) -> DispatchResult { + T::ControllerOrigin::ensure_origin(origin)?; + + QueueSuspended::::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))] + pub fn resume_xcm_execution(origin: OriginFor) -> DispatchResult { + T::ControllerOrigin::ensure_origin(origin)?; + + QueueSuspended::::put(false); + + Ok(()) + } } #[pallet::event] @@ -221,6 +258,10 @@ pub mod pallet { /// available free overweight index. #[pallet::storage] pub(super) type OverweightCount = StorageValue<_, OverweightIndex, ValueQuery>; + + /// Whether or not the XCMP queue is suspended from executing incoming XCMs or not. + #[pallet::storage] + pub(super) type QueueSuspended = StorageValue<_, bool, ValueQuery>; } #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug, TypeInfo)] @@ -618,6 +659,8 @@ impl Pallet { /// 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::::get(); + let mut status = >::get(); // <- sorted. if status.len() == 0 { return 0 @@ -649,6 +692,17 @@ impl Pallet { { 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 diff --git a/pallets/xcmp-queue/src/mock.rs b/pallets/xcmp-queue/src/mock.rs index 5f753fb3470..b783ddf11f4 100644 --- a/pallets/xcmp-queue/src/mock.rs +++ b/pallets/xcmp-queue/src/mock.rs @@ -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, ParentIsPreset, }; +use xcm_executor::traits::ConvertOrigin; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -154,12 +158,36 @@ pub type XcmRouter = ( XcmpQueue, ); +pub struct SystemParachainAsSuperuser(PhantomData); +impl ConvertOrigin for SystemParachainAsSuperuser { + fn convert_origin( + origin: impl Into, + kind: OriginKind, + ) -> Result { + 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; type ChannelInfo = ParachainSystem; type VersionWrapper = (); type ExecuteOverweightOrigin = EnsureRoot; + type ControllerOrigin = EnsureRoot; + type ControllerOriginConverter = SystemParachainAsSuperuser; } 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 59726172eb9..1ec894c5676 100644 --- a/pallets/xcmp-queue/src/tests.rs +++ b/pallets/xcmp-queue/src/tests.rs @@ -16,7 +16,7 @@ use super::*; use cumulus_primitives_core::XcmpMessageHandler; use frame_support::assert_noop; -use mock::{new_test_ext, Origin, Test, XcmpQueue}; +use mock::{new_test_ext, Call, Origin, Test, XcmpQueue}; #[test] fn one_message_does_not_panic() { @@ -83,3 +83,29 @@ fn service_overweight_bad_xcm_format() { assert_noop!(XcmpQueue::service_overweight(Origin::root(), 0, 1000), Error::::BadXcm); }); } + +#[test] +fn suspend_xcm_execution_works() { + new_test_ext().execute_with(|| { + QueueSuspended::::put(true); + + let xcm = VersionedXcm::from(Xcm::(vec![Instruction::::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::::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::::get(ParaId::from(2000), 1u32); + assert_eq!(queued_xcm, xcm); + }); +} diff --git a/parachain-template/runtime/src/lib.rs b/parachain-template/runtime/src/lib.rs index 944933f42cd..e04bbfbc04e 100644 --- a/parachain-template/runtime/src/lib.rs +++ b/parachain-template/runtime/src/lib.rs @@ -525,6 +525,8 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type ChannelInfo = ParachainSystem; type VersionWrapper = (); type ExecuteOverweightOrigin = EnsureRoot; + type ControllerOrigin = EnsureRoot; + type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; } 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 218d5ff5d6a..fdef68a3da3 100644 --- a/polkadot-parachains/rococo-parachain/src/lib.rs +++ b/polkadot-parachains/rococo-parachain/src/lib.rs @@ -454,6 +454,8 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type ChannelInfo = ParachainSystem; type VersionWrapper = (); type ExecuteOverweightOrigin = EnsureRoot; + type ControllerOrigin = EnsureRoot; + type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; } 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 21d509441cd..de27fa64745 100644 --- a/polkadot-parachains/statemine/src/lib.rs +++ b/polkadot-parachains/statemine/src/lib.rs @@ -609,6 +609,9 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type ChannelInfo = ParachainSystem; type VersionWrapper = PolkadotXcm; type ExecuteOverweightOrigin = EnsureRoot; + type ControllerOrigin = + EnsureOneOf, EnsureXcm>>; + type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; } 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 a7a2305088b..22d5ac64022 100644 --- a/polkadot-parachains/statemint/src/lib.rs +++ b/polkadot-parachains/statemint/src/lib.rs @@ -622,6 +622,9 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type ChannelInfo = ParachainSystem; type VersionWrapper = PolkadotXcm; type ExecuteOverweightOrigin = EnsureRoot; + type ControllerOrigin = + EnsureOneOf, EnsureXcm>>; + type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; } 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 c7145497bba..04046e63a60 100644 --- a/polkadot-parachains/westmint/src/lib.rs +++ b/polkadot-parachains/westmint/src/lib.rs @@ -596,6 +596,8 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type ChannelInfo = ParachainSystem; type VersionWrapper = PolkadotXcm; type ExecuteOverweightOrigin = EnsureRoot; + type ControllerOrigin = EnsureRoot; + type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; } impl cumulus_pallet_dmp_queue::Config for Runtime { diff --git a/primitives/core/src/lib.rs b/primitives/core/src/lib.rs index 93019c47b75..e2519e67879 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -25,7 +25,7 @@ use sp_std::prelude::*; pub use polkadot_core_primitives::InboundDownwardMessage; pub use polkadot_parachain::primitives::{ - DmpMessageHandler, Id as ParaId, UpwardMessage, ValidationParams, XcmpMessageFormat, + DmpMessageHandler, Id as ParaId, IsSystem, UpwardMessage, ValidationParams, XcmpMessageFormat, XcmpMessageHandler, }; pub use polkadot_primitives::v1::{