|
15 | 15 | // See the License for the specific language governing permissions and |
16 | 16 | // limitations under the License. |
17 | 17 |
|
| 18 | +#[cfg(feature = "try-runtime")] |
| 19 | +use crate::storage::unhashed::contains_prefixed_key; |
18 | 20 | use crate::{ |
19 | 21 | traits::{GetStorageVersion, PalletInfoAccess}, |
20 | 22 | weights::{RuntimeDbWeight, Weight}, |
21 | 23 | }; |
22 | 24 | use impl_trait_for_tuples::impl_for_tuples; |
| 25 | +use sp_core::Get; |
| 26 | +use sp_io::{hashing::twox_128, storage::clear_prefix, KillStorageResult}; |
| 27 | +use sp_std::marker::PhantomData; |
| 28 | +#[cfg(feature = "try-runtime")] |
| 29 | +use sp_std::vec::Vec; |
23 | 30 |
|
24 | 31 | /// Trait used by [`migrate_from_pallet_version_to_storage_version`] to do the actual migration. |
25 | 32 | pub trait PalletVersionToStorageVersionHelper { |
@@ -67,3 +74,112 @@ pub fn migrate_from_pallet_version_to_storage_version< |
67 | 74 | ) -> Weight { |
68 | 75 | Pallets::migrate(db_weight) |
69 | 76 | } |
| 77 | + |
| 78 | +/// `RemovePallet` is a utility struct used to remove all storage items associated with a specific |
| 79 | +/// pallet. |
| 80 | +/// |
| 81 | +/// This struct is generic over two parameters: |
| 82 | +/// - `P` is a type that implements the `Get` trait for a static string, representing the pallet's |
| 83 | +/// name. |
| 84 | +/// - `DbWeight` is a type that implements the `Get` trait for `RuntimeDbWeight`, providing the |
| 85 | +/// weight for database operations. |
| 86 | +/// |
| 87 | +/// On runtime upgrade, the `on_runtime_upgrade` function will clear all storage items associated |
| 88 | +/// with the specified pallet, logging the number of keys removed. If the `try-runtime` feature is |
| 89 | +/// enabled, the `pre_upgrade` and `post_upgrade` functions can be used to verify the storage |
| 90 | +/// removal before and after the upgrade. |
| 91 | +/// |
| 92 | +/// # Examples: |
| 93 | +/// ```ignore |
| 94 | +/// construct_runtime! { |
| 95 | +/// pub enum Runtime where |
| 96 | +/// Block = Block, |
| 97 | +/// NodeBlock = primitives::Block, |
| 98 | +/// UncheckedExtrinsic = UncheckedExtrinsic |
| 99 | +/// { |
| 100 | +/// System: frame_system::{Pallet, Call, Storage, Config, Event<T>} = 0, |
| 101 | +/// |
| 102 | +/// SomePalletToRemove: pallet_something::{Pallet, Call, Storage, Event<T>} = 1, |
| 103 | +/// AnotherPalletToRemove: pallet_something_else::{Pallet, Call, Storage, Event<T>} = 2, |
| 104 | +/// |
| 105 | +/// YourOtherPallets... |
| 106 | +/// } |
| 107 | +/// }; |
| 108 | +/// |
| 109 | +/// parameter_types! { |
| 110 | +/// pub const SomePalletToRemoveStr: &'static str = "SomePalletToRemove"; |
| 111 | +/// pub const AnotherPalletToRemoveStr: &'static str = "AnotherPalletToRemove"; |
| 112 | +/// } |
| 113 | +/// |
| 114 | +/// pub type Migrations = ( |
| 115 | +/// RemovePallet<SomePalletToRemoveStr, RocksDbWeight>, |
| 116 | +/// RemovePallet<AnotherPalletToRemoveStr, RocksDbWeight>, |
| 117 | +/// AnyOtherMigrations... |
| 118 | +/// ); |
| 119 | +/// |
| 120 | +/// pub type Executive = frame_executive::Executive< |
| 121 | +/// Runtime, |
| 122 | +/// Block, |
| 123 | +/// frame_system::ChainContext<Runtime>, |
| 124 | +/// Runtime, |
| 125 | +/// Migrations |
| 126 | +/// >; |
| 127 | +/// ``` |
| 128 | +/// |
| 129 | +/// WARNING: `RemovePallet` has no guard rails preventing it from bricking the chain if the |
| 130 | +/// operation of removing storage for the given pallet would exceed the block weight limit. |
| 131 | +/// |
| 132 | +/// If your pallet has too many keys to be removed in a single block, it is advised to wait for |
| 133 | +/// a multi-block scheduler currently under development which will allow for removal of storage |
| 134 | +/// items (and performing other heavy migrations) over multiple blocks |
| 135 | +/// (see <https://github.com/paritytech/substrate/issues/13690>). |
| 136 | +pub struct RemovePallet<P: Get<&'static str>, DbWeight: Get<RuntimeDbWeight>>( |
| 137 | + PhantomData<(P, DbWeight)>, |
| 138 | +); |
| 139 | +impl<P: Get<&'static str>, DbWeight: Get<RuntimeDbWeight>> frame_support::traits::OnRuntimeUpgrade |
| 140 | + for RemovePallet<P, DbWeight> |
| 141 | +{ |
| 142 | + fn on_runtime_upgrade() -> frame_support::weights::Weight { |
| 143 | + let hashed_prefix = twox_128(P::get().as_bytes()); |
| 144 | + let keys_removed = match clear_prefix(&hashed_prefix, None) { |
| 145 | + KillStorageResult::AllRemoved(value) => value, |
| 146 | + KillStorageResult::SomeRemaining(value) => { |
| 147 | + log::error!( |
| 148 | + "`clear_prefix` failed to remove all keys for {}. THIS SHOULD NEVER HAPPEN! 🚨", |
| 149 | + P::get() |
| 150 | + ); |
| 151 | + value |
| 152 | + }, |
| 153 | + } as u64; |
| 154 | + |
| 155 | + log::info!("Removed {} {} keys 🧹", keys_removed, P::get()); |
| 156 | + |
| 157 | + DbWeight::get().reads_writes(keys_removed + 1, keys_removed) |
| 158 | + } |
| 159 | + |
| 160 | + #[cfg(feature = "try-runtime")] |
| 161 | + fn pre_upgrade() -> Result<Vec<u8>, &'static str> { |
| 162 | + let hashed_prefix = twox_128(P::get().as_bytes()); |
| 163 | + match contains_prefixed_key(&hashed_prefix) { |
| 164 | + true => log::info!("Found {} keys pre-removal 👀", P::get()), |
| 165 | + false => log::warn!( |
| 166 | + "Migration RemovePallet<{}> can be removed (no keys found pre-removal).", |
| 167 | + P::get() |
| 168 | + ), |
| 169 | + }; |
| 170 | + Ok(Vec::new()) |
| 171 | + } |
| 172 | + |
| 173 | + #[cfg(feature = "try-runtime")] |
| 174 | + fn post_upgrade(_state: Vec<u8>) -> Result<(), &'static str> { |
| 175 | + let hashed_prefix = twox_128(P::get().as_bytes()); |
| 176 | + match contains_prefixed_key(&hashed_prefix) { |
| 177 | + true => { |
| 178 | + log::error!("{} has keys remaining post-removal ❗", P::get()); |
| 179 | + return Err("Keys remaining post-removal, this should never happen 🚨") |
| 180 | + }, |
| 181 | + false => log::info!("No {} keys found post-removal 🎉", P::get()), |
| 182 | + }; |
| 183 | + Ok(()) |
| 184 | + } |
| 185 | +} |
0 commit comments