mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-04 08:07:24 +00:00
Use Message Queue as DMP and XCMP dispatch queue (#1246)
(imported from https://github.com/paritytech/cumulus/pull/2157) ## Changes This MR refactores the XCMP, Parachains System and DMP pallets to use the [MessageQueue](https://github.com/paritytech/substrate/pull/12485) for delayed execution of incoming messages. The DMP pallet is entirely replaced by the MQ and thereby removed. This allows for PoV-bounded execution and resolves a number of issues that stem from the current work-around. All System Parachains adopt this change. The most important changes are in `primitives/core/src/lib.rs`, `parachains/common/src/process_xcm_message.rs`, `pallets/parachain-system/src/lib.rs`, `pallets/xcmp-queue/src/lib.rs` and the runtime configs. ### DMP Queue Pallet The pallet got removed and its logic refactored into parachain-system. Overweight message management can be done directly through the MQ pallet. Final undeployment migrations are provided by `cumulus_pallet_dmp_queue::UndeployDmpQueue` and `DeleteDmpQueue` that can be configured with an aux config trait like: ```rust parameter_types! { pub const DmpQueuePalletName: &'static str = \"DmpQueue\" < CHANGE ME; pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent; } impl cumulus_pallet_dmp_queue::MigrationConfig for Runtime { type PalletName = DmpQueuePalletName; type DmpHandler = frame_support::traits::EnqueueWithOrigin<MessageQueue, RelayOrigin>; type DbWeight = <Runtime as frame_system::Config>::DbWeight; } // And adding them to your Migrations tuple: pub type Migrations = ( ... cumulus_pallet_dmp_queue::UndeployDmpQueue<Runtime>, cumulus_pallet_dmp_queue::DeleteDmpQueue<Runtime>, ); ``` ### XCMP Queue pallet Removed all dispatch queue functionality. Incoming XCMP messages are now either: Immediately handled if they are Signals, enqueued into the MQ pallet otherwise. New config items for the XCMP queue pallet: ```rust /// The actual queue implementation that retains the messages for later processing. type XcmpQueue: EnqueueMessage<ParaId>; /// How a XCM over HRMP from a sibling parachain should be processed. type XcmpProcessor: ProcessMessage<Origin = ParaId>; /// The maximal number of suspended XCMP channels at the same time. #[pallet::constant] type MaxInboundSuspended: Get<u32>; ``` How to configure those: ```rust // Use the MessageQueue pallet to store messages for later processing. The `TransformOrigin` is needed since // the MQ pallet itself operators on `AggregateMessageOrigin` but we want to enqueue `ParaId`s. type XcmpQueue = TransformOrigin<MessageQueue, AggregateMessageOrigin, ParaId, ParaIdToSibling>; // Process XCMP messages from siblings. This is type-safe to only accept `ParaId`s. They will be dispatched // with origin `Junction::Sibling(…)`. type XcmpProcessor = ProcessFromSibling< ProcessXcmMessage< AggregateMessageOrigin, xcm_executor::XcmExecutor<xcm_config::XcmConfig>, RuntimeCall, >, >; // Not really important what to choose here. Just something larger than the maximal number of channels. type MaxInboundSuspended = sp_core::ConstU32<1_000>; ``` The `InboundXcmpStatus` storage item was replaced by `InboundXcmpSuspended` since it now only tracks inbound queue suspension and no message indices anymore. Now only sends the most recent channel `Signals`, as all prio ones are out-dated anyway. ### Parachain System pallet For `DMP` messages instead of forwarding them to the `DMP` pallet, it now pushes them to the configured `DmpQueue`. The message processing which was triggered in `set_validation_data` is now being done by the MQ pallet `on_initialize`. XCMP messages are still handed off to the `XcmpMessageHandler` (XCMP-Queue pallet) - no change here. New config items for the parachain system pallet: ```rust /// Queues inbound downward messages for delayed processing. /// /// Analogous to the `XcmpQueue` of the XCMP queue pallet. type DmpQueue: EnqueueMessage<AggregateMessageOrigin>; ``` How to configure: ```rust /// Use the MQ pallet to store DMP messages for delayed processing. type DmpQueue = MessageQueue; ``` ## Message Flow The flow of messages on the parachain side. Messages come in from the left via the `Validation Data` and finally end up at the `Xcm Executor` on the right.  ## Further changes - Bumped the default suspension, drop and resume thresholds in `QueueConfigData::default()`. - `XcmpQueue::{suspend_xcm_execution, resume_xcm_execution}` errors when they would be a noop. - Properly validate the `QueueConfigData` before setting it. - Marked weight files as auto-generated so they wont auto-expand in the MR files view. - Move the `hypothetical` asserts to `frame_support` under the name `experimental_hypothetically` Questions: - [ ] What about the ugly `#[cfg(feature = \"runtime-benchmarks\")]` in the runtimes? Not sure how to best fix. Just having them like this makes tests fail that rely on the real message processor when the feature is enabled. - [ ] Need a good weight for `MessageQueueServiceWeight`. The scheduler already takes 80% so I put it to 10% but that is quite low. TODO: - [x] Remove c&p code after https://github.com/paritytech/polkadot/pull/6271 - [x] Use `HandleMessage` once it is public in Substrate - [x] fix `runtime-benchmarks` feature https://github.com/paritytech/polkadot/pull/6966 - [x] Benchmarks - [x] Tests - [ ] Migrate `InboundXcmpStatus` to `InboundXcmpSuspended` - [x] Possibly cleanup Migrations (DMP+XCMP) - [x] optional: create `TransformProcessMessageOrigin` in Substrate and replace `ProcessFromSibling` - [ ] Rerun weights on ref HW --------- Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: command-bot <>
This commit is contained in:
committed by
GitHub
parent
7df0417bcd
commit
e1c033ebe1
@@ -13,247 +13,325 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::*;
|
||||
use super::{
|
||||
mock::{mk_page, v2_xcm, v3_xcm, EnqueuedMessages, HRMP_PARA_ID},
|
||||
*,
|
||||
};
|
||||
use XcmpMessageFormat::*;
|
||||
|
||||
use codec::Input;
|
||||
use cumulus_primitives_core::{ParaId, XcmpMessageHandler};
|
||||
use frame_support::{assert_noop, assert_ok};
|
||||
use mock::{new_test_ext, ParachainSystem, RuntimeCall, RuntimeOrigin, Test, XcmpQueue};
|
||||
use sp_runtime::traits::BadOrigin;
|
||||
use frame_support::{
|
||||
assert_err, assert_noop, assert_ok, assert_storage_noop, hypothetically,
|
||||
traits::{Footprint, Hooks},
|
||||
StorageNoopGuard,
|
||||
};
|
||||
use mock::{new_test_ext, ParachainSystem, RuntimeOrigin as Origin, Test, XcmpQueue};
|
||||
use sp_runtime::traits::{BadOrigin, Zero};
|
||||
use std::iter::{once, repeat};
|
||||
|
||||
#[test]
|
||||
fn one_message_does_not_panic() {
|
||||
fn empty_concatenated_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let message_format = XcmpMessageFormat::ConcatenatedVersionedXcm.encode();
|
||||
let messages = vec![(Default::default(), 1u32, message_format.as_slice())];
|
||||
let data = ConcatenatedVersionedXcm.encode();
|
||||
|
||||
// This shouldn't cause a panic
|
||||
XcmpQueue::handle_xcmp_messages(messages.into_iter(), Weight::MAX);
|
||||
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "Invalid incoming blob message data"]
|
||||
fn xcm_enqueueing_basic_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let xcm = VersionedXcm::<Test>::from(Xcm::<Test>(vec![ClearOrigin])).encode();
|
||||
let data = [ConcatenatedVersionedXcm.encode(), xcm.clone()].concat();
|
||||
|
||||
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX);
|
||||
|
||||
assert_eq!(EnqueuedMessages::get(), vec![(1000.into(), xcm)]);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xcm_enqueueing_many_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let mut encoded_xcms = vec![];
|
||||
for i in 0..10 {
|
||||
let xcm = VersionedXcm::<Test>::from(Xcm::<Test>(vec![ClearOrigin; i as usize]));
|
||||
encoded_xcms.push(xcm.encode());
|
||||
}
|
||||
let mut data = ConcatenatedVersionedXcm.encode();
|
||||
data.extend(encoded_xcms.iter().flatten());
|
||||
|
||||
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX);
|
||||
|
||||
assert_eq!(
|
||||
EnqueuedMessages::get(),
|
||||
encoded_xcms.into_iter().map(|xcm| (1000.into(), xcm)).collect::<Vec<_>>(),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xcm_enqueueing_multiple_times_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let mut encoded_xcms = vec![];
|
||||
for _ in 0..10 {
|
||||
let xcm = VersionedXcm::<Test>::from(Xcm::<Test>(vec![ClearOrigin]));
|
||||
encoded_xcms.push(xcm.encode());
|
||||
}
|
||||
let mut data = ConcatenatedVersionedXcm.encode();
|
||||
data.extend(encoded_xcms.iter().flatten());
|
||||
|
||||
for i in 0..10 {
|
||||
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX);
|
||||
assert_eq!((i + 1) * 10, EnqueuedMessages::get().len());
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
EnqueuedMessages::get(),
|
||||
encoded_xcms
|
||||
.into_iter()
|
||||
.map(|xcm| (1000.into(), xcm))
|
||||
.cycle()
|
||||
.take(100)
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(debug_assertions, should_panic = "Defensive failure")]
|
||||
fn xcm_enqueueing_starts_dropping_on_overflow() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let xcm = VersionedXcm::<Test>::from(Xcm::<Test>(vec![ClearOrigin]));
|
||||
let data = (ConcatenatedVersionedXcm, xcm).encode();
|
||||
// Its possible to enqueue 256 messages at most:
|
||||
let limit = 256;
|
||||
|
||||
XcmpQueue::handle_xcmp_messages(
|
||||
repeat((1000.into(), 1, data.as_slice())).take(limit * 2),
|
||||
Weight::MAX,
|
||||
);
|
||||
assert_eq!(EnqueuedMessages::get().len(), limit);
|
||||
// The drop threshold for pages is 48, the others numbers dont really matter:
|
||||
assert_eq!(
|
||||
<Test as Config>::XcmpQueue::footprint(1000.into()),
|
||||
QueueFootprint { storage: Footprint { count: 256, size: 768 }, pages: 48 }
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
/// First enqueue 10 good, 1 bad and then 10 good XCMs.
|
||||
///
|
||||
/// It should only process the first 10 good though.
|
||||
#[test]
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn xcm_enqueueing_broken_xcm_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let mut encoded_xcms = vec![];
|
||||
for _ in 0..10 {
|
||||
let xcm = VersionedXcm::<Test>::from(Xcm::<Test>(vec![ClearOrigin]));
|
||||
encoded_xcms.push(xcm.encode());
|
||||
}
|
||||
let mut good = ConcatenatedVersionedXcm.encode();
|
||||
good.extend(encoded_xcms.iter().flatten());
|
||||
|
||||
let mut bad = ConcatenatedVersionedXcm.encode();
|
||||
bad.extend(vec![0u8].into_iter());
|
||||
|
||||
// Of we enqueue them in multiple pages, then its fine.
|
||||
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, good.as_slice())), Weight::MAX);
|
||||
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, bad.as_slice())), Weight::MAX);
|
||||
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, good.as_slice())), Weight::MAX);
|
||||
assert_eq!(20, EnqueuedMessages::get().len());
|
||||
|
||||
assert_eq!(
|
||||
EnqueuedMessages::get(),
|
||||
encoded_xcms
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|xcm| (1000.into(), xcm))
|
||||
.cycle()
|
||||
.take(20)
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
EnqueuedMessages::set(&vec![]);
|
||||
|
||||
// But if we do it all in one page, then it only uses the first 10:
|
||||
XcmpQueue::handle_xcmp_messages(
|
||||
vec![(1000.into(), 1, good.as_slice()), (1000.into(), 1, bad.as_slice())].into_iter(),
|
||||
Weight::MAX,
|
||||
);
|
||||
assert_eq!(10, EnqueuedMessages::get().len());
|
||||
assert_eq!(
|
||||
EnqueuedMessages::get(),
|
||||
encoded_xcms
|
||||
.into_iter()
|
||||
.map(|xcm| (1000.into(), xcm))
|
||||
.cycle()
|
||||
.take(10)
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
/// Message blobs are not supported and panic in debug mode.
|
||||
#[test]
|
||||
#[should_panic = "Blob messages are unhandled"]
|
||||
#[cfg(debug_assertions)]
|
||||
fn bad_message_is_handled() {
|
||||
fn bad_blob_message_panics() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let bad_data = vec![
|
||||
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, 139, 0,
|
||||
0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 37, 0,
|
||||
0, 0, 0, 0, 0, 0, 16, 0, 127, 147,
|
||||
];
|
||||
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),
|
||||
&mut 0,
|
||||
Weight::from_parts(10_000_000_000, 0),
|
||||
Weight::from_parts(10_000_000_000, 0),
|
||||
);
|
||||
let data = [ConcatenatedEncodedBlob.encode(), vec![1].encode()].concat();
|
||||
|
||||
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX);
|
||||
});
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// Message blobs do not panic in release mode but are just a No-OP.
|
||||
#[test]
|
||||
#[should_panic = "Blob messages not handled."]
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn bad_blob_message_no_panic() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let data = [ConcatenatedEncodedBlob.encode(), vec![1].encode()].concat();
|
||||
|
||||
frame_support::assert_storage_noop!(XcmpQueue::handle_xcmp_messages(
|
||||
once((1000.into(), 1, data.as_slice())),
|
||||
Weight::MAX,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
/// Invalid concatenated XCMs panic in debug mode.
|
||||
#[test]
|
||||
#[should_panic = "HRMP inbound decode stream broke; page will be dropped."]
|
||||
#[cfg(debug_assertions)]
|
||||
fn handle_blob_message() {
|
||||
fn handle_invalid_data_panics() {
|
||||
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,
|
||||
139, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0,
|
||||
37, 0, 0, 0, 0, 0, 0, 0, 16, 0, 127, 147,
|
||||
];
|
||||
InboundXcmpMessages::<Test>::insert(ParaId::from(1000), 1, bad_data);
|
||||
let format = XcmpMessageFormat::ConcatenatedEncodedBlob;
|
||||
XcmpQueue::process_xcmp_message(
|
||||
1000.into(),
|
||||
(1, format),
|
||||
&mut 0,
|
||||
Weight::from_parts(10_000_000_000, 0),
|
||||
Weight::from_parts(10_000_000_000, 0),
|
||||
);
|
||||
let data = [ConcatenatedVersionedXcm.encode(), Xcm::<Test>(vec![]).encode()].concat();
|
||||
|
||||
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX);
|
||||
});
|
||||
}
|
||||
|
||||
/// Invalid concatenated XCMs do not panic in release mode but are just a No-OP.
|
||||
#[test]
|
||||
#[should_panic = "Invalid incoming XCMP message data"]
|
||||
#[cfg(debug_assertions)]
|
||||
fn handle_invalid_data() {
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn handle_invalid_data_no_panic() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let data = Xcm::<Test>(vec![]).encode();
|
||||
InboundXcmpMessages::<Test>::insert(ParaId::from(1000), 1, data);
|
||||
let format = XcmpMessageFormat::ConcatenatedVersionedXcm;
|
||||
XcmpQueue::process_xcmp_message(
|
||||
1000.into(),
|
||||
(1, format),
|
||||
&mut 0,
|
||||
Weight::from_parts(10_000_000_000, 0),
|
||||
Weight::from_parts(10_000_000_000, 0),
|
||||
);
|
||||
});
|
||||
}
|
||||
let data = [ConcatenatedVersionedXcm.encode(), Xcm::<Test>(vec![]).encode()].concat();
|
||||
|
||||
#[test]
|
||||
fn service_overweight_unknown() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(
|
||||
XcmpQueue::service_overweight(RuntimeOrigin::root(), 0, Weight::from_parts(1000, 1000)),
|
||||
Error::<Test>::BadOverweightIndex,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn service_overweight_bad_xcm_format() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let bad_xcm = vec![255];
|
||||
Overweight::<Test>::insert(0, (ParaId::from(1000), 0, bad_xcm));
|
||||
|
||||
assert_noop!(
|
||||
XcmpQueue::service_overweight(RuntimeOrigin::root(), 0, Weight::from_parts(1000, 1000)),
|
||||
Error::<Test>::BadXcm
|
||||
);
|
||||
frame_support::assert_storage_noop!(XcmpQueue::handle_xcmp_messages(
|
||||
once((1000.into(), 1, data.as_slice())),
|
||||
Weight::MAX,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suspend_xcm_execution_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert!(!XcmpQueue::is_paused(&2000.into()));
|
||||
QueueSuspended::<Test>::put(true);
|
||||
assert!(XcmpQueue::is_paused(&2000.into()));
|
||||
// System parachains can bypass suspension:
|
||||
assert!(!XcmpQueue::is_paused(&999.into()));
|
||||
});
|
||||
}
|
||||
|
||||
let xcm =
|
||||
VersionedXcm::from(Xcm::<RuntimeCall>(vec![Instruction::<RuntimeCall>::ClearOrigin]))
|
||||
.encode();
|
||||
let mut message_format = XcmpMessageFormat::ConcatenatedVersionedXcm.encode();
|
||||
message_format.extend(xcm.clone());
|
||||
let messages = vec![(ParaId::from(999), 1u32, message_format.as_slice())];
|
||||
#[test]
|
||||
fn suspend_and_resume_xcm_execution_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(XcmpQueue::suspend_xcm_execution(Origin::signed(1)), BadOrigin);
|
||||
assert_ok!(XcmpQueue::suspend_xcm_execution(Origin::root()));
|
||||
assert_noop!(
|
||||
XcmpQueue::suspend_xcm_execution(Origin::root()),
|
||||
Error::<Test>::AlreadySuspended
|
||||
);
|
||||
assert!(QueueSuspended::<Test>::get());
|
||||
|
||||
// This should have executed the incoming XCM, because it came from a system parachain
|
||||
XcmpQueue::handle_xcmp_messages(messages.into_iter(), Weight::MAX);
|
||||
assert_noop!(XcmpQueue::resume_xcm_execution(Origin::signed(1)), BadOrigin);
|
||||
assert_ok!(XcmpQueue::resume_xcm_execution(Origin::root()));
|
||||
assert_noop!(
|
||||
XcmpQueue::resume_xcm_execution(Origin::root()),
|
||||
Error::<Test>::AlreadyResumed
|
||||
);
|
||||
assert!(!QueueSuspended::<Test>::get());
|
||||
});
|
||||
}
|
||||
|
||||
let queued_xcm = InboundXcmpMessages::<Test>::get(ParaId::from(999), 1u32);
|
||||
assert!(queued_xcm.is_empty());
|
||||
#[test]
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn xcm_enqueueing_backpressure_works() {
|
||||
let para: ParaId = 1000.into();
|
||||
new_test_ext().execute_with(|| {
|
||||
let xcm = VersionedXcm::<Test>::from(Xcm::<Test>(vec![ClearOrigin]));
|
||||
let data = (ConcatenatedVersionedXcm, xcm).encode();
|
||||
|
||||
let messages = vec![(ParaId::from(2000), 1u32, message_format.as_slice())];
|
||||
XcmpQueue::handle_xcmp_messages(repeat((para, 1, data.as_slice())).take(170), Weight::MAX);
|
||||
|
||||
// This shouldn't have executed the incoming XCM
|
||||
XcmpQueue::handle_xcmp_messages(messages.into_iter(), Weight::MAX);
|
||||
assert_eq!(EnqueuedMessages::get().len(), 170,);
|
||||
// Not yet suspended:
|
||||
assert!(InboundXcmpSuspended::<Test>::get().is_empty());
|
||||
// Enqueueing one more will suspend it:
|
||||
let xcm = VersionedXcm::<Test>::from(Xcm::<Test>(vec![ClearOrigin])).encode();
|
||||
let small = [ConcatenatedVersionedXcm.encode(), xcm].concat();
|
||||
|
||||
let queued_xcm = InboundXcmpMessages::<Test>::get(ParaId::from(2000), 1u32);
|
||||
assert_eq!(queued_xcm, xcm);
|
||||
XcmpQueue::handle_xcmp_messages(once((para, 1, small.as_slice())), Weight::MAX);
|
||||
// Suspended:
|
||||
assert_eq!(InboundXcmpSuspended::<Test>::get().iter().collect::<Vec<_>>(), vec![¶]);
|
||||
|
||||
// Now enqueueing many more will only work until the drop threshold:
|
||||
XcmpQueue::handle_xcmp_messages(repeat((para, 1, data.as_slice())).take(100), Weight::MAX);
|
||||
assert_eq!(mock::EnqueuedMessages::get().len(), 256);
|
||||
|
||||
crate::mock::EnqueueToLocalStorage::<Pallet<Test>>::sweep_queue(para);
|
||||
XcmpQueue::handle_xcmp_messages(once((para, 1, small.as_slice())), Weight::MAX);
|
||||
// Got resumed:
|
||||
assert!(InboundXcmpSuspended::<Test>::get().is_empty());
|
||||
// Still resumed:
|
||||
XcmpQueue::handle_xcmp_messages(once((para, 1, small.as_slice())), Weight::MAX);
|
||||
assert!(InboundXcmpSuspended::<Test>::get().is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
#[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(RuntimeOrigin::root(), 3));
|
||||
assert_noop!(XcmpQueue::update_suspend_threshold(RuntimeOrigin::signed(2), 5), BadOrigin);
|
||||
let data: QueueConfigData = <QueueConfig<Test>>::get();
|
||||
assert_eq!(<QueueConfig<Test>>::get().suspend_threshold, 32);
|
||||
assert_noop!(XcmpQueue::update_suspend_threshold(Origin::signed(2), 49), BadOrigin);
|
||||
|
||||
assert_eq!(data.suspend_threshold, 3);
|
||||
assert_ok!(XcmpQueue::update_suspend_threshold(Origin::root(), 33));
|
||||
assert_eq!(<QueueConfig<Test>>::get().suspend_threshold, 33);
|
||||
});
|
||||
}
|
||||
|
||||
#[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(RuntimeOrigin::root(), 6));
|
||||
assert_noop!(XcmpQueue::update_drop_threshold(RuntimeOrigin::signed(2), 7), BadOrigin);
|
||||
let data: QueueConfigData = <QueueConfig<Test>>::get();
|
||||
assert_eq!(<QueueConfig<Test>>::get().drop_threshold, 48);
|
||||
assert_ok!(XcmpQueue::update_drop_threshold(Origin::root(), 4000));
|
||||
assert_noop!(XcmpQueue::update_drop_threshold(Origin::signed(2), 7), BadOrigin);
|
||||
|
||||
assert_eq!(data.drop_threshold, 6);
|
||||
assert_eq!(<QueueConfig<Test>>::get().drop_threshold, 4000);
|
||||
});
|
||||
}
|
||||
|
||||
#[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(RuntimeOrigin::root(), 2));
|
||||
assert_noop!(XcmpQueue::update_resume_threshold(RuntimeOrigin::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, Weight::from_parts(100_000, 0));
|
||||
assert_ok!(XcmpQueue::update_threshold_weight(
|
||||
RuntimeOrigin::root(),
|
||||
Weight::from_parts(10_000, 0)
|
||||
));
|
||||
assert_eq!(<QueueConfig<Test>>::get().resume_threshold, 8);
|
||||
assert_noop!(
|
||||
XcmpQueue::update_threshold_weight(
|
||||
RuntimeOrigin::signed(5),
|
||||
Weight::from_parts(10_000_000, 0),
|
||||
),
|
||||
BadOrigin
|
||||
XcmpQueue::update_resume_threshold(Origin::root(), 0),
|
||||
Error::<Test>::BadQueueConfig
|
||||
);
|
||||
let data: QueueConfigData = <QueueConfig<Test>>::get();
|
||||
|
||||
assert_eq!(data.threshold_weight, Weight::from_parts(10_000, 0));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_weight_restrict_decay_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let data: QueueConfigData = <QueueConfig<Test>>::get();
|
||||
assert_eq!(data.weight_restrict_decay, Weight::from_parts(2, 0));
|
||||
assert_ok!(XcmpQueue::update_weight_restrict_decay(
|
||||
RuntimeOrigin::root(),
|
||||
Weight::from_parts(5, 0)
|
||||
));
|
||||
assert_noop!(
|
||||
XcmpQueue::update_weight_restrict_decay(
|
||||
RuntimeOrigin::signed(6),
|
||||
Weight::from_parts(4, 0),
|
||||
),
|
||||
BadOrigin
|
||||
XcmpQueue::update_resume_threshold(Origin::root(), 33),
|
||||
Error::<Test>::BadQueueConfig
|
||||
);
|
||||
let data: QueueConfigData = <QueueConfig<Test>>::get();
|
||||
assert_ok!(XcmpQueue::update_resume_threshold(Origin::root(), 16));
|
||||
assert_noop!(XcmpQueue::update_resume_threshold(Origin::signed(7), 3), BadOrigin);
|
||||
|
||||
assert_eq!(data.weight_restrict_decay, Weight::from_parts(5, 0));
|
||||
});
|
||||
}
|
||||
|
||||
#[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,
|
||||
Weight::from_parts(20u64 * WEIGHT_REF_TIME_PER_MILLIS, DEFAULT_POV_SIZE),
|
||||
);
|
||||
assert_ok!(XcmpQueue::update_xcmp_max_individual_weight(
|
||||
RuntimeOrigin::root(),
|
||||
Weight::from_parts(30u64 * WEIGHT_REF_TIME_PER_MILLIS, 0)
|
||||
));
|
||||
assert_noop!(
|
||||
XcmpQueue::update_xcmp_max_individual_weight(
|
||||
RuntimeOrigin::signed(3),
|
||||
Weight::from_parts(10u64 * WEIGHT_REF_TIME_PER_MILLIS, 0)
|
||||
),
|
||||
BadOrigin
|
||||
);
|
||||
let data: QueueConfigData = <QueueConfig<Test>>::get();
|
||||
|
||||
assert_eq!(
|
||||
data.xcmp_max_individual_weight,
|
||||
Weight::from_parts(30u64 * WEIGHT_REF_TIME_PER_MILLIS, 0)
|
||||
);
|
||||
assert_eq!(<QueueConfig<Test>>::get().resume_threshold, 16);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -343,6 +421,287 @@ fn xcmp_queue_consumes_dest_and_msg_on_ok_validate() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xcmp_queue_validate_nested_xcm_works() {
|
||||
let dest = (Parent, X1(Parachain(5555)));
|
||||
// Message that is not too deeply nested:
|
||||
let mut good = Xcm(vec![ClearOrigin]);
|
||||
for _ in 0..MAX_XCM_DECODE_DEPTH - 1 {
|
||||
good = Xcm(vec![SetAppendix(good)]);
|
||||
}
|
||||
|
||||
new_test_ext().execute_with(|| {
|
||||
// Check that the good message is validated:
|
||||
assert_ok!(<XcmpQueue as SendXcm>::validate(
|
||||
&mut Some(dest.into()),
|
||||
&mut Some(good.clone())
|
||||
));
|
||||
|
||||
// Nesting the message one more time should reject it:
|
||||
let bad = Xcm(vec![SetAppendix(good)]);
|
||||
assert_eq!(
|
||||
Err(SendError::ExceedsMaxMessageSize),
|
||||
<XcmpQueue as SendXcm>::validate(&mut Some(dest.into()), &mut Some(bad))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_xcm_nested_works() {
|
||||
let dest = (Parent, X1(Parachain(HRMP_PARA_ID)));
|
||||
// Message that is not too deeply nested:
|
||||
let mut good = Xcm(vec![ClearOrigin]);
|
||||
for _ in 0..MAX_XCM_DECODE_DEPTH - 1 {
|
||||
good = Xcm(vec![SetAppendix(good)]);
|
||||
}
|
||||
|
||||
// Check that the good message is sent:
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(send_xcm::<XcmpQueue>(dest.into(), good.clone()));
|
||||
assert_eq!(
|
||||
XcmpQueue::take_outbound_messages(usize::MAX),
|
||||
vec![(
|
||||
HRMP_PARA_ID.into(),
|
||||
(XcmpMessageFormat::ConcatenatedVersionedXcm, VersionedXcm::V3(good.clone()))
|
||||
.encode(),
|
||||
)]
|
||||
);
|
||||
});
|
||||
|
||||
// Nesting the message one more time should not send it:
|
||||
let bad = Xcm(vec![SetAppendix(good)]);
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_err!(send_xcm::<XcmpQueue>(dest.into(), bad), SendError::ExceedsMaxMessageSize);
|
||||
assert!(XcmpQueue::take_outbound_messages(usize::MAX).is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hrmp_signals_are_prioritized() {
|
||||
let message = Xcm(vec![Trap(5)]);
|
||||
|
||||
let sibling_para_id = ParaId::from(12345);
|
||||
let dest = (Parent, X1(Parachain(sibling_para_id.into())));
|
||||
let mut dest_wrapper = Some(dest.into());
|
||||
let mut msg_wrapper = Some(message.clone());
|
||||
|
||||
new_test_ext().execute_with(|| {
|
||||
frame_system::Pallet::<Test>::set_block_number(1);
|
||||
<XcmpQueue as SendXcm>::validate(&mut dest_wrapper, &mut msg_wrapper).unwrap();
|
||||
|
||||
// check wrapper were consumed
|
||||
assert_eq!(None, dest_wrapper.take());
|
||||
assert_eq!(None, msg_wrapper.take());
|
||||
|
||||
ParachainSystem::open_custom_outbound_hrmp_channel_for_benchmarks_or_tests(
|
||||
sibling_para_id,
|
||||
cumulus_primitives_core::AbridgedHrmpChannel {
|
||||
max_capacity: 128,
|
||||
max_total_size: 1 << 16,
|
||||
max_message_size: 128,
|
||||
msg_count: 0,
|
||||
total_size: 0,
|
||||
mqc_head: None,
|
||||
},
|
||||
);
|
||||
|
||||
let taken = XcmpQueue::take_outbound_messages(130);
|
||||
assert_eq!(taken, vec![]);
|
||||
|
||||
// Enqueue some messages
|
||||
let num_events = frame_system::Pallet::<Test>::events().len();
|
||||
for _ in 0..256 {
|
||||
assert_ok!(send_xcm::<XcmpQueue>(dest.into(), message.clone()));
|
||||
}
|
||||
assert_eq!(num_events + 256, frame_system::Pallet::<Test>::events().len());
|
||||
|
||||
// Without a signal we get the messages in order:
|
||||
let mut expected_msg = XcmpMessageFormat::ConcatenatedVersionedXcm.encode();
|
||||
for _ in 0..31 {
|
||||
expected_msg.extend(VersionedXcm::V3(message.clone()).encode());
|
||||
}
|
||||
|
||||
hypothetically!({
|
||||
let taken = XcmpQueue::take_outbound_messages(usize::MAX);
|
||||
assert_eq!(taken, vec![(sibling_para_id.into(), expected_msg,)]);
|
||||
});
|
||||
|
||||
// But a signal gets prioritized instead of the messages:
|
||||
XcmpQueue::send_signal(sibling_para_id.into(), ChannelSignal::Suspend);
|
||||
|
||||
let taken = XcmpQueue::take_outbound_messages(130);
|
||||
assert_eq!(
|
||||
taken,
|
||||
vec![(
|
||||
sibling_para_id.into(),
|
||||
(XcmpMessageFormat::Signals, ChannelSignal::Suspend).encode()
|
||||
)]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maybe_double_encoded_versioned_xcm_works() {
|
||||
// pre conditions
|
||||
assert_eq!(VersionedXcm::<()>::V2(Default::default()).encode(), &[2, 0]);
|
||||
assert_eq!(VersionedXcm::<()>::V3(Default::default()).encode(), &[3, 0]);
|
||||
}
|
||||
|
||||
// Now also testing a page instead of just concat messages.
|
||||
#[test]
|
||||
fn maybe_double_encoded_versioned_xcm_decode_page_works() {
|
||||
let page = mk_page();
|
||||
|
||||
// Now try to decode the page.
|
||||
let input = &mut &page[..];
|
||||
for i in 0..100 {
|
||||
match (i % 2, VersionedXcm::<()>::decode(input)) {
|
||||
(0, Ok(xcm)) => {
|
||||
assert_eq!(xcm, v2_xcm());
|
||||
},
|
||||
(1, Ok(xcm)) => {
|
||||
assert_eq!(xcm, v3_xcm());
|
||||
},
|
||||
unexpected => unreachable!("{:?}", unexpected),
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(input.remaining_len(), Ok(Some(0)), "All data consumed");
|
||||
}
|
||||
|
||||
/// Check that `take_first_concatenated_xcm` correctly splits a page into its XCMs.
|
||||
#[test]
|
||||
fn take_first_concatenated_xcm_works() {
|
||||
let page = mk_page();
|
||||
let input = &mut &page[..];
|
||||
|
||||
for i in 0..100 {
|
||||
let xcm = XcmpQueue::take_first_concatenated_xcm(input, &mut WeightMeter::new()).unwrap();
|
||||
match (i % 2, xcm) {
|
||||
(0, data) | (2, data) => {
|
||||
assert_eq!(data, v2_xcm().encode());
|
||||
},
|
||||
(1, data) | (3, data) => {
|
||||
assert_eq!(data, v3_xcm().encode());
|
||||
},
|
||||
unexpected => unreachable!("{:?}", unexpected),
|
||||
}
|
||||
}
|
||||
assert_eq!(input.remaining_len(), Ok(Some(0)), "All data consumed");
|
||||
}
|
||||
|
||||
/// A message that is not too deeply nested will be accepted by `take_first_concatenated_xcm`.
|
||||
#[test]
|
||||
fn take_first_concatenated_xcm_good_recursion_depth_works() {
|
||||
let mut good = Xcm::<()>(vec![ClearOrigin]);
|
||||
for _ in 0..MAX_XCM_DECODE_DEPTH - 1 {
|
||||
good = Xcm(vec![SetAppendix(good)]);
|
||||
}
|
||||
let good = VersionedXcm::V3(good);
|
||||
|
||||
let page = good.encode();
|
||||
assert_ok!(XcmpQueue::take_first_concatenated_xcm(&mut &page[..], &mut WeightMeter::new()));
|
||||
}
|
||||
|
||||
/// A message that is too deeply nested will be rejected by `take_first_concatenated_xcm`.
|
||||
#[test]
|
||||
fn take_first_concatenated_xcm_good_bad_depth_errors() {
|
||||
let mut bad = Xcm::<()>(vec![ClearOrigin]);
|
||||
for _ in 0..MAX_XCM_DECODE_DEPTH {
|
||||
bad = Xcm(vec![SetAppendix(bad)]);
|
||||
}
|
||||
let bad = VersionedXcm::V3(bad);
|
||||
|
||||
let page = bad.encode();
|
||||
assert_err!(
|
||||
XcmpQueue::take_first_concatenated_xcm(&mut &page[..], &mut WeightMeter::new()),
|
||||
()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lazy_migration_works() {
|
||||
use crate::migration::v3::*;
|
||||
|
||||
new_test_ext().execute_with(|| {
|
||||
EnqueuedMessages::set(vec![]);
|
||||
let _g = StorageNoopGuard::default(); // No storage is leaked.
|
||||
|
||||
let mut channels = vec![];
|
||||
for i in 0..20 {
|
||||
let para = ParaId::from(i);
|
||||
let mut message_metadata = vec![];
|
||||
for block in 0..30 {
|
||||
message_metadata.push((block, XcmpMessageFormat::ConcatenatedVersionedXcm));
|
||||
InboundXcmpMessages::<Test>::insert(para, block, vec![(i + block) as u8]);
|
||||
}
|
||||
|
||||
channels.push(InboundChannelDetails {
|
||||
sender: para,
|
||||
state: InboundState::Ok,
|
||||
message_metadata,
|
||||
});
|
||||
}
|
||||
InboundXcmpStatus::<Test>::set(Some(channels));
|
||||
|
||||
for para in 0..20u32 {
|
||||
assert_eq!(InboundXcmpStatus::<Test>::get().unwrap().len() as u32, 20 - para);
|
||||
assert_eq!(InboundXcmpMessages::<Test>::iter_prefix(ParaId::from(para)).count(), 30);
|
||||
|
||||
for block in 0..30 {
|
||||
XcmpQueue::on_idle(0u32.into(), Weight::MAX);
|
||||
assert_eq!(
|
||||
EnqueuedMessages::get(),
|
||||
vec![(para.into(), vec![(29 - block + para) as u8])]
|
||||
);
|
||||
EnqueuedMessages::set(vec![]);
|
||||
}
|
||||
// One more to jump to the next channel:
|
||||
XcmpQueue::on_idle(0u32.into(), Weight::MAX);
|
||||
|
||||
assert_eq!(InboundXcmpStatus::<Test>::get().unwrap().len() as u32, 20 - para - 1);
|
||||
assert_eq!(InboundXcmpMessages::<Test>::iter_prefix(ParaId::from(para)).count(), 0);
|
||||
}
|
||||
// One more for the cleanup:
|
||||
XcmpQueue::on_idle(0u32.into(), Weight::MAX);
|
||||
|
||||
assert!(!InboundXcmpStatus::<Test>::exists());
|
||||
assert_eq!(InboundXcmpMessages::<Test>::iter().count(), 0);
|
||||
EnqueuedMessages::set(vec![]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lazy_migration_noop_when_out_of_weight() {
|
||||
use crate::migration::v3::*;
|
||||
assert!(!XcmpQueue::on_idle_weight().is_zero(), "pre condition");
|
||||
|
||||
new_test_ext().execute_with(|| {
|
||||
let _g = StorageNoopGuard::default(); // No storage is leaked.
|
||||
let block = 5;
|
||||
let para = ParaId::from(4);
|
||||
let message_metadata = vec![(block, XcmpMessageFormat::ConcatenatedVersionedXcm)];
|
||||
|
||||
InboundXcmpMessages::<Test>::insert(para, block, vec![123u8]);
|
||||
InboundXcmpStatus::<Test>::set(Some(vec![InboundChannelDetails {
|
||||
sender: para,
|
||||
state: InboundState::Ok,
|
||||
message_metadata,
|
||||
}]));
|
||||
|
||||
// Hypothetically, it would do something with enough weight limit:
|
||||
hypothetically!({
|
||||
XcmpQueue::on_idle(0u32.into(), Weight::MAX);
|
||||
assert_eq!(EnqueuedMessages::get(), vec![(para, vec![123u8])]);
|
||||
});
|
||||
// But does not, since the limit is zero:
|
||||
assert_storage_noop!({ XcmpQueue::on_idle(0u32.into(), Weight::zero()) });
|
||||
|
||||
InboundXcmpMessages::<Test>::remove(para, block);
|
||||
InboundXcmpStatus::<Test>::kill();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xcmp_queue_send_xcm_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
|
||||
Reference in New Issue
Block a user