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}