polkadot_runtime_parachains/
dmp.rs1use crate::{
46 configuration::{self, HostConfiguration},
47 initializer, paras, FeeTracker, GetMinFeeFactor,
48};
49use alloc::vec::Vec;
50use core::fmt;
51use frame_support::{pallet_prelude::*, traits::Defensive, weights::WeightMeter};
52use frame_system::pallet_prelude::BlockNumberFor;
53use inbound_downward_queue::InboundDownwardQueue;
54use polkadot_core_primitives::{InboundDownwardQueueMeta, PageIndex};
55use polkadot_primitives::{DownwardMessage, Hash, Id as ParaId, InboundDownwardMessage};
56use sp_core::MAX_POSSIBLE_ALLOCATION;
57use sp_runtime::{
58 traits::{BlakeTwo256, Hash as HashT},
59 FixedU128,
60};
61use xcm::latest::SendError;
62
63pub use pallet::*;
64pub use weights::WeightInfo;
65
66#[cfg(feature = "runtime-benchmarks")]
67mod benchmarking;
68pub mod inbound_downward_queue;
69pub mod migration;
70#[cfg(test)]
71mod mock;
72#[cfg(test)]
73mod tests;
74pub mod weights;
75
76const THRESHOLD_FACTOR: u32 = 2;
77
78#[derive(Debug)]
80pub enum QueueDownwardMessageError {
81 ExceedsMaxMessageSize,
83 ExceedsMaxQueueSize,
85 Unroutable,
87}
88
89impl From<QueueDownwardMessageError> for SendError {
90 fn from(err: QueueDownwardMessageError) -> Self {
91 match err {
92 QueueDownwardMessageError::ExceedsMaxMessageSize |
93 QueueDownwardMessageError::ExceedsMaxQueueSize => SendError::ExceedsMaxMessageSize,
94 QueueDownwardMessageError::Unroutable => SendError::Unroutable,
95 }
96 }
97}
98
99pub(crate) enum ProcessedDownwardMessagesAcceptanceErr {
102 AdvancementRule,
104 Underflow { processed_downward_messages: u32, dmq_length: u32 },
106}
107
108impl fmt::Debug for ProcessedDownwardMessagesAcceptanceErr {
109 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
110 use ProcessedDownwardMessagesAcceptanceErr::*;
111 match *self {
112 AdvancementRule => {
113 write!(fmt, "DMQ is not empty, but processed_downward_messages is 0",)
114 },
115 Underflow { processed_downward_messages, dmq_length } => write!(
116 fmt,
117 "processed_downward_messages = {}, but dmq_length is only {}",
118 processed_downward_messages, dmq_length,
119 ),
120 }
121 }
122}
123
124#[frame_support::pallet]
125pub mod pallet {
126 use super::*;
127
128 #[pallet::pallet]
129 #[pallet::storage_version(migration::STORAGE_VERSION)]
130 #[pallet::without_storage_info]
131 pub struct Pallet<T>(_);
132
133 #[pallet::config]
134 pub trait Config: frame_system::Config + configuration::Config + paras::Config {
135 type WeightInfo: WeightInfo;
137 }
138
139 #[pallet::storage]
143 pub type DownwardMessageQueueMeta<T: Config> =
144 StorageMap<_, Twox64Concat, ParaId, InboundDownwardQueueMeta, OptionQuery>;
145
146 #[pallet::storage]
151 pub type DownwardMessageQueuePages<T: Config> = StorageDoubleMap<
152 _,
153 Blake2_128Concat,
154 ParaId,
155 Twox64Concat,
156 PageIndex,
157 InboundDownwardMessage<BlockNumberFor<T>>,
158 OptionQuery,
159 >;
160
161 #[pallet::storage]
165 pub type DownwardMessageQueueLazyDelete<T: Config> = StorageMap<
166 _,
167 Blake2_128Concat,
168 ParaId,
169 (PageIndex, PageIndex), OptionQuery,
171 >;
172
173 #[pallet::storage]
181 pub(crate) type DownwardMessageQueueHeads<T: Config> =
182 StorageMap<_, Twox64Concat, ParaId, Hash, ValueQuery>;
183
184 #[pallet::storage]
186 pub(crate) type DeliveryFeeFactor<T: Config> =
187 StorageMap<_, Twox64Concat, ParaId, FixedU128, ValueQuery, GetMinFeeFactor<Pallet<T>>>;
188
189 #[pallet::event]
190 #[pallet::generate_deposit(pub(super) fn deposit_event)]
191 pub enum Event<T: Config> {
192 DmpQueueV0Cleaned { para: ParaId },
194 }
195
196 #[pallet::hooks]
197 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
198 #[cfg(all(feature = "std", not(test)))]
200 fn integrity_test() {
201 let min_mbm_weight = <T as Config>::WeightInfo::migrate_v0_to_v1_step_base()
202 .saturating_add(<T as Config>::WeightInfo::migrate_v0_to_v1_step_iter())
203 .saturating_add(<T as Config>::WeightInfo::migrate_v0_to_v1_step_msg());
204
205 let max = T::BlockWeights::get().max_block.saturating_div(3);
206 assert!(
207 max.all_gte(min_mbm_weight),
208 "DMP queue migration uses more than 1/3 of max block weight"
209 );
210
211 let lazy_delete_some_weight = <T as Config>::WeightInfo::lazy_delete_some();
212 assert!(
213 max.all_gte(lazy_delete_some_weight),
214 "DMP queue lazy delete uses more than 1/3 block weight"
215 );
216 }
217
218 fn on_idle(_now: BlockNumberFor<T>, weight: Weight) -> Weight {
219 let mut meter = WeightMeter::with_limit(weight);
220
221 InboundDownwardQueue::<T>::lazy_delete_some(&mut meter);
222
223 meter.consumed()
224 }
225
226 #[cfg(feature = "try-runtime")]
227 fn try_state(_now: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
228 InboundDownwardQueue::<T>::try_state();
229
230 Ok(())
231 }
232 }
233}
234impl<T: Config> Pallet<T> {
236 pub(crate) fn initializer_initialize(_now: BlockNumberFor<T>) -> Weight {
238 Weight::zero()
239 }
240
241 pub(crate) fn initializer_finalize() {}
243
244 pub(crate) fn initializer_on_new_session(
246 _notification: &initializer::SessionChangeNotification<BlockNumberFor<T>>,
247 outgoing_paras: &[ParaId],
248 ) {
249 Self::perform_outgoing_para_cleanup(outgoing_paras);
250 }
251
252 fn perform_outgoing_para_cleanup(outgoing: &[ParaId]) {
255 for outgoing_para in outgoing {
256 Self::clean_dmp_after_outgoing(outgoing_para);
257 }
258 }
259
260 fn clean_dmp_after_outgoing(outgoing_para: &ParaId) {
262 InboundDownwardQueue::<T>::delete_all(*outgoing_para);
263 DownwardMessageQueueHeads::<T>::remove(outgoing_para);
264 }
265
266 pub fn can_queue_downward_message(
270 config: &HostConfiguration<BlockNumberFor<T>>,
271 para: &ParaId,
272 msg: &DownwardMessage,
273 ) -> Result<(), QueueDownwardMessageError> {
274 let serialized_len = msg.len() as u32;
275 if serialized_len > config.max_downward_message_size {
276 return Err(QueueDownwardMessageError::ExceedsMaxMessageSize);
277 }
278
279 if Self::dmq_length(*para) > Self::dmq_max_length(config.max_downward_message_size) {
281 return Err(QueueDownwardMessageError::ExceedsMaxMessageSize);
282 }
283
284 if !paras::Heads::<T>::contains_key(para) {
286 return Err(QueueDownwardMessageError::Unroutable);
287 }
288
289 Ok(())
290 }
291
292 pub fn queue_downward_message(
301 config: &HostConfiguration<BlockNumberFor<T>>,
302 para: ParaId,
303 msg: DownwardMessage,
304 ) -> Result<(), QueueDownwardMessageError> {
305 let serialized_len = msg.len();
306 Self::can_queue_downward_message(config, ¶, &msg)?;
307
308 let inbound = InboundDownwardQueue::<T>::push_back(para, msg)
309 .map_err(|_| QueueDownwardMessageError::ExceedsMaxQueueSize)?;
310 let q_len = InboundDownwardQueue::<T>::len(para).unwrap_or(0);
311
312 DownwardMessageQueueHeads::<T>::mutate(para, |head| {
314 let new_head =
315 BlakeTwo256::hash_of(&(*head, inbound.sent_at, T::Hashing::hash_of(&inbound.msg)));
316 *head = new_head;
317 });
318
319 let threshold =
320 Self::dmq_max_length(config.max_downward_message_size).saturating_div(THRESHOLD_FACTOR);
321 if q_len > threshold as u64 {
322 Self::increase_fee_factor(para, serialized_len as u128);
323 }
324
325 Ok(())
326 }
327
328 pub(crate) fn check_processed_downward_messages(
330 para: ParaId,
331 relay_parent_number: BlockNumberFor<T>,
332 processed_downward_messages: u32,
333 ) -> Result<(), ProcessedDownwardMessagesAcceptanceErr> {
334 let dmq_length = Self::dmq_length(para);
335
336 if dmq_length > 0 && processed_downward_messages == 0 {
337 let first = InboundDownwardQueue::<T>::peek_front(para);
341
342 if first.map_or(false, |msg| msg.sent_at <= relay_parent_number) {
344 return Err(ProcessedDownwardMessagesAcceptanceErr::AdvancementRule);
345 }
346 }
347
348 if dmq_length < processed_downward_messages {
353 return Err(ProcessedDownwardMessagesAcceptanceErr::Underflow {
354 processed_downward_messages,
355 dmq_length,
356 });
357 }
358
359 Ok(())
360 }
361
362 pub(crate) fn prune_dmq(para: ParaId, processed_downward_messages: u32) {
364 InboundDownwardQueue::<T>::drop_front_n(para, processed_downward_messages as u64);
365 let q_len = InboundDownwardQueue::<T>::len(para).unwrap_or(0);
366
367 let config = configuration::ActiveConfig::<T>::get();
368 let threshold =
369 Self::dmq_max_length(config.max_downward_message_size).saturating_div(THRESHOLD_FACTOR);
370 if q_len <= threshold as u64 {
371 Self::decrease_fee_factor(para);
372 }
373 }
374
375 #[cfg(test)]
378 fn dmq_mqc_head(para: ParaId) -> Hash {
379 DownwardMessageQueueHeads::<T>::get(¶)
380 }
381
382 pub(crate) fn dmq_length(para: ParaId) -> u32 {
386 InboundDownwardQueue::<T>::len(para)
387 .unwrap_or(0)
388 .try_into()
389 .defensive_unwrap_or(u32::MAX)
390 }
391
392 fn dmq_max_length(max_downward_message_size: u32) -> u32 {
393 MAX_POSSIBLE_ALLOCATION.checked_div(max_downward_message_size).unwrap_or(0)
394 }
395
396 pub fn dmq_contents_do_not_call_in_consensus(
400 recipient: ParaId,
401 ) -> Vec<InboundDownwardMessage<BlockNumberFor<T>>> {
402 InboundDownwardQueue::<T>::peek_all_do_not_call_in_consensus(recipient)
403 }
404
405 #[cfg(any(feature = "runtime-benchmarks", feature = "std"))]
409 pub fn make_parachain_reachable(para: impl Into<ParaId>) {
410 let para = para.into();
411 crate::paras::Heads::<T>::insert(para, para.encode());
412 }
413}
414
415impl<T: Config> FeeTracker for Pallet<T> {
416 type Id = ParaId;
417
418 fn get_fee_factor(id: Self::Id) -> FixedU128 {
419 DeliveryFeeFactor::<T>::get(id)
420 }
421
422 fn set_fee_factor(id: Self::Id, val: FixedU128) {
423 <DeliveryFeeFactor<T>>::set(id, val);
424 }
425}
426
427#[cfg(feature = "runtime-benchmarks")]
428impl<T: Config> crate::EnsureForParachain for Pallet<T> {
429 fn ensure(para: ParaId) {
430 Self::make_parachain_reachable(para);
431 }
432}