referrerpolicy=no-referrer-when-downgrade

frame_support/
migrations.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
18use crate::{
19	defensive,
20	storage::{storage_prefix, transactional::with_transaction_opaque_err},
21	traits::{
22		Defensive, GetStorageVersion, NoStorageVersionSet, PalletInfoAccess, SafeMode,
23		StorageVersion,
24	},
25	weights::{RuntimeDbWeight, Weight, WeightMeter},
26};
27use alloc::vec::Vec;
28use codec::{Decode, Encode, MaxEncodedLen};
29use core::marker::PhantomData;
30use impl_trait_for_tuples::impl_for_tuples;
31use sp_arithmetic::traits::Bounded;
32use sp_core::Get;
33use sp_io::{hashing::twox_128, storage::clear_prefix, KillStorageResult};
34use sp_runtime::traits::Zero;
35
36/// Handles storage migration pallet versioning.
37///
38/// [`VersionedMigration`] allows developers to write migrations without worrying about checking and
39/// setting storage versions. Instead, the developer wraps their migration in this struct which
40/// takes care of version handling using best practices.
41///
42/// It takes 5 type parameters:
43/// - `From`: The version being upgraded from.
44/// - `To`: The version being upgraded to.
45/// - `Inner`: An implementation of `UncheckedOnRuntimeUpgrade`.
46/// - `Pallet`: The Pallet being upgraded.
47/// - `Weight`: The runtime's RuntimeDbWeight implementation.
48///
49/// When a [`VersionedMigration`] `on_runtime_upgrade`, `pre_upgrade`, or `post_upgrade` method is
50/// called, the on-chain version of the pallet is compared to `From`. If they match, the `Inner`
51/// `UncheckedOnRuntimeUpgrade` is called and the pallets on-chain version is set to `To`
52/// after the migration. Otherwise, a warning is logged notifying the developer that the upgrade was
53/// a noop and should probably be removed.
54///
55/// By not bounding `Inner` with `OnRuntimeUpgrade`, we prevent developers from
56/// accidentally using the unchecked version of the migration in a runtime upgrade instead of
57/// [`VersionedMigration`].
58///
59/// ### Examples
60/// ```ignore
61/// // In file defining migrations
62///
63/// /// Private module containing *version unchecked* migration logic.
64/// ///
65/// /// Should only be used by the [`VersionedMigration`] type in this module to create something to
66/// /// export.
67/// ///
68/// /// We keep this private so the unversioned migration cannot accidentally be used in any runtimes.
69/// ///
70/// /// For more about this pattern of keeping items private, see
71/// /// - https://github.com/rust-lang/rust/issues/30905
72/// /// - https://internals.rust-lang.org/t/lang-team-minutes-private-in-public-rules/4504/40
73/// mod version_unchecked {
74/// 	use super::*;
75/// 	pub struct VersionUncheckedMigrateV5ToV6<T>(core::marker::PhantomData<T>);
76/// 	impl<T: Config> UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateV5ToV6<T> {
77/// 		// `UncheckedOnRuntimeUpgrade` implementation...
78/// 	}
79/// }
80///
81/// pub type MigrateV5ToV6<T, I> =
82/// 	VersionedMigration<
83/// 		5,
84/// 		6,
85/// 		VersionUncheckedMigrateV5ToV6<T, I>,
86/// 		crate::pallet::Pallet<T, I>,
87/// 		<T as frame_system::Config>::DbWeight
88/// 	>;
89///
90/// // Migrations tuple to pass to the Executive pallet:
91/// pub type Migrations = (
92/// 	// other migrations...
93/// 	MigrateV5ToV6<T, ()>,
94/// 	// other migrations...
95/// );
96/// ```
97pub struct VersionedMigration<const FROM: u16, const TO: u16, Inner, Pallet, Weight> {
98	_marker: PhantomData<(Inner, Pallet, Weight)>,
99}
100
101/// A helper enum to wrap the pre_upgrade bytes like an Option before passing them to post_upgrade.
102/// This enum is used rather than an Option to make the API clearer to the developer.
103#[derive(Encode, Decode)]
104pub enum VersionedPostUpgradeData {
105	/// The migration ran, inner vec contains pre_upgrade data.
106	MigrationExecuted(alloc::vec::Vec<u8>),
107	/// This migration is a noop, do not run post_upgrade checks.
108	Noop,
109}
110
111/// Implementation of the `OnRuntimeUpgrade` trait for `VersionedMigration`.
112///
113/// Its main function is to perform the runtime upgrade in `on_runtime_upgrade` only if the on-chain
114/// version of the pallets storage matches `From`, and after the upgrade set the on-chain storage to
115/// `To`. If the versions do not match, it writes a log notifying the developer that the migration
116/// is a noop.
117impl<
118		const FROM: u16,
119		const TO: u16,
120		Inner: crate::traits::UncheckedOnRuntimeUpgrade,
121		Pallet: GetStorageVersion<InCodeStorageVersion = StorageVersion> + PalletInfoAccess,
122		DbWeight: Get<RuntimeDbWeight>,
123	> crate::traits::OnRuntimeUpgrade for VersionedMigration<FROM, TO, Inner, Pallet, DbWeight>
124{
125	/// Executes pre_upgrade if the migration will run, and wraps the pre_upgrade bytes in
126	/// [`VersionedPostUpgradeData`] before passing them to post_upgrade, so it knows whether the
127	/// migration ran or not.
128	#[cfg(feature = "try-runtime")]
129	fn pre_upgrade() -> Result<alloc::vec::Vec<u8>, sp_runtime::TryRuntimeError> {
130		let on_chain_version = Pallet::on_chain_storage_version();
131		if on_chain_version == FROM {
132			Ok(VersionedPostUpgradeData::MigrationExecuted(Inner::pre_upgrade()?).encode())
133		} else {
134			Ok(VersionedPostUpgradeData::Noop.encode())
135		}
136	}
137
138	/// Executes the versioned runtime upgrade.
139	///
140	/// First checks if the pallets on-chain storage version matches the version of this upgrade. If
141	/// it matches, it calls `Inner::on_runtime_upgrade`, updates the on-chain version, and returns
142	/// the weight. If it does not match, it writes a log notifying the developer that the migration
143	/// is a noop.
144	fn on_runtime_upgrade() -> Weight {
145		let on_chain_version = Pallet::on_chain_storage_version();
146		if on_chain_version == FROM {
147			log::info!(
148				"๐Ÿšš Pallet {:?} VersionedMigration migrating storage version from {:?} to {:?}.",
149				Pallet::name(),
150				FROM,
151				TO
152			);
153
154			// Execute the migration
155			let weight = Inner::on_runtime_upgrade();
156
157			// Update the on-chain version
158			StorageVersion::new(TO).put::<Pallet>();
159
160			weight.saturating_add(DbWeight::get().reads_writes(1, 1))
161		} else {
162			log::warn!(
163				"๐Ÿšš Pallet {:?} VersionedMigration migration {}->{} can be removed; on-chain is already at {:?}.",
164				Pallet::name(),
165				FROM,
166				TO,
167				on_chain_version
168			);
169			DbWeight::get().reads(1)
170		}
171	}
172
173	/// Executes `Inner::post_upgrade` if the migration just ran.
174	///
175	/// pre_upgrade passes [`VersionedPostUpgradeData::MigrationExecuted`] to post_upgrade if
176	/// the migration ran, and [`VersionedPostUpgradeData::Noop`] otherwise.
177	#[cfg(feature = "try-runtime")]
178	fn post_upgrade(
179		versioned_post_upgrade_data_bytes: alloc::vec::Vec<u8>,
180	) -> Result<(), sp_runtime::TryRuntimeError> {
181		use codec::DecodeAll;
182		match <VersionedPostUpgradeData>::decode_all(&mut &versioned_post_upgrade_data_bytes[..])
183			.map_err(|_| "VersionedMigration post_upgrade failed to decode PreUpgradeData")?
184		{
185			VersionedPostUpgradeData::MigrationExecuted(inner_bytes) =>
186				Inner::post_upgrade(inner_bytes),
187			VersionedPostUpgradeData::Noop => Ok(()),
188		}
189	}
190}
191
192/// Can store the in-code pallet version on-chain.
193pub trait StoreInCodeStorageVersion<T: GetStorageVersion + PalletInfoAccess> {
194	/// Write the in-code storage version on-chain.
195	fn store_in_code_storage_version();
196}
197
198impl<T: GetStorageVersion<InCodeStorageVersion = StorageVersion> + PalletInfoAccess>
199	StoreInCodeStorageVersion<T> for StorageVersion
200{
201	fn store_in_code_storage_version() {
202		let version = <T as GetStorageVersion>::in_code_storage_version();
203		version.put::<T>();
204	}
205}
206
207impl<T: GetStorageVersion<InCodeStorageVersion = NoStorageVersionSet> + PalletInfoAccess>
208	StoreInCodeStorageVersion<T> for NoStorageVersionSet
209{
210	fn store_in_code_storage_version() {
211		StorageVersion::default().put::<T>();
212	}
213}
214
215/// Trait used by [`migrate_from_pallet_version_to_storage_version`] to do the actual migration.
216pub trait PalletVersionToStorageVersionHelper {
217	fn migrate(db_weight: &RuntimeDbWeight) -> Weight;
218}
219
220impl<T: GetStorageVersion + PalletInfoAccess> PalletVersionToStorageVersionHelper for T
221where
222	T::InCodeStorageVersion: StoreInCodeStorageVersion<T>,
223{
224	fn migrate(db_weight: &RuntimeDbWeight) -> Weight {
225		const PALLET_VERSION_STORAGE_KEY_POSTFIX: &[u8] = b":__PALLET_VERSION__:";
226
227		fn pallet_version_key(name: &str) -> [u8; 32] {
228			crate::storage::storage_prefix(name.as_bytes(), PALLET_VERSION_STORAGE_KEY_POSTFIX)
229		}
230
231		sp_io::storage::clear(&pallet_version_key(<T as PalletInfoAccess>::name()));
232
233		<T::InCodeStorageVersion as StoreInCodeStorageVersion<T>>::store_in_code_storage_version();
234
235		db_weight.writes(2)
236	}
237}
238
239#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))]
240#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))]
241#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))]
242impl PalletVersionToStorageVersionHelper for T {
243	fn migrate(db_weight: &RuntimeDbWeight) -> Weight {
244		let mut weight = Weight::zero();
245
246		for_tuples!( #( weight = weight.saturating_add(T::migrate(db_weight)); )* );
247
248		weight
249	}
250}
251
252/// Migrate from the `PalletVersion` struct to the new [`StorageVersion`] struct.
253///
254/// This will remove all `PalletVersion's` from the state and insert the in-code storage version.
255pub fn migrate_from_pallet_version_to_storage_version<
256	Pallets: PalletVersionToStorageVersionHelper,
257>(
258	db_weight: &RuntimeDbWeight,
259) -> Weight {
260	Pallets::migrate(db_weight)
261}
262
263/// `RemovePallet` is a utility struct used to remove all storage items associated with a specific
264/// pallet.
265///
266/// This struct is generic over two parameters:
267/// - `P` is a type that implements the `Get` trait for a static string, representing the pallet's
268///   name.
269/// - `DbWeight` is a type that implements the `Get` trait for `RuntimeDbWeight`, providing the
270///   weight for database operations.
271///
272/// On runtime upgrade, the `on_runtime_upgrade` function will clear all storage items associated
273/// with the specified pallet, logging the number of keys removed. If the `try-runtime` feature is
274/// enabled, the `pre_upgrade` and `post_upgrade` functions can be used to verify the storage
275/// removal before and after the upgrade.
276///
277/// # Examples:
278/// ```ignore
279/// construct_runtime! {
280/// 	pub enum Runtime
281/// 	{
282/// 		System: frame_system = 0,
283///
284/// 		SomePalletToRemove: pallet_something = 1,
285/// 		AnotherPalletToRemove: pallet_something_else = 2,
286///
287/// 		YourOtherPallets...
288/// 	}
289/// };
290///
291/// parameter_types! {
292/// 		pub const SomePalletToRemoveStr: &'static str = "SomePalletToRemove";
293/// 		pub const AnotherPalletToRemoveStr: &'static str = "AnotherPalletToRemove";
294/// }
295///
296/// pub type Migrations = (
297/// 	RemovePallet<SomePalletToRemoveStr, RocksDbWeight>,
298/// 	RemovePallet<AnotherPalletToRemoveStr, RocksDbWeight>,
299/// 	AnyOtherMigrations...
300/// );
301///
302/// impl frame_system::Config for Runtime {
303/// 	type SingleBlockMigrations = Migrations;
304/// }
305/// ```
306///
307/// WARNING: `RemovePallet` has no guard rails preventing it from bricking the chain if the
308/// operation of removing storage for the given pallet would exceed the block weight limit.
309///
310/// If your pallet has too many keys to be removed in a single block, it is advised to wait for
311/// a multi-block scheduler currently under development which will allow for removal of storage
312/// items (and performing other heavy migrations) over multiple blocks
313/// (see <https://github.com/paritytech/substrate/issues/13690>).
314pub struct RemovePallet<P: Get<&'static str>, DbWeight: Get<RuntimeDbWeight>>(
315	PhantomData<(P, DbWeight)>,
316);
317impl<P: Get<&'static str>, DbWeight: Get<RuntimeDbWeight>> frame_support::traits::OnRuntimeUpgrade
318	for RemovePallet<P, DbWeight>
319{
320	fn on_runtime_upgrade() -> frame_support::weights::Weight {
321		let hashed_prefix = twox_128(P::get().as_bytes());
322		let keys_removed = match clear_prefix(&hashed_prefix, None) {
323			KillStorageResult::AllRemoved(value) => value,
324			KillStorageResult::SomeRemaining(value) => {
325				log::error!(
326					"`clear_prefix` failed to remove all keys for {}. THIS SHOULD NEVER HAPPEN! ๐Ÿšจ",
327					P::get()
328				);
329				value
330			},
331		} as u64;
332
333		log::info!("Removed {} {} keys ๐Ÿงน", keys_removed, P::get());
334
335		DbWeight::get().reads_writes(keys_removed + 1, keys_removed)
336	}
337
338	#[cfg(feature = "try-runtime")]
339	fn pre_upgrade() -> Result<alloc::vec::Vec<u8>, sp_runtime::TryRuntimeError> {
340		use crate::storage::unhashed::contains_prefixed_key;
341
342		let hashed_prefix = twox_128(P::get().as_bytes());
343		match contains_prefixed_key(&hashed_prefix) {
344			true => log::info!("Found {} keys pre-removal ๐Ÿ‘€", P::get()),
345			false => log::warn!(
346				"Migration RemovePallet<{}> can be removed (no keys found pre-removal).",
347				P::get()
348			),
349		};
350		Ok(alloc::vec::Vec::new())
351	}
352
353	#[cfg(feature = "try-runtime")]
354	fn post_upgrade(_state: alloc::vec::Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
355		use crate::storage::unhashed::contains_prefixed_key;
356
357		let hashed_prefix = twox_128(P::get().as_bytes());
358		match contains_prefixed_key(&hashed_prefix) {
359			true => {
360				log::error!("{} has keys remaining post-removal โ—", P::get());
361				return Err("Keys remaining post-removal, this should never happen ๐Ÿšจ".into())
362			},
363			false => log::info!("No {} keys found post-removal ๐ŸŽ‰", P::get()),
364		};
365		Ok(())
366	}
367}
368
369/// `RemoveStorage` is a utility struct used to remove a storage item from a specific pallet.
370///
371/// This struct is generic over three parameters:
372/// - `P` is a type that implements the [`Get`] trait for a static string, representing the pallet's
373///   name.
374/// - `S` is a type that implements the [`Get`] trait for a static string, representing the storage
375///   name.
376/// - `DbWeight` is a type that implements the [`Get`] trait for [`RuntimeDbWeight`], providing the
377///   weight for database operations.
378///
379/// On runtime upgrade, the `on_runtime_upgrade` function will clear the storage from the specified
380/// storage, logging the number of keys removed. If the `try-runtime` feature is enabled, the
381/// `pre_upgrade` and `post_upgrade` functions can be used to verify the storage removal before and
382/// after the upgrade.
383///
384/// # Examples:
385/// ```ignore
386/// construct_runtime! {
387/// 	pub enum Runtime
388/// 	{
389/// 		System: frame_system = 0,
390///
391/// 		SomePallet: pallet_something = 1,
392///
393/// 		YourOtherPallets...
394/// 	}
395/// };
396///
397/// parameter_types! {
398/// 		pub const SomePallet: &'static str = "SomePallet";
399/// 		pub const StorageAccounts: &'static str = "Accounts";
400/// 		pub const StorageAccountCount: &'static str = "AccountCount";
401/// }
402///
403/// pub type Migrations = (
404/// 	RemoveStorage<SomePallet, StorageAccounts, RocksDbWeight>,
405/// 	RemoveStorage<SomePallet, StorageAccountCount, RocksDbWeight>,
406/// 	AnyOtherMigrations...
407/// );
408///
409/// impl frame_system::Config for Runtime {
410/// 	type SingleBlockMigrations = Migrations;
411/// }
412/// ```
413///
414/// WARNING: `RemoveStorage` has no guard rails preventing it from bricking the chain if the
415/// operation of removing storage for the given pallet would exceed the block weight limit.
416///
417/// If your storage has too many keys to be removed in a single block, it is advised to wait for
418/// a multi-block scheduler currently under development which will allow for removal of storage
419/// items (and performing other heavy migrations) over multiple blocks
420/// (see <https://github.com/paritytech/substrate/issues/13690>).
421pub struct RemoveStorage<P: Get<&'static str>, S: Get<&'static str>, DbWeight: Get<RuntimeDbWeight>>(
422	PhantomData<(P, S, DbWeight)>,
423);
424impl<P: Get<&'static str>, S: Get<&'static str>, DbWeight: Get<RuntimeDbWeight>>
425	frame_support::traits::OnRuntimeUpgrade for RemoveStorage<P, S, DbWeight>
426{
427	fn on_runtime_upgrade() -> frame_support::weights::Weight {
428		let hashed_prefix = storage_prefix(P::get().as_bytes(), S::get().as_bytes());
429		let keys_removed = match clear_prefix(&hashed_prefix, None) {
430			KillStorageResult::AllRemoved(value) => value,
431			KillStorageResult::SomeRemaining(value) => {
432				log::error!(
433					"`clear_prefix` failed to remove all keys for storage `{}` from pallet `{}`. THIS SHOULD NEVER HAPPEN! ๐Ÿšจ",
434					S::get(), P::get()
435				);
436				value
437			},
438		} as u64;
439
440		log::info!("Removed `{}` `{}` `{}` keys ๐Ÿงน", keys_removed, P::get(), S::get());
441
442		DbWeight::get().reads_writes(keys_removed + 1, keys_removed)
443	}
444
445	#[cfg(feature = "try-runtime")]
446	fn pre_upgrade() -> Result<alloc::vec::Vec<u8>, sp_runtime::TryRuntimeError> {
447		use crate::storage::unhashed::contains_prefixed_key;
448
449		let hashed_prefix = storage_prefix(P::get().as_bytes(), S::get().as_bytes());
450		match contains_prefixed_key(&hashed_prefix) {
451			true => log::info!("Found `{}` `{}` keys pre-removal ๐Ÿ‘€", P::get(), S::get()),
452			false => log::warn!(
453				"Migration RemoveStorage<{}, {}> can be removed (no keys found pre-removal).",
454				P::get(),
455				S::get()
456			),
457		};
458		Ok(Default::default())
459	}
460
461	#[cfg(feature = "try-runtime")]
462	fn post_upgrade(_state: alloc::vec::Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
463		use crate::storage::unhashed::contains_prefixed_key;
464
465		let hashed_prefix = storage_prefix(P::get().as_bytes(), S::get().as_bytes());
466		match contains_prefixed_key(&hashed_prefix) {
467			true => {
468				log::error!("`{}` `{}` has keys remaining post-removal โ—", P::get(), S::get());
469				return Err("Keys remaining post-removal, this should never happen ๐Ÿšจ".into())
470			},
471			false => log::info!("No `{}` `{}` keys found post-removal ๐ŸŽ‰", P::get(), S::get()),
472		};
473		Ok(())
474	}
475}
476
477/// A migration that can proceed in multiple steps.
478pub trait SteppedMigration {
479	/// The cursor type that stores the progress (aka. state) of this migration.
480	type Cursor: codec::FullCodec + codec::MaxEncodedLen;
481
482	/// The unique identifier type of this migration.
483	type Identifier: codec::FullCodec + codec::MaxEncodedLen;
484
485	/// The unique identifier of this migration.
486	///
487	/// If two migrations have the same identifier, then they are assumed to be identical.
488	fn id() -> Self::Identifier;
489
490	/// The maximum number of steps that this migration can take.
491	///
492	/// This can be used to enforce progress and prevent migrations becoming stuck forever. A
493	/// migration that exceeds its max steps is treated as failed. `None` means that there is no
494	/// limit.
495	fn max_steps() -> Option<u32> {
496		None
497	}
498
499	/// Try to migrate as much as possible with the given weight.
500	///
501	/// **ANY STORAGE CHANGES MUST BE ROLLED-BACK BY THE CALLER UPON ERROR.** This is necessary
502	/// since the caller cannot return a cursor in the error case. [`Self::transactional_step`] is
503	/// provided as convenience for a caller. A cursor of `None` implies that the migration is at
504	/// its end. A migration that once returned `Nonce` is guaranteed to never be called again.
505	fn step(
506		cursor: Option<Self::Cursor>,
507		meter: &mut WeightMeter,
508	) -> Result<Option<Self::Cursor>, SteppedMigrationError>;
509
510	/// Same as [`Self::step`], but rolls back pending changes in the error case.
511	fn transactional_step(
512		mut cursor: Option<Self::Cursor>,
513		meter: &mut WeightMeter,
514	) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
515		with_transaction_opaque_err(move || match Self::step(cursor, meter) {
516			Ok(new_cursor) => {
517				cursor = new_cursor;
518				sp_runtime::TransactionOutcome::Commit(Ok(cursor))
519			},
520			Err(err) => sp_runtime::TransactionOutcome::Rollback(Err(err)),
521		})
522		.map_err(|()| SteppedMigrationError::Failed)?
523	}
524
525	/// Hook for testing that is run before the migration is started.
526	///
527	/// Returns some bytes which are passed into `post_upgrade` after the migration is completed.
528	/// This is not run for the real migration, so panicking is not an issue here.
529	#[cfg(feature = "try-runtime")]
530	fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
531		Ok(Vec::new())
532	}
533
534	/// Hook for testing that is run after the migration is completed.
535	///
536	/// Should be used to verify the state of the chain after the migration. The `state` parameter
537	/// is the return value from `pre_upgrade`. This is not run for the real migration, so panicking
538	/// is not an issue here.
539	#[cfg(feature = "try-runtime")]
540	fn post_upgrade(_state: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
541		Ok(())
542	}
543}
544
545/// Error that can occur during a [`SteppedMigration`].
546#[derive(Debug, Encode, Decode, MaxEncodedLen, scale_info::TypeInfo)]
547pub enum SteppedMigrationError {
548	// Transient errors:
549	/// The remaining weight is not enough to do anything.
550	///
551	/// Can be resolved by calling with at least `required` weight. Note that calling it with
552	/// exactly `required` weight could cause it to not make any progress.
553	InsufficientWeight {
554		/// Amount of weight required to make progress.
555		required: Weight,
556	},
557	// Permanent errors:
558	/// The migration cannot decode its cursor and therefore not proceed.
559	///
560	/// This should not happen unless (1) the migration itself returned an invalid cursor in a
561	/// previous iteration, (2) the storage got corrupted or (3) there is a bug in the caller's
562	/// code.
563	InvalidCursor,
564	/// The migration encountered a permanent error and cannot continue.
565	Failed,
566}
567
568/// A generic migration identifier that can be used by MBMs.
569///
570/// It is not required that migrations use this identifier type, but it can help.
571#[derive(MaxEncodedLen, Encode, Decode)]
572pub struct MigrationId<const N: usize> {
573	pub pallet_id: [u8; N],
574	pub version_from: u8,
575	pub version_to: u8,
576}
577
578/// Notification handler for status updates regarding Multi-Block-Migrations.
579#[impl_trait_for_tuples::impl_for_tuples(8)]
580pub trait MigrationStatusHandler {
581	/// Notifies of the start of a runtime migration.
582	fn started() {}
583
584	/// Notifies of the completion of a runtime migration.
585	fn completed() {}
586}
587
588/// Handles a failed runtime migration.
589///
590/// This should never happen, but is here for completeness.
591pub trait FailedMigrationHandler {
592	/// Infallibly handle a failed runtime migration.
593	///
594	/// Gets passed in the optional index of the migration in the batch that caused the failure.
595	/// Returning `None` means that no automatic handling should take place and the callee decides
596	/// in the implementation what to do.
597	fn failed(migration: Option<u32>) -> FailedMigrationHandling;
598}
599
600/// Do now allow any transactions to be processed after a runtime upgrade failed.
601///
602/// This is **not a sane default**, since it prevents governance intervention.
603pub struct FreezeChainOnFailedMigration;
604
605impl FailedMigrationHandler for FreezeChainOnFailedMigration {
606	fn failed(_migration: Option<u32>) -> FailedMigrationHandling {
607		FailedMigrationHandling::KeepStuck
608	}
609}
610
611/// Enter safe mode on a failed runtime upgrade.
612///
613/// This can be very useful to manually intervene and fix the chain state. `Else` is used in case
614/// that the safe mode could not be entered.
615pub struct EnterSafeModeOnFailedMigration<SM, Else: FailedMigrationHandler>(
616	PhantomData<(SM, Else)>,
617);
618
619impl<Else: FailedMigrationHandler, SM: SafeMode> FailedMigrationHandler
620	for EnterSafeModeOnFailedMigration<SM, Else>
621where
622	<SM as SafeMode>::BlockNumber: Bounded,
623{
624	fn failed(migration: Option<u32>) -> FailedMigrationHandling {
625		let entered = if SM::is_entered() {
626			SM::extend(Bounded::max_value())
627		} else {
628			SM::enter(Bounded::max_value())
629		};
630
631		// If we could not enter or extend safe mode (for whatever reason), then we try the next.
632		if entered.is_err() {
633			Else::failed(migration)
634		} else {
635			FailedMigrationHandling::KeepStuck
636		}
637	}
638}
639
640/// How to proceed after a runtime upgrade failed.
641///
642/// There is NO SANE DEFAULT HERE. All options are very dangerous and should be used with care.
643#[derive(Debug, Clone, Copy, PartialEq, Eq)]
644pub enum FailedMigrationHandling {
645	/// Resume extrinsic processing of the chain. This will not resume the upgrade.
646	///
647	/// This should be supplemented with additional measures to ensure that the broken chain state
648	/// does not get further messed up by user extrinsics.
649	ForceUnstuck,
650	/// Set the cursor to `Stuck` and keep blocking extrinsics.
651	KeepStuck,
652	/// Don't do anything with the cursor and let the handler decide.
653	///
654	/// This can be useful in cases where the other two options would overwrite any changes that
655	/// were done by the handler to the cursor.
656	Ignore,
657}
658
659/// Something that can do multi step migrations.
660pub trait MultiStepMigrator {
661	/// Hint for whether [`Self::step`] should be called.
662	fn ongoing() -> bool;
663
664	/// Do the next step in the MBM process.
665	///
666	/// Must gracefully handle the case that it is currently not upgrading.
667	fn step() -> Weight;
668}
669
670impl MultiStepMigrator for () {
671	fn ongoing() -> bool {
672		false
673	}
674
675	fn step() -> Weight {
676		Weight::zero()
677	}
678}
679
680/// Multiple [`SteppedMigration`].
681pub trait SteppedMigrations {
682	/// The number of migrations that `Self` aggregates.
683	fn len() -> u32;
684
685	/// The `n`th [`SteppedMigration::id`].
686	///
687	/// Is guaranteed to return `Some` if `n < Self::len()`. Calling this with any index larger or
688	/// equal to `Self::len()` MUST return `None`.
689	fn nth_id(n: u32) -> Option<Vec<u8>>;
690
691	/// The [`SteppedMigration::max_steps`] of the `n`th migration.
692	///
693	/// Is guaranteed to return `Some` if `n < Self::len()`.
694	fn nth_max_steps(n: u32) -> Option<Option<u32>>;
695
696	/// Do a [`SteppedMigration::step`] on the `n`th migration.
697	///
698	/// Is guaranteed to return `Some` if `n < Self::len()`.
699	fn nth_step(
700		n: u32,
701		cursor: Option<Vec<u8>>,
702		meter: &mut WeightMeter,
703	) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>>;
704
705	/// Do a [`SteppedMigration::transactional_step`] on the `n`th migration.
706	///
707	/// Is guaranteed to return `Some` if `n < Self::len()`.
708	fn nth_transactional_step(
709		n: u32,
710		cursor: Option<Vec<u8>>,
711		meter: &mut WeightMeter,
712	) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>>;
713
714	/// Call the pre-upgrade hooks of the `n`th migration.
715	///
716	/// Returns `None` if the index is out of bounds.
717	#[cfg(feature = "try-runtime")]
718	fn nth_pre_upgrade(n: u32) -> Option<Result<Vec<u8>, sp_runtime::TryRuntimeError>>;
719
720	/// Call the post-upgrade hooks of the `n`th migration.
721	///
722	/// Returns `None` if the index is out of bounds.
723	#[cfg(feature = "try-runtime")]
724	fn nth_post_upgrade(n: u32, _state: Vec<u8>)
725		-> Option<Result<(), sp_runtime::TryRuntimeError>>;
726
727	/// The maximal encoded length across all cursors.
728	fn cursor_max_encoded_len() -> usize;
729
730	/// The maximal encoded length across all identifiers.
731	fn identifier_max_encoded_len() -> usize;
732
733	/// Assert the integrity of the migrations.
734	///
735	/// Should be executed as part of a test prior to runtime usage. May or may not need
736	/// externalities.
737	#[cfg(feature = "std")]
738	fn integrity_test() -> Result<(), &'static str> {
739		use crate::ensure;
740		let l = Self::len();
741
742		for n in 0..l {
743			ensure!(Self::nth_id(n).is_some(), "id is None");
744			ensure!(Self::nth_max_steps(n).is_some(), "steps is None");
745
746			// The cursor that we use does not matter. Hence use empty.
747			ensure!(
748				Self::nth_step(n, Some(vec![]), &mut WeightMeter::new()).is_some(),
749				"steps is None"
750			);
751			ensure!(
752				Self::nth_transactional_step(n, Some(vec![]), &mut WeightMeter::new()).is_some(),
753				"steps is None"
754			);
755		}
756
757		Ok(())
758	}
759}
760
761impl SteppedMigrations for () {
762	fn len() -> u32 {
763		0
764	}
765
766	fn nth_id(_n: u32) -> Option<Vec<u8>> {
767		None
768	}
769
770	fn nth_max_steps(_n: u32) -> Option<Option<u32>> {
771		None
772	}
773
774	fn nth_step(
775		_n: u32,
776		_cursor: Option<Vec<u8>>,
777		_meter: &mut WeightMeter,
778	) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
779		None
780	}
781
782	fn nth_transactional_step(
783		_n: u32,
784		_cursor: Option<Vec<u8>>,
785		_meter: &mut WeightMeter,
786	) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
787		None
788	}
789
790	#[cfg(feature = "try-runtime")]
791	fn nth_pre_upgrade(_n: u32) -> Option<Result<Vec<u8>, sp_runtime::TryRuntimeError>> {
792		Some(Ok(Vec::new()))
793	}
794
795	#[cfg(feature = "try-runtime")]
796	fn nth_post_upgrade(
797		_n: u32,
798		_state: Vec<u8>,
799	) -> Option<Result<(), sp_runtime::TryRuntimeError>> {
800		Some(Ok(()))
801	}
802
803	fn cursor_max_encoded_len() -> usize {
804		0
805	}
806
807	fn identifier_max_encoded_len() -> usize {
808		0
809	}
810}
811
812// A collection consisting of only a single migration.
813impl<T: SteppedMigration> SteppedMigrations for T {
814	fn len() -> u32 {
815		1
816	}
817
818	fn nth_id(n: u32) -> Option<Vec<u8>> {
819		n.is_zero()
820			.then(|| T::id().encode())
821			.defensive_proof("nth_id should only be called with n==0")
822	}
823
824	fn nth_max_steps(n: u32) -> Option<Option<u32>> {
825		// It should be generally fine to call with n>0, but the code should not attempt to.
826		n.is_zero()
827			.then(|| T::max_steps())
828			.defensive_proof("nth_max_steps should only be called with n==0")
829	}
830
831	fn nth_step(
832		n: u32,
833		cursor: Option<Vec<u8>>,
834		meter: &mut WeightMeter,
835	) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
836		if !n.is_zero() {
837			defensive!("nth_step should only be called with n==0");
838			return None
839		}
840
841		let cursor = match cursor {
842			Some(cursor) => match T::Cursor::decode(&mut &cursor[..]) {
843				Ok(cursor) => Some(cursor),
844				Err(_) => return Some(Err(SteppedMigrationError::InvalidCursor)),
845			},
846			None => None,
847		};
848
849		Some(T::step(cursor, meter).map(|cursor| cursor.map(|cursor| cursor.encode())))
850	}
851
852	fn nth_transactional_step(
853		n: u32,
854		cursor: Option<Vec<u8>>,
855		meter: &mut WeightMeter,
856	) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
857		if n != 0 {
858			defensive!("nth_transactional_step should only be called with n==0");
859			return None
860		}
861
862		let cursor = match cursor {
863			Some(cursor) => match T::Cursor::decode(&mut &cursor[..]) {
864				Ok(cursor) => Some(cursor),
865				Err(_) => return Some(Err(SteppedMigrationError::InvalidCursor)),
866			},
867			None => None,
868		};
869
870		Some(
871			T::transactional_step(cursor, meter).map(|cursor| cursor.map(|cursor| cursor.encode())),
872		)
873	}
874
875	#[cfg(feature = "try-runtime")]
876	fn nth_pre_upgrade(n: u32) -> Option<Result<Vec<u8>, sp_runtime::TryRuntimeError>> {
877		if n != 0 {
878			defensive!("nth_pre_upgrade should only be called with n==0");
879		}
880
881		Some(T::pre_upgrade())
882	}
883
884	#[cfg(feature = "try-runtime")]
885	fn nth_post_upgrade(n: u32, state: Vec<u8>) -> Option<Result<(), sp_runtime::TryRuntimeError>> {
886		if n != 0 {
887			defensive!("nth_post_upgrade should only be called with n==0");
888		}
889		Some(T::post_upgrade(state))
890	}
891
892	fn cursor_max_encoded_len() -> usize {
893		T::Cursor::max_encoded_len()
894	}
895
896	fn identifier_max_encoded_len() -> usize {
897		T::Identifier::max_encoded_len()
898	}
899}
900
901#[impl_trait_for_tuples::impl_for_tuples(1, 30)]
902impl SteppedMigrations for Tuple {
903	fn len() -> u32 {
904		for_tuples!( #( Tuple::len() )+* )
905	}
906
907	fn nth_id(n: u32) -> Option<Vec<u8>> {
908		let mut i = 0;
909
910		for_tuples!( #(
911			if (i + Tuple::len()) > n {
912				return Tuple::nth_id(n - i)
913			}
914
915			i += Tuple::len();
916		)* );
917
918		None
919	}
920
921	fn nth_step(
922		n: u32,
923		cursor: Option<Vec<u8>>,
924		meter: &mut WeightMeter,
925	) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
926		let mut i = 0;
927
928		for_tuples!( #(
929			if (i + Tuple::len()) > n {
930				return Tuple::nth_step(n - i, cursor, meter)
931			}
932
933			i += Tuple::len();
934		)* );
935
936		None
937	}
938
939	fn nth_transactional_step(
940		n: u32,
941		cursor: Option<Vec<u8>>,
942		meter: &mut WeightMeter,
943	) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
944		let mut i = 0;
945
946		for_tuples! ( #(
947			if (i + Tuple::len()) > n {
948				return Tuple::nth_transactional_step(n - i, cursor, meter)
949			}
950
951			i += Tuple::len();
952		)* );
953
954		None
955	}
956
957	#[cfg(feature = "try-runtime")]
958	fn nth_pre_upgrade(n: u32) -> Option<Result<Vec<u8>, sp_runtime::TryRuntimeError>> {
959		let mut i = 0;
960
961		for_tuples! ( #(
962			if (i + Tuple::len()) > n {
963				return Tuple::nth_pre_upgrade(n - i)
964			}
965
966			i += Tuple::len();
967		)* );
968
969		None
970	}
971
972	#[cfg(feature = "try-runtime")]
973	fn nth_post_upgrade(n: u32, state: Vec<u8>) -> Option<Result<(), sp_runtime::TryRuntimeError>> {
974		let mut i = 0;
975
976		for_tuples! ( #(
977			if (i + Tuple::len()) > n {
978				return Tuple::nth_post_upgrade(n - i, state)
979			}
980
981			i += Tuple::len();
982		)* );
983
984		None
985	}
986
987	fn nth_max_steps(n: u32) -> Option<Option<u32>> {
988		let mut i = 0;
989
990		for_tuples!( #(
991			if (i + Tuple::len()) > n {
992				return Tuple::nth_max_steps(n - i)
993			}
994
995			i += Tuple::len();
996		)* );
997
998		None
999	}
1000
1001	fn cursor_max_encoded_len() -> usize {
1002		let mut max_len = 0;
1003
1004		for_tuples!( #(
1005			max_len = max_len.max(Tuple::cursor_max_encoded_len());
1006		)* );
1007
1008		max_len
1009	}
1010
1011	fn identifier_max_encoded_len() -> usize {
1012		let mut max_len = 0;
1013
1014		for_tuples!( #(
1015			max_len = max_len.max(Tuple::identifier_max_encoded_len());
1016		)* );
1017
1018		max_len
1019	}
1020}
1021
1022#[cfg(test)]
1023mod tests {
1024	use super::*;
1025	use crate::{assert_ok, storage::unhashed};
1026
1027	#[derive(Decode, Encode, MaxEncodedLen, Eq, PartialEq)]
1028	pub enum Either<L, R> {
1029		Left(L),
1030		Right(R),
1031	}
1032
1033	pub struct M0;
1034	impl SteppedMigration for M0 {
1035		type Cursor = ();
1036		type Identifier = u8;
1037
1038		fn id() -> Self::Identifier {
1039			0
1040		}
1041
1042		fn step(
1043			_cursor: Option<Self::Cursor>,
1044			_meter: &mut WeightMeter,
1045		) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
1046			log::info!("M0");
1047			unhashed::put(&[0], &());
1048			Ok(None)
1049		}
1050	}
1051
1052	pub struct M1;
1053	impl SteppedMigration for M1 {
1054		type Cursor = ();
1055		type Identifier = u8;
1056
1057		fn id() -> Self::Identifier {
1058			1
1059		}
1060
1061		fn step(
1062			_cursor: Option<Self::Cursor>,
1063			_meter: &mut WeightMeter,
1064		) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
1065			log::info!("M1");
1066			unhashed::put(&[1], &());
1067			Ok(None)
1068		}
1069
1070		fn max_steps() -> Option<u32> {
1071			Some(1)
1072		}
1073	}
1074
1075	pub struct M2;
1076	impl SteppedMigration for M2 {
1077		type Cursor = ();
1078		type Identifier = u8;
1079
1080		fn id() -> Self::Identifier {
1081			2
1082		}
1083
1084		fn step(
1085			_cursor: Option<Self::Cursor>,
1086			_meter: &mut WeightMeter,
1087		) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
1088			log::info!("M2");
1089			unhashed::put(&[2], &());
1090			Ok(None)
1091		}
1092
1093		fn max_steps() -> Option<u32> {
1094			Some(2)
1095		}
1096	}
1097
1098	pub struct F0;
1099	impl SteppedMigration for F0 {
1100		type Cursor = ();
1101		type Identifier = u8;
1102
1103		fn id() -> Self::Identifier {
1104			3
1105		}
1106
1107		fn step(
1108			_cursor: Option<Self::Cursor>,
1109			_meter: &mut WeightMeter,
1110		) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
1111			log::info!("F0");
1112			unhashed::put(&[3], &());
1113			Err(SteppedMigrationError::Failed)
1114		}
1115	}
1116
1117	// Three migrations combined to execute in order:
1118	type Triple = (M0, (M1, M2));
1119	// Six migrations, just concatenating the ones from before:
1120	type Hextuple = (Triple, Triple);
1121
1122	#[test]
1123	fn singular_migrations_work() {
1124		assert_eq!(M0::max_steps(), None);
1125		assert_eq!(M1::max_steps(), Some(1));
1126		assert_eq!(M2::max_steps(), Some(2));
1127
1128		assert_eq!(<(M0, M1)>::nth_max_steps(0), Some(None));
1129		assert_eq!(<(M0, M1)>::nth_max_steps(1), Some(Some(1)));
1130		assert_eq!(<(M0, M1, M2)>::nth_max_steps(2), Some(Some(2)));
1131
1132		assert_eq!(<(M0, M1)>::nth_max_steps(2), None);
1133	}
1134
1135	#[test]
1136	fn tuple_migrations_work() {
1137		assert_eq!(<() as SteppedMigrations>::len(), 0);
1138		assert_eq!(<((), ((), ())) as SteppedMigrations>::len(), 0);
1139		assert_eq!(<Triple as SteppedMigrations>::len(), 3);
1140		assert_eq!(<Hextuple as SteppedMigrations>::len(), 6);
1141
1142		// Check the IDs. The index specific functions all return an Option,
1143		// to account for the out-of-range case.
1144		assert_eq!(<Triple as SteppedMigrations>::nth_id(0), Some(0u8.encode()));
1145		assert_eq!(<Triple as SteppedMigrations>::nth_id(1), Some(1u8.encode()));
1146		assert_eq!(<Triple as SteppedMigrations>::nth_id(2), Some(2u8.encode()));
1147
1148		sp_io::TestExternalities::default().execute_with(|| {
1149			for n in 0..3 {
1150				<Triple as SteppedMigrations>::nth_step(
1151					n,
1152					Default::default(),
1153					&mut WeightMeter::new(),
1154				);
1155			}
1156		});
1157	}
1158
1159	#[test]
1160	fn integrity_test_works() {
1161		sp_io::TestExternalities::default().execute_with(|| {
1162			assert_ok!(<() as SteppedMigrations>::integrity_test());
1163			assert_ok!(<M0 as SteppedMigrations>::integrity_test());
1164			assert_ok!(<M1 as SteppedMigrations>::integrity_test());
1165			assert_ok!(<M2 as SteppedMigrations>::integrity_test());
1166			assert_ok!(<Triple as SteppedMigrations>::integrity_test());
1167			assert_ok!(<Hextuple as SteppedMigrations>::integrity_test());
1168		});
1169	}
1170
1171	#[test]
1172	fn transactional_rollback_works() {
1173		sp_io::TestExternalities::default().execute_with(|| {
1174			assert_ok!(<(M0, F0) as SteppedMigrations>::nth_transactional_step(
1175				0,
1176				Default::default(),
1177				&mut WeightMeter::new()
1178			)
1179			.unwrap());
1180			assert!(unhashed::exists(&[0]));
1181
1182			let _g = crate::StorageNoopGuard::new();
1183			assert!(<(M0, F0) as SteppedMigrations>::nth_transactional_step(
1184				1,
1185				Default::default(),
1186				&mut WeightMeter::new()
1187			)
1188			.unwrap()
1189			.is_err());
1190			assert!(<(F0, M1) as SteppedMigrations>::nth_transactional_step(
1191				0,
1192				Default::default(),
1193				&mut WeightMeter::new()
1194			)
1195			.unwrap()
1196			.is_err());
1197		});
1198	}
1199}