referrerpolicy=no-referrer-when-downgrade

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}