referrerpolicy=no-referrer-when-downgrade

pallet_migrations/
lib.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#![deny(missing_docs)]
19#![deny(rustdoc::broken_intra_doc_links)]
20
21//! # `pallet-migrations`
22//!
23//! Provides multi block migrations for FRAME runtimes.
24//!
25//! ## Overview
26//!
27//! The pallet takes care of executing a batch of multi-step migrations over multiple blocks. The
28//! process starts on each runtime upgrade. Normal and operational transactions are paused while
29//! migrations are on-going.
30//!
31//! ### Example
32//!
33//! This example demonstrates a simple mocked walk through of a basic success scenario. The pallet
34//! is configured with two migrations: one succeeding after just one step, and the second one
35//! succeeding after two steps. A runtime upgrade is then enacted and the block number is advanced
36//! until all migrations finish executing. Afterwards, the recorded historic migrations are
37//! checked and events are asserted.
38#![doc = docify::embed!("src/tests.rs", simple_works)]
39//! ## Pallet API
40//!
41//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
42//! including its configuration trait, dispatchables, storage items, events and errors.
43//!
44//! Otherwise noteworthy API of this pallet include its implementation of the
45//! [`MultiStepMigrator`] trait. This must be plugged into
46//! [`frame_system::Config::MultiBlockMigrator`] for proper function.
47//!
48//! The API contains some calls for emergency management. They are all prefixed with `force_` and
49//! should normally not be needed. Pay special attention prior to using them.
50//!
51//! ### Design Goals
52//!
53//! 1. Must automatically execute migrations over multiple blocks.
54//! 2. Must expose information about whether migrations are ongoing.
55//! 3. Must respect pessimistic weight bounds of migrations.
56//! 4. Must execute migrations in order. Skipping is not allowed; migrations are run on a
57//! all-or-nothing basis.
58//! 5. Must prevent re-execution of past migrations.
59//! 6. Must provide transactional storage semantics for migrations.
60//! 7. Must guarantee progress.
61//!
62//! ### Design
63//!
64//! Migrations are provided to the pallet through the associated type [`Config::Migrations`] of type
65//! [`SteppedMigrations`]. This allows multiple migrations to be aggregated through a tuple. It
66//! simplifies the trait bounds since all associated types of the trait must be provided by the
67//! pallet. The actual progress of the pallet is stored in the [`Cursor`] storage item. This can
68//! either be [`MigrationCursor::Active`] or [`MigrationCursor::Stuck`]. In the active case it
69//! points to the currently active migration and stores its inner cursor. The inner cursor can then
70//! be used by the migration to store its inner state and advance. Each time when the migration
71//! returns `Some(cursor)`, it signals the pallet that it is not done yet.
72//!
73//! The cursor is reset on each runtime upgrade. This ensures that it starts to execute at the
74//! first migration in the vector. The pallets cursor is only ever incremented or set to `Stuck`
75//! once it encounters an error (Goal 4). Once in the stuck state, the pallet will stay stuck until
76//! it is fixed through manual governance intervention.
77//!
78//! As soon as the cursor of the pallet becomes `Some(_)`; [`MultiStepMigrator::ongoing`] returns
79//! `true` (Goal 2). This can be used by upstream code to possibly pause transactions.
80//! In `on_initialize` the pallet will load the current migration and check whether it was already
81//! executed in the past by checking for membership of its ID in the [`Historic`] set. Historic
82//! migrations are skipped without causing an error. Each successfully executed migration is added
83//! to this set (Goal 5).
84//!
85//! This proceeds until no more migrations remain. At that point, the event `UpgradeCompleted` is
86//! emitted (Goal 1).
87//!
88//! The execution of each migration happens by calling [`SteppedMigration::transactional_step`].
89//! This function wraps the inner `step` function into a transactional layer to allow rollback in
90//! the error case (Goal 6).
91//!
92//! Weight limits must be checked by the migration itself. The pallet provides a [`WeightMeter`] for
93//! that purpose. The pallet may return [`SteppedMigrationError::InsufficientWeight`] at any point.
94//! In that scenario, one of two things will happen: if that migration was exclusively executed
95//! in this block, and therefore required more than the maximum amount of weight possible, the
96//! process becomes `Stuck`. Otherwise, one re-attempt is executed with the same logic in the next
97//! block (Goal 3). Progress through the migrations is guaranteed by providing a timeout for each
98//! migration via [`SteppedMigration::max_steps`]. The pallet **ONLY** guarantees progress if this
99//! is set to sensible limits (Goal 7).
100//!
101//! ### Scenario: Governance cleanup
102//!
103//! Every now and then, governance can make use of the [`clear_historic`][Pallet::clear_historic]
104//! call. This ensures that no old migrations pile up in the [`Historic`] set. This can be done very
105//! rarely, since the storage should not grow quickly and the lookup weight does not suffer much.
106//! Another possibility would be to have a synchronous single-block migration perpetually deployed
107//! that cleans them up before the MBMs start.
108//!
109//! ### Scenario: Successful upgrade
110//!
111//! The standard procedure for a successful runtime upgrade can look like this:
112//! 1. Migrations are configured in the `Migrations` config item. All migrations expose
113//! [`max_steps`][SteppedMigration::max_steps], are error tolerant, check their weight bounds and
114//! have a unique identifier.
115//! 2. The runtime upgrade is enacted. An `UpgradeStarted` event is
116//! followed by lots of `MigrationAdvanced` and `MigrationCompleted` events. Finally
117//! `UpgradeCompleted` is emitted.
118//! 3. Cleanup as described in the governance scenario be executed at any time after the migrations
119//! completed.
120//!
121//! ### Advice: Failed upgrades
122//!
123//! Failed upgrades cannot be recovered from automatically and require governance intervention. Set
124//! up monitoring for `UpgradeFailed` events to be made aware of any failures. The hook
125//! [`FailedMigrationHandler::failed`] should be setup in a way that it allows governance to act,
126//! but still prevent other transactions from interacting with the inconsistent storage state. Note
127//! that this is paramount, since the inconsistent state might contain a faulty balance amount or
128//! similar that could cause great harm if user transactions don't remain suspended. One way to
129//! implement this would be to use the `SafeMode` or `TxPause` pallets that can prevent most user
130//! interactions but still allow a whitelisted set of governance calls.
131//!
132//! ### Remark: Failed migrations
133//!
134//! Failed migrations are not added to the `Historic` set. This means that an erroneous
135//! migration must be removed and fixed manually. This already applies, even before considering the
136//! historic set.
137//!
138//! ### Remark: Transactional processing
139//!
140//! You can see the transactional semantics for migration steps as mostly useless, since in the
141//! stuck case the state is already messed up. This just prevents it from becoming even more messed
142//! up, but doesn't prevent it in the first place.
143
144#![cfg_attr(not(feature = "std"), no_std)]
145
146mod benchmarking;
147pub mod migrations;
148mod mock;
149pub mod mock_helpers;
150mod tests;
151pub mod weights;
152
153extern crate alloc;
154
155pub use pallet::*;
156pub use weights::WeightInfo;
157
158use alloc::vec::Vec;
159use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
160use core::ops::ControlFlow;
161use frame_support::{
162	defensive, defensive_assert,
163	migrations::*,
164	pallet_prelude::*,
165	traits::Get,
166	weights::{Weight, WeightMeter},
167	BoundedVec,
168};
169use frame_system::{
170	pallet_prelude::{BlockNumberFor, *},
171	Pallet as System,
172};
173use sp_runtime::{SaturatedConversion, Saturating};
174
175/// Points to the next migration to execute.
176#[derive(
177	Debug,
178	Clone,
179	Eq,
180	PartialEq,
181	Encode,
182	Decode,
183	DecodeWithMemTracking,
184	scale_info::TypeInfo,
185	MaxEncodedLen,
186)]
187pub enum MigrationCursor<Cursor, BlockNumber> {
188	/// Points to the currently active migration and its inner cursor.
189	Active(ActiveCursor<Cursor, BlockNumber>),
190
191	/// Migration got stuck and cannot proceed. This is bad.
192	Stuck,
193}
194
195impl<Cursor, BlockNumber> MigrationCursor<Cursor, BlockNumber> {
196	/// Try to return self as an [`ActiveCursor`].
197	pub fn as_active(&self) -> Option<&ActiveCursor<Cursor, BlockNumber>> {
198		match self {
199			MigrationCursor::Active(active) => Some(active),
200			MigrationCursor::Stuck => None,
201		}
202	}
203}
204
205impl<Cursor, BlockNumber> From<ActiveCursor<Cursor, BlockNumber>>
206	for MigrationCursor<Cursor, BlockNumber>
207{
208	fn from(active: ActiveCursor<Cursor, BlockNumber>) -> Self {
209		MigrationCursor::Active(active)
210	}
211}
212
213/// Points to the currently active migration and its inner cursor.
214#[derive(
215	Debug,
216	Clone,
217	Eq,
218	PartialEq,
219	Encode,
220	Decode,
221	DecodeWithMemTracking,
222	scale_info::TypeInfo,
223	MaxEncodedLen,
224)]
225pub struct ActiveCursor<Cursor, BlockNumber> {
226	/// The index of the migration in the MBM tuple.
227	pub index: u32,
228	/// The cursor of the migration that is referenced by `index`.
229	pub inner_cursor: Option<Cursor>,
230	/// The block number that the migration started at.
231	///
232	/// This is used to calculate how many blocks it took.
233	pub started_at: BlockNumber,
234}
235
236impl<Cursor, BlockNumber> ActiveCursor<Cursor, BlockNumber> {
237	/// Advance the overarching cursor to the next migration.
238	pub(crate) fn goto_next_migration(&mut self, current_block: BlockNumber) {
239		self.index.saturating_inc();
240		self.inner_cursor = None;
241		self.started_at = current_block;
242	}
243}
244
245/// How to clear the records of historic migrations.
246#[derive(
247	Debug, Clone, Eq, PartialEq, Encode, Decode, DecodeWithMemTracking, scale_info::TypeInfo,
248)]
249pub enum HistoricCleanupSelector<Id> {
250	/// Clear exactly these entries.
251	///
252	/// This is the advised way of doing it.
253	Specific(Vec<Id>),
254
255	/// Clear up to this many entries
256	Wildcard {
257		/// How many should be cleared in this call at most.
258		limit: Option<u32>,
259		/// The cursor that was emitted from any previous `HistoricCleared`.
260		///
261		/// Does not need to be passed when clearing the first batch.
262		previous_cursor: Option<Vec<u8>>,
263	},
264}
265
266/// The default number of entries that should be cleared by a `HistoricCleanupSelector::Wildcard`.
267///
268/// The caller can explicitly specify a higher amount. Benchmarks are run with twice this value.
269const DEFAULT_HISTORIC_BATCH_CLEAR_SIZE: u32 = 128;
270
271impl<Id> HistoricCleanupSelector<Id> {
272	/// The maximal number of entries that this will remove.
273	///
274	/// Needed for weight calculation.
275	pub fn limit(&self) -> u32 {
276		match self {
277			Self::Specific(ids) => ids.len() as u32,
278			Self::Wildcard { limit, .. } => limit.unwrap_or(DEFAULT_HISTORIC_BATCH_CLEAR_SIZE),
279		}
280	}
281}
282
283/// Convenience alias for [`MigrationCursor`].
284pub type CursorOf<T> = MigrationCursor<RawCursorOf<T>, BlockNumberFor<T>>;
285
286/// Convenience alias for the raw inner cursor of a migration.
287pub type RawCursorOf<T> = BoundedVec<u8, <T as Config>::CursorMaxLen>;
288
289/// Convenience alias for the identifier of a migration.
290pub type IdentifierOf<T> = BoundedVec<u8, <T as Config>::IdentifierMaxLen>;
291
292/// Convenience alias for [`ActiveCursor`].
293pub type ActiveCursorOf<T> = ActiveCursor<RawCursorOf<T>, BlockNumberFor<T>>;
294
295/// Trait for a tuple of No-OP migrations with one element.
296#[impl_trait_for_tuples::impl_for_tuples(30)]
297pub trait MockedMigrations: SteppedMigrations {
298	/// The migration should fail after `n` steps.
299	fn set_fail_after(n: u32);
300	/// The migration should succeed after `n` steps.
301	fn set_success_after(n: u32);
302}
303
304#[cfg(feature = "try-runtime")]
305/// Wrapper for pre-upgrade bytes, allowing us to impl MEL on it.
306///
307/// For `try-runtime` testing only.
308#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, scale_info::TypeInfo, Default)]
309struct PreUpgradeBytesWrapper(pub Vec<u8>);
310
311/// Data stored by the pre-upgrade hook of the MBMs. Only used for `try-runtime` testing.
312///
313/// Define this outside of the pallet so it is not confused with actual storage.
314#[cfg(feature = "try-runtime")]
315#[frame_support::storage_alias]
316type PreUpgradeBytes<T: Config> =
317	StorageMap<Pallet<T>, Twox64Concat, IdentifierOf<T>, PreUpgradeBytesWrapper, ValueQuery>;
318
319/// The status of multi-block migrations.
320#[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Eq)]
321pub enum MbmIsOngoing {
322	/// Migrations are ongoing.
323	Yes,
324	/// Migrations are not ongoing.
325	No,
326	/// Migrations are stuck.
327	Stuck,
328}
329
330/// The comprehensive status of multi-block migrations.
331#[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Eq)]
332pub struct MbmStatus {
333	/// Whether migrations are ongoing.
334	pub ongoing: MbmIsOngoing,
335	/// Progress information about the current migration, if any.
336	pub progress: Option<MbmProgress>,
337	/// The storage prefixes that are affected by the current migration.
338	///
339	/// Can be empty if the migration does not know or there are no prefixes.
340	pub prefixes: Vec<Vec<u8>>,
341}
342
343/// Progress information for the current migration.
344#[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Eq)]
345pub struct MbmProgress {
346	/// The index of the current migration.
347	pub current_migration: u32,
348	/// The total number of migrations.
349	pub total_migrations: u32,
350	/// The number of steps that the current migration has taken.
351	pub current_migration_steps: u32,
352	/// The maximum number of steps that the current migration can take.
353	///
354	/// Can be `None` if the migration does not know or there is no limit.
355	pub current_migration_max_steps: Option<u32>,
356}
357
358#[frame_support::pallet]
359pub mod pallet {
360	use super::*;
361
362	/// The in-code storage version.
363	const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
364
365	#[pallet::pallet]
366	#[pallet::storage_version(STORAGE_VERSION)]
367	pub struct Pallet<T>(_);
368
369	#[pallet::config(with_default)]
370	pub trait Config: frame_system::Config {
371		/// The overarching event type of the runtime.
372		#[pallet::no_default_bounds]
373		#[allow(deprecated)]
374		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
375
376		/// All the multi-block migrations to run.
377		///
378		/// Should only be updated in a runtime-upgrade once all the old migrations have completed.
379		/// (Check that [`Cursor`] is `None`).
380		#[cfg(not(feature = "runtime-benchmarks"))]
381		#[pallet::no_default]
382		type Migrations: SteppedMigrations;
383
384		/// Mocked migrations for benchmarking only.
385		///
386		/// Should be configured to [`crate::mock_helpers::MockedMigrations`] in benchmarks.
387		#[cfg(feature = "runtime-benchmarks")]
388		#[pallet::no_default]
389		type Migrations: MockedMigrations;
390
391		/// The maximal length of an encoded cursor.
392		///
393		/// A good default needs to selected such that no migration will ever have a cursor with MEL
394		/// above this limit. This is statically checked in `integrity_test`.
395		#[pallet::constant]
396		type CursorMaxLen: Get<u32>;
397
398		/// The maximal length of an encoded identifier.
399		///
400		/// A good default needs to selected such that no migration will ever have an identifier
401		/// with MEL above this limit. This is statically checked in `integrity_test`.
402		#[pallet::constant]
403		type IdentifierMaxLen: Get<u32>;
404
405		/// Notifications for status updates of a runtime upgrade.
406		///
407		/// Could be used to pause XCM etc.
408		type MigrationStatusHandler: MigrationStatusHandler;
409
410		/// Handler for failed migrations.
411		type FailedMigrationHandler: FailedMigrationHandler;
412
413		/// The maximum weight to spend each block to execute migrations.
414		type MaxServiceWeight: Get<Weight>;
415
416		/// Weight information for the calls and functions of this pallet.
417		type WeightInfo: WeightInfo;
418	}
419
420	/// Default implementations of [`DefaultConfig`], which can be used to implement [`Config`].
421	pub mod config_preludes {
422		use super::{inject_runtime_type, DefaultConfig};
423		use frame_support::{
424			derive_impl,
425			migrations::FreezeChainOnFailedMigration,
426			pallet_prelude::{ConstU32, *},
427		};
428		use frame_system::limits::BlockWeights;
429
430		/// Provides a viable default config that can be used with
431		/// [`derive_impl`](`frame_support::derive_impl`) to derive a testing pallet config
432		/// based on this one.
433		///
434		/// See `Test` in the `default-config` example pallet's `test.rs` for an example of
435		/// a downstream user of this particular `TestDefaultConfig`
436		pub struct TestDefaultConfig;
437
438		frame_support::parameter_types! {
439			/// Maximal weight per block that can be spent on migrations in tests.
440			pub TestMaxServiceWeight: Weight = <<TestDefaultConfig as frame_system::DefaultConfig>::BlockWeights as Get<BlockWeights>>::get().max_block.div(2);
441		}
442
443		#[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)]
444		impl frame_system::DefaultConfig for TestDefaultConfig {}
445
446		#[frame_support::register_default_impl(TestDefaultConfig)]
447		impl DefaultConfig for TestDefaultConfig {
448			#[inject_runtime_type]
449			type RuntimeEvent = ();
450			type CursorMaxLen = ConstU32<{ 1 << 16 }>;
451			type IdentifierMaxLen = ConstU32<{ 256 }>;
452			type MigrationStatusHandler = ();
453			type FailedMigrationHandler = FreezeChainOnFailedMigration;
454			type MaxServiceWeight = TestMaxServiceWeight;
455			type WeightInfo = ();
456		}
457	}
458
459	/// The currently active migration to run and its cursor.
460	///
461	/// `None` indicates that no migration is running.
462	#[pallet::storage]
463	pub type Cursor<T: Config> = StorageValue<_, CursorOf<T>, OptionQuery>;
464
465	/// Set of all successfully executed migrations.
466	///
467	/// This is used as blacklist, to not re-execute migrations that have not been removed from the
468	/// codebase yet. Governance can regularly clear this out via `clear_historic`.
469	#[pallet::storage]
470	pub type Historic<T: Config> = StorageMap<_, Twox64Concat, IdentifierOf<T>, (), OptionQuery>;
471
472	#[pallet::event]
473	#[pallet::generate_deposit(pub(super) fn deposit_event)]
474	pub enum Event<T: Config> {
475		/// A Runtime upgrade started.
476		///
477		/// Its end is indicated by `UpgradeCompleted` or `UpgradeFailed`.
478		UpgradeStarted {
479			/// The number of migrations that this upgrade contains.
480			///
481			/// This can be used to design a progress indicator in combination with counting the
482			/// `MigrationCompleted` and `MigrationSkipped` events.
483			migrations: u32,
484		},
485		/// The current runtime upgrade completed.
486		///
487		/// This implies that all of its migrations completed successfully as well.
488		UpgradeCompleted,
489		/// Runtime upgrade failed.
490		///
491		/// This is very bad and will require governance intervention.
492		UpgradeFailed,
493		/// A migration was skipped since it was already executed in the past.
494		MigrationSkipped {
495			/// The index of the skipped migration within the [`Config::Migrations`] list.
496			index: u32,
497		},
498		/// A migration progressed.
499		MigrationAdvanced {
500			/// The index of the migration within the [`Config::Migrations`] list.
501			index: u32,
502			/// The number of blocks that this migration took so far.
503			took: BlockNumberFor<T>,
504		},
505		/// A Migration completed.
506		MigrationCompleted {
507			/// The index of the migration within the [`Config::Migrations`] list.
508			index: u32,
509			/// The number of blocks that this migration took so far.
510			took: BlockNumberFor<T>,
511		},
512		/// A Migration failed.
513		///
514		/// This implies that the whole upgrade failed and governance intervention is required.
515		MigrationFailed {
516			/// The index of the migration within the [`Config::Migrations`] list.
517			index: u32,
518			/// The number of blocks that this migration took so far.
519			took: BlockNumberFor<T>,
520		},
521		/// The set of historical migrations has been cleared.
522		HistoricCleared {
523			/// Should be passed to `clear_historic` in a successive call.
524			next_cursor: Option<Vec<u8>>,
525		},
526	}
527
528	#[pallet::error]
529	pub enum Error<T> {
530		/// The operation cannot complete since some MBMs are ongoing.
531		Ongoing,
532	}
533
534	#[pallet::hooks]
535	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
536		fn on_runtime_upgrade() -> Weight {
537			Self::onboard_new_mbms()
538		}
539
540		#[cfg(feature = "std")]
541		fn integrity_test() {
542			// Check that the migrations tuple is legit.
543			frame_support::assert_ok!(T::Migrations::integrity_test());
544
545			// Very important! Ensure that the pallet is configured in `System::Config`.
546			{
547				assert!(!Cursor::<T>::exists(), "Externalities storage should be clean");
548				assert!(!<T as frame_system::Config>::MultiBlockMigrator::ongoing());
549
550				Cursor::<T>::put(MigrationCursor::Stuck);
551				assert!(<T as frame_system::Config>::MultiBlockMigrator::ongoing());
552
553				Cursor::<T>::kill();
554			}
555
556			// The per-block service weight is sane.
557			{
558				let want = T::MaxServiceWeight::get();
559				let max = <T as frame_system::Config>::BlockWeights::get().max_block;
560
561				assert!(want.all_lte(max), "Service weight is larger than a block: {want} > {max}");
562			}
563
564			// Cursor MEL
565			{
566				let mel = T::Migrations::cursor_max_encoded_len();
567				let max_mel = T::CursorMaxLen::get() as usize;
568				assert!(
569					mel <= max_mel,
570					"A Cursor is not guaranteed to fit into the storage: {mel} > {max_mel}",
571				);
572			}
573
574			// Identifier MEL
575			{
576				let mel = T::Migrations::identifier_max_encoded_len();
577				let max_mel = T::IdentifierMaxLen::get() as usize;
578				assert!(
579					mel <= max_mel,
580					"An Identifier is not guaranteed to fit into the storage: {mel} > {max_mel}",
581				);
582			}
583		}
584	}
585
586	#[pallet::call(weight = T::WeightInfo)]
587	impl<T: Config> Pallet<T> {
588		/// Allows root to set a cursor to forcefully start, stop or forward the migration process.
589		///
590		/// Should normally not be needed and is only in place as emergency measure. Note that
591		/// restarting the migration process in this manner will not call the
592		/// [`MigrationStatusHandler::started`] hook or emit an `UpgradeStarted` event.
593		#[pallet::call_index(0)]
594		pub fn force_set_cursor(
595			origin: OriginFor<T>,
596			cursor: Option<CursorOf<T>>,
597		) -> DispatchResult {
598			ensure_root(origin)?;
599
600			Cursor::<T>::set(cursor);
601
602			Ok(())
603		}
604
605		/// Allows root to set an active cursor to forcefully start/forward the migration process.
606		///
607		/// This is an edge-case version of [`Self::force_set_cursor`] that allows to set the
608		/// `started_at` value to the next block number. Otherwise this would not be possible, since
609		/// `force_set_cursor` takes an absolute block number. Setting `started_at` to `None`
610		/// indicates that the current block number plus one should be used.
611		#[pallet::call_index(1)]
612		pub fn force_set_active_cursor(
613			origin: OriginFor<T>,
614			index: u32,
615			inner_cursor: Option<RawCursorOf<T>>,
616			started_at: Option<BlockNumberFor<T>>,
617		) -> DispatchResult {
618			ensure_root(origin)?;
619
620			let started_at = started_at.unwrap_or(
621				System::<T>::block_number().saturating_add(sp_runtime::traits::One::one()),
622			);
623			Cursor::<T>::put(MigrationCursor::Active(ActiveCursor {
624				index,
625				inner_cursor,
626				started_at,
627			}));
628
629			Ok(())
630		}
631
632		/// Forces the onboarding of the migrations.
633		///
634		/// This process happens automatically on a runtime upgrade. It is in place as an emergency
635		/// measurement. The cursor needs to be `None` for this to succeed.
636		#[pallet::call_index(2)]
637		pub fn force_onboard_mbms(origin: OriginFor<T>) -> DispatchResult {
638			ensure_root(origin)?;
639
640			ensure!(!Cursor::<T>::exists(), Error::<T>::Ongoing);
641			Self::onboard_new_mbms();
642
643			Ok(())
644		}
645
646		/// Clears the `Historic` set.
647		///
648		/// `map_cursor` must be set to the last value that was returned by the
649		/// `HistoricCleared` event. The first time `None` can be used. `limit` must be chosen in a
650		/// way that will result in a sensible weight.
651		#[pallet::call_index(3)]
652		#[pallet::weight(T::WeightInfo::clear_historic(selector.limit()))]
653		pub fn clear_historic(
654			origin: OriginFor<T>,
655			selector: HistoricCleanupSelector<IdentifierOf<T>>,
656		) -> DispatchResult {
657			ensure_root(origin)?;
658
659			match &selector {
660				HistoricCleanupSelector::Specific(ids) => {
661					for id in ids {
662						Historic::<T>::remove(id);
663					}
664					Self::deposit_event(Event::HistoricCleared { next_cursor: None });
665				},
666				HistoricCleanupSelector::Wildcard { previous_cursor, .. } => {
667					let next = Historic::<T>::clear(selector.limit(), previous_cursor.as_deref());
668					Self::deposit_event(Event::HistoricCleared { next_cursor: next.maybe_cursor });
669				},
670			}
671
672			Ok(())
673		}
674	}
675
676	#[pallet::view_functions]
677	impl<T: Config> Pallet<T> {
678		/// Returns the ongoing status of migrations.
679		pub fn ongoing_status() -> MbmIsOngoing {
680			match Cursor::<T>::get() {
681				Some(MigrationCursor::Active(_)) => MbmIsOngoing::Yes,
682				Some(MigrationCursor::Stuck) => MbmIsOngoing::Stuck,
683				None => MbmIsOngoing::No,
684			}
685		}
686
687		/// Returns progress information about the current migration, if any.
688		///
689		/// This function provides detailed information about the current migration's progress,
690		/// including the number of steps completed and the maximum allowed steps.
691		pub fn progress() -> Option<MbmProgress> {
692			match Cursor::<T>::get() {
693				Some(MigrationCursor::Active(cursor)) => {
694					let blocks_elapsed =
695						System::<T>::block_number().saturating_sub(cursor.started_at);
696					let estimated_steps = blocks_elapsed.saturated_into::<u32>();
697
698					Some(MbmProgress {
699						current_migration: cursor.index,
700						total_migrations: T::Migrations::len(),
701						current_migration_steps: estimated_steps,
702						current_migration_max_steps: T::Migrations::nth_max_steps(cursor.index)?,
703					})
704				},
705				_ => None,
706			}
707		}
708
709		/// Returns the storage prefixes affected by the current migration.
710		///
711		/// Can be empty if the migration does not know or there are no prefixes.
712		pub fn affected_prefixes() -> Vec<Vec<u8>> {
713			match Cursor::<T>::get() {
714				Some(MigrationCursor::Active(cursor)) => {
715					T::Migrations::nth_migrating_prefixes(cursor.index)
716						.flatten()
717						.unwrap_or_default()
718				},
719				_ => Vec::new(),
720			}
721		}
722
723		/// Returns the comprehensive status of multi-block migrations.
724		pub fn status() -> MbmStatus {
725			let ongoing = Self::ongoing_status();
726			let progress = Self::progress();
727			let prefixes = Self::affected_prefixes();
728
729			MbmStatus { ongoing, progress, prefixes }
730		}
731	}
732}
733
734impl<T: Config> Pallet<T> {
735	/// Onboard all new Multi-Block-Migrations and start the process of executing them.
736	///
737	/// Should only be called once all previous migrations completed.
738	fn onboard_new_mbms() -> Weight {
739		if let Some(cursor) = Cursor::<T>::get() {
740			log::error!("Ongoing migrations interrupted - chain stuck");
741
742			let maybe_index = cursor.as_active().map(|c| c.index);
743			Self::upgrade_failed(maybe_index);
744			return T::WeightInfo::onboard_new_mbms();
745		}
746
747		let migrations = T::Migrations::len();
748		log::debug!("Onboarding {migrations} new MBM migrations");
749
750		if migrations > 0 {
751			// Set the cursor to the first migration:
752			Cursor::<T>::set(Some(
753				ActiveCursor {
754					index: 0,
755					inner_cursor: None,
756					started_at: System::<T>::block_number(),
757				}
758				.into(),
759			));
760			Self::deposit_event(Event::UpgradeStarted { migrations });
761			T::MigrationStatusHandler::started();
762		}
763
764		T::WeightInfo::onboard_new_mbms()
765	}
766
767	/// Tries to make progress on the Multi-Block-Migrations process.
768	fn progress_mbms(n: BlockNumberFor<T>) -> Weight {
769		let mut meter = WeightMeter::with_limit(T::MaxServiceWeight::get());
770		meter.consume(T::WeightInfo::progress_mbms_none());
771
772		let mut cursor = match Cursor::<T>::get() {
773			None => {
774				log::trace!("[Block {n:?}] Waiting for cursor to become `Some`.");
775				return meter.consumed();
776			},
777			Some(MigrationCursor::Active(cursor)) => {
778				log::debug!("Progressing MBM #{}", cursor.index);
779				cursor
780			},
781			Some(MigrationCursor::Stuck) => {
782				log::error!("Migration stuck. Governance intervention required.");
783				return meter.consumed();
784			},
785		};
786		debug_assert!(Self::ongoing());
787
788		// The limit here is a defensive measure to prevent an infinite loop. It expresses that we
789		// allow no more than 8 MBMs to finish in a single block. This should be harmless, since we
790		// generally expect *Multi*-Block-Migrations to take *multiple* blocks.
791		for i in 0..8 {
792			match Self::exec_migration(cursor, i == 0, &mut meter) {
793				None => return meter.consumed(),
794				Some(ControlFlow::Continue(next_cursor)) => {
795					cursor = next_cursor;
796				},
797				Some(ControlFlow::Break(last_cursor)) => {
798					cursor = last_cursor;
799					break;
800				},
801			}
802		}
803
804		Cursor::<T>::set(Some(cursor.into()));
805
806		meter.consumed()
807	}
808
809	/// Try to make progress on the current migration.
810	///
811	/// Returns whether processing should continue or break for this block. The return value means:
812	/// - `None`: The migration process is completely finished.
813	/// - `ControlFlow::Break`: Continue in the *next* block with the given cursor.
814	/// - `ControlFlow::Continue`: Continue in the *current* block with the given cursor.
815	fn exec_migration(
816		mut cursor: ActiveCursorOf<T>,
817		is_first: bool,
818		meter: &mut WeightMeter,
819	) -> Option<ControlFlow<ActiveCursorOf<T>, ActiveCursorOf<T>>> {
820		// The differences between the single branches' weights is not that big. And since we do
821		// only one step per block, we can just use the maximum instead of more precise accounting.
822		if meter.try_consume(Self::exec_migration_max_weight()).is_err() {
823			defensive_assert!(!is_first, "There should be enough weight to do this at least once");
824			return Some(ControlFlow::Break(cursor));
825		}
826
827		if cursor.index >= T::Migrations::len() {
828			// No more migrations in the tuple - we are done.
829			defensive_assert!(cursor.index == T::Migrations::len(), "Inconsistent MBMs tuple");
830			Self::deposit_event(Event::UpgradeCompleted);
831			Cursor::<T>::kill();
832			T::MigrationStatusHandler::completed();
833			return None;
834		};
835
836		let id = T::Migrations::nth_id(cursor.index).map(TryInto::try_into);
837		let Some(Ok(bounded_id)): Option<Result<IdentifierOf<T>, _>> = id else {
838			defensive!("integrity_test ensures that all identifiers are present and bounde; qed.");
839			Self::upgrade_failed(Some(cursor.index));
840			return None;
841		};
842
843		if Historic::<T>::contains_key(&bounded_id) {
844			Self::deposit_event(Event::MigrationSkipped { index: cursor.index });
845			cursor.goto_next_migration(System::<T>::block_number());
846			return Some(ControlFlow::Continue(cursor));
847		}
848
849		let max_steps = T::Migrations::nth_max_steps(cursor.index);
850
851		// If this is the first time running this migration, exec the pre-upgrade hook.
852		#[cfg(feature = "try-runtime")]
853		if !PreUpgradeBytes::<T>::contains_key(&bounded_id) {
854			let bytes = T::Migrations::nth_pre_upgrade(cursor.index)
855				.expect("Invalid cursor.index")
856				.expect("Pre-upgrade failed");
857			PreUpgradeBytes::<T>::insert(&bounded_id, PreUpgradeBytesWrapper(bytes));
858		}
859
860		let next_cursor = T::Migrations::nth_transactional_step(
861			cursor.index,
862			cursor.inner_cursor.clone().map(|c| c.into_inner()),
863			meter,
864		);
865		let Some((max_steps, next_cursor)) = max_steps.zip(next_cursor) else {
866			defensive!("integrity_test ensures that the tuple is valid; qed");
867			Self::upgrade_failed(Some(cursor.index));
868			return None;
869		};
870
871		let took = System::<T>::block_number().saturating_sub(cursor.started_at);
872		match next_cursor {
873			Ok(Some(next_cursor)) => {
874				let Ok(bound_next_cursor) = next_cursor.try_into() else {
875					defensive!("The integrity check ensures that all cursors' MEL bound fits into CursorMaxLen; qed");
876					Self::upgrade_failed(Some(cursor.index));
877					return None;
878				};
879
880				Self::deposit_event(Event::MigrationAdvanced { index: cursor.index, took });
881				cursor.inner_cursor = Some(bound_next_cursor);
882
883				if max_steps.is_some_and(|max| took > max.into()) {
884					Self::deposit_event(Event::MigrationFailed { index: cursor.index, took });
885					Self::upgrade_failed(Some(cursor.index));
886					None
887				} else {
888					// A migration cannot progress more than one step per block, we therefore break.
889					Some(ControlFlow::Break(cursor))
890				}
891			},
892			Ok(None) => {
893				// A migration is done when it returns cursor `None`.
894
895				// Run post-upgrade checks.
896				#[cfg(feature = "try-runtime")]
897				T::Migrations::nth_post_upgrade(
898					cursor.index,
899					PreUpgradeBytes::<T>::get(&bounded_id).0,
900				)
901				.expect("Invalid cursor.index.")
902				.expect("Post-upgrade failed.");
903
904				Self::deposit_event(Event::MigrationCompleted { index: cursor.index, took });
905				Historic::<T>::insert(&bounded_id, ());
906				cursor.goto_next_migration(System::<T>::block_number());
907				Some(ControlFlow::Continue(cursor))
908			},
909			Err(SteppedMigrationError::InsufficientWeight { required }) => {
910				if is_first || required.any_gt(meter.limit()) {
911					Self::deposit_event(Event::MigrationFailed { index: cursor.index, took });
912					Self::upgrade_failed(Some(cursor.index));
913					None
914				} else {
915					// Retry and hope that there is more weight in the next block.
916					Some(ControlFlow::Break(cursor))
917				}
918			},
919			Err(SteppedMigrationError::InvalidCursor | SteppedMigrationError::Failed) => {
920				Self::deposit_event(Event::MigrationFailed { index: cursor.index, took });
921				Self::upgrade_failed(Some(cursor.index));
922				None
923			},
924		}
925	}
926
927	/// Fail the current runtime upgrade, caused by `migration`.
928	///
929	/// When the `try-runtime` feature is enabled, this function will panic.
930	// Allow unreachable code so it can compile without warnings when `try-runtime` is enabled.
931	fn upgrade_failed(migration: Option<u32>) {
932		use FailedMigrationHandling::*;
933		Self::deposit_event(Event::UpgradeFailed);
934
935		if cfg!(feature = "try-runtime") {
936			panic!("Migration with index {migration:?} failed.");
937		} else {
938			match T::FailedMigrationHandler::failed(migration) {
939				KeepStuck => Cursor::<T>::set(Some(MigrationCursor::Stuck)),
940				ForceUnstuck => Cursor::<T>::kill(),
941				Ignore => {},
942			}
943		}
944	}
945
946	/// The maximal weight of calling the private `Self::exec_migration` function.
947	pub fn exec_migration_max_weight() -> Weight {
948		T::WeightInfo::exec_migration_complete()
949			.max(T::WeightInfo::exec_migration_completed())
950			.max(T::WeightInfo::exec_migration_skipped_historic())
951			.max(T::WeightInfo::exec_migration_advance())
952			.max(T::WeightInfo::exec_migration_fail())
953	}
954}
955
956impl<T: Config> MultiStepMigrator for Pallet<T> {
957	fn ongoing() -> bool {
958		Cursor::<T>::exists()
959	}
960
961	fn step() -> Weight {
962		Self::progress_mbms(System::<T>::block_number())
963	}
964}