pezframe_support/
migrations.rs

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