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
//! A module that is responsible for migration of storage.
18

            
19
pub mod v5;
20

            
21
use crate::{Config, OverweightIndex, Pallet, QueueConfig, QueueConfigData, DEFAULT_POV_SIZE};
22
use alloc::vec::Vec;
23
use cumulus_primitives_core::XcmpMessageFormat;
24
use 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.
31
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(5);
32

            
33
pub const LOG: &str = "runtime::xcmp-queue-migration";
34

            
35
mod 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

            
66
pub 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

            
144
pub 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

            
265
pub 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))]
318
mod 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
}