referrerpolicy=no-referrer-when-downgrade

polkadot_runtime_parachains/dmp/
inbound_downward_queue.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Inbound downward message queue types.
18
19use super::*;
20
21use frame_support::traits::DefensiveSaturating;
22
23pub const LAZY_DELETE_MAX_PAGES: u32 = 3;
24
25/// Interface to modify
26pub struct InboundDownwardQueue<T>(pub core::marker::PhantomData<T>);
27impl<T: Config> InboundDownwardQueue<T> {
28	/// Metadata of the given para message queue.
29	pub fn meta(para: ParaId) -> Option<InboundDownwardQueueMeta> {
30		DownwardMessageQueueMeta::<T>::get(para)
31	}
32
33	/// Length of a queue or `None` if not exists.
34	pub fn len(para: ParaId) -> Option<u64> {
35		let len_v0 = migration::v0::DownwardMessageQueues::<T>::decode_len(para);
36		let len_v1 = Self::meta(para)
37			.map(|meta| meta.first_free.defensive_saturating_sub(meta.first_full) as usize);
38
39		if len_v0.is_none() && len_v1.is_none() {
40			None
41		} else {
42			Some(len_v0.unwrap_or_default().saturating_add(len_v1.unwrap_or_default()) as u64)
43		}
44	}
45
46	/// Append the message at the end of the queue and return the appended message.
47	pub fn push_back(
48		para: ParaId,
49		msg: DownwardMessage,
50	) -> Result<InboundDownwardMessage<BlockNumberFor<T>>, ()> {
51		let inbound =
52			InboundDownwardMessage { sent_at: frame_system::Pallet::<T>::block_number(), msg };
53		Self::push_back_inbound(para, &inbound)?;
54		Ok(inbound)
55	}
56
57	pub fn push_back_inbound(
58		para: ParaId,
59		inbound: &InboundDownwardMessage<BlockNumberFor<T>>,
60	) -> Result<(), ()> {
61		// v0 else v1
62		if migration::v0::DownwardMessageQueues::<T>::decode_len(para).is_some_and(|l| l > 0) {
63			migration::v0::DownwardMessageQueues::<T>::append(para, inbound);
64			return Ok(());
65		}
66
67		Self::push_back_inbound_v1(para, inbound)
68	}
69
70	/// Append a [`InboundDownwardMessage`].
71	pub fn push_back_inbound_v1(
72		para: ParaId,
73		inbound: &InboundDownwardMessage<BlockNumberFor<T>>,
74	) -> Result<(), ()> {
75		let mut meta = Self::meta(para).unwrap_or_else(|| Self::new_meta(para));
76
77		let insert_location = meta.first_free;
78		meta.first_free = meta.first_free.checked_add(1).ok_or(())?;
79		DownwardMessageQueuePages::<T>::insert(para, insert_location, inbound);
80
81		DownwardMessageQueueMeta::<T>::insert(para, meta);
82
83		Ok(())
84	}
85
86	/// Create a new metadata for a new queue.
87	///
88	/// This must be used over plain construction since a lazy deletion could still be ongoing.
89	fn new_meta(para: ParaId) -> InboundDownwardQueueMeta {
90		let Some((_, last)) = DownwardMessageQueueLazyDelete::<T>::get(para) else {
91			return InboundDownwardQueueMeta { first_full: 0, first_free: 0 };
92		};
93
94		InboundDownwardQueueMeta { first_full: last, first_free: last }
95	}
96
97	/// Try to remove the next message from the front of the queue.
98	#[cfg(test)]
99	pub fn pop_front(para: ParaId) -> Option<InboundDownwardMessage<BlockNumberFor<T>>> {
100		// v1 else v0
101		let Some(mut meta) = Self::meta(para) else {
102			return Self::pop_front_v0(para);
103		};
104
105		let front = DownwardMessageQueuePages::<T>::take(para, meta.first_full)?;
106		meta.first_full = meta.first_full.checked_add(1)?;
107		DownwardMessageQueueMeta::<T>::insert(para, meta);
108
109		Some(front)
110	}
111
112	pub fn pop_front_v0(para: ParaId) -> Option<InboundDownwardMessage<BlockNumberFor<T>>> {
113		let mut msgs = migration::v0::DownwardMessageQueues::<T>::get(para);
114		if msgs.is_empty() {
115			migration::v0::DownwardMessageQueues::<T>::remove(para); // should not happen
116			return None;
117		}
118
119		let front = msgs.remove(0); // safe, checked above
120		if msgs.is_empty() {
121			migration::v0::DownwardMessageQueues::<T>::remove(para);
122		} else {
123			migration::v0::DownwardMessageQueues::<T>::set(para, msgs);
124		}
125
126		Some(front)
127	}
128
129	/// Peek at the first message in the queue without removing it.
130	pub fn peek_front(para: ParaId) -> Option<InboundDownwardMessage<BlockNumberFor<T>>> {
131		let front_v0 = || migration::v0::DownwardMessageQueues::<T>::get(para).first().cloned();
132
133		let front_v1 = Self::meta(para)
134			.and_then(|meta| DownwardMessageQueuePages::<T>::get(para, meta.first_full));
135
136		front_v1.or_else(front_v0)
137	}
138
139	pub fn drop_front_n(para: ParaId, mut n: u64) -> Option<u64> {
140		// v1 else v0
141		let dropped_v1 = Self::drop_front_n_v1(para, n);
142		n = n.saturating_sub(dropped_v1.unwrap_or_default());
143		let dropped_v0 = Self::drop_front_n_v0(para, n);
144
145		if dropped_v0.is_none() && dropped_v1.is_none() {
146			None
147		} else {
148			Some(dropped_v0.unwrap_or_default().saturating_add(dropped_v1.unwrap_or_default()))
149		}
150	}
151
152	/// Drop first `n` messages from the queue.
153	///
154	/// Returns the number of messages dropped or `None` if the queue does not exist.
155	fn drop_front_n_v1(para: ParaId, n: u64) -> Option<u64> {
156		let mut meta = Self::meta(para)?;
157
158		let old_first_full = meta.first_full;
159		meta.first_full = meta.first_full.saturating_add(n).min(meta.first_free);
160		DownwardMessageQueueMeta::<T>::insert(para, &meta);
161
162		let to_drop = meta.first_full.saturating_sub(old_first_full);
163		for i in old_first_full..meta.first_full {
164			DownwardMessageQueuePages::<T>::remove(para, i);
165		}
166
167		Some(to_drop)
168	}
169
170	fn drop_front_n_v0(para: ParaId, n: u64) -> Option<u64> {
171		if !migration::v0::DownwardMessageQueues::<T>::decode_len(para).is_some_and(|l| l > 0) {
172			return None;
173		}
174
175		let mut msgs = migration::v0::DownwardMessageQueues::<T>::get(para);
176		let take = n.min(msgs.len() as u64) as usize;
177		msgs.drain(..take);
178
179		if msgs.is_empty() {
180			migration::v0::DownwardMessageQueues::<T>::remove(para);
181		} else {
182			migration::v0::DownwardMessageQueues::<T>::set(para, msgs);
183		}
184
185		Some(take as u64)
186	}
187
188	/// Try to delete all messages at once and schedule lazy deletion if not possible.
189	pub fn delete_all(para: ParaId) {
190		migration::v0::DownwardMessageQueues::<T>::remove(para);
191
192		let Some(meta) = DownwardMessageQueueMeta::<T>::take(para) else {
193			return;
194		};
195		if meta.first_full >= meta.first_free {
196			return;
197		}
198
199		// Try to delete all at once but do it lazy otherwise. Note that the clearing will happen in
200		// random order and not key order but it does not matter.
201		let cursor =
202			DownwardMessageQueuePages::<T>::clear_prefix(para, LAZY_DELETE_MAX_PAGES, None);
203
204		if cursor.maybe_cursor.is_none() {
205			// all done
206			return;
207		}
208
209		let (lo, hi) = match DownwardMessageQueueLazyDelete::<T>::get(para) {
210			Some((old_first, old_last)) => (old_first, meta.first_free.max(old_last)),
211			None => (meta.first_full, meta.first_free),
212		};
213		DownwardMessageQueueLazyDelete::<T>::insert(para, (lo, hi));
214	}
215
216	/// Progressive lazy deletion tick of old messages.
217	pub fn lazy_delete_some(weight_meter: &mut WeightMeter) {
218		if weight_meter.try_consume(<T as Config>::WeightInfo::lazy_delete_some()).is_err() {
219			return;
220		}
221
222		let Some((para_id, (first, last))) = DownwardMessageQueueLazyDelete::<T>::iter().next()
223		else {
224			return;
225		};
226
227		let mut next = first;
228		let end = next.saturating_add(LAZY_DELETE_MAX_PAGES as u64).min(last);
229		// Note: We DO NOT use clear_prefix here to not accidentally delete new incoming pages.
230		while next < end {
231			DownwardMessageQueuePages::<T>::remove(para_id, next);
232			next += 1;
233		}
234
235		if next >= last {
236			DownwardMessageQueueLazyDelete::<T>::remove(para_id);
237		} else {
238			DownwardMessageQueueLazyDelete::<T>::insert(para_id, (next, last));
239		}
240	}
241
242	/// DO NOT CALL IN CONSENSUS. Inspect all messages in the queue.
243	pub fn peek_all_do_not_call_in_consensus(
244		para: ParaId,
245	) -> Vec<InboundDownwardMessage<BlockNumberFor<T>>> {
246		let mut messages = Vec::new();
247
248		// v1 is head
249		if let Some(meta) = Self::meta(para) {
250			for i in meta.first_full..meta.first_free {
251				if let Some(page) = DownwardMessageQueuePages::<T>::get(para, i).defensive() {
252					messages.push(page);
253				}
254			}
255		};
256
257		messages.extend(migration::v0::DownwardMessageQueues::<T>::get(para));
258
259		messages
260	}
261
262	/// Run integrity checks for testing.
263	///
264	/// Invariants:
265	/// - For every meta `{first_full, first_free}`: `first_full <= first_free`.
266	/// - For every lazy-delete `(first, last)`: `first <= last`.
267	/// - Every page `(para, idx)` in storage is covered by *either* the para's meta range
268	///   `[first_full, first_free)` *or* its lazy-delete range `[first, last)`. Anything else is an
269	///   orphan.
270	/// - No v0 entry is present but empty: drained queues must be removed, not left as `[]`.
271	#[cfg(any(feature = "std", feature = "try-runtime"))]
272	pub fn try_state() {
273		for (para, msgs) in migration::v0::DownwardMessageQueues::<T>::iter() {
274			assert!(!msgs.is_empty(), "v0 queue for {:?} is present but empty", para);
275		}
276		for (para, meta) in DownwardMessageQueueMeta::<T>::iter() {
277			assert!(
278				meta.first_full <= meta.first_free,
279				"meta for {:?} has first_full ({}) > first_free ({})",
280				para,
281				meta.first_full,
282				meta.first_free,
283			);
284		}
285		for (para, (first, last)) in DownwardMessageQueueLazyDelete::<T>::iter() {
286			assert!(
287				first <= last,
288				"lazy delete for {:?} has first ({}) > last ({})",
289				para,
290				first,
291				last,
292			);
293		}
294
295		for (para, idx) in DownwardMessageQueuePages::<T>::iter_keys() {
296			let in_meta = DownwardMessageQueueMeta::<T>::get(para)
297				.is_some_and(|m| idx >= m.first_full && idx < m.first_free);
298			let in_lazy = DownwardMessageQueueLazyDelete::<T>::get(para)
299				.is_some_and(|(first, last)| idx >= first && idx < last);
300
301			assert!(
302				in_meta || in_lazy,
303				"page ({:?}, {}) is orphaned: not covered by meta or lazy-delete range",
304				para,
305				idx,
306			);
307		}
308	}
309}