Skip to content

Commit

Permalink
Initialise on-chain StorageVersion for pallets added after genesis (#…
Browse files Browse the repository at this point in the history
…1297)

Original PR paritytech/substrate#14641

---

Closes #109

### Problem
Quoting from the above issue:

> When adding a pallet to chain after genesis we currently don't set the
StorageVersion. So, when calling on_chain_storage_version it returns 0
while the pallet is maybe already at storage version 9 when it was added
to the chain. This could lead to issues when running migrations.

### Solution

- Create a new trait `BeforeAllRuntimeMigrations` with a single method
`fn before_all_runtime_migrations() -> Weight` trait with a noop default
implementation
- Modify `Executive` to call
`BeforeAllRuntimeMigrations::before_all_runtime_migrations` for all
pallets before running any other hooks
- Implement `BeforeAllRuntimeMigrations` in the pallet proc macro to
initialize the on-chain version to the current pallet version if the
pallet has no storage set (indicating it has been recently added to the
runtime and needs to have its version initialised).

### Other changes in this PR

- Abstracted repeated boilerplate to access the `pallet_name` in the
pallet expand proc macro.

### FAQ

#### Why create a new hook instead of adding this logic to the pallet
`pre_upgrade`?

`Executive` currently runs `COnRuntimeUpgrade` (custom migrations)
before `AllPalletsWithSystem` migrations. We need versions to be
initialized before the `COnRuntimeUpgrade` migrations are run, because
`COnRuntimeUpgrade` migrations may use the on-chain version for critical
logic. e.g. `VersionedRuntimeUpgrade` uses it to decide whether or not
to execute.

We cannot reorder `COnRuntimeUpgrade` and `AllPalletsWithSystem` so
`AllPalletsWithSystem` runs first, because `AllPalletsWithSystem` have
some logic in their `post_upgrade` hooks to verify that the on-chain
version and current pallet version match. A common use case of
`COnRuntimeUpgrade` migrations is to perform a migration which will
result in the versions matching, so if they were reordered these
`post_upgrade` checks would fail.

#### Why init the on-chain version for pallets without a current storage
version?

We must init the on-chain version for pallets even if they don't have a
defined storage version so if there is a future version bump, the
on-chain version is not automatically set to that new version without a
proper migration.

e.g. bad scenario:

1. A pallet with no 'current version' is added to the runtime
2. Later, the pallet is upgraded with the 'current version' getting set
to 1 and a migration is added to Executive Migrations to migrate the
storage from 0 to 1
    a. Runtime upgrade occurs
    b. `before_all` hook initializes the on-chain version to 1
c. `on_runtime_upgrade` of the migration executes, and sees the on-chain
version is already 1 therefore think storage is already migrated and
does not execute the storage migration
Now, on-chain version is 1 but storage is still at version 0.

By always initializing the on-chain version when the pallet is added to
the runtime we avoid that scenario.

---------

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: Bastian Köcher <git@kchr.de>
  • Loading branch information
3 people authored and ordian committed Nov 8, 2023
1 parent 63f265e commit 2ad7a43
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 41 deletions.
16 changes: 12 additions & 4 deletions substrate/frame/executive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ use frame_support::{
dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, PostDispatchInfo},
pallet_prelude::InvalidTransaction,
traits::{
EnsureInherentsAreFirst, ExecuteBlock, OffchainWorker, OnFinalize, OnIdle, OnInitialize,
OnRuntimeUpgrade,
BeforeAllRuntimeMigrations, EnsureInherentsAreFirst, ExecuteBlock, OffchainWorker,
OnFinalize, OnIdle, OnInitialize, OnRuntimeUpgrade,
},
weights::Weight,
};
Expand Down Expand Up @@ -194,6 +194,7 @@ impl<
Context: Default,
UnsignedValidator,
AllPalletsWithSystem: OnRuntimeUpgrade
+ BeforeAllRuntimeMigrations
+ OnInitialize<BlockNumberFor<System>>
+ OnIdle<BlockNumberFor<System>>
+ OnFinalize<BlockNumberFor<System>>
Expand Down Expand Up @@ -231,6 +232,7 @@ impl<
Context: Default,
UnsignedValidator,
AllPalletsWithSystem: OnRuntimeUpgrade
+ BeforeAllRuntimeMigrations
+ OnInitialize<BlockNumberFor<System>>
+ OnIdle<BlockNumberFor<System>>
+ OnFinalize<BlockNumberFor<System>>
Expand Down Expand Up @@ -364,7 +366,9 @@ where
///
/// The `checks` param determines whether to execute `pre/post_upgrade` and `try_state` hooks.
pub fn try_runtime_upgrade(checks: UpgradeCheckSelect) -> Result<Weight, TryRuntimeError> {
let weight =
let before_all_weight =
<AllPalletsWithSystem as BeforeAllRuntimeMigrations>::before_all_runtime_migrations();
let try_on_runtime_upgrade_weight =
<(COnRuntimeUpgrade, AllPalletsWithSystem) as OnRuntimeUpgrade>::try_on_runtime_upgrade(
checks.pre_and_post(),
)?;
Expand All @@ -385,7 +389,7 @@ where
)?;
}

Ok(weight)
Ok(before_all_weight.saturating_add(try_on_runtime_upgrade_weight))
}

/// Logs the result of trying to decode the entire state.
Expand Down Expand Up @@ -429,6 +433,7 @@ impl<
Context: Default,
UnsignedValidator,
AllPalletsWithSystem: OnRuntimeUpgrade
+ BeforeAllRuntimeMigrations
+ OnInitialize<BlockNumberFor<System>>
+ OnIdle<BlockNumberFor<System>>
+ OnFinalize<BlockNumberFor<System>>
Expand All @@ -445,7 +450,10 @@ where
{
/// Execute all `OnRuntimeUpgrade` of this runtime, and return the aggregate weight.
pub fn execute_on_runtime_upgrade() -> Weight {
let before_all_weight =
<AllPalletsWithSystem as BeforeAllRuntimeMigrations>::before_all_runtime_migrations();
<(COnRuntimeUpgrade, AllPalletsWithSystem) as OnRuntimeUpgrade>::on_runtime_upgrade()
.saturating_add(before_all_weight)
}

/// Start the execution of a particular block.
Expand Down
93 changes: 64 additions & 29 deletions substrate/frame/support/procedural/src/pallet/expand/hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,38 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream {
let type_use_gen = &def.type_use_generics(span);
let pallet_ident = &def.pallet_struct.pallet;
let frame_system = &def.frame_system;
let pallet_name = quote::quote! {
<
<T as #frame_system::Config>::PalletInfo
as
#frame_support::traits::PalletInfo
>::name::<Self>().unwrap_or("<unknown pallet name>")
};

let initialize_on_chain_storage_version = if let Some(current_version) =
&def.pallet_struct.storage_version
{
quote::quote! {
#frame_support::__private::log::info!(
target: #frame_support::LOG_TARGET,
"🐥 New pallet {:?} detected in the runtime. Initializing the on-chain storage version to match the storage version defined in the pallet: {:?}",
#pallet_name,
#current_version
);
#current_version.put::<Self>();
}
} else {
quote::quote! {
let default_version = #frame_support::traits::StorageVersion::new(0);
#frame_support::__private::log::info!(
target: #frame_support::LOG_TARGET,
"🐥 New pallet {:?} detected in the runtime. The pallet has no defined storage version, so the on-chain version is being initialized to {:?}.",
#pallet_name,
default_version
);
default_version.put::<Self>();
}
};

let log_runtime_upgrade = if has_runtime_upgrade {
// a migration is defined here.
Expand All @@ -42,7 +74,7 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream {
target: #frame_support::LOG_TARGET,
"⚠️ {} declares internal migrations (which *might* execute). \
On-chain `{:?}` vs current storage version `{:?}`",
pallet_name,
#pallet_name,
<Self as #frame_support::traits::GetStorageVersion>::on_chain_storage_version(),
<Self as #frame_support::traits::GetStorageVersion>::current_storage_version(),
);
Expand All @@ -53,7 +85,7 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream {
#frame_support::__private::log::debug!(
target: #frame_support::LOG_TARGET,
"✅ no migration for {}",
pallet_name,
#pallet_name,
);
}
};
Expand All @@ -78,16 +110,10 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream {
let current_version = <Self as #frame_support::traits::GetStorageVersion>::current_storage_version();

if on_chain_version != current_version {
let pallet_name = <
<T as #frame_system::Config>::PalletInfo
as
#frame_support::traits::PalletInfo
>::name::<Self>().unwrap_or("<unknown pallet name>");

#frame_support::__private::log::error!(
target: #frame_support::LOG_TARGET,
"{}: On chain storage version {:?} doesn't match current storage version {:?}.",
pallet_name,
#pallet_name,
on_chain_version,
current_version,
);
Expand All @@ -100,17 +126,11 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream {
let on_chain_version = <Self as #frame_support::traits::GetStorageVersion>::on_chain_storage_version();

if on_chain_version != #frame_support::traits::StorageVersion::new(0) {
let pallet_name = <
<T as #frame_system::Config>::PalletInfo
as
#frame_support::traits::PalletInfo
>::name::<Self>().unwrap_or("<unknown pallet name>");

#frame_support::__private::log::error!(
target: #frame_support::LOG_TARGET,
"{}: On chain storage version {:?} is set to non zero, \
while the pallet is missing the `#[pallet::storage_version(VERSION)]` attribute.",
pallet_name,
#pallet_name,
on_chain_version,
);

Expand Down Expand Up @@ -173,6 +193,32 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream {
}
}

impl<#type_impl_gen>
#frame_support::traits::BeforeAllRuntimeMigrations
for #pallet_ident<#type_use_gen> #where_clause
{
fn before_all_runtime_migrations() -> #frame_support::weights::Weight {
use #frame_support::traits::{Get, PalletInfoAccess};
use #frame_support::__private::hashing::twox_128;
use #frame_support::storage::unhashed::contains_prefixed_key;
#frame_support::__private::sp_tracing::enter_span!(
#frame_support::__private::sp_tracing::trace_span!("before_all")
);

// Check if the pallet has any keys set, including the storage version. If there are
// no keys set, the pallet was just added to the runtime and needs to have its
// version initialized.
let pallet_hashed_prefix = <Self as PalletInfoAccess>::name_hash();
let exists = contains_prefixed_key(&pallet_hashed_prefix);
if !exists {
#initialize_on_chain_storage_version
<T as #frame_system::Config>::DbWeight::get().reads_writes(1, 1)
} else {
<T as #frame_system::Config>::DbWeight::get().reads(1)
}
}
}

impl<#type_impl_gen>
#frame_support::traits::OnRuntimeUpgrade
for #pallet_ident<#type_use_gen> #where_clause
Expand All @@ -183,11 +229,6 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream {
);

// log info about the upgrade.
let pallet_name = <
<T as #frame_system::Config>::PalletInfo
as
#frame_support::traits::PalletInfo
>::name::<Self>().unwrap_or("<unknown pallet name>");
#log_runtime_upgrade

<
Expand Down Expand Up @@ -258,16 +299,10 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream {
n: #frame_system::pallet_prelude::BlockNumberFor::<T>,
_s: #frame_support::traits::TryStateSelect
) -> Result<(), #frame_support::sp_runtime::TryRuntimeError> {
let pallet_name = <
<T as #frame_system::Config>::PalletInfo
as
#frame_support::traits::PalletInfo
>::name::<Self>().expect("No name found for the pallet! This usually means that the pallet wasn't added to `construct_runtime!`.");

#frame_support::__private::log::info!(
target: #frame_support::LOG_TARGET,
"🩺 Running {:?} try-state checks",
pallet_name,
#pallet_name,
);
<
Self as #frame_support::traits::Hooks<
Expand All @@ -277,7 +312,7 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream {
#frame_support::__private::log::error!(
target: #frame_support::LOG_TARGET,
"❌ {:?} try_state checks failed: {:?}",
pallet_name,
#pallet_name,
err
);

Expand Down
2 changes: 1 addition & 1 deletion substrate/frame/support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub mod __private {
pub use sp_core::{OpaqueMetadata, Void};
pub use sp_core_hashing_proc_macro;
pub use sp_inherents;
pub use sp_io::{self, storage::root as storage_root};
pub use sp_io::{self, hashing, storage::root as storage_root};
pub use sp_metadata_ir as metadata_ir;
#[cfg(feature = "std")]
pub use sp_runtime::{bounded_btree_map, bounded_vec};
Expand Down
4 changes: 2 additions & 2 deletions substrate/frame/support/src/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ impl<
let on_chain_version = Pallet::on_chain_storage_version();
if on_chain_version == FROM {
log::info!(
"🚚 Pallet {:?} migrating storage version from {:?} to {:?}.",
"🚚 Pallet {:?} VersionedMigration migrating storage version from {:?} to {:?}.",
Pallet::name(),
FROM,
TO
Expand All @@ -134,7 +134,7 @@ impl<
weight.saturating_add(DbWeight::get().reads_writes(1, 1))
} else {
log::warn!(
"🚚 Pallet {:?} migration {}->{} can be removed; on-chain is already at {:?}.",
"🚚 Pallet {:?} VersionedMigration migration {}->{} can be removed; on-chain is already at {:?}.",
Pallet::name(),
FROM,
TO,
Expand Down
4 changes: 2 additions & 2 deletions substrate/frame/support/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ mod hooks;
#[allow(deprecated)]
pub use hooks::GenesisBuild;
pub use hooks::{
BuildGenesisConfig, Hooks, IntegrityTest, OnFinalize, OnGenesis, OnIdle, OnInitialize,
OnRuntimeUpgrade, OnTimestampSet,
BeforeAllRuntimeMigrations, BuildGenesisConfig, Hooks, IntegrityTest, OnFinalize, OnGenesis,
OnIdle, OnInitialize, OnRuntimeUpgrade, OnTimestampSet,
};

pub mod schedule;
Expand Down
27 changes: 27 additions & 0 deletions substrate/frame/support/src/traits/hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,19 @@ pub trait OnGenesis {
fn on_genesis() {}
}

/// Implemented by pallets, allows defining logic to run prior to any [`OnRuntimeUpgrade`] logic.
///
/// This hook is intended to be used internally in FRAME and not be exposed to FRAME developers.
///
/// It is defined as a seperate trait from [`OnRuntimeUpgrade`] precisely to not pollute the public
/// API.
pub trait BeforeAllRuntimeMigrations {
/// Something that should happen before runtime migrations are executed.
fn before_all_runtime_migrations() -> Weight {
Weight::zero()
}
}

/// See [`Hooks::on_runtime_upgrade`].
pub trait OnRuntimeUpgrade {
/// See [`Hooks::on_runtime_upgrade`].
Expand Down Expand Up @@ -150,10 +163,24 @@ pub trait OnRuntimeUpgrade {
}
}

#[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))]
impl BeforeAllRuntimeMigrations for Tuple {
/// Implements the default behavior of
/// [`BeforeAllRuntimeMigrations::before_all_runtime_migrations`] for tuples.
fn before_all_runtime_migrations() -> Weight {
let mut weight = Weight::zero();
for_tuples!( #( weight = weight.saturating_add(Tuple::before_all_runtime_migrations()); )* );
weight
}
}

#[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))]
impl OnRuntimeUpgrade for Tuple {
/// Implements the default behavior of [`OnRuntimeUpgrade::on_runtime_upgrade`] for tuples.
fn on_runtime_upgrade() -> Weight {
let mut weight = Weight::zero();
for_tuples!( #( weight = weight.saturating_add(Tuple::on_runtime_upgrade()); )* );
Expand Down
17 changes: 17 additions & 0 deletions substrate/frame/support/src/traits/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,23 @@ impl StorageVersion {

crate::storage::unhashed::get_or_default(&key)
}

/// Returns if the storage version key for the given pallet exists in storage.
///
/// See [`STORAGE_VERSION_STORAGE_KEY_POSTFIX`] on how this key is built.
///
/// # Panics
///
/// This function will panic iff `Pallet` can not be found by `PalletInfo`.
/// In a runtime that is put together using
/// [`construct_runtime!`](crate::construct_runtime) this should never happen.
///
/// It will also panic if this function isn't executed in an externalities
/// provided environment.
pub fn exists<P: PalletInfoAccess>() -> bool {
let key = Self::storage_key::<P>();
crate::storage::unhashed::exists(&key)
}
}

impl PartialEq<u16> for StorageVersion {
Expand Down
Loading

0 comments on commit 2ad7a43

Please sign in to comment.