1pub 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
30pub 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 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 #[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 #[frame_support::storage_alias]
150 pub(crate) type InboundXcmpStatus<T: Config> =
151 StorageValue<Pallet<T>, Vec<InboundChannelDetails>, OptionQuery>;
152
153 #[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 pub sender: ParaId,
173 pub state: InboundState,
175 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 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 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 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 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 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 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 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 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}