Skip to content

Commit 855c47b

Browse files
Improve XCMP weight metering (#7963)
This PR Improves the weight metering for the `enqueue_xcmp_message()` method, by accounting for cached reads/writes and for the size of the enqueued messages --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 470bada commit 855c47b

File tree

16 files changed

+699
-302
lines changed

16 files changed

+699
-302
lines changed

cumulus/pallets/xcmp-queue/src/benchmarking.rs

+40-7
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::*;
2020
use alloc::vec;
2121
use codec::DecodeAll;
2222
use frame_benchmarking::v2::*;
23-
use frame_support::traits::Hooks;
23+
use frame_support::{assert_ok, traits::Hooks};
2424
use frame_system::RawOrigin;
2525
use xcm::MAX_INSTRUCTIONS_TO_DECODE;
2626

@@ -40,15 +40,48 @@ mod benchmarks {
4040
Pallet::<T>::update_resume_threshold(RawOrigin::Root, 1);
4141
}
4242

43+
/// Add a XCMP message of `n` bytes to the message queue.
44+
///
45+
/// The message will be added on a new page and also, the `BookState` will be added
46+
/// to the ready ring.
4347
#[benchmark]
44-
fn enqueue_xcmp_message() {
45-
assert!(QueueConfig::<T>::get().drop_threshold * MaxXcmpMessageLenOf::<T>::get() > 1000);
46-
let msg = BoundedVec::<u8, MaxXcmpMessageLenOf<T>>::default();
48+
fn enqueue_n_bytes_xcmp_message(n: Linear<1, { MaxXcmpMessageLenOf::<T>::get() }>) {
49+
#[cfg(test)]
50+
{
51+
mock::EnqueuedMessages::set(vec![]);
52+
}
53+
54+
let msg = BoundedVec::<u8, MaxXcmpMessageLenOf<T>>::try_from(vec![0; n as usize]).unwrap();
55+
let fp_before = T::XcmpQueue::footprint(0.into());
56+
#[block]
57+
{
58+
assert_ok!(Pallet::<T>::enqueue_xcmp_message(0.into(), msg));
59+
}
60+
let fp_after = T::XcmpQueue::footprint(0.into());
61+
assert_eq!(fp_after.ready_pages, fp_before.ready_pages + 1);
62+
}
63+
64+
/// Add 2 XCMP message of 0 bytes to the message queue.
65+
///
66+
/// Only for the first message a new page will be created and the `BookState` will be added
67+
/// to the ready ring.
68+
#[benchmark]
69+
fn enqueue_2_empty_xcmp_messages() {
70+
#[cfg(test)]
71+
{
72+
mock::EnqueuedMessages::set(vec![]);
73+
}
4774

75+
let msg_1 = BoundedVec::<u8, MaxXcmpMessageLenOf<T>>::default();
76+
let msg_2 = BoundedVec::<u8, MaxXcmpMessageLenOf<T>>::default();
77+
let fp_before = T::XcmpQueue::footprint(0.into());
4878
#[block]
4979
{
50-
Pallet::<T>::enqueue_xcmp_message(0.into(), msg, &mut WeightMeter::new()).unwrap();
80+
assert_ok!(Pallet::<T>::enqueue_xcmp_message(0.into(), msg_1));
81+
assert_ok!(Pallet::<T>::enqueue_xcmp_message(0.into(), msg_2));
5182
}
83+
let fp_after = T::XcmpQueue::footprint(0.into());
84+
assert_eq!(fp_after.ready_pages, fp_before.ready_pages + 1);
5285
}
5386

5487
#[benchmark]
@@ -94,7 +127,7 @@ mod benchmarks {
94127
/// Split a singular XCM.
95128
#[benchmark]
96129
fn take_first_concatenated_xcm() {
97-
let max_downward_message_size = MaxXcmpMessageLenOf::<T>::get() as usize;
130+
let max_message_size = MaxXcmpMessageLenOf::<T>::get() as usize;
98131

99132
assert!(MAX_INSTRUCTIONS_TO_DECODE as u32 > MAX_XCM_DECODE_DEPTH, "Preconditon failed");
100133
let max_instrs = MAX_INSTRUCTIONS_TO_DECODE as u32 - MAX_XCM_DECODE_DEPTH;
@@ -105,7 +138,7 @@ mod benchmarks {
105138
}
106139

107140
let data = VersionedXcm::<T>::from(xcm).encode();
108-
assert!(data.len() < max_downward_message_size, "Page size is too small");
141+
assert!(data.len() < max_message_size, "Page size is too small");
109142
// Verify that decoding works with the exact recursion limit:
110143
VersionedXcm::<T::RuntimeCall>::decode_all_with_depth_limit(
111144
MAX_XCM_DECODE_DEPTH,

cumulus/pallets/xcmp-queue/src/lib.rs

+31-10
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ mod benchmarking;
4848
#[cfg(feature = "bridging")]
4949
pub mod bridging;
5050
pub mod weights;
51+
pub mod weights_ext;
52+
5153
pub use weights::WeightInfo;
54+
pub use weights_ext::WeightInfoExt;
5255

5356
extern crate alloc;
5457

@@ -164,7 +167,7 @@ pub mod pallet {
164167
type PriceForSiblingDelivery: PriceForMessageDelivery<Id = ParaId>;
165168

166169
/// The weight information of this pallet.
167-
type WeightInfo: WeightInfo;
170+
type WeightInfo: WeightInfoExt;
168171
}
169172

170173
#[pallet::call]
@@ -637,13 +640,7 @@ impl<T: Config> Pallet<T> {
637640
fn enqueue_xcmp_message(
638641
sender: ParaId,
639642
xcm: BoundedVec<u8, MaxXcmpMessageLenOf<T>>,
640-
meter: &mut WeightMeter,
641643
) -> Result<(), ()> {
642-
if meter.try_consume(T::WeightInfo::enqueue_xcmp_message()).is_err() {
643-
defensive!("Out of weight: cannot enqueue XCMP messages; dropping msg");
644-
return Err(())
645-
}
646-
647644
let QueueConfigData { drop_threshold, .. } = <QueueConfig<T>>::get();
648645
let fp = T::XcmpQueue::footprint(sender);
649646
// Assume that it will not fit into the current page:
@@ -791,22 +788,46 @@ impl<T: Config> XcmpMessageHandler for Pallet<T> {
791788
},
792789
}
793790
},
794-
XcmpMessageFormat::ConcatenatedVersionedXcm =>
791+
XcmpMessageFormat::ConcatenatedVersionedXcm => {
792+
// We need to know if the current message is the first on the current XCMP page
793+
// for weight metering accuracy.
794+
let mut is_first_xcm_on_page = true;
795795
while !data.is_empty() {
796796
let Ok(xcm) = Self::take_first_concatenated_xcm(&mut data, &mut meter)
797797
else {
798798
defensive!("HRMP inbound decode stream broke; page will be dropped.",);
799799
break
800800
};
801801

802-
if let Err(()) = Self::enqueue_xcmp_message(sender, xcm, &mut meter) {
802+
// For simplicity, we consider that each new XCMP page results in a new
803+
// message queue page. This is not always true, but it's a good enough
804+
// estimation.
805+
if meter
806+
.try_consume(T::WeightInfo::enqueue_xcmp_message(
807+
xcm.len(),
808+
is_first_xcm_on_page,
809+
))
810+
.is_err()
811+
{
812+
defensive!(
813+
"Out of weight: cannot enqueue XCMP messages; dropping msg; \
814+
Used weight: ",
815+
meter.consumed_ratio()
816+
);
817+
break;
818+
}
819+
820+
if let Err(()) = Self::enqueue_xcmp_message(sender, xcm) {
803821
defensive!(
804822
"Could not enqueue XCMP messages. Used weight: ",
805823
meter.consumed_ratio()
806824
);
807825
break
808826
}
809-
},
827+
828+
is_first_xcm_on_page = false;
829+
}
830+
},
810831
XcmpMessageFormat::ConcatenatedEncodedBlob => {
811832
defensive!("Blob messages are unhandled - dropping");
812833
continue

cumulus/pallets/xcmp-queue/src/mock.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
use super::*;
1717
use crate as xcmp_queue;
18-
use core::marker::PhantomData;
18+
use core::{cmp::max, marker::PhantomData};
1919
use cumulus_pallet_parachain_system::AnyRelayNumber;
2020
use cumulus_primitives_core::{ChannelInfo, IsSystem, ParaId};
2121
use frame_support::{
@@ -144,7 +144,7 @@ parameter_types! {
144144
pub struct EnqueueToLocalStorage<T>(PhantomData<T>);
145145

146146
impl<T: OnQueueChanged<ParaId>> EnqueueMessage<ParaId> for EnqueueToLocalStorage<T> {
147-
type MaxMessageLen = sp_core::ConstU32<65_536>;
147+
type MaxMessageLen = sp_core::ConstU32<256>;
148148

149149
fn enqueue_message(message: BoundedSlice<u8, Self::MaxMessageLen>, origin: ParaId) {
150150
let mut msgs = EnqueuedMessages::get();
@@ -179,7 +179,11 @@ impl<T: OnQueueChanged<ParaId>> EnqueueMessage<ParaId> for EnqueueToLocalStorage
179179
footprint.storage.size += m.len() as u64;
180180
}
181181
}
182-
footprint.pages = footprint.storage.size as u32 / 16; // Number does not matter
182+
footprint.pages =
183+
(footprint.storage.size as u32).div_ceil(<Self::MaxMessageLen as Get<u32>>::get());
184+
if footprint.storage.count > 0 {
185+
footprint.pages = max(footprint.pages, 1);
186+
}
183187
footprint.ready_pages = footprint.pages;
184188
footprint
185189
}
@@ -228,7 +232,7 @@ pub struct MockedChannelInfo;
228232
impl GetChannelInfo for MockedChannelInfo {
229233
fn get_channel_status(id: ParaId) -> ChannelStatus {
230234
if id == HRMP_PARA_ID.into() {
231-
return ChannelStatus::Ready(usize::MAX, usize::MAX)
235+
return ChannelStatus::Ready(usize::MAX, usize::MAX);
232236
}
233237

234238
ParachainSystem::get_channel_status(id)
@@ -242,7 +246,7 @@ impl GetChannelInfo for MockedChannelInfo {
242246
max_message_size: u32::MAX,
243247
msg_count: 0,
244248
total_size: 0,
245-
})
249+
});
246250
}
247251

248252
ParachainSystem::get_channel_info(id)

cumulus/pallets/xcmp-queue/src/tests.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,16 @@ fn xcm_enqueueing_multiple_times_works() {
104104
#[cfg_attr(debug_assertions, should_panic = "Could not enqueue XCMP messages.")]
105105
fn xcm_enqueueing_starts_dropping_on_overflow() {
106106
new_test_ext().execute_with(|| {
107-
let xcm = VersionedXcm::<Test>::from(Xcm::<Test>(vec![ClearOrigin]));
107+
let xcm = VersionedXcm::<Test>::from(Xcm::<Test>(vec![
108+
ClearOrigin;
109+
MAX_INSTRUCTIONS_TO_DECODE as usize
110+
]));
108111
let data = (ConcatenatedVersionedXcm, xcm).encode();
109-
// Its possible to enqueue 256 messages at most:
110-
let limit = 256;
112+
// It's possible to enqueue at most `limit` messages:
113+
let max_message_len: u32 =
114+
<<Test as Config>::XcmpQueue as EnqueueMessage<ParaId>>::MaxMessageLen::get();
115+
let drop_threshold = <QueueConfig<Test>>::get().drop_threshold;
116+
let limit = max_message_len as usize / data.len() * drop_threshold as usize;
111117

112118
XcmpQueue::handle_xcmp_messages(
113119
repeat((1000.into(), 1, data.as_slice())).take(limit * 2),

0 commit comments

Comments
 (0)