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