referrerpolicy=no-referrer-when-downgrade

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