referrerpolicy=no-referrer-when-downgrade

cumulus_pallet_xcmp_queue/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3// SPDX-License-Identifier: Apache-2.0
4
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// 	http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17//! A pallet which uses the XCMP transport layer to handle both incoming and outgoing XCM message
18//! sending and dispatch, queuing, signalling and backpressure. To do so, it implements:
19//! * `XcmpMessageHandler`
20//! * `XcmpMessageSource`
21//!
22//! Also provides an implementation of `SendXcm` which can be placed in a router tuple for relaying
23//! XCM over XCMP if the destination is `Parent/Parachain`. It requires an implementation of
24//! `XcmExecutor` for dispatching incoming XCM messages.
25//!
26//! To prevent out of memory errors on the `OutboundXcmpMessages` queue, an exponential fee factor
27//! (`DeliveryFeeFactor`) is set, much like the one used in DMP.
28//! The fee factor increases whenever the total size of messages in a particular channel passes a
29//! threshold. This threshold is defined as a percentage of the maximum total size the channel can
30//! have. More concretely, the threshold is `max_total_size` / `THRESHOLD_FACTOR`, where:
31//! - `max_total_size` is the maximum size, in bytes, of the channel, not number of messages.
32//! It is defined in the channel configuration.
33//! - `THRESHOLD_FACTOR` just declares which percentage of the max size is the actual threshold.
34//! If it's 2, then the threshold is half of the max size, if it's 4, it's a quarter, and so on.
35
36#![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
89/// Index used to identify overweight XCMs.
90pub type OverweightIndex = u64;
91/// The max length of an XCMP message.
92pub 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; // 64 KB
97/// The size of an XCM messages batch.
98pub const XCM_BATCH_SIZE: usize = 250;
99/// The maximum number of signals that we can have in an XCMP page.
100pub const MAX_SIGNALS_PER_PAGE: usize = 3;
101
102/// Constants related to delivery fee calculation
103pub mod delivery_fee_constants {
104	/// Fees will start increasing when queue is half full
105	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		/// Information on the available XCMP channels.
124		type ChannelInfo: GetChannelInfo;
125
126		/// Means of converting an `Xcm` into a `VersionedXcm`.
127		type VersionWrapper: WrapVersion;
128
129		/// Enqueue an inbound horizontal message for later processing.
130		///
131		/// This defines the maximal message length via [`crate::MaxXcmpMessageLenOf`]. The pallet
132		/// assumes that this hook will eventually process all the pushed messages.
133		type XcmpQueue: EnqueueMessage<ParaId>
134			+ QueueFootprintQuery<ParaId, MaxMessageLen = MaxXcmpMessageLenOf<Self>>;
135
136		/// The maximum number of inbound XCMP channels that can be suspended simultaneously.
137		///
138		/// Any further channel suspensions will fail and messages may get dropped without further
139		/// notice. Choosing a high value (1000) is okay; the trade-off that is described in
140		/// [`InboundXcmpSuspended`] still applies at that scale.
141		#[pallet::constant]
142		type MaxInboundSuspended: Get<u32>;
143
144		/// Maximal number of outbound XCMP channels that can have messages queued at the same time.
145		///
146		/// If this is reached, then no further messages can be sent to channels that do not yet
147		/// have a message queued. This should be set to the expected maximum of outbound channels
148		/// which is determined by [`Self::ChannelInfo`]. It is important to set this large enough,
149		/// since otherwise the congestion control protocol will not work as intended and messages
150		/// may be dropped. This value increases the PoV and should therefore not be picked too
151		/// high. Governance needs to pay attention to not open more channels than this value.
152		#[pallet::constant]
153		type MaxActiveOutboundChannels: Get<u32>;
154
155		/// The maximal page size for HRMP message pages.
156		///
157		/// A lower limit can be set dynamically, but this is the hard-limit for the PoV worst case
158		/// benchmarking. The limit for the size of a message is slightly below this, since some
159		/// overhead is incurred for encoding the format.
160		#[pallet::constant]
161		type MaxPageSize: Get<u32>;
162
163		/// The origin that is allowed to resume or suspend the XCMP queue.
164		type ControllerOrigin: EnsureOrigin<Self::RuntimeOrigin>;
165
166		/// The conversion function used to attempt to convert an XCM `Location` origin to a
167		/// superuser origin.
168		type ControllerOriginConverter: ConvertOrigin<Self::RuntimeOrigin>;
169
170		/// The price for delivering an XCM to a sibling parachain destination.
171		type PriceForSiblingDelivery: PriceForMessageDelivery<Id = ParaId>;
172
173		/// The weight information of this pallet.
174		type WeightInfo: WeightInfoExt;
175	}
176
177	#[pallet::call]
178	impl<T: Config> Pallet<T> {
179		/// Suspends all XCM executions for the XCMP queue, regardless of the sender's origin.
180		///
181		/// - `origin`: Must pass `ControllerOrigin`.
182		#[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		/// Resumes all XCM executions for the XCMP queue.
198		///
199		/// Note that this function doesn't change the status of the in/out bound channels.
200		///
201		/// - `origin`: Must pass `ControllerOrigin`.
202		#[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		/// Overwrites the number of pages which must be in the queue for the other side to be
218		/// told to suspend their sending.
219		///
220		/// - `origin`: Must pass `Root`.
221		/// - `new`: Desired value for `QueueConfigData.suspend_value`
222		#[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		/// Overwrites the number of pages which must be in the queue after which we drop any
234		/// further messages from the channel.
235		///
236		/// - `origin`: Must pass `Root`.
237		/// - `new`: Desired value for `QueueConfigData.drop_threshold`
238		#[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		/// Overwrites the number of pages which the queue must be reduced to before it signals
250		/// that message sending may recommence after it has been suspended.
251		///
252		/// - `origin`: Must pass `Root`.
253		/// - `new`: Desired value for `QueueConfigData.resume_threshold`
254		#[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		/// An HRMP message was sent to a sibling parachain.
300		XcmpMessageSent { message_hash: XcmHash },
301	}
302
303	#[pallet::error]
304	pub enum Error<T> {
305		/// Setting the queue config failed since one of its values was invalid.
306		BadQueueConfig,
307		/// The execution is already suspended.
308		AlreadySuspended,
309		/// The execution is already resumed.
310		AlreadyResumed,
311		/// There are too many active outbound channels.
312		TooManyActiveOutboundChannels,
313		/// The message is too big.
314		TooBig,
315		/// The page couldn't be processed, but it should be retried.
316		RetryPage,
317	}
318
319	/// The suspended inbound XCMP channels. All others are not suspended.
320	///
321	/// This is a `StorageValue` instead of a `StorageMap` since we expect multiple reads per block
322	/// to different keys with a one byte payload. The access to `BoundedBTreeSet` will be cached
323	/// within the block and therefore only included once in the proof size.
324	///
325	/// NOTE: The PoV benchmarking cannot know this and will over-estimate, but the actual proof
326	/// will be smaller.
327	#[pallet::storage]
328	pub type InboundXcmpSuspended<T: Config> =
329		StorageValue<_, BoundedBTreeSet<ParaId, T::MaxInboundSuspended>, ValueQuery>;
330
331	/// The non-empty XCMP channels in order of becoming non-empty, and the index of the first
332	/// and last outbound message. If the two indices are equal, then it indicates an empty
333	/// queue and there must be a non-`Ok` `OutboundStatus`. We assume queues grow no greater
334	/// than 65535 items. Queue indices for normal messages begin at one; zero is reserved in
335	/// case of the need to send a high-priority signal message this block.
336	/// The bool is true if there is a signal message waiting to be sent.
337	#[pallet::storage]
338	pub(super) type OutboundXcmpStatus<T: Config> = StorageValue<
339		_,
340		BoundedVec<OutboundChannelDetails, T::MaxActiveOutboundChannels>,
341		ValueQuery,
342	>;
343
344	/// The messages outbound in a given XCMP channel.
345	#[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	/// Any signal messages waiting to be sent.
357	#[pallet::storage]
358	pub(super) type SignalMessages<T: Config> =
359		StorageMap<_, Blake2_128Concat, ParaId, WeakBoundedVec<u8, T::MaxPageSize>, ValueQuery>;
360
361	/// The configuration which controls the dynamics of the outbound queue.
362	#[pallet::storage]
363	pub(super) type QueueConfig<T: Config> = StorageValue<_, QueueConfigData, ValueQuery>;
364
365	/// Whether or not the XCMP queue is suspended from executing incoming XCMs or not.
366	#[pallet::storage]
367	pub(super) type QueueSuspended<T: Config> = StorageValue<_, bool, ValueQuery>;
368
369	/// The factor to multiply the base delivery fee by.
370	#[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	// Check whether the recipient supports `ConcatenatedOpaqueVersionedXcm`.
391	fn has_concatenated_opaque_versioned_xcm_support(&self) -> bool {
392		*self & Self::CONCATENATED_OPAQUE_VERSIONED_XCM_SUPPORT != Self::empty()
393	}
394
395	// Check whether we should send a notification to the recipient, advertising that we support
396	// `ConcatenatedOpaqueVersionedXcm`.
397	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	// Remember that the recipient supports `ConcatenatedOpaqueVersionedXcm`.
410	fn notice_concatenated_opaque_versioned_xcm_support(&mut self) {
411		*self = *self | Self::CONCATENATED_OPAQUE_VERSIONED_XCM_SUPPORT;
412	}
413
414	// Remember that we advertised the `ConcatenatedOpaqueVersionedXcm` support to the recipient.
415	fn notice_concatenated_opaque_versioned_xcm_notification_sent(&mut self) {
416		*self = *self | Self::CONCATENATED_OPAQUE_VERSIONED_XCM_NOTIFICATION_SENT;
417	}
418}
419
420/// Struct containing detailed information about the outbound channel.
421#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo, Debug, MaxEncodedLen)]
422pub struct OutboundChannelDetails {
423	/// The `ParaId` of the parachain that this channel is connected with.
424	recipient: ParaId,
425	/// The state of the channel.
426	state: OutboundState,
427	/// Whether any signals exist in this channel.
428	signals_exist: bool,
429	/// The index of the first outbound message.
430	first_index: u16,
431	/// The index of the last outbound message.
432	last_index: u16,
433	/// Flags
434	flags: OutboundChannelFlags,
435	/// Cached total byte size of the pages currently queued in this channel.
436	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	/// The number of pages which must be in the queue for the other side to be told to suspend
466	/// their sending.
467	suspend_threshold: u32,
468	/// The number of pages which must be in the queue after which we drop any further messages
469	/// from the channel. This should normally not happen since the `suspend_threshold` can be used
470	/// to suspend the channel.
471	drop_threshold: u32,
472	/// The number of pages which the queue must be reduced to before it signals that
473	/// message sending may recommence after it has been suspended.
474	resume_threshold: u32,
475}
476
477impl Default for QueueConfigData {
478	fn default() -> Self {
479		// NOTE that these default values are only used on genesis. They should give a rough idea of
480		// what to set these values to, but is in no way a requirement.
481		Self {
482			drop_threshold: 48,    // 64KiB * 48 = 3MiB
483			suspend_threshold: 32, // 64KiB * 32 = 2MiB
484			resume_threshold: 8,   // 64KiB * 8 = 512KiB
485		}
486	}
487}
488
489impl QueueConfigData {
490	/// Validate all assumptions about `Self`.
491	///
492	/// Should be called prior to accepting this as new config.
493	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	/// Place a message `fragment` on the outgoing XCMP queue for `recipient`.
557	///
558	/// Format is the type of aggregate message that the `fragment` may be safely encoded and
559	/// appended onto.
560	///
561	/// ## Background
562	///
563	/// For our purposes, one HRMP "message" is actually an aggregated block of XCM "messages".
564	///
565	/// For the sake of clarity, we distinguish between them as message AGGREGATEs versus
566	/// message FRAGMENTs.
567	///
568	/// So each AGGREGATE is comprised of one or more concatenated SCALE-encoded `Vec<u8>`
569	/// FRAGMENTs. Though each fragment is already probably a SCALE-encoded Xcm, we can't be
570	/// certain, so we SCALE encode each `Vec<u8>` fragment in order to ensure we have the
571	/// length prefixed and can thus decode each fragment from the aggregate stream. With this,
572	/// we can concatenate them into a single aggregate blob without needing to be concerned
573	/// about encoding fragment boundaries.
574	///
575	/// If successful, returns the number of pages in the outbound queue after enqueuing the new
576	/// fragment.
577	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		// Optimization note: `max_message_size` could potentially be stored in
586		// `OutboundXcmpMessages` once known; that way it's only accessed when a new page is needed.
587
588		let channel_info =
589			T::ChannelInfo::get_channel_info(recipient).ok_or(MessageSendError::NoChannel)?;
590		// Max message size refers to aggregates, or pages. Not to individual fragments.
591		let max_message_size = channel_info.max_message_size.min(T::MaxPageSize::get()) as usize;
592		let format_size = format.encoded_size();
593		// We check the encoded fragment length plus the format size against the max message size
594		// because the format is concatenated if a new page is needed.
595		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			// We need to add a new page.
629			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	/// Sends a signal to the `dest` chain over XCMP. This is guaranteed to be dispatched on this
658	/// block.
659	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	/// Split concatenated encoded `VersionedXcm`s into individual items.
784	///
785	/// We directly encode them again since that is needed later on.
786	///
787	/// On error returns a partial batch with all the XCMs processed before the failure.
788	/// This can happen in case of a decoding/re-encoding failure.
789	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's make sure that we can decode at least an empty xcm message.
794		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		// Consume the extra weight that it took to decode this message.
812		// This depends on the message len in bytes.
813		// Saturates if it's over the limit.
814		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	/// Split concatenated opaque `VersionedXcm`s into individual items.
831	///
832	/// This method is not benchmarked because it's almost free.
833	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	/// Split concatenated encoded `VersionedXcm`s into batches.
862	///
863	/// We directly encode them again since that is needed later on.
864	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	/// Handle XCMP page containing signals.
896	///
897	/// If `can_retry_page` is true, and we are out of weight, the method will return an error,
898	/// rolling back all the storage operations and informing the caller to retry the page in the
899	/// next block.
900	#[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	/// Handle XCMP page containing XCM messages.
950	///
951	/// If `can_retry_page` is true, and we are out of weight, the method will return an error,
952	/// rolling back all the storage operations and informing the caller to retry the page in the
953	/// next block.
954	#[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		// Now that we know that the changes won't be rolled back, let's update `known_xcm_senders`.
1015		known_xcm_senders.insert(sender);
1016		Ok(())
1017	}
1018
1019	/// The worst-case weight of `on_idle`.
1020	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	/// Returns tuple of `OutboundState` and number of queued pages.
1032	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	// Suspends/Resumes the queue when certain thresholds are reached.
1042	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(&para);
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(&para);
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				// It will retry if `drop_threshold` is not reached, but it could be too late.
1065				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		// Make an exception for the superuser queue:
1090		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/// The encoding of the XCM messages in an XCMP page.
1102#[derive(Copy, Clone, PartialEq)]
1103enum XcmEncoding {
1104	/// Simple encoded (`xcm.encode()`)
1105	///
1106	/// When we receive this king of messages, we have to decode and then re-encoded them before
1107	/// enqueueing them for later processing.
1108	Simple,
1109	/// Double encoded (`xcm.encode().encode()`)
1110	///
1111	/// The XCM message is encoded first, resulting a vector of bytes. And then the vector of bytes
1112	/// is encoded again. This has 2 advantages:
1113	/// 1. We can just decode them before enqueueing them for later processing. They don't need to
1114	///    be re-encoded.
1115	/// 2. Decoding a `Vec<u8>` is much more efficient than decoding XCM messages.
1116	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			// We can retry a page only if it's not the first one. If it was the first one,
1130			// and it failed it means that even with the max allocated weight we couldn't process
1131			// it completely. So we leave it partially processed.
1132			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							// This branch is unreachable.
1171							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					// This means that there is no such channel anymore. Nothing to be done but
1224					// swallow the messages and discard the status.
1225					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			// Check if we should omit the recipient.
1238			if excluded_recipients.contains(para_id) {
1239				return true;
1240			}
1241
1242			// This is a hard limit from the host config; not even signals can bypass it.
1243			if result.len() == max_message_count {
1244				// We check this condition in the beginning of the loop so that we don't include
1245				// a message where the limit is 0.
1246				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					// Only signals are exempt from suspension.
1266					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				// Send a notification to the recipient advertising that we support
1280				// `XcmpMessageFormat::ConcatenatedOpaqueVersionedXcm` if needed.
1281				// We do this only once during the entire lifetime of the channel.
1282				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				// TODO: #274 This means that the channel's max message size has changed since
1306				//   the message was sent. We should parse it and split into smaller messages but
1307				//   since it's so unlikely then for now we just drop it.
1308				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					// We use this as a fallback in case the messaging state is not present
1318					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		// Sort the outbound messages by ascending recipient para id to satisfy the acceptance
1332		// criteria requirement.
1333		result.sort_by_key(|(recipient, _msg)| *recipient);
1334
1335		// old_status_len must be >= status.len() since we never add anything to status.
1336		let pruned = old_statuses_len - statuses.len();
1337		// Because it may so happen that we only gave attention to some channels it's important
1338		// to change the order. Otherwise, the next `on_finalize` we will again give attention
1339		// only to those channels that happen to be in the beginning, until they are emptied.
1340		// This leads to "starvation" of the channels near to the end.
1341		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
1351/// Xcm sender for sending to a sibling parachain.
1352impl<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			// An HRMP message for a sibling parachain.
1363			(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				// Anything else is unhandled. This includes a message that is not meant for us.
1377				// We need to make sure that dest/msg is not consumed here.
1378				*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		// Best effort.
1422		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}