| 
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