Runtime Upgrade ref docs and Single Block Migration example pallet (#1554)

Closes https://github.com/paritytech/polkadot-sdk-docs/issues/55

- Changes 'current storage version' terminology to less ambiguous
'in-code storage version' (suggestion by @ggwpez)
- Adds a new example pallet `pallet-example-single-block-migrations`
- Adds a new reference doc to replace
https://docs.substrate.io/maintain/runtime-upgrades/ (temporarily living
in the pallet while we wait for developer hub PR to merge)
- Adds documentation for the `storage_alias` macro
- Improves `trait Hooks` docs 
- Improves `trait GetStorageVersion` docs
- Update the suggested patterns for using `VersionedMigration`, so that
version unchecked migrations are never exported
- Prevents accidental usage of version unchecked migrations in runtimes

https://github.com/paritytech/substrate/pull/14421#discussion_r1255467895
- Unversioned migration code is kept inside `mod version_unchecked`,
versioned code is kept in `pub mod versioned`
- It is necessary to use modules to limit visibility because the inner
migration must be `pub`. See
https://github.com/rust-lang/rust/issues/30905 and

https://internals.rust-lang.org/t/lang-team-minutes-private-in-public-rules/4504/40
for more.

### todo

- [x] move to reference docs to proper place within sdk-docs (now that
https://github.com/paritytech/polkadot-sdk/pull/2102 is merged)
- [x] prdoc

---------

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: Juan <juangirini@gmail.com>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: command-bot <>
Co-authored-by: gupnik <nikhilgupta.iitk@gmail.com>
This commit is contained in:
Liam Aharon
2024-02-28 18:32:02 +11:00
committed by GitHub
parent 7ec0b8741b
commit 12ce4f7d04
87 changed files with 1222 additions and 369 deletions
+2 -2
View File
@@ -1178,7 +1178,7 @@ pub mod pallet_prelude {
/// # `pallet::storage_version`
///
/// Because the [`pallet::pallet`](#pallet-struct-placeholder-palletpallet-mandatory) macro
/// implements [`traits::GetStorageVersion`], the current storage version needs to be
/// implements [`traits::GetStorageVersion`], the in-code storage version needs to be
/// communicated to the macro. This can be done by using the `pallet::storage_version`
/// attribute:
///
@@ -1190,7 +1190,7 @@ pub mod pallet_prelude {
/// pub struct Pallet<T>(_);
/// ```
///
/// If not present, the current storage version is set to the default value.
/// If not present, the in-code storage version is set to the default value.
///
/// Also see [`pallet::storage_version`](`frame_support::pallet_macros::storage_version`)
///
+36 -19
View File
@@ -43,12 +43,30 @@ use sp_std::marker::PhantomData;
/// 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.
///
/// ### Examples
/// ```ignore
/// // In file defining migrations
/// pub struct VersionUncheckedMigrateV5ToV6<T>(sp_std::marker::PhantomData<T>);
/// impl<T: Config> OnRuntimeUpgrade for VersionUncheckedMigrateV5ToV6<T> {
/// // OnRuntimeUpgrade implementation...
///
/// /// Private module containing *version unchecked* migration logic.
/// ///
/// /// Should only be used by the [`VersionedMigration`] type in this module to create something to
/// /// export.
/// ///
/// /// We keep this private so the unversioned migration cannot accidentally be used in any runtimes.
/// ///
/// /// For more about this pattern of keeping items private, see
/// /// - https://github.com/rust-lang/rust/issues/30905
/// /// - https://internals.rust-lang.org/t/lang-team-minutes-private-in-public-rules/4504/40
/// mod version_unchecked {
/// use super::*;
/// pub struct MigrateV5ToV6<T>(sp_std::marker::PhantomData<T>);
/// impl<T: Config> OnRuntimeUpgrade for VersionUncheckedMigrateV5ToV6<T> {
/// // OnRuntimeUpgrade implementation...
/// }
/// }
///
/// pub type MigrateV5ToV6<T, I> =
@@ -91,7 +109,7 @@ impl<
const FROM: u16,
const TO: u16,
Inner: crate::traits::OnRuntimeUpgrade,
Pallet: GetStorageVersion<CurrentStorageVersion = StorageVersion> + PalletInfoAccess,
Pallet: GetStorageVersion<InCodeStorageVersion = StorageVersion> + PalletInfoAccess,
DbWeight: Get<RuntimeDbWeight>,
> crate::traits::OnRuntimeUpgrade for VersionedMigration<FROM, TO, Inner, Pallet, DbWeight>
{
@@ -163,25 +181,25 @@ impl<
}
}
/// Can store the current pallet version in storage.
pub trait StoreCurrentStorageVersion<T: GetStorageVersion + PalletInfoAccess> {
/// Write the current storage version to the storage.
fn store_current_storage_version();
/// Can store the in-code pallet version on-chain.
pub trait StoreInCodeStorageVersion<T: GetStorageVersion + PalletInfoAccess> {
/// Write the in-code storage version on-chain.
fn store_in_code_storage_version();
}
impl<T: GetStorageVersion<CurrentStorageVersion = StorageVersion> + PalletInfoAccess>
StoreCurrentStorageVersion<T> for StorageVersion
impl<T: GetStorageVersion<InCodeStorageVersion = StorageVersion> + PalletInfoAccess>
StoreInCodeStorageVersion<T> for StorageVersion
{
fn store_current_storage_version() {
let version = <T as GetStorageVersion>::current_storage_version();
fn store_in_code_storage_version() {
let version = <T as GetStorageVersion>::in_code_storage_version();
version.put::<T>();
}
}
impl<T: GetStorageVersion<CurrentStorageVersion = NoStorageVersionSet> + PalletInfoAccess>
StoreCurrentStorageVersion<T> for NoStorageVersionSet
impl<T: GetStorageVersion<InCodeStorageVersion = NoStorageVersionSet> + PalletInfoAccess>
StoreInCodeStorageVersion<T> for NoStorageVersionSet
{
fn store_current_storage_version() {
fn store_in_code_storage_version() {
StorageVersion::default().put::<T>();
}
}
@@ -193,7 +211,7 @@ pub trait PalletVersionToStorageVersionHelper {
impl<T: GetStorageVersion + PalletInfoAccess> PalletVersionToStorageVersionHelper for T
where
T::CurrentStorageVersion: StoreCurrentStorageVersion<T>,
T::InCodeStorageVersion: StoreInCodeStorageVersion<T>,
{
fn migrate(db_weight: &RuntimeDbWeight) -> Weight {
const PALLET_VERSION_STORAGE_KEY_POSTFIX: &[u8] = b":__PALLET_VERSION__:";
@@ -204,8 +222,7 @@ where
sp_io::storage::clear(&pallet_version_key(<T as PalletInfoAccess>::name()));
<T::CurrentStorageVersion as StoreCurrentStorageVersion<T>>::store_current_storage_version(
);
<T::InCodeStorageVersion as StoreInCodeStorageVersion<T>>::store_in_code_storage_version();
db_weight.writes(2)
}
@@ -226,7 +243,7 @@ impl PalletVersionToStorageVersionHelper for T {
/// Migrate from the `PalletVersion` struct to the new [`StorageVersion`] struct.
///
/// This will remove all `PalletVersion's` from the state and insert the current storage version.
/// This will remove all `PalletVersion's` from the state and insert the in-code storage version.
pub fn migrate_from_pallet_version_to_storage_version<
Pallets: PalletVersionToStorageVersionHelper,
>(
+41 -29
View File
@@ -90,7 +90,7 @@ impl<BlockNumber: Copy + AtLeast32BitUnsigned> OnIdle<BlockNumber> for Tuple {
///
/// Implementing this trait for a pallet let's you express operations that should
/// happen at genesis. It will be called in an externalities provided environment and
/// will see the genesis state after all pallets have written their genesis state.
/// will set the genesis state after all pallets have written their genesis state.
#[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))]
@@ -306,19 +306,23 @@ pub trait IntegrityTest {
/// end
/// ```
///
/// * `OnRuntimeUpgrade` is only executed before everything else if a code
/// * `OnRuntimeUpgrade` is mandatorily at the beginning of the block body (extrinsics) being
/// processed. change is detected.
/// * Extrinsics start with inherents, and continue with other signed or unsigned extrinsics.
/// * `OnIdle` optionally comes after extrinsics.
/// `OnFinalize` mandatorily comes after `OnIdle`.
/// * [`OnRuntimeUpgrade`](Hooks::OnRuntimeUpgrade) hooks are only executed when a code change is
/// detected.
/// * [`OnRuntimeUpgrade`](Hooks::OnRuntimeUpgrade) hooks are mandatorily executed at the very
/// beginning of the block body, before any extrinsics are processed.
/// * [`Inherents`](sp_inherents) are always executed before any other other signed or unsigned
/// extrinsics.
/// * [`OnIdle`](Hooks::OnIdle) hooks are executed after extrinsics if there is weight remaining in
/// the block.
/// * [`OnFinalize`](Hooks::OnFinalize) hooks are mandatorily executed after
/// [`OnIdle`](Hooks::OnIdle).
///
/// > `OffchainWorker` is not part of this flow, as it is not really part of the consensus/main
/// > block import path, and is called optionally, and in other circumstances. See
/// > [`crate::traits::misc::OffchainWorker`] for more information.
/// > [`OffchainWorker`](crate::traits::misc::OffchainWorker) hooks are not part of this flow,
/// > because they are not part of the consensus/main block building logic. See
/// > [`OffchainWorker`](crate::traits::misc::OffchainWorker) for more information.
///
/// To learn more about the execution of hooks see `frame-executive` as this component is is charge
/// of dispatching extrinsics and placing the hooks in the correct order.
/// To learn more about the execution of hooks see the FRAME `Executive` pallet which is in charge
/// of dispatching extrinsics and calling hooks in the correct order.
pub trait Hooks<BlockNumber> {
/// Block initialization hook. This is called at the very beginning of block execution.
///
@@ -370,30 +374,38 @@ pub trait Hooks<BlockNumber> {
Weight::zero()
}
/// Hook executed when a code change (aka. a "runtime upgrade") is detected by FRAME.
/// Hook executed when a code change (aka. a "runtime upgrade") is detected by the FRAME
/// `Executive` pallet.
///
/// Be aware that this is called before [`Hooks::on_initialize`] of any pallet; therefore, a lot
/// of the critical storage items such as `block_number` in system pallet might have not been
/// set.
/// set yet.
///
/// Vert similar to [`Hooks::on_initialize`], any code in this block is mandatory and MUST
/// execute. Use with care.
/// Similar to [`Hooks::on_initialize`], any code in this block is mandatory and MUST execute.
/// It is strongly recommended to dry-run the execution of these hooks using
/// [try-runtime-cli](https://github.com/paritytech/try-runtime-cli) to ensure they will not
/// produce and overweight block which can brick your chain. Use with care!
///
/// ## Implementation Note: Versioning
/// ## Implementation Note: Standalone Migrations
///
/// 1. An implementation of this should typically follow a pattern where the version of the
/// pallet is checked against the onchain version, and a decision is made about what needs to be
/// done. This is helpful to prevent accidental repetitive execution of this hook, which can be
/// catastrophic.
/// Additional migrations can be created by directly implementing [`OnRuntimeUpgrade`] on
/// structs and passing them to `Executive`.
///
/// Alternatively, [`frame_support::migrations::VersionedMigration`] can be used to assist with
/// this.
/// ## Implementation Note: Pallet Versioning
///
/// ## Implementation Note: Runtime Level Migration
/// Implementations of this hook are typically wrapped in
/// [`crate::migrations::VersionedMigration`] to ensure the migration is executed exactly
/// once and only when it is supposed to.
///
/// Additional "upgrade hooks" can be created by pallets by a manual implementation of
/// [`Hooks::on_runtime_upgrade`] which can be passed on to `Executive` at the top level
/// runtime.
/// Alternatively, developers can manually implement version checks.
///
/// Failure to adequately check storage versions can result in accidental repetitive execution
/// of the hook, which can be catastrophic.
///
/// ## Implementation Note: Weight
///
/// Typically, implementations of this method are simple enough that weights can be calculated
/// manually. However, if required, a benchmark can also be used.
fn on_runtime_upgrade() -> Weight {
Weight::zero()
}
@@ -403,7 +415,7 @@ pub trait Hooks<BlockNumber> {
/// It should focus on certain checks to ensure that the state is sensible. This is never
/// executed in a consensus code-path, therefore it can consume as much weight as it needs.
///
/// This hook should not alter any storage.
/// This hook must not alter any storage.
#[cfg(feature = "try-runtime")]
fn try_state(_n: BlockNumber) -> Result<(), TryRuntimeError> {
Ok(())
@@ -415,7 +427,7 @@ pub trait Hooks<BlockNumber> {
/// which will be passed to `post_upgrade` after upgrading for post-check. An empty vector
/// should be returned if there is no such need.
///
/// This hook is never meant to be executed on-chain but is meant to be used by testing tools.
/// This hook is never executed on-chain but instead used by testing tools.
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
Ok(Vec::new())
+46 -23
View File
@@ -261,41 +261,64 @@ impl Add<u16> for StorageVersion {
}
}
/// Special marker struct if no storage version is set for a pallet.
/// Special marker struct used when [`storage_version`](crate::pallet_macros::storage_version) is
/// not defined for a pallet.
///
/// If you (the reader) end up here, it probably means that you tried to compare
/// [`GetStorageVersion::on_chain_storage_version`] against
/// [`GetStorageVersion::current_storage_version`]. This basically means that the
/// [`storage_version`](crate::pallet_macros::storage_version) is missing in the pallet where the
/// mentioned functions are being called.
/// [`GetStorageVersion::in_code_storage_version`]. This basically means that the
/// [`storage_version`](crate::pallet_macros::storage_version) is missing from the pallet where the
/// mentioned functions are being called, and needs to be defined.
#[derive(Debug, Default)]
pub struct NoStorageVersionSet;
/// Provides information about the storage version of a pallet.
/// Provides information about a pallet's storage versions.
///
/// It differentiates between current and on-chain storage version. Both should be only out of sync
/// when a new runtime upgrade was applied and the runtime migrations did not yet executed.
/// Otherwise it means that the pallet works with an unsupported storage version and unforeseen
/// stuff can happen.
/// Every pallet has two storage versions:
/// 1. An in-code storage version
/// 2. An on-chain storage version
///
/// The current storage version is the version of the pallet as supported at runtime. The active
/// storage version is the version of the pallet in the storage.
/// The in-code storage version is the version of the pallet as defined in the runtime blob, and the
/// on-chain storage version is the version of the pallet stored on-chain.
///
/// It is required to update the on-chain storage version manually when a migration was applied.
/// Storage versions should be only ever be out of sync when a pallet has been updated to a new
/// version and the in-code version is incremented, but the migration has not yet been executed
/// on-chain as part of a runtime upgrade.
///
/// It is the responsibility of the developer to ensure that the on-chain storage version is set
/// correctly during a migration so that it matches the in-code storage version.
pub trait GetStorageVersion {
/// This will be filled out by the [`pallet`](crate::pallet) macro.
/// This type is generated by the [`pallet`](crate::pallet) macro.
///
/// If the [`storage_version`](crate::pallet_macros::storage_version) attribute isn't given
/// this is set to [`NoStorageVersionSet`] to inform the user that the attribute is missing.
/// This should prevent that the user forgets to set a storage version when required. However,
/// this will only work when the user actually tries to call [`Self::current_storage_version`]
/// to compare it against the [`Self::on_chain_storage_version`]. If the attribute is given,
/// this will be set to [`StorageVersion`].
type CurrentStorageVersion;
/// If the [`storage_version`](crate::pallet_macros::storage_version) attribute isn't specified,
/// this is set to [`NoStorageVersionSet`] to signify that it is missing.
///
/// If the [`storage_version`](crate::pallet_macros::storage_version) attribute is specified,
/// this is be set to a [`StorageVersion`] corresponding to the attribute.
///
/// The intention of using [`NoStorageVersionSet`] instead of defaulting to a [`StorageVersion`]
/// of zero is to prevent developers from forgetting to set
/// [`storage_version`](crate::pallet_macros::storage_version) when it is required, like in the
/// case that they wish to compare the in-code storage version to the on-chain storage version.
type InCodeStorageVersion;
/// Returns the current storage version as supported by the pallet.
fn current_storage_version() -> Self::CurrentStorageVersion;
/// Returns the on-chain storage version of the pallet as stored in the storage.
#[deprecated(
note = "This method has been renamed to `in_code_storage_version` and will be removed after March 2024."
)]
/// DEPRECATED: Use [`Self::current_storage_version`] instead.
///
/// Returns the in-code storage version as specified in the
/// [`storage_version`](crate::pallet_macros::storage_version) attribute, or
/// [`NoStorageVersionSet`] if the attribute is missing.
fn current_storage_version() -> Self::InCodeStorageVersion {
Self::in_code_storage_version()
}
/// Returns the in-code storage version as specified in the
/// [`storage_version`](crate::pallet_macros::storage_version) attribute, or
/// [`NoStorageVersionSet`] if the attribute is missing.
fn in_code_storage_version() -> Self::InCodeStorageVersion;
/// Returns the storage version of the pallet as last set in the actual on-chain storage.
fn on_chain_storage_version() -> StorageVersion;
}