pallet_example_mbm/migrations/v1/mod.rs
1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! # Multi-Block Migration v1
19//!
20//! This module showcases a simple migration that iterates over the values in the
21//! [`v0::MyMap`](`crate::migrations::v1::v0::MyMap`) storage map, transforms them,
22//! and inserts them into the [`MyMap`](`crate::pallet::MyMap`) storage map.
23
24extern crate alloc;
25
26use super::PALLET_MIGRATIONS_ID;
27use crate::pallet::{Config, MyMap};
28use frame_support::{
29 migrations::{MigrationId, SteppedMigration, SteppedMigrationError},
30 pallet_prelude::PhantomData,
31 weights::WeightMeter,
32};
33
34#[cfg(feature = "try-runtime")]
35use alloc::collections::btree_map::BTreeMap;
36
37#[cfg(feature = "try-runtime")]
38use alloc::vec::Vec;
39
40mod benchmarks;
41mod tests;
42pub mod weights;
43
44/// Module containing the OLD (v0) storage items.
45///
46/// Before running this migration, the storage alias defined here represents the
47/// `on_chain` storage.
48// This module is public only for the purposes of linking it in the documentation. It is not
49// intended to be used by any other code.
50pub mod v0 {
51 use super::Config;
52 use crate::pallet::Pallet;
53 use frame_support::{storage_alias, Blake2_128Concat};
54
55 #[storage_alias]
56 /// The storage item that is being migrated from.
57 pub type MyMap<T: Config> = StorageMap<Pallet<T>, Blake2_128Concat, u32, u32>;
58}
59
60/// Migrates the items of the [`crate::MyMap`] map from `u32` to `u64`.
61///
62/// The `step` function will be called once per block. It is very important that this function
63/// *never* panics and never uses more weight than it got in its meter. The migrations should also
64/// try to make maximal progress per step, so that the total time it takes to migrate stays low.
65pub struct LazyMigrationV1<T: Config, W: weights::WeightInfo>(PhantomData<(T, W)>);
66impl<T: Config, W: weights::WeightInfo> SteppedMigration for LazyMigrationV1<T, W> {
67 type Cursor = u32;
68 // Without the explicit length here the construction of the ID would not be infallible.
69 type Identifier = MigrationId<18>;
70
71 /// The identifier of this migration. Which should be globally unique.
72 fn id() -> Self::Identifier {
73 MigrationId { pallet_id: *PALLET_MIGRATIONS_ID, version_from: 0, version_to: 1 }
74 }
75
76 /// The actual logic of the migration.
77 ///
78 /// This function is called repeatedly until it returns `Ok(None)`, indicating that the
79 /// migration is complete. Ideally, the migration should be designed in such a way that each
80 /// step consumes as much weight as possible. However, this is simplified to perform one stored
81 /// value mutation per block.
82 fn step(
83 mut cursor: Option<Self::Cursor>,
84 meter: &mut WeightMeter,
85 ) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
86 let required = W::step();
87 // If there is not enough weight for a single step, return an error. This case can be
88 // problematic if it is the first migration that ran in this block. But there is nothing
89 // that we can do about it here.
90 if meter.remaining().any_lt(required) {
91 return Err(SteppedMigrationError::InsufficientWeight { required });
92 }
93
94 // We loop here to do as much progress as possible per step.
95 loop {
96 if meter.try_consume(required).is_err() {
97 break;
98 }
99
100 let mut iter = if let Some(last_key) = cursor {
101 // If a cursor is provided, start iterating from the stored value
102 // corresponding to the last key processed in the previous step.
103 // Note that this only works if the old and the new map use the same way to hash
104 // storage keys.
105 v0::MyMap::<T>::iter_from(v0::MyMap::<T>::hashed_key_for(last_key))
106 } else {
107 // If no cursor is provided, start iterating from the beginning.
108 v0::MyMap::<T>::iter()
109 };
110
111 // If there's a next item in the iterator, perform the migration.
112 if let Some((last_key, value)) = iter.next() {
113 // Migrate the inner value: u32 -> u64.
114 let value = value as u64;
115 // We can just insert here since the old and the new map share the same key-space.
116 // Otherwise it would have to invert the concat hash function and re-hash it.
117 MyMap::<T>::insert(last_key, value);
118 cursor = Some(last_key) // Return the processed key as the new cursor.
119 } else {
120 cursor = None; // Signal that the migration is complete (no more items to process).
121 break
122 }
123 }
124 Ok(cursor)
125 }
126
127 #[cfg(feature = "try-runtime")]
128 fn pre_upgrade() -> Result<Vec<u8>, frame_support::sp_runtime::TryRuntimeError> {
129 use codec::Encode;
130
131 // Return the state of the storage before the migration.
132 Ok(v0::MyMap::<T>::iter().collect::<BTreeMap<_, _>>().encode())
133 }
134
135 #[cfg(feature = "try-runtime")]
136 fn post_upgrade(prev: Vec<u8>) -> Result<(), frame_support::sp_runtime::TryRuntimeError> {
137 use codec::Decode;
138
139 // Check the state of the storage after the migration.
140 let prev_map = BTreeMap::<u32, u32>::decode(&mut &prev[..])
141 .expect("Failed to decode the previous storage state");
142
143 // Check the len of prev and post are the same.
144 assert_eq!(
145 MyMap::<T>::iter().count(),
146 prev_map.len(),
147 "Migration failed: the number of items in the storage after the migration is not the same as before"
148 );
149
150 for (key, value) in prev_map {
151 let new_value =
152 MyMap::<T>::get(key).expect("Failed to get the value after the migration");
153 assert_eq!(
154 value as u64, new_value,
155 "Migration failed: the value after the migration is not the same as before"
156 );
157 }
158
159 Ok(())
160 }
161}