1#![cfg_attr(not(feature = "std"), no_std)]
37
38pub mod migration;
39
40#[cfg(test)]
41mod mock;
42
43#[cfg(test)]
44mod tests;
45
46#[cfg(feature = "runtime-benchmarks")]
47mod benchmarking;
48#[cfg(feature = "bridging")]
49pub mod bridging;
50pub mod weights;
51pub mod weights_ext;
52
53pub use weights::WeightInfo;
54pub use weights_ext::WeightInfoExt;
55
56extern crate alloc;
57
58use alloc::{collections::BTreeSet, vec, vec::Vec};
59use bitflags::bitflags;
60use bounded_collections::{BoundedBTreeSet, BoundedSlice, BoundedVec};
61use codec::{Compact, Decode, DecodeLimit, Encode, MaxEncodedLen};
62use cumulus_primitives_core::{
63 relay_chain::BlockNumber as RelayBlockNumber, ChannelStatus, GetChannelInfo, MessageSendError,
64 ParaId, XcmpMessageFormat, XcmpMessageHandler, XcmpMessageSource,
65};
66
67use frame_support::{
68 defensive, defensive_assert,
69 pallet_prelude::DispatchResult,
70 traits::{
71 Defensive, DefensiveTruncateFrom, EnqueueMessage, EnsureOrigin, Get, Len, QueueFootprint,
72 QueueFootprintQuery, QueuePausedQuery,
73 },
74 transactional,
75 weights::{Weight, WeightMeter},
76};
77use pallet_message_queue::OnQueueChanged;
78use polkadot_runtime_common::xcm_sender::PriceForMessageDelivery;
79use polkadot_runtime_parachains::{FeeTracker, GetMinFeeFactor};
80use scale_info::TypeInfo;
81use sp_core::MAX_POSSIBLE_ALLOCATION;
82use sp_runtime::{DispatchError, FixedU128, SaturatedConversion, WeakBoundedVec};
83use xcm::{latest::prelude::*, VersionedLocation, VersionedXcm, WrapVersion, MAX_XCM_DECODE_DEPTH};
84use xcm_builder::InspectMessageQueues;
85use xcm_executor::traits::ConvertOrigin;
86
87pub use pallet::*;
88
89pub type OverweightIndex = u64;
91pub type MaxXcmpMessageLenOf<T> =
93 <<T as Config>::XcmpQueue as EnqueueMessage<ParaId>>::MaxMessageLen;
94
95const LOG_TARGET: &str = "xcmp_queue";
96const DEFAULT_POV_SIZE: u64 = 64 * 1024; pub const XCM_BATCH_SIZE: usize = 250;
99pub const MAX_SIGNALS_PER_PAGE: usize = 3;
101
102pub mod delivery_fee_constants {
104 pub const THRESHOLD_FACTOR: u32 = 2;
106}
107
108#[frame_support::pallet]
109pub mod pallet {
110 use super::*;
111 use frame_support::{pallet_prelude::*, Twox64Concat};
112 use frame_system::pallet_prelude::*;
113
114 #[pallet::pallet]
115 #[pallet::storage_version(migration::STORAGE_VERSION)]
116 pub struct Pallet<T>(_);
117
118 #[pallet::config]
119 pub trait Config: frame_system::Config {
120 #[allow(deprecated)]
121 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
122
123 type ChannelInfo: GetChannelInfo;
125
126 type VersionWrapper: WrapVersion;
128
129 type XcmpQueue: EnqueueMessage<ParaId>
134 + QueueFootprintQuery<ParaId, MaxMessageLen = MaxXcmpMessageLenOf<Self>>;
135
136 #[pallet::constant]
142 type MaxInboundSuspended: Get<u32>;
143
144 #[pallet::constant]
153 type MaxActiveOutboundChannels: Get<u32>;
154
155 #[pallet::constant]
161 type MaxPageSize: Get<u32>;
162
163 type ControllerOrigin: EnsureOrigin<Self::RuntimeOrigin>;
165
166 type ControllerOriginConverter: ConvertOrigin<Self::RuntimeOrigin>;
169
170 type PriceForSiblingDelivery: PriceForMessageDelivery<Id = ParaId>;
172
173 type WeightInfo: WeightInfoExt;
175 }
176
177 #[pallet::call]
178 impl<T: Config> Pallet<T> {
179 #[pallet::call_index(1)]
183 #[pallet::weight((T::DbWeight::get().writes(1), DispatchClass::Operational,))]
184 pub fn suspend_xcm_execution(origin: OriginFor<T>) -> DispatchResult {
185 T::ControllerOrigin::ensure_origin(origin)?;
186
187 QueueSuspended::<T>::try_mutate(|suspended| {
188 if *suspended {
189 Err(Error::<T>::AlreadySuspended.into())
190 } else {
191 *suspended = true;
192 Ok(())
193 }
194 })
195 }
196
197 #[pallet::call_index(2)]
203 #[pallet::weight((T::DbWeight::get().writes(1), DispatchClass::Operational,))]
204 pub fn resume_xcm_execution(origin: OriginFor<T>) -> DispatchResult {
205 T::ControllerOrigin::ensure_origin(origin)?;
206
207 QueueSuspended::<T>::try_mutate(|suspended| {
208 if !*suspended {
209 Err(Error::<T>::AlreadyResumed.into())
210 } else {
211 *suspended = false;
212 Ok(())
213 }
214 })
215 }
216
217 #[pallet::call_index(3)]
223 #[pallet::weight((T::WeightInfo::set_config_with_u32(), DispatchClass::Operational,))]
224 pub fn update_suspend_threshold(origin: OriginFor<T>, new: u32) -> DispatchResult {
225 ensure_root(origin)?;
226
227 QueueConfig::<T>::try_mutate(|data| {
228 data.suspend_threshold = new;
229 data.validate::<T>()
230 })
231 }
232
233 #[pallet::call_index(4)]
239 #[pallet::weight((T::WeightInfo::set_config_with_u32(),DispatchClass::Operational,))]
240 pub fn update_drop_threshold(origin: OriginFor<T>, new: u32) -> DispatchResult {
241 ensure_root(origin)?;
242
243 QueueConfig::<T>::try_mutate(|data| {
244 data.drop_threshold = new;
245 data.validate::<T>()
246 })
247 }
248
249 #[pallet::call_index(5)]
255 #[pallet::weight((T::WeightInfo::set_config_with_u32(), DispatchClass::Operational,))]
256 pub fn update_resume_threshold(origin: OriginFor<T>, new: u32) -> DispatchResult {
257 ensure_root(origin)?;
258
259 QueueConfig::<T>::try_mutate(|data| {
260 data.resume_threshold = new;
261 data.validate::<T>()
262 })
263 }
264 }
265
266 #[pallet::hooks]
267 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
268 fn integrity_test() {
269 assert!(!T::MaxPageSize::get().is_zero(), "MaxPageSize too low");
270
271 let w = Self::on_idle_weight();
272 assert!(w != Weight::zero());
273 assert!(w.all_lte(T::BlockWeights::get().max_block));
274
275 <T::WeightInfo as WeightInfoExt>::check_accuracy::<MaxXcmpMessageLenOf<T>>(0.15);
276 }
277
278 fn on_idle(_block: BlockNumberFor<T>, limit: Weight) -> Weight {
279 let mut meter = WeightMeter::with_limit(limit);
280
281 if meter.try_consume(Self::on_idle_weight()).is_err() {
282 tracing::debug!(
283 target: LOG_TARGET,
284 "Not enough weight for on_idle. {} < {}",
285 Self::on_idle_weight(), limit
286 );
287 return meter.consumed();
288 }
289
290 migration::v3::lazy_migrate_inbound_queue::<T>();
291
292 meter.consumed()
293 }
294 }
295
296 #[pallet::event]
297 #[pallet::generate_deposit(pub(super) fn deposit_event)]
298 pub enum Event<T: Config> {
299 XcmpMessageSent { message_hash: XcmHash },
301 }
302
303 #[pallet::error]
304 pub enum Error<T> {
305 BadQueueConfig,
307 AlreadySuspended,
309 AlreadyResumed,
311 TooManyActiveOutboundChannels,
313 TooBig,
315 RetryPage,
317 }
318
319 #[pallet::storage]
328 pub type InboundXcmpSuspended<T: Config> =
329 StorageValue<_, BoundedBTreeSet<ParaId, T::MaxInboundSuspended>, ValueQuery>;
330
331 #[pallet::storage]
338 pub(super) type OutboundXcmpStatus<T: Config> = StorageValue<
339 _,
340 BoundedVec<OutboundChannelDetails, T::MaxActiveOutboundChannels>,
341 ValueQuery,
342 >;
343
344 #[pallet::storage]
346 pub(super) type OutboundXcmpMessages<T: Config> = StorageDoubleMap<
347 _,
348 Blake2_128Concat,
349 ParaId,
350 Twox64Concat,
351 u16,
352 WeakBoundedVec<u8, T::MaxPageSize>,
353 ValueQuery,
354 >;
355
356 #[pallet::storage]
358 pub(super) type SignalMessages<T: Config> =
359 StorageMap<_, Blake2_128Concat, ParaId, WeakBoundedVec<u8, T::MaxPageSize>, ValueQuery>;
360
361 #[pallet::storage]
363 pub(super) type QueueConfig<T: Config> = StorageValue<_, QueueConfigData, ValueQuery>;
364
365 #[pallet::storage]
367 pub(super) type QueueSuspended<T: Config> = StorageValue<_, bool, ValueQuery>;
368
369 #[pallet::storage]
371 pub(super) type DeliveryFeeFactor<T: Config> =
372 StorageMap<_, Twox64Concat, ParaId, FixedU128, ValueQuery, GetMinFeeFactor<Pallet<T>>>;
373}
374
375#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
376pub enum OutboundState {
377 Ok,
378 Suspended,
379}
380
381bitflags! {
382 #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]
383 struct OutboundChannelFlags: u32 {
384 const CONCATENATED_OPAQUE_VERSIONED_XCM_SUPPORT = 1;
385 const CONCATENATED_OPAQUE_VERSIONED_XCM_NOTIFICATION_SENT = 1 << 1;
386 }
387}
388
389impl OutboundChannelFlags {
390 fn has_concatenated_opaque_versioned_xcm_support(&self) -> bool {
392 *self & Self::CONCATENATED_OPAQUE_VERSIONED_XCM_SUPPORT != Self::empty()
393 }
394
395 fn should_send_concatenated_opaque_versioned_xcm_notification(&self) -> bool {
398 if self.has_concatenated_opaque_versioned_xcm_support() {
399 return false;
400 }
401
402 if *self & Self::CONCATENATED_OPAQUE_VERSIONED_XCM_NOTIFICATION_SENT != Self::empty() {
403 return false;
404 }
405
406 true
407 }
408
409 fn notice_concatenated_opaque_versioned_xcm_support(&mut self) {
411 *self = *self | Self::CONCATENATED_OPAQUE_VERSIONED_XCM_SUPPORT;
412 }
413
414 fn notice_concatenated_opaque_versioned_xcm_notification_sent(&mut self) {
416 *self = *self | Self::CONCATENATED_OPAQUE_VERSIONED_XCM_NOTIFICATION_SENT;
417 }
418}
419
420#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo, Debug, MaxEncodedLen)]
422pub struct OutboundChannelDetails {
423 recipient: ParaId,
425 state: OutboundState,
427 signals_exist: bool,
429 first_index: u16,
431 last_index: u16,
433 flags: OutboundChannelFlags,
435 queued_bytes: u32,
437}
438
439impl OutboundChannelDetails {
440 pub fn new(recipient: ParaId) -> OutboundChannelDetails {
441 OutboundChannelDetails {
442 recipient,
443 state: OutboundState::Ok,
444 signals_exist: false,
445 first_index: 0,
446 last_index: 0,
447 flags: OutboundChannelFlags::empty(),
448 queued_bytes: 0,
449 }
450 }
451
452 pub fn with_signals(mut self) -> OutboundChannelDetails {
453 self.signals_exist = true;
454 self
455 }
456
457 pub fn with_suspended_state(mut self) -> OutboundChannelDetails {
458 self.state = OutboundState::Suspended;
459 self
460 }
461}
462
463#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
464pub struct QueueConfigData {
465 suspend_threshold: u32,
468 drop_threshold: u32,
472 resume_threshold: u32,
475}
476
477impl Default for QueueConfigData {
478 fn default() -> Self {
479 Self {
482 drop_threshold: 48, suspend_threshold: 32, resume_threshold: 8, }
486 }
487}
488
489impl QueueConfigData {
490 pub fn validate<T: crate::Config>(&self) -> sp_runtime::DispatchResult {
494 if self.resume_threshold < self.suspend_threshold &&
495 self.suspend_threshold <= self.drop_threshold &&
496 self.resume_threshold > 0
497 {
498 Ok(())
499 } else {
500 Err(Error::<T>::BadQueueConfig.into())
501 }
502 }
503}
504
505#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, TypeInfo)]
506pub enum ChannelSignal {
507 Suspend,
508 Resume,
509}
510
511#[derive(Debug, PartialEq)]
512enum TakeXcmError {
513 InvalidData,
514 OutOfWeight,
515}
516
517#[derive(Default, Debug)]
518struct EnqueueXcmpMessagesResult {
519 has_dropped_msgs: bool,
520 has_out_of_weight_msgs: bool,
521}
522
523impl<T: Config> Pallet<T> {
524 fn try_get_outbound_channel(
525 all_channels: &BoundedVec<OutboundChannelDetails, T::MaxActiveOutboundChannels>,
526 recipient: ParaId,
527 ) -> Option<&OutboundChannelDetails> {
528 for channel_idx in 0..all_channels.len() {
529 if all_channels[channel_idx].recipient == recipient {
530 return Some(&all_channels[channel_idx]);
531 }
532 }
533
534 None
535 }
536
537 fn try_get_or_insert_outbound_channel(
538 all_channels: &mut BoundedVec<OutboundChannelDetails, T::MaxActiveOutboundChannels>,
539 recipient: ParaId,
540 ) -> Option<&mut OutboundChannelDetails> {
541 for channel_idx in 0..all_channels.len() {
542 if all_channels[channel_idx].recipient == recipient {
543 return Some(&mut all_channels[channel_idx]);
544 }
545 }
546
547 all_channels
548 .try_push(OutboundChannelDetails::new(recipient))
549 .inspect_err(|e| {
550 tracing::error!(target: LOG_TARGET, error=?e, "Failed to insert outbound HRMP channel");
551 })
552 .ok()?;
553 all_channels.last_mut()
554 }
555
556 fn send_fragment<Fragment: Encode>(
578 recipient: ParaId,
579 format: XcmpMessageFormat,
580 fragment: Fragment,
581 ) -> Result<u32, MessageSendError> {
582 let mut encoded_fragment = fragment.encode();
583 let encoded_fragment_len = encoded_fragment.len();
584
585 let channel_info =
589 T::ChannelInfo::get_channel_info(recipient).ok_or(MessageSendError::NoChannel)?;
590 let max_message_size = channel_info.max_message_size.min(T::MaxPageSize::get()) as usize;
592 let format_size = format.encoded_size();
593 let size_to_check = encoded_fragment
596 .len()
597 .checked_add(format_size)
598 .ok_or(MessageSendError::TooBig)?;
599 if size_to_check > max_message_size {
600 return Err(MessageSendError::TooBig);
601 }
602
603 let mut all_channels = <OutboundXcmpStatus<T>>::get();
604 let channel_details =
605 Self::try_get_or_insert_outbound_channel(&mut all_channels, recipient)
606 .ok_or(MessageSendError::TooManyChannels)?;
607 if let XcmpMessageFormat::ConcatenatedOpaqueVersionedXcm = format {
608 channel_details
609 .flags
610 .notice_concatenated_opaque_versioned_xcm_notification_sent();
611 }
612
613 let mut existing_page = None;
614 'existing_page_check: {
615 if channel_details.last_index > channel_details.first_index {
616 let page =
617 OutboundXcmpMessages::<T>::get(recipient, channel_details.last_index - 1);
618 if XcmpMessageFormat::decode(&mut &page[..]) != Ok(format) {
619 break 'existing_page_check;
620 }
621 if page.len() + encoded_fragment.len() > max_message_size {
622 break 'existing_page_check;
623 }
624 existing_page = Some(page.into_inner());
625 }
626 }
627 let mut current_page = existing_page.unwrap_or_else(|| {
628 channel_details.last_index += 1;
630 let new_page = format.encode();
631 channel_details.queued_bytes =
632 channel_details.queued_bytes.saturating_add(new_page.len() as u32);
633 new_page
634 });
635
636 current_page.append(&mut encoded_fragment);
637 channel_details.queued_bytes =
638 channel_details.queued_bytes.saturating_add(encoded_fragment_len as u32);
639 let current_page = WeakBoundedVec::try_from(current_page).map_err(|error| {
640 tracing::debug!(target: LOG_TARGET, ?error, "Failed to create bounded message page");
641 MessageSendError::TooBig
642 })?;
643 let page_count =
644 channel_details.last_index.saturating_sub(channel_details.first_index) as u32;
645 <OutboundXcmpMessages<T>>::insert(recipient, channel_details.last_index - 1, current_page);
646
647 let threshold = channel_info.max_total_size / delivery_fee_constants::THRESHOLD_FACTOR;
648 if channel_details.queued_bytes > threshold {
649 Self::increase_fee_factor(recipient, encoded_fragment_len as u128);
650 }
651
652 <OutboundXcmpStatus<T>>::put(all_channels);
653
654 Ok(page_count)
655 }
656
657 fn send_signal(dest: ParaId, signal: ChannelSignal) -> Result<(), Error<T>> {
660 let mut s = <OutboundXcmpStatus<T>>::get();
661 if let Some(details) = s.iter_mut().find(|item| item.recipient == dest) {
662 details.signals_exist = true;
663 } else {
664 s.try_push(OutboundChannelDetails::new(dest).with_signals()).map_err(|error| {
665 tracing::debug!(target: LOG_TARGET, ?error, "Failed to activate XCMP channel");
666 Error::<T>::TooManyActiveOutboundChannels
667 })?;
668 }
669
670 let page = BoundedVec::<u8, T::MaxPageSize>::try_from(
671 (XcmpMessageFormat::Signals, signal).encode(),
672 )
673 .map_err(|error| {
674 tracing::debug!(target: LOG_TARGET, ?error, "Failed to encode signal message");
675 Error::<T>::TooBig
676 })?;
677 let page = WeakBoundedVec::force_from(page.into_inner(), None);
678
679 <SignalMessages<T>>::insert(dest, page);
680 <OutboundXcmpStatus<T>>::put(s);
681 Ok(())
682 }
683
684 fn suspend_channel(target: ParaId) {
685 <OutboundXcmpStatus<T>>::mutate(|s| {
686 if let Some(details) = s.iter_mut().find(|item| item.recipient == target) {
687 let ok = details.state == OutboundState::Ok;
688 defensive_assert!(ok, "WARNING: Attempt to suspend channel that was not Ok.");
689 details.state = OutboundState::Suspended;
690 } else {
691 if s.try_push(OutboundChannelDetails::new(target).with_suspended_state()).is_err() {
692 defensive!("Cannot pause channel; too many outbound channels");
693 }
694 }
695 });
696 }
697
698 fn resume_channel(target: ParaId) {
699 <OutboundXcmpStatus<T>>::mutate(|s| {
700 if let Some(index) = s.iter().position(|item| item.recipient == target) {
701 let suspended = s[index].state == OutboundState::Suspended;
702 defensive_assert!(
703 suspended,
704 "WARNING: Attempt to resume channel that was not suspended."
705 );
706 if s[index].first_index == s[index].last_index {
707 s.remove(index);
708 } else {
709 s[index].state = OutboundState::Ok;
710 }
711 } else {
712 defensive!("WARNING: Attempt to resume channel that was not suspended.");
713 }
714 });
715 }
716
717 fn enqueue_xcmp_messages<'a>(
718 sender: ParaId,
719 xcms: &[BoundedSlice<'a, u8, MaxXcmpMessageLenOf<T>>],
720 is_first_sender_batch: bool,
721 meter: &mut WeightMeter,
722 ) -> EnqueueXcmpMessagesResult {
723 let mut result = EnqueueXcmpMessagesResult::default();
724
725 if xcms.is_empty() {
726 return result;
727 }
728
729 let QueueConfigData { drop_threshold, .. } = <QueueConfig<T>>::get();
730 let batches_footprints =
731 T::XcmpQueue::get_batches_footprints(sender, xcms.iter().copied(), drop_threshold);
732
733 let msgs_count = batches_footprints
734 .footprints
735 .last()
736 .map(|batch_footprint| batch_footprint.msgs_count)
737 .unwrap_or(0);
738 if msgs_count < xcms.len() {
739 tracing::error!(
740 target: LOG_TARGET,
741 "Drop threshold exceeded: cannot enqueue entire XCMP messages batch; \
742 dropped some or all messages in batch."
743 );
744 result.has_dropped_msgs = true;
745 }
746
747 let best_batch_footprint = batches_footprints.search_best_by(|batch_info| {
748 let required_weight = T::WeightInfo::enqueue_xcmp_messages(
749 batches_footprints.first_page_pos.saturated_into(),
750 batch_info,
751 is_first_sender_batch,
752 );
753
754 match meter.can_consume(required_weight) {
755 true => core::cmp::Ordering::Less,
756 false => core::cmp::Ordering::Greater,
757 }
758 });
759
760 meter.consume(T::WeightInfo::enqueue_xcmp_messages(
761 batches_footprints.first_page_pos.saturated_into(),
762 best_batch_footprint,
763 is_first_sender_batch,
764 ));
765 T::XcmpQueue::enqueue_messages(
766 xcms.iter().take(best_batch_footprint.msgs_count).copied(),
767 sender,
768 );
769
770 if best_batch_footprint.msgs_count < msgs_count {
771 tracing::error!(
772 target: LOG_TARGET,
773 used_weight=?meter.consumed_ratio(),
774 "Out of weight: cannot enqueue entire XCMP messages batch; \
775 dropped some or all messages in batch."
776 );
777 result.has_out_of_weight_msgs = true;
778 }
779
780 result
781 }
782
783 pub(crate) fn take_first_concatenated_xcm<'a>(
790 data: &mut &'a [u8],
791 meter: &mut WeightMeter,
792 ) -> Result<BoundedSlice<'a, u8, MaxXcmpMessageLenOf<T>>, TakeXcmError> {
793 let base_weight = T::WeightInfo::take_first_concatenated_xcm(0);
795 if meter.try_consume(base_weight).is_err() {
796 tracing::error!("Out of weight; could not decode all; dropping");
797 return Err(TakeXcmError::OutOfWeight);
798 }
799
800 let input_data = &mut &data[..];
801 let mut input = codec::CountedInput::new(input_data);
802 VersionedXcm::<()>::decode_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut input).map_err(
803 |error| {
804 tracing::debug!(target: LOG_TARGET, ?error, "Failed to decode XCM with depth limit");
805 TakeXcmError::InvalidData
806 },
807 )?;
808 let (xcm_data, remaining_data) = data.split_at(input.count() as usize);
809 *data = remaining_data;
810
811 let extra_weight = T::WeightInfo::take_first_concatenated_xcm(xcm_data.len() as u32)
815 .saturating_sub(base_weight);
816 meter.consume(extra_weight);
817
818 let xcm = BoundedSlice::try_from(xcm_data).map_err(|error| {
819 tracing::error!(
820 target: LOG_TARGET,
821 ?error,
822 "Failed to take XCM after decoding: message is too long"
823 );
824 TakeXcmError::InvalidData
825 })?;
826
827 Ok(xcm)
828 }
829
830 pub(crate) fn take_first_concatenated_opaque_xcm<'a>(
834 data: &mut &'a [u8],
835 ) -> Result<BoundedSlice<'a, u8, MaxXcmpMessageLenOf<T>>, TakeXcmError> {
836 let xcm_len = Compact::<u32>::decode(data).map_err(|error| {
837 tracing::debug!(target: LOG_TARGET, ?error, "Failed to decode opaque XCM length");
838 TakeXcmError::InvalidData
839 })?;
840 let (xcm_data, remaining_data) = match data.split_at_checked(xcm_len.0 as usize) {
841 Some((xcm_data, remaining_data)) => (xcm_data, remaining_data),
842 None => {
843 tracing::debug!(target: LOG_TARGET, ?xcm_len, "Wrong opaque XCM length");
844 return Err(TakeXcmError::InvalidData);
845 },
846 };
847 *data = remaining_data;
848
849 let xcm = BoundedSlice::try_from(xcm_data).map_err(|error| {
850 tracing::error!(
851 target: LOG_TARGET,
852 ?error,
853 "Failed to take opaque XCM after decoding: message is too long"
854 );
855 TakeXcmError::InvalidData
856 })?;
857
858 Ok(xcm)
859 }
860
861 pub(crate) fn take_first_concatenated_xcms<'a>(
865 data: &mut &'a [u8],
866 encoding: XcmEncoding,
867 batch_size: usize,
868 meter: &mut WeightMeter,
869 ) -> Result<
870 Vec<BoundedSlice<'a, u8, MaxXcmpMessageLenOf<T>>>,
871 (TakeXcmError, Vec<BoundedSlice<'a, u8, MaxXcmpMessageLenOf<T>>>),
872 > {
873 let mut batch = vec![];
874 loop {
875 if data.is_empty() {
876 return Ok(batch);
877 }
878
879 let maybe_xcm = match encoding {
880 XcmEncoding::Simple => Self::take_first_concatenated_xcm(data, meter),
881 XcmEncoding::Double => Self::take_first_concatenated_opaque_xcm(data),
882 };
883 match maybe_xcm {
884 Ok(xcm) => {
885 batch.push(xcm);
886 if batch.len() >= batch_size {
887 return Ok(batch);
888 }
889 },
890 Err(e) => return Err((e, batch)),
891 }
892 }
893 }
894
895 #[transactional]
901 fn handle_signals_page<'a>(
902 sender: ParaId,
903 data: &mut &'a [u8],
904 meter: &mut WeightMeter,
905 can_retry_page: bool,
906 ) -> Result<(), DispatchError> {
907 let mut signal_count = 0;
908 while !data.is_empty() {
909 signal_count += 1;
910 if signal_count > MAX_SIGNALS_PER_PAGE {
911 tracing::error!(
912 "Already processed {} signals for HRMP page. Dropping the rest.",
913 MAX_SIGNALS_PER_PAGE
914 );
915 return Ok(());
916 }
917
918 match ChannelSignal::decode(data) {
919 Ok(ChannelSignal::Suspend) => {
920 if meter.try_consume(T::WeightInfo::suspend_channel()).is_err() {
921 tracing::error!("Not enough weight to process suspend signal");
922 if can_retry_page {
923 return Err(Error::<T>::RetryPage.into());
924 }
925 break;
926 }
927 Self::suspend_channel(sender)
928 },
929 Ok(ChannelSignal::Resume) => {
930 if meter.try_consume(T::WeightInfo::resume_channel()).is_err() {
931 tracing::error!("Not enough weight to process resume signal - dropping");
932 if can_retry_page {
933 return Err(Error::<T>::RetryPage.into());
934 }
935 break;
936 }
937 Self::resume_channel(sender)
938 },
939 Err(_) => {
940 defensive!("Undecodable channel signal - dropping");
941 break;
942 },
943 }
944 }
945
946 Ok(())
947 }
948
949 #[transactional]
955 fn handle_xcms_page<'a>(
956 sender: ParaId,
957 encoding: XcmEncoding,
958 data: &mut &'a [u8],
959 known_xcm_senders: &mut BTreeSet<ParaId>,
960 meter: &mut WeightMeter,
961 can_retry_page: bool,
962 ) -> Result<(), DispatchError> {
963 let mut is_first_sender_batch = !known_xcm_senders.contains(&sender);
964 if is_first_sender_batch {
965 if meter.try_consume(T::WeightInfo::uncached_enqueue_xcmp_messages()).is_err() {
966 tracing::error!(
967 "Out of weight: cannot enqueue XCMP messages; dropping page; \
968 Used weight: {:?}",
969 meter.consumed_ratio()
970 );
971
972 if can_retry_page {
973 return Err(Error::<T>::RetryPage.into());
974 } else {
975 return Ok(());
976 }
977 }
978 }
979
980 let mut can_process_next_batch = true;
981 while can_process_next_batch {
982 let batch =
983 match Self::take_first_concatenated_xcms(data, encoding, XCM_BATCH_SIZE, meter) {
984 Ok(batch) => batch,
985 Err((e, batch)) => {
986 if e == TakeXcmError::OutOfWeight && can_retry_page {
987 return Err(Error::<T>::RetryPage.into());
988 }
989
990 can_process_next_batch = false;
991 tracing::error!("HRMP inbound decode stream broke; page will be dropped.");
992 batch
993 },
994 };
995 if batch.is_empty() {
996 break;
997 }
998
999 let enqueueing_result =
1000 Self::enqueue_xcmp_messages(sender, &batch, is_first_sender_batch, meter);
1001 if enqueueing_result.has_out_of_weight_msgs {
1002 if can_retry_page {
1003 return Err(Error::<T>::RetryPage.into());
1004 }
1005
1006 break;
1007 }
1008 if enqueueing_result.has_dropped_msgs {
1009 break;
1010 }
1011 is_first_sender_batch = false;
1012 }
1013
1014 known_xcm_senders.insert(sender);
1016 Ok(())
1017 }
1018
1019 pub fn on_idle_weight() -> Weight {
1021 <T as crate::Config>::WeightInfo::on_idle_good_msg()
1022 .max(<T as crate::Config>::WeightInfo::on_idle_large_msg())
1023 }
1024
1025 #[cfg(feature = "bridging")]
1026 fn is_inbound_channel_suspended(sender: ParaId) -> bool {
1027 <InboundXcmpSuspended<T>>::get().iter().any(|c| c == &sender)
1028 }
1029
1030 #[cfg(feature = "bridging")]
1031 fn outbound_channel_state(target: ParaId) -> Option<(OutboundState, u16)> {
1033 <OutboundXcmpStatus<T>>::get().iter().find(|c| c.recipient == target).map(|c| {
1034 let queued_pages = c.last_index.saturating_sub(c.first_index);
1035 (c.state, queued_pages)
1036 })
1037 }
1038}
1039
1040impl<T: Config> OnQueueChanged<ParaId> for Pallet<T> {
1041 fn on_queue_changed(para: ParaId, fp: QueueFootprint) {
1043 let QueueConfigData { resume_threshold, suspend_threshold, .. } = <QueueConfig<T>>::get();
1044
1045 let mut suspended_channels = <InboundXcmpSuspended<T>>::get();
1046 let suspended = suspended_channels.contains(¶);
1047
1048 if suspended && fp.ready_pages <= resume_threshold {
1049 if let Err(err) = Self::send_signal(para, ChannelSignal::Resume) {
1050 tracing::error!(
1051 target: LOG_TARGET,
1052 error=?err,
1053 sibling=?para,
1054 "defensive: Could not send resumption signal to inbound channel of sibling; channel remains suspended."
1055 );
1056 } else {
1057 suspended_channels.remove(¶);
1058 <InboundXcmpSuspended<T>>::put(suspended_channels);
1059 }
1060 } else if !suspended && fp.ready_pages >= suspend_threshold {
1061 tracing::warn!(target: LOG_TARGET, sibling=?para, "XCMP queue for sibling is full; suspending channel.");
1062
1063 if let Err(err) = Self::send_signal(para, ChannelSignal::Suspend) {
1064 tracing::error!(
1066 target: LOG_TARGET, error=?err,
1067 "defensive: Could not send suspension signal; future messages may be dropped."
1068 );
1069 } else if let Err(err) = suspended_channels.try_insert(para) {
1070 tracing::error!(
1071 target: LOG_TARGET,
1072 error=?err,
1073 sibling=?para,
1074 "Too many channels suspended; cannot suspend sibling; further messages may be dropped."
1075 );
1076 } else {
1077 <InboundXcmpSuspended<T>>::put(suspended_channels);
1078 }
1079 }
1080 }
1081}
1082
1083impl<T: Config> QueuePausedQuery<ParaId> for Pallet<T> {
1084 fn is_paused(para: &ParaId) -> bool {
1085 if !QueueSuspended::<T>::get() {
1086 return false;
1087 }
1088
1089 let sender_origin = T::ControllerOriginConverter::convert_origin(
1091 (Parent, Parachain((*para).into())),
1092 OriginKind::Superuser,
1093 );
1094 let is_controller =
1095 sender_origin.map_or(false, |origin| T::ControllerOrigin::try_origin(origin).is_ok());
1096
1097 !is_controller
1098 }
1099}
1100
1101#[derive(Copy, Clone, PartialEq)]
1103enum XcmEncoding {
1104 Simple,
1109 Double,
1117}
1118
1119impl<T: Config> XcmpMessageHandler for Pallet<T> {
1120 fn handle_xcmp_messages<'a, I: Iterator<Item = (ParaId, RelayBlockNumber, &'a [u8])>>(
1121 iter: I,
1122 max_weight: Weight,
1123 ) -> (usize, Weight) {
1124 let mut num_processed_pages = 0;
1125 let mut meter = WeightMeter::with_limit(max_weight);
1126
1127 let mut known_xcm_senders = BTreeSet::new();
1128 for (sender, _sent_at, mut data) in iter {
1129 let can_retry_page = num_processed_pages > 0;
1133
1134 let format = match XcmpMessageFormat::decode(&mut data) {
1135 Ok(f) => f,
1136 Err(_) => {
1137 tracing::error!("Unknown XCMP message format - dropping");
1138 num_processed_pages += 1;
1139 continue;
1140 },
1141 };
1142
1143 match format {
1144 XcmpMessageFormat::Signals => {
1145 if let Err(_) =
1146 Self::handle_signals_page(sender, &mut data, &mut meter, can_retry_page)
1147 {
1148 break;
1149 }
1150 num_processed_pages += 1;
1151 },
1152 XcmpMessageFormat::ConcatenatedVersionedXcm |
1153 XcmpMessageFormat::ConcatenatedOpaqueVersionedXcm => {
1154 let encoding = match format {
1155 XcmpMessageFormat::ConcatenatedVersionedXcm => XcmEncoding::Simple,
1156 XcmpMessageFormat::ConcatenatedOpaqueVersionedXcm => {
1157 let mut all_channels = <OutboundXcmpStatus<T>>::get();
1158 if let Some(channel_details) =
1159 Self::try_get_or_insert_outbound_channel(&mut all_channels, sender)
1160 {
1161 channel_details
1162 .flags
1163 .notice_concatenated_opaque_versioned_xcm_support();
1164 }
1165 <OutboundXcmpStatus<T>>::put(all_channels);
1166
1167 XcmEncoding::Double
1168 },
1169 _ => {
1170 num_processed_pages += 1;
1172 continue;
1173 },
1174 };
1175
1176 if let Err(_) = Self::handle_xcms_page(
1177 sender,
1178 encoding,
1179 &mut data,
1180 &mut known_xcm_senders,
1181 &mut meter,
1182 can_retry_page,
1183 ) {
1184 break;
1185 }
1186 num_processed_pages += 1;
1187 },
1188 XcmpMessageFormat::ConcatenatedEncodedBlob => {
1189 tracing::error!("Blob messages are unhandled - dropping page");
1190 num_processed_pages += 1;
1191 continue;
1192 },
1193 }
1194 }
1195
1196 (num_processed_pages, meter.consumed())
1197 }
1198}
1199
1200impl<T: Config> XcmpMessageSource for Pallet<T> {
1201 fn take_outbound_messages(
1202 maximum_channels: usize,
1203 excluded_recipients: &[ParaId],
1204 ) -> Vec<(ParaId, Vec<u8>)> {
1205 let mut statuses = <OutboundXcmpStatus<T>>::get().into_inner();
1206 let old_statuses_len = statuses.len();
1207 let max_message_count = statuses.len().min(maximum_channels);
1208 let mut result = Vec::with_capacity(max_message_count);
1209
1210 statuses.retain_mut(|status| {
1211 let OutboundChannelDetails {
1212 recipient: para_id,
1213 state: outbound_state,
1214 signals_exist,
1215 first_index,
1216 last_index,
1217 flags,
1218 queued_bytes,
1219 } = status;
1220
1221 let (max_size_now, max_size_ever) = match T::ChannelInfo::get_channel_status(*para_id) {
1222 ChannelStatus::Closed => {
1223 for i in *first_index..*last_index {
1226 <OutboundXcmpMessages<T>>::remove(*para_id, i);
1227 }
1228 if *signals_exist {
1229 <SignalMessages<T>>::remove(*para_id);
1230 }
1231 return false;
1232 }
1233 ChannelStatus::Full => return true,
1234 ChannelStatus::Ready(max_size_now, max_size_ever) => (max_size_now, max_size_ever),
1235 };
1236
1237 if excluded_recipients.contains(para_id) {
1239 return true;
1240 }
1241
1242 if result.len() == max_message_count {
1244 return true;
1247 }
1248
1249 let page = 'page_fetch: {
1250 if *signals_exist {
1251 let page = <SignalMessages<T>>::get(*para_id);
1252 defensive_assert!(!page.is_empty(), "Signals must exist");
1253
1254 if page.len() < max_size_now {
1255 <SignalMessages<T>>::remove(*para_id);
1256 *signals_exist = false;
1257 break 'page_fetch page;
1258 }
1259
1260 defensive!("Signals should fit into a single page");
1261 return true;
1262 }
1263
1264 if *outbound_state == OutboundState::Suspended {
1265 return true;
1267 }
1268
1269 if last_index > first_index {
1270 let page = <OutboundXcmpMessages<T>>::get(*para_id, *first_index);
1271 if page.len() < max_size_now {
1272 <OutboundXcmpMessages<T>>::remove(*para_id, *first_index);
1273 *first_index += 1;
1274 *queued_bytes = queued_bytes.saturating_sub(page.len() as u32);
1275 break 'page_fetch page;
1276 }
1277 }
1278
1279 if flags.should_send_concatenated_opaque_versioned_xcm_notification() {
1283 match WeakBoundedVec::try_from(XcmpMessageFormat::ConcatenatedOpaqueVersionedXcm.encode()) {
1284 Ok(page) => {
1285 flags.notice_concatenated_opaque_versioned_xcm_notification_sent();
1286 break 'page_fetch page;
1287 }
1288 Err(_) => {
1289 defensive!("XcmpMessageFormat should fit into a single page");
1290 return true;
1291 }
1292 };
1293 }
1294
1295 return true;
1296 };
1297
1298 if first_index == last_index {
1299 *first_index = 0;
1300 *last_index = 0;
1301 *queued_bytes = 0;
1302 }
1303
1304 if page.len() > max_size_ever {
1305 defensive!("WARNING: oversize message in queue - dropping");
1309 } else {
1310 result.push((*para_id, page.into_inner()));
1311 }
1312
1313 let max_total_size = match T::ChannelInfo::get_channel_info(*para_id) {
1314 Some(channel_info) => channel_info.max_total_size,
1315 None => {
1316 tracing::warn!(target: LOG_TARGET, "calling `get_channel_info` with no RelevantMessagingState?!");
1317 MAX_POSSIBLE_ALLOCATION
1319 }
1320 };
1321 let threshold = max_total_size.saturating_div(delivery_fee_constants::THRESHOLD_FACTOR);
1322 if *queued_bytes <= threshold {
1323 Self::decrease_fee_factor(*para_id);
1324 }
1325
1326 true
1327 });
1328 debug_assert!(!statuses.iter().any(|s| s.signals_exist), "Signals should be handled");
1329 let mut statuses = BoundedVec::defensive_truncate_from(statuses);
1330
1331 result.sort_by_key(|(recipient, _msg)| *recipient);
1334
1335 let pruned = old_statuses_len - statuses.len();
1337 let _ = statuses.try_rotate_left(result.len().saturating_sub(pruned)).defensive_proof(
1342 "Could not store HRMP channels config. Some HRMP channels may be broken.",
1343 );
1344
1345 <OutboundXcmpStatus<T>>::put(statuses);
1346
1347 result
1348 }
1349}
1350
1351impl<T: Config> SendXcm for Pallet<T> {
1353 type Ticket = (ParaId, VersionedXcm<()>);
1354
1355 fn validate(
1356 dest: &mut Option<Location>,
1357 msg: &mut Option<Xcm<()>>,
1358 ) -> SendResult<(ParaId, VersionedXcm<()>)> {
1359 let d = dest.take().ok_or(SendError::MissingArgument)?;
1360
1361 match d.unpack() {
1362 (1, [Parachain(id)]) => {
1364 let xcm = msg.take().ok_or(SendError::MissingArgument)?;
1365 let id = ParaId::from(*id);
1366 let price = T::PriceForSiblingDelivery::price_for_delivery(id, &xcm);
1367 let versioned_xcm = T::VersionWrapper::wrap_version(&d, xcm)
1368 .map_err(|()| SendError::DestinationUnsupported)?;
1369 versioned_xcm
1370 .check_is_decodable()
1371 .map_err(|()| SendError::ExceedsMaxMessageSize)?;
1372
1373 Ok(((id, versioned_xcm), price))
1374 },
1375 _ => {
1376 *dest = Some(d);
1379 Err(SendError::NotApplicable)
1380 },
1381 }
1382 }
1383
1384 fn deliver((recipient, xcm): (ParaId, VersionedXcm<()>)) -> Result<XcmHash, SendError> {
1385 let hash = xcm.using_encoded(sp_io::hashing::blake2_256);
1386
1387 let mut encoding = XcmEncoding::Simple;
1388 let mut all_channels = <OutboundXcmpStatus<T>>::get();
1389 if let Some(channel_details) = Self::try_get_outbound_channel(&mut all_channels, recipient)
1390 {
1391 if channel_details.flags.has_concatenated_opaque_versioned_xcm_support() {
1392 encoding = XcmEncoding::Double;
1393 }
1394 }
1395
1396 let result = match encoding {
1397 XcmEncoding::Simple => {
1398 Self::send_fragment(recipient, XcmpMessageFormat::ConcatenatedVersionedXcm, xcm)
1399 },
1400 XcmEncoding::Double => Self::send_fragment(
1401 recipient,
1402 XcmpMessageFormat::ConcatenatedOpaqueVersionedXcm,
1403 xcm.encode(),
1404 ),
1405 };
1406 match result {
1407 Ok(_) => {
1408 Self::deposit_event(Event::XcmpMessageSent { message_hash: hash });
1409 Ok(hash)
1410 },
1411 Err(e) => {
1412 tracing::error!(target: LOG_TARGET, error=?e, "Deliver error");
1413 Err(SendError::Transport(e.into()))
1414 },
1415 }
1416 }
1417}
1418
1419impl<T: Config> InspectMessageQueues for Pallet<T> {
1420 fn clear_messages() {
1421 let _ = OutboundXcmpMessages::<T>::clear(u32::MAX, None);
1423 OutboundXcmpStatus::<T>::mutate(|details_vec| {
1424 for details in details_vec {
1425 details.first_index = 0;
1426 details.last_index = 0;
1427 details.queued_bytes = 0;
1428 }
1429 });
1430 }
1431
1432 fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
1433 use xcm::prelude::*;
1434
1435 OutboundXcmpMessages::<T>::iter()
1436 .map(|(para_id, _, messages)| {
1437 let data = &mut &messages[..];
1438
1439 let decoded_format = XcmpMessageFormat::decode(data).unwrap();
1440 let mut decoded_messages = Vec::new();
1441 while !data.is_empty() {
1442 let message_bytes = match decoded_format {
1443 XcmpMessageFormat::ConcatenatedVersionedXcm => {
1444 Self::take_first_concatenated_xcm(data, &mut WeightMeter::new())
1445 },
1446 XcmpMessageFormat::ConcatenatedOpaqueVersionedXcm => {
1447 Self::take_first_concatenated_opaque_xcm(data)
1448 },
1449 unexpected_format => {
1450 panic!("Unexpected XCMP format: {unexpected_format:?}!")
1451 },
1452 }
1453 .unwrap();
1454 let decoded_message = VersionedXcm::<()>::decode_all_with_mem_and_depth_limit(
1455 &mut &message_bytes[..],
1456 )
1457 .unwrap();
1458 decoded_messages.push(decoded_message);
1459 }
1460
1461 (
1462 VersionedLocation::from(Location::new(1, Parachain(para_id.into()))),
1463 decoded_messages,
1464 )
1465 })
1466 .collect()
1467 }
1468}
1469
1470impl<T: Config> FeeTracker for Pallet<T> {
1471 type Id = ParaId;
1472
1473 fn get_fee_factor(id: Self::Id) -> FixedU128 {
1474 <DeliveryFeeFactor<T>>::get(id)
1475 }
1476
1477 fn set_fee_factor(id: Self::Id, val: FixedU128) {
1478 <DeliveryFeeFactor<T>>::set(id, val);
1479 }
1480}