// This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #[cfg(feature = "try-runtime")] use crate::storage::unhashed::contains_prefixed_key; use crate::{ traits::{GetStorageVersion, NoStorageVersionSet, PalletInfoAccess, StorageVersion}, weights::{RuntimeDbWeight, Weight}, }; use impl_trait_for_tuples::impl_for_tuples; use sp_core::Get; use sp_io::{hashing::twox_128, storage::clear_prefix, KillStorageResult}; use sp_std::marker::PhantomData; #[cfg(feature = "try-runtime")] use sp_std::vec::Vec; /// Can store the current pallet version in storage. pub trait StoreCurrentStorageVersion { /// Write the current storage version to the storage. fn store_current_storage_version(); } impl + PalletInfoAccess> StoreCurrentStorageVersion for StorageVersion { fn store_current_storage_version() { let version = ::current_storage_version(); version.put::(); } } impl + PalletInfoAccess> StoreCurrentStorageVersion for NoStorageVersionSet { fn store_current_storage_version() { StorageVersion::default().put::(); } } /// Trait used by [`migrate_from_pallet_version_to_storage_version`] to do the actual migration. pub trait PalletVersionToStorageVersionHelper { fn migrate(db_weight: &RuntimeDbWeight) -> Weight; } impl PalletVersionToStorageVersionHelper for T where T::CurrentStorageVersion: StoreCurrentStorageVersion, { fn migrate(db_weight: &RuntimeDbWeight) -> Weight { const PALLET_VERSION_STORAGE_KEY_POSTFIX: &[u8] = b":__PALLET_VERSION__:"; fn pallet_version_key(name: &str) -> [u8; 32] { crate::storage::storage_prefix(name.as_bytes(), PALLET_VERSION_STORAGE_KEY_POSTFIX) } sp_io::storage::clear(&pallet_version_key(::name())); >::store_current_storage_version( ); db_weight.writes(2) } } #[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 PalletVersionToStorageVersionHelper for T { fn migrate(db_weight: &RuntimeDbWeight) -> Weight { let mut weight = Weight::zero(); for_tuples!( #( weight = weight.saturating_add(T::migrate(db_weight)); )* ); weight } } /// Migrate from the `PalletVersion` struct to the new /// [`StorageVersion`](crate::traits::StorageVersion) struct. /// /// This will remove all `PalletVersion's` from the state and insert the current storage version. pub fn migrate_from_pallet_version_to_storage_version< Pallets: PalletVersionToStorageVersionHelper, >( db_weight: &RuntimeDbWeight, ) -> Weight { Pallets::migrate(db_weight) } /// `RemovePallet` is a utility struct used to remove all storage items associated with a specific /// pallet. /// /// This struct is generic over two parameters: /// - `P` is a type that implements the `Get` trait for a static string, representing the pallet's /// name. /// - `DbWeight` is a type that implements the `Get` trait for `RuntimeDbWeight`, providing the /// weight for database operations. /// /// On runtime upgrade, the `on_runtime_upgrade` function will clear all storage items associated /// with the specified pallet, logging the number of keys removed. If the `try-runtime` feature is /// enabled, the `pre_upgrade` and `post_upgrade` functions can be used to verify the storage /// removal before and after the upgrade. /// /// # Examples: /// ```ignore /// construct_runtime! { /// pub enum Runtime where /// Block = Block, /// NodeBlock = primitives::Block, /// UncheckedExtrinsic = UncheckedExtrinsic /// { /// System: frame_system::{Pallet, Call, Storage, Config, Event} = 0, /// /// SomePalletToRemove: pallet_something::{Pallet, Call, Storage, Event} = 1, /// AnotherPalletToRemove: pallet_something_else::{Pallet, Call, Storage, Event} = 2, /// /// YourOtherPallets... /// } /// }; /// /// parameter_types! { /// pub const SomePalletToRemoveStr: &'static str = "SomePalletToRemove"; /// pub const AnotherPalletToRemoveStr: &'static str = "AnotherPalletToRemove"; /// } /// /// pub type Migrations = ( /// RemovePallet, /// RemovePallet, /// AnyOtherMigrations... /// ); /// /// pub type Executive = frame_executive::Executive< /// Runtime, /// Block, /// frame_system::ChainContext, /// Runtime, /// Migrations /// >; /// ``` /// /// WARNING: `RemovePallet` has no guard rails preventing it from bricking the chain if the /// operation of removing storage for the given pallet would exceed the block weight limit. /// /// If your pallet has too many keys to be removed in a single block, it is advised to wait for /// a multi-block scheduler currently under development which will allow for removal of storage /// items (and performing other heavy migrations) over multiple blocks /// (see ). pub struct RemovePallet, DbWeight: Get>( PhantomData<(P, DbWeight)>, ); impl, DbWeight: Get> frame_support::traits::OnRuntimeUpgrade for RemovePallet { fn on_runtime_upgrade() -> frame_support::weights::Weight { let hashed_prefix = twox_128(P::get().as_bytes()); let keys_removed = match clear_prefix(&hashed_prefix, None) { KillStorageResult::AllRemoved(value) => value, KillStorageResult::SomeRemaining(value) => { log::error!( "`clear_prefix` failed to remove all keys for {}. THIS SHOULD NEVER HAPPEN! ๐Ÿšจ", P::get() ); value }, } as u64; log::info!("Removed {} {} keys ๐Ÿงน", keys_removed, P::get()); DbWeight::get().reads_writes(keys_removed + 1, keys_removed) } #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { let hashed_prefix = twox_128(P::get().as_bytes()); match contains_prefixed_key(&hashed_prefix) { true => log::info!("Found {} keys pre-removal ๐Ÿ‘€", P::get()), false => log::warn!( "Migration RemovePallet<{}> can be removed (no keys found pre-removal).", P::get() ), }; Ok(Vec::new()) } #[cfg(feature = "try-runtime")] fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { let hashed_prefix = twox_128(P::get().as_bytes()); match contains_prefixed_key(&hashed_prefix) { true => { log::error!("{} has keys remaining post-removal โ—", P::get()); return Err("Keys remaining post-removal, this should never happen ๐Ÿšจ".into()) }, false => log::info!("No {} keys found post-removal ๐ŸŽ‰", P::get()), }; Ok(()) } }