From 5c2df94f70247488881d635e9874b24d6eab0b28 Mon Sep 17 00:00:00 2001 From: Dastan <88332432+dastansam@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:43:09 +0200 Subject: [PATCH] migrations: prevent accidentally using unversioned migrations instead of `VersionedMigration` (#3835) closes #1324 #### Problem Currently, it is possible to accidentally use inner unversioned migration instead of `VersionedMigration` since both implement `OnRuntimeUpgrade`. #### Solution With this change, we make it clear that value of `Inner` is not intended to be used directly. It is achieved by bounding `Inner` to new trait `UncheckedOnRuntimeUpgrade`, which has the same interface (except `unchecked_` prefix) as `OnRuntimeUpgrade`. #### `try-runtime` functions Since developers can implement `try-runtime` for `Inner` value in `VersionedMigration` and have custom logic for it, I added the same `try-runtime` functions to `UncheckedOnRuntimeUpgrade`. I looked for a ways to not duplicate functions, but couldn't find anything that doesn't significantly change the codebase. So I would appreciate If you have any suggestions to improve this cc @liamaharon @xlc polkadot address: 16FqwPZ8GRC5U5D4Fu7W33nA55ZXzXGWHwmbnE1eT6pxuqcT --------- Co-authored-by: Liam Aharon --- cumulus/pallets/xcmp-queue/src/migration.rs | 9 +- .../common/src/assigned_slots/migration.rs | 5 +- .../common/src/paras_registrar/migration.rs | 4 +- .../src/assigner_on_demand/migration.rs | 8 +- .../src/configuration/migration/v10.rs | 10 +- .../src/configuration/migration/v11.rs | 8 +- .../src/configuration/migration/v12.rs | 4 +- .../parachains/src/inclusion/migration.rs | 10 +- .../parachains/src/scheduler/migration.rs | 9 +- polkadot/xcm/pallet-xcm/src/migration.rs | 4 +- prdoc/pr_3835.prdoc | 54 +++++ .../single-block-migrations/src/lib.rs | 24 +-- .../src/migrations/v1.rs | 196 ++++++++---------- substrate/frame/grandpa/src/migrations/v5.rs | 15 +- substrate/frame/identity/src/migration.rs | 6 +- .../frame/nomination-pools/src/migration.rs | 8 +- substrate/frame/society/src/migrations.rs | 4 +- substrate/frame/support/src/migrations.rs | 22 +- substrate/frame/support/src/traits.rs | 2 +- substrate/frame/support/src/traits/hooks.rs | 28 ++- .../support/test/tests/versioned_migration.rs | 8 +- substrate/frame/uniques/src/migration.rs | 8 +- 22 files changed, 257 insertions(+), 189 deletions(-) create mode 100644 prdoc/pr_3835.prdoc diff --git a/cumulus/pallets/xcmp-queue/src/migration.rs b/cumulus/pallets/xcmp-queue/src/migration.rs index c7fa61a3e3f05..1702cd70bc2fb 100644 --- a/cumulus/pallets/xcmp-queue/src/migration.rs +++ b/cumulus/pallets/xcmp-queue/src/migration.rs @@ -20,7 +20,7 @@ use crate::{Config, OverweightIndex, Pallet, QueueConfig, QueueConfigData, DEFAU use cumulus_primitives_core::XcmpMessageFormat; use frame_support::{ pallet_prelude::*, - traits::{EnqueueMessage, OnRuntimeUpgrade, StorageVersion}, + traits::{EnqueueMessage, StorageVersion, UncheckedOnRuntimeUpgrade}, weights::{constants::WEIGHT_REF_TIME_PER_MILLIS, Weight}, }; @@ -96,7 +96,7 @@ pub mod v2 { /// 2D weights). pub struct UncheckedMigrationToV2(PhantomData); - impl OnRuntimeUpgrade for UncheckedMigrationToV2 { + impl UncheckedOnRuntimeUpgrade for UncheckedMigrationToV2 { #[allow(deprecated)] fn on_runtime_upgrade() -> Weight { let translate = |pre: v1::QueueConfigData| -> v2::QueueConfigData { @@ -187,7 +187,7 @@ pub mod v3 { /// Migrates the pallet storage to v3. pub struct UncheckedMigrationToV3(PhantomData); - impl OnRuntimeUpgrade for UncheckedMigrationToV3 { + impl UncheckedOnRuntimeUpgrade for UncheckedMigrationToV3 { fn on_runtime_upgrade() -> Weight { #[frame_support::storage_alias] type Overweight = @@ -266,7 +266,7 @@ pub mod v4 { /// thresholds to at least the default values. pub struct UncheckedMigrationToV4(PhantomData); - impl OnRuntimeUpgrade for UncheckedMigrationToV4 { + impl UncheckedOnRuntimeUpgrade for UncheckedMigrationToV4 { fn on_runtime_upgrade() -> Weight { let translate = |pre: v2::QueueConfigData| -> QueueConfigData { let pre_default = v2::QueueConfigData::default(); @@ -315,6 +315,7 @@ pub mod v4 { mod tests { use super::*; use crate::mock::{new_test_ext, Test}; + use frame_support::traits::OnRuntimeUpgrade; #[test] #[allow(deprecated)] diff --git a/polkadot/runtime/common/src/assigned_slots/migration.rs b/polkadot/runtime/common/src/assigned_slots/migration.rs index def6bad692a23..7e582dfa59635 100644 --- a/polkadot/runtime/common/src/assigned_slots/migration.rs +++ b/polkadot/runtime/common/src/assigned_slots/migration.rs @@ -15,7 +15,7 @@ // along with Polkadot. If not, see . use super::{Config, MaxPermanentSlots, MaxTemporarySlots, Pallet, LOG_TARGET}; -use frame_support::traits::{Get, GetStorageVersion, OnRuntimeUpgrade}; +use frame_support::traits::{Get, GetStorageVersion, UncheckedOnRuntimeUpgrade}; #[cfg(feature = "try-runtime")] use frame_support::ensure; @@ -23,10 +23,9 @@ use frame_support::ensure; use sp_std::vec::Vec; pub mod v1 { - use super::*; pub struct VersionUncheckedMigrateToV1(sp_std::marker::PhantomData); - impl OnRuntimeUpgrade for VersionUncheckedMigrateToV1 { + impl UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateToV1 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { let on_chain_version = Pallet::::on_chain_storage_version(); diff --git a/polkadot/runtime/common/src/paras_registrar/migration.rs b/polkadot/runtime/common/src/paras_registrar/migration.rs index f977674a1e4e2..18bb6bbfb559a 100644 --- a/polkadot/runtime/common/src/paras_registrar/migration.rs +++ b/polkadot/runtime/common/src/paras_registrar/migration.rs @@ -15,7 +15,7 @@ // along with Polkadot. If not, see . use super::*; -use frame_support::traits::{Contains, OnRuntimeUpgrade}; +use frame_support::traits::{Contains, UncheckedOnRuntimeUpgrade}; #[derive(Encode, Decode)] pub struct ParaInfoV1 { @@ -27,7 +27,7 @@ pub struct ParaInfoV1 { pub struct VersionUncheckedMigrateToV1( sp_std::marker::PhantomData<(T, UnlockParaIds)>, ); -impl> OnRuntimeUpgrade +impl> UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateToV1 { fn on_runtime_upgrade() -> Weight { diff --git a/polkadot/runtime/parachains/src/assigner_on_demand/migration.rs b/polkadot/runtime/parachains/src/assigner_on_demand/migration.rs index 5071653377d49..8589ddc292bdd 100644 --- a/polkadot/runtime/parachains/src/assigner_on_demand/migration.rs +++ b/polkadot/runtime/parachains/src/assigner_on_demand/migration.rs @@ -18,7 +18,7 @@ use super::*; use frame_support::{ migrations::VersionedMigration, pallet_prelude::ValueQuery, storage_alias, - traits::OnRuntimeUpgrade, weights::Weight, + traits::UncheckedOnRuntimeUpgrade, weights::Weight, }; mod v0 { @@ -51,7 +51,7 @@ mod v1 { /// Migration to V1 pub struct UncheckedMigrateToV1(sp_std::marker::PhantomData); - impl OnRuntimeUpgrade for UncheckedMigrateToV1 { + impl UncheckedOnRuntimeUpgrade for UncheckedMigrateToV1 { fn on_runtime_upgrade() -> Weight { let mut weight: Weight = Weight::zero(); @@ -141,7 +141,7 @@ pub type MigrateV0ToV1 = VersionedMigration< #[cfg(test)] mod tests { - use super::{v0, v1, OnRuntimeUpgrade, Weight}; + use super::{v0, v1, UncheckedOnRuntimeUpgrade, Weight}; use crate::mock::{new_test_ext, MockGenesisConfig, OnDemandAssigner, Test}; use primitives::Id as ParaId; @@ -163,7 +163,7 @@ mod tests { // For tests, db weight is zero. assert_eq!( - as OnRuntimeUpgrade>::on_runtime_upgrade(), + as UncheckedOnRuntimeUpgrade>::on_runtime_upgrade(), Weight::zero() ); diff --git a/polkadot/runtime/parachains/src/configuration/migration/v10.rs b/polkadot/runtime/parachains/src/configuration/migration/v10.rs index 3c8d6084ace72..fa72c357d7dab 100644 --- a/polkadot/runtime/parachains/src/configuration/migration/v10.rs +++ b/polkadot/runtime/parachains/src/configuration/migration/v10.rs @@ -17,7 +17,11 @@ //! A module that is responsible for migration of storage. use crate::configuration::{Config, Pallet}; -use frame_support::{pallet_prelude::*, traits::Defensive, weights::Weight}; +use frame_support::{ + pallet_prelude::*, + traits::{Defensive, UncheckedOnRuntimeUpgrade}, + weights::Weight, +}; use frame_system::pallet_prelude::BlockNumberFor; use primitives::{ AsyncBackingParams, Balance, ExecutorParams, NodeFeatures, SessionIndex, @@ -26,8 +30,6 @@ use primitives::{ use sp_runtime::Perbill; use sp_std::vec::Vec; -use frame_support::traits::OnRuntimeUpgrade; - use super::v9::V9HostConfiguration; // All configuration of the runtime with respect to paras. #[derive(Clone, Encode, PartialEq, Decode, Debug)] @@ -163,7 +165,7 @@ mod v10 { } pub struct VersionUncheckedMigrateToV10(sp_std::marker::PhantomData); -impl OnRuntimeUpgrade for VersionUncheckedMigrateToV10 { +impl UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateToV10 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { log::trace!(target: crate::configuration::LOG_TARGET, "Running pre_upgrade() for HostConfiguration MigrateToV10"); diff --git a/polkadot/runtime/parachains/src/configuration/migration/v11.rs b/polkadot/runtime/parachains/src/configuration/migration/v11.rs index 7ed9d08688555..65656e8d7c065 100644 --- a/polkadot/runtime/parachains/src/configuration/migration/v11.rs +++ b/polkadot/runtime/parachains/src/configuration/migration/v11.rs @@ -18,7 +18,10 @@ use crate::configuration::{self, Config, Pallet}; use frame_support::{ - migrations::VersionedMigration, pallet_prelude::*, traits::Defensive, weights::Weight, + migrations::VersionedMigration, + pallet_prelude::*, + traits::{Defensive, UncheckedOnRuntimeUpgrade}, + weights::Weight, }; use frame_system::pallet_prelude::BlockNumberFor; use primitives::{ @@ -27,7 +30,6 @@ use primitives::{ }; use sp_std::vec::Vec; -use frame_support::traits::OnRuntimeUpgrade; use polkadot_core_primitives::Balance; use sp_arithmetic::Perbill; @@ -176,7 +178,7 @@ pub type MigrateToV11 = VersionedMigration< >; pub struct UncheckedMigrateToV11(sp_std::marker::PhantomData); -impl OnRuntimeUpgrade for UncheckedMigrateToV11 { +impl UncheckedOnRuntimeUpgrade for UncheckedMigrateToV11 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { log::trace!(target: crate::configuration::LOG_TARGET, "Running pre_upgrade() for HostConfiguration MigrateToV11"); diff --git a/polkadot/runtime/parachains/src/configuration/migration/v12.rs b/polkadot/runtime/parachains/src/configuration/migration/v12.rs index 4295a79893e8e..69bacc83d0446 100644 --- a/polkadot/runtime/parachains/src/configuration/migration/v12.rs +++ b/polkadot/runtime/parachains/src/configuration/migration/v12.rs @@ -20,7 +20,7 @@ use crate::configuration::{self, migration::v11::V11HostConfiguration, Config, P use frame_support::{ migrations::VersionedMigration, pallet_prelude::*, - traits::{Defensive, OnRuntimeUpgrade}, + traits::{Defensive, UncheckedOnRuntimeUpgrade}, }; use frame_system::pallet_prelude::BlockNumberFor; use primitives::vstaging::SchedulerParams; @@ -70,7 +70,7 @@ pub type MigrateToV12 = VersionedMigration< pub struct UncheckedMigrateToV12(sp_std::marker::PhantomData); -impl OnRuntimeUpgrade for UncheckedMigrateToV12 { +impl UncheckedOnRuntimeUpgrade for UncheckedMigrateToV12 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { log::trace!(target: crate::configuration::LOG_TARGET, "Running pre_upgrade() for HostConfiguration MigrateToV12"); diff --git a/polkadot/runtime/parachains/src/inclusion/migration.rs b/polkadot/runtime/parachains/src/inclusion/migration.rs index 1e63b209f4e78..5f35680ee694c 100644 --- a/polkadot/runtime/parachains/src/inclusion/migration.rs +++ b/polkadot/runtime/parachains/src/inclusion/migration.rs @@ -73,7 +73,7 @@ mod v1 { CandidatePendingAvailability as V1CandidatePendingAvailability, Config, Pallet, PendingAvailability as V1PendingAvailability, }; - use frame_support::{traits::OnRuntimeUpgrade, weights::Weight}; + use frame_support::{traits::UncheckedOnRuntimeUpgrade, weights::Weight}; use sp_core::Get; use sp_std::{collections::vec_deque::VecDeque, vec::Vec}; @@ -87,7 +87,7 @@ mod v1 { pub struct VersionUncheckedMigrateToV1(sp_std::marker::PhantomData); - impl OnRuntimeUpgrade for VersionUncheckedMigrateToV1 { + impl UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateToV1 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { log::trace!(target: crate::inclusion::LOG_TARGET, "Running pre_upgrade() for inclusion MigrateToV1"); @@ -216,7 +216,7 @@ mod tests { }, mock::{new_test_ext, MockGenesisConfig, Test}, }; - use frame_support::traits::OnRuntimeUpgrade; + use frame_support::traits::UncheckedOnRuntimeUpgrade; use primitives::{AvailabilityBitfield, Id as ParaId}; use test_helpers::{dummy_candidate_commitments, dummy_candidate_descriptor, dummy_hash}; @@ -225,7 +225,7 @@ mod tests { new_test_ext(MockGenesisConfig::default()).execute_with(|| { // No data to migrate. assert_eq!( - as OnRuntimeUpgrade>::on_runtime_upgrade(), + as UncheckedOnRuntimeUpgrade>::on_runtime_upgrade(), Weight::zero() ); assert!(V1PendingAvailability::::iter().next().is_none()); @@ -299,7 +299,7 @@ mod tests { // For tests, db weight is zero. assert_eq!( - as OnRuntimeUpgrade>::on_runtime_upgrade(), + as UncheckedOnRuntimeUpgrade>::on_runtime_upgrade(), Weight::zero() ); diff --git a/polkadot/runtime/parachains/src/scheduler/migration.rs b/polkadot/runtime/parachains/src/scheduler/migration.rs index c47fbab046fec..b030940fb41da 100644 --- a/polkadot/runtime/parachains/src/scheduler/migration.rs +++ b/polkadot/runtime/parachains/src/scheduler/migration.rs @@ -19,7 +19,7 @@ use super::*; use frame_support::{ migrations::VersionedMigration, pallet_prelude::ValueQuery, storage_alias, - traits::OnRuntimeUpgrade, weights::Weight, + traits::UncheckedOnRuntimeUpgrade, weights::Weight, }; /// Old/legacy assignment representation (v0). @@ -105,7 +105,8 @@ mod v0 { // - Assignments only consist of `ParaId`, `Assignment` is a concrete type (Same as V0Assignment). mod v1 { use frame_support::{ - pallet_prelude::ValueQuery, storage_alias, traits::OnRuntimeUpgrade, weights::Weight, + pallet_prelude::ValueQuery, storage_alias, traits::UncheckedOnRuntimeUpgrade, + weights::Weight, }; use frame_system::pallet_prelude::BlockNumberFor; @@ -164,7 +165,7 @@ mod v1 { /// Migration to V1 pub struct UncheckedMigrateToV1(sp_std::marker::PhantomData); - impl OnRuntimeUpgrade for UncheckedMigrateToV1 { + impl UncheckedOnRuntimeUpgrade for UncheckedMigrateToV1 { fn on_runtime_upgrade() -> Weight { let mut weight: Weight = Weight::zero(); @@ -302,7 +303,7 @@ mod v2 { /// Migration to V2 pub struct UncheckedMigrateToV2(sp_std::marker::PhantomData); - impl OnRuntimeUpgrade for UncheckedMigrateToV2 { + impl UncheckedOnRuntimeUpgrade for UncheckedMigrateToV2 { fn on_runtime_upgrade() -> Weight { let mut weight: Weight = Weight::zero(); diff --git a/polkadot/xcm/pallet-xcm/src/migration.rs b/polkadot/xcm/pallet-xcm/src/migration.rs index 018436aa3c93a..b157e6b5c3d5f 100644 --- a/polkadot/xcm/pallet-xcm/src/migration.rs +++ b/polkadot/xcm/pallet-xcm/src/migration.rs @@ -19,7 +19,7 @@ use crate::{ }; use frame_support::{ pallet_prelude::*, - traits::{OnRuntimeUpgrade, StorageVersion}, + traits::{OnRuntimeUpgrade, StorageVersion, UncheckedOnRuntimeUpgrade}, weights::Weight, }; @@ -35,7 +35,7 @@ pub mod v1 { /// /// Use experimental [`MigrateToV1`] instead. pub struct VersionUncheckedMigrateToV1(sp_std::marker::PhantomData); - impl OnRuntimeUpgrade for VersionUncheckedMigrateToV1 { + impl UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateToV1 { fn on_runtime_upgrade() -> Weight { let mut weight = T::DbWeight::get().reads(1); diff --git a/prdoc/pr_3835.prdoc b/prdoc/pr_3835.prdoc new file mode 100644 index 0000000000000..d2f49f8fc1161 --- /dev/null +++ b/prdoc/pr_3835.prdoc @@ -0,0 +1,54 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "migrations: prevent accidentally using inner unversioned migration instead of `VersionedMigration`" + +doc: + - audience: Runtime Dev + description: | + Currently, it is possible to accidentally use inner unversioned migration instead of `VersionedMigration` + since both implement `OnRuntimeUpgrade`. With this change, we make it clear that `Inner` is not intended + to be used directly. It is achieved by bounding `Inner` to new trait `UncheckedOnRuntimeUpgrade`, which + has the same interface as `OnRuntimeUpgrade`, but can not be used directly for runtime upgrade migrations. + + This change will break all existing migrations passed to `VersionedMigration`. Developers should simply change + those migrations to implement `UncheckedOnRuntimeUpgrade` instead of `OnRuntimeUpgrade`. + + Example: + + ``` + --- a/path/to/migration.rs + +++ b/path/to/migration.rs + @@ -1,7 +1,7 @@ + -impl OnRuntimeUpgrade for MigrateVNToVM { + +impl UncheckedOnRuntimeUpgrade for MigrateVNToVM { + fn on_runtime_upgrade() -> Weight { + // Migration logic here + // Adjust the migration logic if necessary to align with the expectations + // of new `UncheckedOnRuntimeUpgrade` trait. + 0 + } + } + ``` + +crates: + - name: "pallet-example-single-block-migrations" + bump: "major" + - name: "pallet-xcm" + bump: "major" + - name: "pallet-grandpa" + bump: "major" + - name: "pallet-identity" + bump: "major" + - name: "pallet-nomination-pools" + bump: "major" + - name: "pallet-society" + bump: "major" + - name: "frame-support" + bump: "major" + - name: "pallet-uniques" + bump: "major" + - name: "polkadot-runtime-parachains" + bump: "major" + - name: "polkadot-runtime-common" + bump: "major" diff --git a/substrate/frame/examples/single-block-migrations/src/lib.rs b/substrate/frame/examples/single-block-migrations/src/lib.rs index b36d52622678d..411537aa8c65f 100644 --- a/substrate/frame/examples/single-block-migrations/src/lib.rs +++ b/substrate/frame/examples/single-block-migrations/src/lib.rs @@ -89,7 +89,7 @@ //! //! See the migration source code for detailed comments. //! -//! To keep the migration logic organised, it is split across additional modules: +//! Here's a brief overview of modules and types defined in `v1.rs`: //! //! ### `mod v0` //! @@ -98,28 +98,29 @@ //! //! This allows reading the old v0 value from storage during the migration. //! -//! ### `mod version_unchecked` +//! ### `InnerMigrateV0ToV1` //! //! Here we define our raw migration logic, -//! `version_unchecked::MigrateV0ToV1` which implements the [`OnRuntimeUpgrade`] trait. +//! `InnerMigrateV0ToV1` which implements the [`UncheckedOnRuntimeUpgrade`] trait. //! -//! Importantly, it is kept in a private module so that it cannot be accidentally used in a runtime. +//! #### Why [`UncheckedOnRuntimeUpgrade`]? //! -//! Private modules cannot be referenced in docs, so please read the code directly. +//! Otherwise, we would have two implementations of [`OnRuntimeUpgrade`] which could be confusing, +//! and may lead to accidentally using the wrong one. //! //! #### Standalone Struct or Pallet Hook? //! //! Note that the storage migration logic is attached to a standalone struct implementing -//! [`OnRuntimeUpgrade`], rather than implementing the +//! [`UncheckedOnRuntimeUpgrade`], rather than implementing the //! [`Hooks::on_runtime_upgrade`](frame_support::traits::Hooks::on_runtime_upgrade) hook directly on //! the pallet. The pallet hook is better suited for special types of logic that need to execute on //! every runtime upgrade, but not so much for one-off storage migrations. //! -//! ### `pub mod versioned` +//! ### `MigrateV0ToV1` //! -//! Here, `version_unchecked::MigrateV0ToV1` is wrapped in a +//! Here, `InnerMigrateV0ToV1` is wrapped in a //! [`VersionedMigration`] to define -//! [`versioned::MigrateV0ToV1`](crate::migrations::v1::versioned::MigrateV0ToV1), which may be used +//! [`MigrateV0ToV1`](crate::migrations::v1::MigrateV0ToV1), which may be used //! in runtimes. //! //! Using [`VersionedMigration`] ensures that @@ -128,8 +129,6 @@ //! - Reads and writes from checking and setting the on-chain storage version are accounted for in //! the final [`Weight`](frame_support::weights::Weight) //! -//! This is the only public module exported from `v1`. -//! //! ### `mod test` //! //! Here basic unit tests are defined for the migration. @@ -142,7 +141,8 @@ //! [`VersionedMigration`]: frame_support::migrations::VersionedMigration //! [`GetStorageVersion`]: frame_support::traits::GetStorageVersion //! [`OnRuntimeUpgrade`]: frame_support::traits::OnRuntimeUpgrade -//! [`MigrateV0ToV1`]: crate::migrations::v1::versioned::MigrationV0ToV1 +//! [`UncheckedOnRuntimeUpgrade`]: frame_support::traits::UncheckedOnRuntimeUpgrade +//! [`MigrateV0ToV1`]: crate::migrations::v1::MigrateV0ToV1 // We make sure this pallet uses `no_std` for compiling to Wasm. #![cfg_attr(not(feature = "std"), no_std)] diff --git a/substrate/frame/examples/single-block-migrations/src/migrations/v1.rs b/substrate/frame/examples/single-block-migrations/src/migrations/v1.rs index b46640a320207..18ef4e72cc4f5 100644 --- a/substrate/frame/examples/single-block-migrations/src/migrations/v1.rs +++ b/substrate/frame/examples/single-block-migrations/src/migrations/v1.rs @@ -17,7 +17,7 @@ use frame_support::{ storage_alias, - traits::{Get, OnRuntimeUpgrade}, + traits::{Get, UncheckedOnRuntimeUpgrade}, }; #[cfg(feature = "try-runtime")] @@ -34,118 +34,92 @@ mod v0 { pub type Value = StorageValue, u32>; } -/// Private module containing *version unchecked* migration logic. +/// Implements [`UncheckedOnRuntimeUpgrade`], migrating the state of this pallet from V0 to V1. /// -/// Should only be used by the [`VersionedMigration`](frame_support::migrations::VersionedMigration) -/// type in this module to create something to export. +/// In V0 of the template [`crate::Value`] is just a `u32`. In V1, it has been upgraded to +/// contain the struct [`crate::CurrentAndPreviousValue`]. /// -/// The unversioned migration should be kept private so the unversioned migration cannot -/// accidentally be used in any runtimes. -/// -/// For more about this pattern of keeping items private, see -/// - -/// - -mod version_unchecked { - use super::*; +/// In this migration, update the on-chain storage for the pallet to reflect the new storage +/// layout. +pub struct InnerMigrateV0ToV1(sp_std::marker::PhantomData); + +impl UncheckedOnRuntimeUpgrade for InnerMigrateV0ToV1 { + /// Return the existing [`crate::Value`] so we can check that it was correctly set in + /// `InnerMigrateV0ToV1::post_upgrade`. + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + use codec::Encode; + + // Access the old value using the `storage_alias` type + let old_value = v0::Value::::get(); + // Return it as an encoded `Vec` + Ok(old_value.encode()) + } - /// Implements [`OnRuntimeUpgrade`], migrating the state of this pallet from V0 to V1. - /// - /// In V0 of the template [`crate::Value`] is just a `u32`. In V1, it has been upgraded to - /// contain the struct [`crate::CurrentAndPreviousValue`]. + /// Migrate the storage from V0 to V1. /// - /// In this migration, update the on-chain storage for the pallet to reflect the new storage - /// layout. - pub struct MigrateV0ToV1(sp_std::marker::PhantomData); - - impl OnRuntimeUpgrade for MigrateV0ToV1 { - /// Return the existing [`crate::Value`] so we can check that it was correctly set in - /// `version_unchecked::MigrateV0ToV1::post_upgrade`. - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { - use codec::Encode; - - // Access the old value using the `storage_alias` type - let old_value = v0::Value::::get(); - // Return it as an encoded `Vec` - Ok(old_value.encode()) - } - - /// Migrate the storage from V0 to V1. - /// - /// - If the value doesn't exist, there is nothing to do. - /// - If the value exists, it is read and then written back to storage inside a - /// [`crate::CurrentAndPreviousValue`]. - fn on_runtime_upgrade() -> frame_support::weights::Weight { - // Read the old value from storage - if let Some(old_value) = v0::Value::::take() { - // Write the new value to storage - let new = crate::CurrentAndPreviousValue { current: old_value, previous: None }; - crate::Value::::put(new); - // One read for the old value, one write for the new value - T::DbWeight::get().reads_writes(1, 1) - } else { - // One read for trying to access the old value - T::DbWeight::get().reads(1) - } + /// - If the value doesn't exist, there is nothing to do. + /// - If the value exists, it is read and then written back to storage inside a + /// [`crate::CurrentAndPreviousValue`]. + fn on_runtime_upgrade() -> frame_support::weights::Weight { + // Read the old value from storage + if let Some(old_value) = v0::Value::::take() { + // Write the new value to storage + let new = crate::CurrentAndPreviousValue { current: old_value, previous: None }; + crate::Value::::put(new); + // One read for the old value, one write for the new value + T::DbWeight::get().reads_writes(1, 1) + } else { + // One read for trying to access the old value + T::DbWeight::get().reads(1) } + } - /// Verifies the storage was migrated correctly. - /// - /// - If there was no old value, the new value should not be set. - /// - If there was an old value, the new value should be a - /// [`crate::CurrentAndPreviousValue`]. - #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { - use codec::Decode; - use frame_support::ensure; - - let maybe_old_value = Option::::decode(&mut &state[..]).map_err(|_| { - sp_runtime::TryRuntimeError::Other("Failed to decode old value from storage") - })?; - - match maybe_old_value { - Some(old_value) => { - let expected_new_value = - crate::CurrentAndPreviousValue { current: old_value, previous: None }; - let actual_new_value = crate::Value::::get(); - - ensure!(actual_new_value.is_some(), "New value not set"); - ensure!( - actual_new_value == Some(expected_new_value), - "New value not set correctly" - ); - }, - None => { - ensure!(crate::Value::::get().is_none(), "New value unexpectedly set"); - }, - }; - Ok(()) - } + /// Verifies the storage was migrated correctly. + /// + /// - If there was no old value, the new value should not be set. + /// - If there was an old value, the new value should be a [`crate::CurrentAndPreviousValue`]. + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + use codec::Decode; + use frame_support::ensure; + + let maybe_old_value = Option::::decode(&mut &state[..]).map_err(|_| { + sp_runtime::TryRuntimeError::Other("Failed to decode old value from storage") + })?; + + match maybe_old_value { + Some(old_value) => { + let expected_new_value = + crate::CurrentAndPreviousValue { current: old_value, previous: None }; + let actual_new_value = crate::Value::::get(); + + ensure!(actual_new_value.is_some(), "New value not set"); + ensure!( + actual_new_value == Some(expected_new_value), + "New value not set correctly" + ); + }, + None => { + ensure!(crate::Value::::get().is_none(), "New value unexpectedly set"); + }, + }; + Ok(()) } } -/// Public module containing *version checked* migration logic. -/// -/// This is the only module that should be exported from this module. -/// -/// See [`VersionedMigration`](frame_support::migrations::VersionedMigration) docs for more about -/// how it works. -pub mod versioned { - use super::*; - - /// `version_unchecked::MigrateV0ToV1` wrapped in a - /// [`VersionedMigration`](frame_support::migrations::VersionedMigration), which ensures that: - /// - The migration only runs once when the on-chain storage version is 0 - /// - The on-chain storage version is updated to `1` after the migration executes - /// - Reads/Writes from checking/settings the on-chain storage version are accounted for - pub type MigrateV0ToV1 = frame_support::migrations::VersionedMigration< - 0, // The migration will only execute when the on-chain storage version is 0 - 1, // The on-chain storage version will be set to 1 after the migration is complete - version_unchecked::MigrateV0ToV1, - crate::pallet::Pallet, - ::DbWeight, - >; -} +/// [`UncheckedOnRuntimeUpgrade`] implementation [`InnerMigrateV0ToV1`] wrapped in a +/// [`VersionedMigration`](frame_support::migrations::VersionedMigration), which ensures that: +/// - The migration only runs once when the on-chain storage version is 0 +/// - The on-chain storage version is updated to `1` after the migration executes +/// - Reads/Writes from checking/settings the on-chain storage version are accounted for +pub type MigrateV0ToV1 = frame_support::migrations::VersionedMigration< + 0, // The migration will only execute when the on-chain storage version is 0 + 1, // The on-chain storage version will be set to 1 after the migration is complete + InnerMigrateV0ToV1, + crate::pallet::Pallet, + ::DbWeight, +>; /// Tests for our migration. /// @@ -155,10 +129,10 @@ pub mod versioned { /// 3. The storage is in the expected state after the migration #[cfg(any(all(feature = "try-runtime", test), doc))] mod test { + use self::InnerMigrateV0ToV1; use super::*; use crate::mock::{new_test_ext, MockRuntime}; use frame_support::assert_ok; - use version_unchecked::MigrateV0ToV1; #[test] fn handles_no_existing_value() { @@ -168,16 +142,16 @@ mod test { assert!(v0::Value::::get().is_none()); // Get the pre_upgrade bytes - let bytes = match MigrateV0ToV1::::pre_upgrade() { + let bytes = match InnerMigrateV0ToV1::::pre_upgrade() { Ok(bytes) => bytes, Err(e) => panic!("pre_upgrade failed: {:?}", e), }; // Execute the migration - let weight = MigrateV0ToV1::::on_runtime_upgrade(); + let weight = InnerMigrateV0ToV1::::on_runtime_upgrade(); // Verify post_upgrade succeeds - assert_ok!(MigrateV0ToV1::::post_upgrade(bytes)); + assert_ok!(InnerMigrateV0ToV1::::post_upgrade(bytes)); // The weight should be just 1 read for trying to access the old value. assert_eq!(weight, ::DbWeight::get().reads(1)); @@ -195,16 +169,16 @@ mod test { v0::Value::::put(initial_value); // Get the pre_upgrade bytes - let bytes = match MigrateV0ToV1::::pre_upgrade() { + let bytes = match InnerMigrateV0ToV1::::pre_upgrade() { Ok(bytes) => bytes, Err(e) => panic!("pre_upgrade failed: {:?}", e), }; // Execute the migration - let weight = MigrateV0ToV1::::on_runtime_upgrade(); + let weight = InnerMigrateV0ToV1::::on_runtime_upgrade(); // Verify post_upgrade succeeds - assert_ok!(MigrateV0ToV1::::post_upgrade(bytes)); + assert_ok!(InnerMigrateV0ToV1::::post_upgrade(bytes)); // The weight used should be 1 read for the old value, and 1 write for the new // value. diff --git a/substrate/frame/grandpa/src/migrations/v5.rs b/substrate/frame/grandpa/src/migrations/v5.rs index 24cfc34104b5f..a0865a3f2bf9a 100644 --- a/substrate/frame/grandpa/src/migrations/v5.rs +++ b/substrate/frame/grandpa/src/migrations/v5.rs @@ -20,7 +20,7 @@ use codec::Decode; use frame_support::{ migrations::VersionedMigration, storage, - traits::{Get, OnRuntimeUpgrade}, + traits::{Get, UncheckedOnRuntimeUpgrade}, weights::Weight, }; use sp_consensus_grandpa::AuthorityList; @@ -36,9 +36,9 @@ fn load_authority_list() -> AuthorityList { } /// Actual implementation of [`MigrateV4ToV5`]. -pub struct MigrateImpl(PhantomData); +pub struct UncheckedMigrateImpl(PhantomData); -impl OnRuntimeUpgrade for MigrateImpl { +impl UncheckedOnRuntimeUpgrade for UncheckedMigrateImpl { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { use codec::Encode; @@ -92,5 +92,10 @@ impl OnRuntimeUpgrade for MigrateImpl { /// Migrate the storage from V4 to V5. /// /// Switches from `GRANDPA_AUTHORITIES_KEY` to a normal FRAME storage item. -pub type MigrateV4ToV5 = - VersionedMigration<4, 5, MigrateImpl, Pallet, ::DbWeight>; +pub type MigrateV4ToV5 = VersionedMigration< + 4, + 5, + UncheckedMigrateImpl, + Pallet, + ::DbWeight, +>; diff --git a/substrate/frame/identity/src/migration.rs b/substrate/frame/identity/src/migration.rs index 88ac08d1bf564..8725bfd39df14 100644 --- a/substrate/frame/identity/src/migration.rs +++ b/substrate/frame/identity/src/migration.rs @@ -16,7 +16,9 @@ //! Storage migrations for the Identity pallet. use super::*; -use frame_support::{migrations::VersionedMigration, pallet_prelude::*, traits::OnRuntimeUpgrade}; +use frame_support::{ + migrations::VersionedMigration, pallet_prelude::*, traits::UncheckedOnRuntimeUpgrade, +}; #[cfg(feature = "try-runtime")] use codec::{Decode, Encode}; @@ -66,7 +68,7 @@ pub mod v1 { /// prevent stalling a parachain by accumulating too much weight in the migration. To have an /// unlimited migration (e.g. in a chain without PoV limits), set this to `u64::MAX`. pub struct VersionUncheckedMigrateV0ToV1(PhantomData); - impl OnRuntimeUpgrade for VersionUncheckedMigrateV0ToV1 { + impl UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateV0ToV1 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, TryRuntimeError> { let identities = v0::IdentityOf::::iter().count(); diff --git a/substrate/frame/nomination-pools/src/migration.rs b/substrate/frame/nomination-pools/src/migration.rs index ca9c0874a83c4..796b310862afc 100644 --- a/substrate/frame/nomination-pools/src/migration.rs +++ b/substrate/frame/nomination-pools/src/migration.rs @@ -17,7 +17,7 @@ use super::*; use crate::log; -use frame_support::traits::OnRuntimeUpgrade; +use frame_support::traits::{OnRuntimeUpgrade, UncheckedOnRuntimeUpgrade}; use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; #[cfg(feature = "try-runtime")] @@ -132,7 +132,7 @@ pub mod v8 { } pub struct VersionUncheckedMigrateV7ToV8(sp_std::marker::PhantomData); - impl OnRuntimeUpgrade for VersionUncheckedMigrateV7ToV8 { + impl UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateV7ToV8 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, TryRuntimeError> { Ok(Vec::new()) @@ -211,7 +211,7 @@ pub(crate) mod v7 { CountedStorageMap, Twox64Concat, PoolId, V7BondedPoolInner>; pub struct VersionUncheckedMigrateV6ToV7(sp_std::marker::PhantomData); - impl OnRuntimeUpgrade for VersionUncheckedMigrateV6ToV7 { + impl UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateV6ToV7 { fn on_runtime_upgrade() -> Weight { let migrated = BondedPools::::count(); // The TVL should be the sum of all the funds that are actively staked and in the @@ -282,7 +282,7 @@ mod v6 { }) } } - impl OnRuntimeUpgrade for MigrateToV6 { + impl UncheckedOnRuntimeUpgrade for MigrateToV6 { fn on_runtime_upgrade() -> Weight { let mut success = 0u64; let mut fail = 0u64; diff --git a/substrate/frame/society/src/migrations.rs b/substrate/frame/society/src/migrations.rs index 8fd87b1163a48..7ded1f84f5823 100644 --- a/substrate/frame/society/src/migrations.rs +++ b/substrate/frame/society/src/migrations.rs @@ -19,7 +19,7 @@ use super::*; use codec::{Decode, Encode}; -use frame_support::traits::{Defensive, DefensiveOption, Instance, OnRuntimeUpgrade}; +use frame_support::traits::{Defensive, DefensiveOption, Instance, UncheckedOnRuntimeUpgrade}; #[cfg(feature = "try-runtime")] use sp_runtime::TryRuntimeError; @@ -36,7 +36,7 @@ impl< T: Config, I: Instance + 'static, PastPayouts: Get::AccountId, BalanceOf)>>, - > OnRuntimeUpgrade for VersionUncheckedMigrateToV2 + > UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateToV2 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, TryRuntimeError> { diff --git a/substrate/frame/support/src/migrations.rs b/substrate/frame/support/src/migrations.rs index 2ceab44cb16bd..b8cbcd6904814 100644 --- a/substrate/frame/support/src/migrations.rs +++ b/substrate/frame/support/src/migrations.rs @@ -41,19 +41,19 @@ use sp_std::{marker::PhantomData, vec::Vec}; /// It takes 5 type parameters: /// - `From`: The version being upgraded from. /// - `To`: The version being upgraded to. -/// - `Inner`: An implementation of `OnRuntimeUpgrade`. +/// - `Inner`: An implementation of `UncheckedOnRuntimeUpgrade`. /// - `Pallet`: The Pallet being upgraded. /// - `Weight`: The runtime's RuntimeDbWeight implementation. /// /// When a [`VersionedMigration`] `on_runtime_upgrade`, `pre_upgrade`, or `post_upgrade` method is /// called, the on-chain version of the pallet is compared to `From`. If they match, the `Inner` -/// equivalent is called and the pallets on-chain version is set to `To` after the migration. -/// Otherwise, a warning is logged notifying the developer that the upgrade was a noop and should -/// probably be removed. +/// `UncheckedOnRuntimeUpgrade` is called and the pallets on-chain version is set to `To` +/// after the migration. Otherwise, a warning is logged notifying the developer that the upgrade was +/// a noop and should probably be removed. /// -/// It is STRONGLY RECOMMENDED to write the unversioned migration logic in a private module and -/// only export the versioned migration logic to prevent accidentally using the unversioned -/// migration in any runtimes. +/// By not bounding `Inner` with `OnRuntimeUpgrade`, we prevent developers from +/// accidentally using the unchecked version of the migration in a runtime upgrade instead of +/// [`VersionedMigration`]. /// /// ### Examples /// ```ignore @@ -71,9 +71,9 @@ use sp_std::{marker::PhantomData, vec::Vec}; /// /// - https://internals.rust-lang.org/t/lang-team-minutes-private-in-public-rules/4504/40 /// mod version_unchecked { /// use super::*; -/// pub struct MigrateV5ToV6(sp_std::marker::PhantomData); -/// impl OnRuntimeUpgrade for VersionUncheckedMigrateV5ToV6 { -/// // OnRuntimeUpgrade implementation... +/// pub struct VersionUncheckedMigrateV5ToV6(sp_std::marker::PhantomData); +/// impl UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateV5ToV6 { +/// // `UncheckedOnRuntimeUpgrade` implementation... /// } /// } /// @@ -116,7 +116,7 @@ pub enum VersionedPostUpgradeData { impl< const FROM: u16, const TO: u16, - Inner: crate::traits::OnRuntimeUpgrade, + Inner: crate::traits::UncheckedOnRuntimeUpgrade, Pallet: GetStorageVersion + PalletInfoAccess, DbWeight: Get, > crate::traits::OnRuntimeUpgrade for VersionedMigration diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index 24e7e1c8a65c2..66777cef7b8e8 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -87,7 +87,7 @@ pub use hooks::GenesisBuild; pub use hooks::{ BeforeAllRuntimeMigrations, BuildGenesisConfig, Hooks, IntegrityTest, OnFinalize, OnGenesis, OnIdle, OnInitialize, OnPoll, OnRuntimeUpgrade, OnTimestampSet, PostInherents, - PostTransactions, PreInherents, + PostTransactions, PreInherents, UncheckedOnRuntimeUpgrade, }; pub mod schedule; diff --git a/substrate/frame/support/src/traits/hooks.rs b/substrate/frame/support/src/traits/hooks.rs index d83e270474581..ccccc50632866 100644 --- a/substrate/frame/support/src/traits/hooks.rs +++ b/substrate/frame/support/src/traits/hooks.rs @@ -227,6 +227,30 @@ pub trait OnRuntimeUpgrade { } } +/// This trait is intended for use within `VersionedMigration` to execute storage migrations without +/// automatic version checks. Implementations should ensure migration logic is safe and idempotent. +pub trait UncheckedOnRuntimeUpgrade { + /// Called within `VersionedMigration` to execute the actual migration. It is also + /// expected that no version checks are performed within this function. + /// + /// See also [`Hooks::on_runtime_upgrade`]. + fn on_runtime_upgrade() -> Weight { + Weight::zero() + } + + /// See [`Hooks::pre_upgrade`]. + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + Ok(Vec::new()) + } + + /// See [`Hooks::post_upgrade`]. + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { + Ok(()) + } +} + #[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] #[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] #[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] @@ -459,7 +483,9 @@ pub trait Hooks { /// ## Implementation Note: Standalone Migrations /// /// Additional migrations can be created by directly implementing [`OnRuntimeUpgrade`] on - /// structs and passing them to `Executive`. + /// structs and passing them to `Executive`. Or alternatively, by implementing + /// [`UncheckedOnRuntimeUpgrade`], passing it to [`crate::migrations::VersionedMigration`], + /// which already implements [`OnRuntimeUpgrade`]. /// /// ## Implementation Note: Pallet Versioning /// diff --git a/substrate/frame/support/test/tests/versioned_migration.rs b/substrate/frame/support/test/tests/versioned_migration.rs index 3fdfb902129ef..e7d146940cb92 100644 --- a/substrate/frame/support/test/tests/versioned_migration.rs +++ b/substrate/frame/support/test/tests/versioned_migration.rs @@ -23,7 +23,7 @@ use frame_support::{ construct_runtime, derive_impl, migrations::VersionedMigration, parameter_types, - traits::{GetStorageVersion, OnRuntimeUpgrade, StorageVersion}, + traits::{GetStorageVersion, OnRuntimeUpgrade, StorageVersion, UncheckedOnRuntimeUpgrade}, weights::constants::RocksDbWeight, }; use frame_system::Config; @@ -103,9 +103,11 @@ parameter_types! { static PostUpgradeCalledWith: Vec = Vec::new(); } -/// Implement `OnRuntimeUpgrade` for `SomeUnversionedMigration`. +/// Implement `UncheckedOnRuntimeUpgrade` for `SomeUnversionedMigration`. /// It sets SomeStorage to S, and returns a weight derived from UpgradeReads and UpgradeWrites. -impl OnRuntimeUpgrade for SomeUnversionedMigration { +impl UncheckedOnRuntimeUpgrade + for SomeUnversionedMigration +{ fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { PreUpgradeCalled::set(true); Ok(PreUpgradeReturnBytes::get().to_vec()) diff --git a/substrate/frame/uniques/src/migration.rs b/substrate/frame/uniques/src/migration.rs index ba0855a6bb657..90d44e7790d6d 100644 --- a/substrate/frame/uniques/src/migration.rs +++ b/substrate/frame/uniques/src/migration.rs @@ -18,15 +18,15 @@ //! Various pieces of common functionality. use super::*; use core::marker::PhantomData; -use frame_support::traits::{Get, OnRuntimeUpgrade}; +use frame_support::traits::{Get, UncheckedOnRuntimeUpgrade}; mod v1 { use super::*; /// Actual implementation of the storage migration. - pub struct MigrateToV1Impl(PhantomData<(T, I)>); + pub struct UncheckedMigrateToV1Impl(PhantomData<(T, I)>); - impl, I: 'static> OnRuntimeUpgrade for MigrateToV1Impl { + impl, I: 'static> UncheckedOnRuntimeUpgrade for UncheckedMigrateToV1Impl { fn on_runtime_upgrade() -> frame_support::weights::Weight { let mut count = 0; for (collection, detail) in Collection::::iter() { @@ -49,7 +49,7 @@ mod v1 { pub type MigrateV0ToV1 = frame_support::migrations::VersionedMigration< 0, 1, - v1::MigrateToV1Impl, + v1::UncheckedMigrateToV1Impl, Pallet, ::DbWeight, >;