Skip to main content

cumulus_pallet_xcmp_queue/migration/
mod.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 module that is responsible for migration of storage.
18
19pub mod v5;
20pub mod v6;
21
22use crate::{Config, OverweightIndex, Pallet, QueueConfig, QueueConfigData, DEFAULT_POV_SIZE};
23use alloc::vec::Vec;
24use cumulus_primitives_core::XcmpMessageFormat;
25use frame_support::{
26	pallet_prelude::*,
27	traits::{EnqueueMessage, StorageVersion, UncheckedOnRuntimeUpgrade},
28	weights::{constants::WEIGHT_REF_TIME_PER_MILLIS, Weight},
29};
30
31/// The in-code storage version.
32pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(6);
33
34pub const LOG: &str = "runtime::xcmp-queue-migration";
35
36mod v1 {
37	use super::*;
38	use codec::{Decode, Encode};
39
40	#[frame_support::storage_alias]
41	pub(crate) type QueueConfig<T: Config> = StorageValue<Pallet<T>, QueueConfigData, ValueQuery>;
42
43	#[derive(Encode, Decode, Debug)]
44	pub struct QueueConfigData {
45		pub suspend_threshold: u32,
46		pub drop_threshold: u32,
47		pub resume_threshold: u32,
48		pub threshold_weight: u64,
49		pub weight_restrict_decay: u64,
50		pub xcmp_max_individual_weight: u64,
51	}
52
53	impl Default for QueueConfigData {
54		fn default() -> Self {
55			QueueConfigData {
56				suspend_threshold: 2,
57				drop_threshold: 5,
58				resume_threshold: 1,
59				threshold_weight: 100_000,
60				weight_restrict_decay: 2,
61				xcmp_max_individual_weight: 20u64 * WEIGHT_REF_TIME_PER_MILLIS,
62			}
63		}
64	}
65}
66
67pub mod v2 {
68	use super::*;
69
70	#[frame_support::storage_alias]
71	pub(crate) type QueueConfig<T: Config> = StorageValue<Pallet<T>, QueueConfigData, ValueQuery>;
72
73	#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)]
74	pub struct QueueConfigData {
75		pub suspend_threshold: u32,
76		pub drop_threshold: u32,
77		pub resume_threshold: u32,
78		pub threshold_weight: Weight,
79		pub weight_restrict_decay: Weight,
80		pub xcmp_max_individual_weight: Weight,
81	}
82
83	impl Default for QueueConfigData {
84		fn default() -> Self {
85			Self {
86				suspend_threshold: 2,
87				drop_threshold: 5,
88				resume_threshold: 1,
89				threshold_weight: Weight::from_parts(100_000, 0),
90				weight_restrict_decay: Weight::from_parts(2, 0),
91				xcmp_max_individual_weight: Weight::from_parts(
92					20u64 * WEIGHT_REF_TIME_PER_MILLIS,
93					DEFAULT_POV_SIZE,
94				),
95			}
96		}
97	}
98
99	/// Migrates `QueueConfigData` from v1 (using only reference time weights) to v2 (with
100	/// 2D weights).
101	pub struct UncheckedMigrationToV2<T: Config>(PhantomData<T>);
102
103	impl<T: Config> UncheckedOnRuntimeUpgrade for UncheckedMigrationToV2<T> {
104		#[allow(deprecated)]
105		fn on_runtime_upgrade() -> Weight {
106			let translate = |pre: v1::QueueConfigData| -> v2::QueueConfigData {
107				v2::QueueConfigData {
108					suspend_threshold: pre.suspend_threshold,
109					drop_threshold: pre.drop_threshold,
110					resume_threshold: pre.resume_threshold,
111					threshold_weight: Weight::from_parts(pre.threshold_weight, 0),
112					weight_restrict_decay: Weight::from_parts(pre.weight_restrict_decay, 0),
113					xcmp_max_individual_weight: Weight::from_parts(
114						pre.xcmp_max_individual_weight,
115						DEFAULT_POV_SIZE,
116					),
117				}
118			};
119
120			if v2::QueueConfig::<T>::translate(|pre| pre.map(translate)).is_err() {
121				tracing::error!(
122					target: crate::LOG_TARGET,
123					"unexpected error when performing translation of the QueueConfig type \
124					during storage upgrade to v2"
125				);
126			}
127
128			T::DbWeight::get().reads_writes(1, 1)
129		}
130	}
131
132	/// [`UncheckedMigrationToV2`] wrapped in a
133	/// [`VersionedMigration`](frame_support::migrations::VersionedMigration), ensuring the
134	/// migration is only performed when on-chain version is 1.
135	#[allow(dead_code)]
136	pub type MigrationToV2<T> = frame_support::migrations::VersionedMigration<
137		1,
138		2,
139		UncheckedMigrationToV2<T>,
140		Pallet<T>,
141		<T as frame_system::Config>::DbWeight,
142	>;
143}
144
145pub mod v3 {
146	use super::*;
147	use crate::*;
148
149	/// Status of the inbound XCMP channels.
150	#[frame_support::storage_alias]
151	pub(crate) type InboundXcmpStatus<T: Config> =
152		StorageValue<Pallet<T>, Vec<InboundChannelDetails>, OptionQuery>;
153
154	/// Inbound aggregate XCMP messages. It can only be one per ParaId/block.
155	#[frame_support::storage_alias]
156	pub(crate) type InboundXcmpMessages<T: Config> = StorageDoubleMap<
157		Pallet<T>,
158		Blake2_128Concat,
159		ParaId,
160		Twox64Concat,
161		RelayBlockNumber,
162		Vec<u8>,
163		OptionQuery,
164	>;
165
166	#[frame_support::storage_alias]
167	pub(crate) type QueueConfig<T: Config> =
168		StorageValue<Pallet<T>, v2::QueueConfigData, ValueQuery>;
169
170	#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
171	pub struct InboundChannelDetails {
172		/// The `ParaId` of the parachain that this channel is connected with.
173		pub sender: ParaId,
174		/// The state of the channel.
175		pub state: InboundState,
176		/// The ordered metadata of each inbound message.
177		///
178		/// Contains info about the relay block number that the message was sent at, and the format
179		/// of the incoming message.
180		pub message_metadata: Vec<(RelayBlockNumber, XcmpMessageFormat)>,
181	}
182
183	#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo)]
184	pub enum InboundState {
185		Ok,
186		Suspended,
187	}
188
189	/// Migrates the pallet storage to v3.
190	pub struct UncheckedMigrationToV3<T: Config>(PhantomData<T>);
191
192	impl<T: Config> UncheckedOnRuntimeUpgrade for UncheckedMigrationToV3<T> {
193		fn on_runtime_upgrade() -> Weight {
194			#[frame_support::storage_alias]
195			type Overweight<T: Config> =
196				CountedStorageMap<Pallet<T>, Twox64Concat, OverweightIndex, ParaId>;
197			let overweight_messages = Overweight::<T>::initialize_counter() as u64;
198
199			T::DbWeight::get().reads_writes(overweight_messages, 1)
200		}
201	}
202
203	/// [`UncheckedMigrationToV3`] wrapped in a
204	/// [`VersionedMigration`](frame_support::migrations::VersionedMigration), ensuring the
205	/// migration is only performed when on-chain version is 2.
206	pub type MigrationToV3<T> = frame_support::migrations::VersionedMigration<
207		2,
208		3,
209		UncheckedMigrationToV3<T>,
210		Pallet<T>,
211		<T as frame_system::Config>::DbWeight,
212	>;
213
214	pub fn lazy_migrate_inbound_queue<T: Config>() {
215		let Some(mut states) = v3::InboundXcmpStatus::<T>::get() else {
216			tracing::debug!(target: LOG, "Lazy migration finished: item gone");
217			return;
218		};
219		let Some(ref mut next) = states.first_mut() else {
220			tracing::debug!(target: LOG, "Lazy migration finished: item empty");
221			v3::InboundXcmpStatus::<T>::kill();
222			return;
223		};
224		tracing::debug!(
225			target: LOG,
226			sibling=?next.sender,
227			msgs_left=%next.message_metadata.len(),
228			"Migrating inbound HRMP channel."
229		);
230		// We take the last element since the MQ is a FIFO and we want to keep the order.
231		let Some((block_number, format)) = next.message_metadata.pop() else {
232			states.remove(0);
233			v3::InboundXcmpStatus::<T>::put(states);
234			return;
235		};
236		if format != XcmpMessageFormat::ConcatenatedVersionedXcm {
237			tracing::warn!(
238				target: LOG,
239				?format,
240				"Dropping message (not ConcatenatedVersionedXcm)"
241			);
242			v3::InboundXcmpMessages::<T>::remove(&next.sender, &block_number);
243			v3::InboundXcmpStatus::<T>::put(states);
244			return;
245		}
246
247		let Some(msg) = v3::InboundXcmpMessages::<T>::take(&next.sender, &block_number) else {
248			defensive!("Storage corrupted: HRMP message missing:", (next.sender, block_number));
249			v3::InboundXcmpStatus::<T>::put(states);
250			return;
251		};
252
253		let Ok(msg): Result<BoundedVec<_, _>, _> = msg.try_into() else {
254			tracing::error!(target: LOG, "Message dropped: too big");
255			v3::InboundXcmpStatus::<T>::put(states);
256			return;
257		};
258
259		// Finally! We have a proper message.
260		T::XcmpQueue::enqueue_message(msg.as_bounded_slice(), next.sender);
261		tracing::debug!(target: LOG, next_sender=?next.sender, ?block_number, "Migrated HRMP message to MQ");
262		v3::InboundXcmpStatus::<T>::put(states);
263	}
264}
265
266pub mod v4 {
267	use super::*;
268
269	/// Migrates `QueueConfigData` to v4, removing deprecated fields and bumping page
270	/// thresholds to at least the default values.
271	pub struct UncheckedMigrationToV4<T: Config>(PhantomData<T>);
272
273	impl<T: Config> UncheckedOnRuntimeUpgrade for UncheckedMigrationToV4<T> {
274		fn on_runtime_upgrade() -> Weight {
275			let translate = |pre: v2::QueueConfigData| -> QueueConfigData {
276				let pre_default = v2::QueueConfigData::default();
277				// If the previous values are the default ones, let's replace them with the new
278				// default.
279				if pre.suspend_threshold == pre_default.suspend_threshold &&
280					pre.drop_threshold == pre_default.drop_threshold &&
281					pre.resume_threshold == pre_default.resume_threshold
282				{
283					return QueueConfigData::default();
284				}
285
286				// If the previous values are not the default ones, let's leave them as they are.
287				QueueConfigData {
288					suspend_threshold: pre.suspend_threshold,
289					drop_threshold: pre.drop_threshold,
290					resume_threshold: pre.resume_threshold,
291				}
292			};
293
294			if QueueConfig::<T>::translate(|pre| pre.map(translate)).is_err() {
295				tracing::error!(
296					target: crate::LOG_TARGET,
297					"unexpected error when performing translation of the QueueConfig type \
298					during storage upgrade to v4"
299				);
300			}
301
302			T::DbWeight::get().reads_writes(1, 1)
303		}
304	}
305
306	/// [`UncheckedMigrationToV4`] wrapped in a
307	/// [`VersionedMigration`](frame_support::migrations::VersionedMigration), ensuring the
308	/// migration is only performed when on-chain version is 3.
309	pub type MigrationToV4<T> = frame_support::migrations::VersionedMigration<
310		3,
311		4,
312		UncheckedMigrationToV4<T>,
313		Pallet<T>,
314		<T as frame_system::Config>::DbWeight,
315	>;
316}
317
318#[cfg(all(feature = "try-runtime", test))]
319mod tests {
320	use super::*;
321	use crate::mock::{new_test_ext, Test};
322	use frame_support::traits::OnRuntimeUpgrade;
323
324	#[test]
325	#[allow(deprecated)]
326	fn test_migration_to_v2() {
327		let v1 = v1::QueueConfigData {
328			suspend_threshold: 5,
329			drop_threshold: 12,
330			resume_threshold: 3,
331			threshold_weight: 333_333,
332			weight_restrict_decay: 1,
333			xcmp_max_individual_weight: 10_000_000_000,
334		};
335
336		new_test_ext().execute_with(|| {
337			let storage_version = StorageVersion::new(1);
338			storage_version.put::<Pallet<Test>>();
339
340			frame_support::storage::unhashed::put_raw(
341				&crate::QueueConfig::<Test>::hashed_key(),
342				&v1.encode(),
343			);
344
345			let bytes = v2::MigrationToV2::<Test>::pre_upgrade();
346			assert!(bytes.is_ok());
347			v2::MigrationToV2::<Test>::on_runtime_upgrade();
348			assert!(v2::MigrationToV2::<Test>::post_upgrade(bytes.unwrap()).is_ok());
349
350			let v2 = v2::QueueConfig::<Test>::get();
351
352			assert_eq!(v1.suspend_threshold, v2.suspend_threshold);
353			assert_eq!(v1.drop_threshold, v2.drop_threshold);
354			assert_eq!(v1.resume_threshold, v2.resume_threshold);
355			assert_eq!(v1.threshold_weight, v2.threshold_weight.ref_time());
356			assert_eq!(v1.weight_restrict_decay, v2.weight_restrict_decay.ref_time());
357			assert_eq!(v1.xcmp_max_individual_weight, v2.xcmp_max_individual_weight.ref_time());
358		});
359	}
360
361	#[test]
362	#[allow(deprecated)]
363	fn test_migration_to_v4() {
364		new_test_ext().execute_with(|| {
365			let storage_version = StorageVersion::new(3);
366			storage_version.put::<Pallet<Test>>();
367
368			let v2 = v2::QueueConfigData {
369				drop_threshold: 5,
370				suspend_threshold: 2,
371				resume_threshold: 1,
372				..Default::default()
373			};
374
375			frame_support::storage::unhashed::put_raw(
376				&crate::QueueConfig::<Test>::hashed_key(),
377				&v2.encode(),
378			);
379
380			let bytes = v4::MigrationToV4::<Test>::pre_upgrade();
381			assert!(bytes.is_ok());
382			v4::MigrationToV4::<Test>::on_runtime_upgrade();
383			assert!(v4::MigrationToV4::<Test>::post_upgrade(bytes.unwrap()).is_ok());
384
385			let v4 = QueueConfig::<Test>::get();
386
387			assert_eq!(
388				v4,
389				QueueConfigData { suspend_threshold: 32, drop_threshold: 48, resume_threshold: 8 }
390			);
391		});
392
393		new_test_ext().execute_with(|| {
394			let storage_version = StorageVersion::new(3);
395			storage_version.put::<Pallet<Test>>();
396
397			let v2 = v2::QueueConfigData {
398				drop_threshold: 100,
399				suspend_threshold: 50,
400				resume_threshold: 40,
401				..Default::default()
402			};
403
404			frame_support::storage::unhashed::put_raw(
405				&crate::QueueConfig::<Test>::hashed_key(),
406				&v2.encode(),
407			);
408
409			let bytes = v4::MigrationToV4::<Test>::pre_upgrade();
410			assert!(bytes.is_ok());
411			v4::MigrationToV4::<Test>::on_runtime_upgrade();
412			assert!(v4::MigrationToV4::<Test>::post_upgrade(bytes.unwrap()).is_ok());
413
414			let v4 = QueueConfig::<Test>::get();
415
416			assert_eq!(
417				v4,
418				QueueConfigData {
419					suspend_threshold: 50,
420					drop_threshold: 100,
421					resume_threshold: 40
422				}
423			);
424		});
425	}
426}