referrerpolicy=no-referrer-when-downgrade

pallet_collator_selection/
migration.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3// SPDX-License-Identifier: Apache-2.0
4
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// 	http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17//! A module that is responsible for migration of storage for Collator Selection.
18
19use super::*;
20#[cfg(feature = "try-runtime")]
21use alloc::vec::Vec;
22use frame_support::traits::{OnRuntimeUpgrade, UncheckedOnRuntimeUpgrade};
23use log;
24
25/// Migrate to v2. Should have been part of <https://github.com/paritytech/polkadot-sdk/pull/1340>.
26pub mod v2 {
27	use super::*;
28	use frame_support::{
29		pallet_prelude::*,
30		storage_alias,
31		traits::{Currency, ReservableCurrency},
32	};
33	use sp_runtime::traits::{Saturating, Zero};
34
35	/// [`UncheckedMigrationToV2`] wrapped in a
36	/// [`VersionedMigration`](frame_support::migrations::VersionedMigration), ensuring the
37	/// migration is only performed when on-chain version is 1.
38	pub type MigrationToV2<T> = frame_support::migrations::VersionedMigration<
39		1,
40		2,
41		UncheckedMigrationToV2<T>,
42		Pallet<T>,
43		<T as frame_system::Config>::DbWeight,
44	>;
45
46	#[storage_alias]
47	pub type Candidates<T: Config> = StorageValue<
48		Pallet<T>,
49		BoundedVec<CandidateInfo<<T as frame_system::Config>::AccountId, <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance>, <T as Config>::MaxCandidates>,
50		ValueQuery,
51	>;
52
53	/// Migrate to V2.
54	pub struct UncheckedMigrationToV2<T>(PhantomData<T>);
55	impl<T: Config + pallet_balances::Config> UncheckedOnRuntimeUpgrade for UncheckedMigrationToV2<T> {
56		fn on_runtime_upgrade() -> Weight {
57			let mut weight = Weight::zero();
58			let mut count: u64 = 0;
59			// candidates who exist under the old `Candidates` key
60			let candidates = Candidates::<T>::take();
61
62			// New candidates who have registered since the upgrade. Under normal circumstances,
63			// this should not exist because the migration should be applied when the upgrade
64			// happens. But in Polkadot/Kusama we messed this up, and people registered under
65			// `CandidateList` while their funds were locked in `Candidates`.
66			let new_candidate_list = CandidateList::<T>::get();
67			if new_candidate_list.len().is_zero() {
68				// The new list is empty, so this is essentially being applied correctly. We just
69				// put the candidates into the new storage item.
70				CandidateList::<T>::put(&candidates);
71				// 1 write for the new list
72				weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1));
73			} else {
74				// Oops, the runtime upgraded without the migration. There are new candidates in
75				// `CandidateList`. So, let's just refund the old ones and assume they have already
76				// started participating in the new system.
77				for candidate in candidates {
78					let err = T::Currency::unreserve(&candidate.who, candidate.deposit);
79					if err > Zero::zero() {
80						log::error!(
81							target: LOG_TARGET,
82							"{:?} balance was unable to be unreserved from {:?}",
83							err, &candidate.who,
84						);
85					}
86					count.saturating_inc();
87				}
88				weight.saturating_accrue(
89					<<T as pallet_balances::Config>::WeightInfo as pallet_balances::WeightInfo>::force_unreserve().saturating_mul(count.into()),
90				);
91			}
92
93			log::info!(
94				target: LOG_TARGET,
95				"Unreserved locked bond of {} candidates, upgraded storage to version 2",
96				count,
97			);
98
99			weight.saturating_accrue(T::DbWeight::get().reads_writes(3, 2));
100			weight
101		}
102
103		#[cfg(feature = "try-runtime")]
104		fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::DispatchError> {
105			let number_of_candidates = Candidates::<T>::get().to_vec().len();
106			Ok((number_of_candidates as u32).encode())
107		}
108
109		#[cfg(feature = "try-runtime")]
110		fn post_upgrade(_number_of_candidates: Vec<u8>) -> Result<(), sp_runtime::DispatchError> {
111			let new_number_of_candidates = Candidates::<T>::get().to_vec().len();
112			assert_eq!(
113				new_number_of_candidates, 0 as usize,
114				"after migration, the candidates map should be empty"
115			);
116			Ok(())
117		}
118	}
119}
120
121/// Version 1 Migration
122/// This migration ensures that any existing `Invulnerables` storage lists are sorted.
123pub mod v1 {
124	use super::*;
125	use frame_support::pallet_prelude::*;
126
127	pub struct MigrateToV1<T>(PhantomData<T>);
128	impl<T: Config> OnRuntimeUpgrade for MigrateToV1<T> {
129		fn on_runtime_upgrade() -> Weight {
130			let on_chain_version = Pallet::<T>::on_chain_storage_version();
131			if on_chain_version == 0 {
132				let invulnerables_len = Invulnerables::<T>::get().to_vec().len();
133				Invulnerables::<T>::mutate(|invulnerables| {
134					invulnerables.sort();
135				});
136
137				StorageVersion::new(1).put::<Pallet<T>>();
138				log::info!(
139					target: LOG_TARGET,
140					"Sorted {} Invulnerables, upgraded storage to version 1",
141					invulnerables_len,
142				);
143				// Similar complexity to `set_invulnerables` (put storage value)
144				// Plus 1 read for length, 1 read for `on_chain_version`, 1 write to put version
145				T::WeightInfo::set_invulnerables(invulnerables_len as u32)
146					.saturating_add(T::DbWeight::get().reads_writes(2, 1))
147			} else {
148				log::info!(
149					target: LOG_TARGET,
150					"Migration did not execute. This probably should be removed"
151				);
152				T::DbWeight::get().reads(1)
153			}
154		}
155
156		#[cfg(feature = "try-runtime")]
157		fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::DispatchError> {
158			let number_of_invulnerables = Invulnerables::<T>::get().to_vec().len();
159			Ok((number_of_invulnerables as u32).encode())
160		}
161
162		#[cfg(feature = "try-runtime")]
163		fn post_upgrade(number_of_invulnerables: Vec<u8>) -> Result<(), sp_runtime::DispatchError> {
164			let stored_invulnerables = Invulnerables::<T>::get().to_vec();
165			let mut sorted_invulnerables = stored_invulnerables.clone();
166			sorted_invulnerables.sort();
167			assert_eq!(
168				stored_invulnerables, sorted_invulnerables,
169				"after migration, the stored invulnerables should be sorted"
170			);
171
172			let number_of_invulnerables: u32 = Decode::decode(
173				&mut number_of_invulnerables.as_slice(),
174			)
175			.expect("the state parameter should be something that was generated by pre_upgrade");
176			let stored_invulnerables_len = stored_invulnerables.len() as u32;
177			assert_eq!(
178				number_of_invulnerables, stored_invulnerables_len,
179				"after migration, there should be the same number of invulnerables"
180			);
181
182			let on_chain_version = Pallet::<T>::on_chain_storage_version();
183			frame_support::ensure!(on_chain_version >= 1, "must_upgrade");
184
185			Ok(())
186		}
187	}
188}
189
190#[cfg(all(feature = "try-runtime", test))]
191mod tests {
192	use super::*;
193	use crate::{
194		migration::v2::Candidates,
195		mock::{new_test_ext, Balances, Test},
196	};
197	use frame_support::{
198		traits::{Currency, ReservableCurrency, StorageVersion},
199		BoundedVec,
200	};
201	use sp_runtime::traits::ConstU32;
202
203	#[test]
204	fn migrate_to_v2_with_new_candidates() {
205		new_test_ext().execute_with(|| {
206			let storage_version = StorageVersion::new(1);
207			storage_version.put::<Pallet<Test>>();
208
209			let one = 1u64;
210			let two = 2u64;
211			let three = 3u64;
212			let deposit = 10u64;
213
214			// Set balance to 100
215			Balances::make_free_balance_be(&one, 100u64);
216			Balances::make_free_balance_be(&two, 100u64);
217			Balances::make_free_balance_be(&three, 100u64);
218
219			// Reservations: 10 for the "old" candidacy and 10 for the "new"
220			Balances::reserve(&one, 10u64).unwrap(); // old
221			Balances::reserve(&two, 20u64).unwrap(); // old + new
222			Balances::reserve(&three, 10u64).unwrap(); // new
223
224			// Candidate info
225			let candidate_one = CandidateInfo { who: one, deposit };
226			let candidate_two = CandidateInfo { who: two, deposit };
227			let candidate_three = CandidateInfo { who: three, deposit };
228
229			// Storage lists
230			let bounded_candidates =
231				BoundedVec::<CandidateInfo<u64, u64>, ConstU32<20>>::try_from(vec![
232					candidate_one.clone(),
233					candidate_two.clone(),
234				])
235				.expect("it works");
236			let bounded_candidate_list =
237				BoundedVec::<CandidateInfo<u64, u64>, ConstU32<20>>::try_from(vec![
238					candidate_two.clone(),
239					candidate_three.clone(),
240				])
241				.expect("it works");
242
243			// Set storage
244			Candidates::<Test>::put(bounded_candidates);
245			CandidateList::<Test>::put(bounded_candidate_list.clone());
246
247			// Sanity check
248			assert_eq!(Balances::free_balance(one), 90);
249			assert_eq!(Balances::free_balance(two), 80);
250			assert_eq!(Balances::free_balance(three), 90);
251
252			// Run migration
253			v2::MigrationToV2::<Test>::on_runtime_upgrade();
254
255			let new_storage_version = StorageVersion::get::<Pallet<Test>>();
256			assert_eq!(new_storage_version, 2);
257
258			// 10 should have been unreserved from the old candidacy
259			assert_eq!(Balances::free_balance(one), 100);
260			assert_eq!(Balances::free_balance(two), 90);
261			assert_eq!(Balances::free_balance(three), 90);
262			// The storage item should be gone
263			assert!(Candidates::<Test>::get().is_empty());
264			// The new storage item should be preserved
265			assert_eq!(CandidateList::<Test>::get(), bounded_candidate_list);
266		});
267	}
268
269	#[test]
270	fn migrate_to_v2_without_new_candidates() {
271		new_test_ext().execute_with(|| {
272			let storage_version = StorageVersion::new(1);
273			storage_version.put::<Pallet<Test>>();
274
275			let one = 1u64;
276			let two = 2u64;
277			let deposit = 10u64;
278
279			// Set balance to 100
280			Balances::make_free_balance_be(&one, 100u64);
281			Balances::make_free_balance_be(&two, 100u64);
282
283			// Reservations
284			Balances::reserve(&one, 10u64).unwrap(); // old
285			Balances::reserve(&two, 10u64).unwrap(); // old
286
287			// Candidate info
288			let candidate_one = CandidateInfo { who: one, deposit };
289			let candidate_two = CandidateInfo { who: two, deposit };
290
291			// Storage lists
292			let bounded_candidates =
293				BoundedVec::<CandidateInfo<u64, u64>, ConstU32<20>>::try_from(vec![
294					candidate_one.clone(),
295					candidate_two.clone(),
296				])
297				.expect("it works");
298
299			// Set storage
300			Candidates::<Test>::put(bounded_candidates.clone());
301
302			// Sanity check
303			assert_eq!(Balances::free_balance(one), 90);
304			assert_eq!(Balances::free_balance(two), 90);
305
306			// Run migration
307			v2::MigrationToV2::<Test>::on_runtime_upgrade();
308
309			let new_storage_version = StorageVersion::get::<Pallet<Test>>();
310			assert_eq!(new_storage_version, 2);
311
312			// Nothing changes deposit-wise
313			assert_eq!(Balances::free_balance(one), 90);
314			assert_eq!(Balances::free_balance(two), 90);
315			// The storage item should be gone
316			assert!(Candidates::<Test>::get().is_empty());
317			// The new storage item should have the info now
318			assert_eq!(CandidateList::<Test>::get(), bounded_candidates);
319		});
320	}
321}