Skip to content

Commit 06d0d22

Browse files
authored
Backport 4229 Into v1.7.0 (paritytech#4288)
Backport of paritytech#4229 into the v1.7.0 release in order to support dependent runtimes. This version of the SDK doesn't have `UncheckedOnRuntimeUpgrade`, so I do it the "old way".
1 parent 0332ad9 commit 06d0d22

File tree

4 files changed

+250
-9
lines changed

4 files changed

+250
-9
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cumulus/pallets/collator-selection/src/lib.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ pub mod pallet {
119119
use sp_std::vec::Vec;
120120

121121
/// The current storage version.
122-
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
122+
const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
123123

124124
type BalanceOf<T> =
125125
<<T as Config>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
@@ -338,8 +338,8 @@ pub mod pallet {
338338
fn integrity_test() {
339339
assert!(T::MinEligibleCollators::get() > 0, "chain must require at least one collator");
340340
assert!(
341-
T::MaxInvulnerables::get().saturating_add(T::MaxCandidates::get())
342-
>= T::MinEligibleCollators::get(),
341+
T::MaxInvulnerables::get().saturating_add(T::MaxCandidates::get()) >=
342+
T::MinEligibleCollators::get(),
343343
"invulnerables and candidates must be able to satisfy collator demand"
344344
);
345345
}
@@ -374,8 +374,8 @@ pub mod pallet {
374374
if new.is_empty() {
375375
// Casting `u32` to `usize` should be safe on all machines running this.
376376
ensure!(
377-
CandidateList::<T>::decode_len().unwrap_or_default()
378-
>= T::MinEligibleCollators::get() as usize,
377+
CandidateList::<T>::decode_len().unwrap_or_default() >=
378+
T::MinEligibleCollators::get() as usize,
379379
Error::<T>::TooFewEligibleCollators
380380
);
381381
}
@@ -678,8 +678,8 @@ pub mod pallet {
678678
} else if new_deposit < old_deposit {
679679
// Casting `u32` to `usize` should be safe on all machines running this.
680680
ensure!(
681-
idx.saturating_add(<DesiredCandidates<T>>::get() as usize)
682-
< candidate_count,
681+
idx.saturating_add(<DesiredCandidates<T>>::get() as usize) <
682+
candidate_count,
683683
Error::<T>::InvalidUnreserve
684684
);
685685
T::Currency::unreserve(&who, old_deposit - new_deposit);

cumulus/pallets/collator-selection/src/migration.rs

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,104 @@ use super::*;
2020
use frame_support::traits::OnRuntimeUpgrade;
2121
use log;
2222

23+
/// Migrate to v2. Should have been part of <https://github.com/paritytech/polkadot-sdk/pull/1340>.
24+
pub mod v2 {
25+
use super::*;
26+
use frame_support::{
27+
pallet_prelude::*,
28+
storage_alias,
29+
traits::{Currency, ReservableCurrency},
30+
};
31+
use sp_runtime::traits::{Saturating, Zero};
32+
#[cfg(feature = "try-runtime")]
33+
use sp_std::vec::Vec;
34+
35+
#[storage_alias]
36+
pub type Candidates<T: Config> = StorageValue<
37+
Pallet<T>,
38+
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>,
39+
ValueQuery,
40+
>;
41+
42+
/// Migrate to V2.
43+
pub struct MigrationToV2<T>(sp_std::marker::PhantomData<T>);
44+
impl<T: Config + pallet_balances::Config> OnRuntimeUpgrade for MigrationToV2<T> {
45+
fn on_runtime_upgrade() -> Weight {
46+
let on_chain_version = Pallet::<T>::on_chain_storage_version();
47+
if on_chain_version == 1 {
48+
let mut weight = Weight::zero();
49+
let mut count: u64 = 0;
50+
// candidates who exist under the old `Candidates` key
51+
let candidates = Candidates::<T>::take();
52+
53+
// New candidates who have registered since the upgrade. Under normal circumstances,
54+
// this should not exist because the migration should be applied when the upgrade
55+
// happens. But in Polkadot/Kusama we messed this up, and people registered under
56+
// `CandidateList` while their funds were locked in `Candidates`.
57+
let new_candidate_list = CandidateList::<T>::get();
58+
if new_candidate_list.len().is_zero() {
59+
// The new list is empty, so this is essentially being applied correctly. We
60+
// just put the candidates into the new storage item.
61+
CandidateList::<T>::put(&candidates);
62+
// 1 write for the new list
63+
weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1));
64+
} else {
65+
// Oops, the runtime upgraded without the migration. There are new candidates in
66+
// `CandidateList`. So, let's just refund the old ones and assume they have
67+
// already started participating in the new system.
68+
for candidate in candidates {
69+
let err = T::Currency::unreserve(&candidate.who, candidate.deposit);
70+
if err > Zero::zero() {
71+
log::error!(
72+
target: LOG_TARGET,
73+
"{:?} balance was unable to be unreserved from {:?}",
74+
err, &candidate.who,
75+
);
76+
}
77+
count.saturating_inc();
78+
}
79+
weight.saturating_accrue(
80+
<<T as pallet_balances::Config>::WeightInfo as pallet_balances::WeightInfo>::force_unreserve().saturating_mul(count.into()),
81+
);
82+
}
83+
84+
StorageVersion::new(2).put::<Pallet<T>>();
85+
86+
log::info!(
87+
target: LOG_TARGET,
88+
"Unreserved locked bond of {} candidates, upgraded storage to version 2",
89+
count,
90+
);
91+
92+
weight.saturating_accrue(T::DbWeight::get().reads_writes(3, 2));
93+
weight
94+
} else {
95+
log::info!(
96+
target: LOG_TARGET,
97+
"Migration did not execute. This probably should be removed"
98+
);
99+
T::DbWeight::get().reads(1)
100+
}
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+
23121
/// Version 1 Migration
24122
/// This migration ensures that any existing `Invulnerables` storage lists are sorted.
25123
pub mod v1 {
@@ -90,3 +188,136 @@ pub mod v1 {
90188
}
91189
}
92190
}
191+
192+
#[cfg(all(feature = "try-runtime", test))]
193+
mod tests {
194+
use super::*;
195+
use crate::{
196+
migration::v2::Candidates,
197+
mock::{new_test_ext, Balances, Test},
198+
};
199+
use frame_support::{
200+
traits::{Currency, ReservableCurrency, StorageVersion},
201+
BoundedVec,
202+
};
203+
use sp_runtime::traits::ConstU32;
204+
205+
#[test]
206+
fn migrate_to_v2_with_new_candidates() {
207+
new_test_ext().execute_with(|| {
208+
let storage_version = StorageVersion::new(1);
209+
storage_version.put::<Pallet<Test>>();
210+
211+
let one = 1u64;
212+
let two = 2u64;
213+
let three = 3u64;
214+
let deposit = 10u64;
215+
216+
// Set balance to 100
217+
Balances::make_free_balance_be(&one, 100u64);
218+
Balances::make_free_balance_be(&two, 100u64);
219+
Balances::make_free_balance_be(&three, 100u64);
220+
221+
// Reservations: 10 for the "old" candidacy and 10 for the "new"
222+
Balances::reserve(&one, 10u64).unwrap(); // old
223+
Balances::reserve(&two, 20u64).unwrap(); // old + new
224+
Balances::reserve(&three, 10u64).unwrap(); // new
225+
226+
// Candidate info
227+
let candidate_one = CandidateInfo { who: one, deposit };
228+
let candidate_two = CandidateInfo { who: two, deposit };
229+
let candidate_three = CandidateInfo { who: three, deposit };
230+
231+
// Storage lists
232+
let bounded_candidates =
233+
BoundedVec::<CandidateInfo<u64, u64>, ConstU32<20>>::try_from(vec![
234+
candidate_one.clone(),
235+
candidate_two.clone(),
236+
])
237+
.expect("it works");
238+
let bounded_candidate_list =
239+
BoundedVec::<CandidateInfo<u64, u64>, ConstU32<20>>::try_from(vec![
240+
candidate_two.clone(),
241+
candidate_three.clone(),
242+
])
243+
.expect("it works");
244+
245+
// Set storage
246+
Candidates::<Test>::put(bounded_candidates);
247+
CandidateList::<Test>::put(bounded_candidate_list.clone());
248+
249+
// Sanity check
250+
assert_eq!(Balances::free_balance(one), 90);
251+
assert_eq!(Balances::free_balance(two), 80);
252+
assert_eq!(Balances::free_balance(three), 90);
253+
254+
// Run migration
255+
v2::MigrationToV2::<Test>::on_runtime_upgrade();
256+
257+
let new_storage_version = StorageVersion::get::<Pallet<Test>>();
258+
assert_eq!(new_storage_version, 2);
259+
260+
// 10 should have been unreserved from the old candidacy
261+
assert_eq!(Balances::free_balance(one), 100);
262+
assert_eq!(Balances::free_balance(two), 90);
263+
assert_eq!(Balances::free_balance(three), 90);
264+
// The storage item should be gone
265+
assert!(Candidates::<Test>::get().is_empty());
266+
// The new storage item should be preserved
267+
assert_eq!(CandidateList::<Test>::get(), bounded_candidate_list);
268+
});
269+
}
270+
271+
#[test]
272+
fn migrate_to_v2_without_new_candidates() {
273+
new_test_ext().execute_with(|| {
274+
let storage_version = StorageVersion::new(1);
275+
storage_version.put::<Pallet<Test>>();
276+
277+
let one = 1u64;
278+
let two = 2u64;
279+
let deposit = 10u64;
280+
281+
// Set balance to 100
282+
Balances::make_free_balance_be(&one, 100u64);
283+
Balances::make_free_balance_be(&two, 100u64);
284+
285+
// Reservations
286+
Balances::reserve(&one, 10u64).unwrap(); // old
287+
Balances::reserve(&two, 10u64).unwrap(); // old
288+
289+
// Candidate info
290+
let candidate_one = CandidateInfo { who: one, deposit };
291+
let candidate_two = CandidateInfo { who: two, deposit };
292+
293+
// Storage lists
294+
let bounded_candidates =
295+
BoundedVec::<CandidateInfo<u64, u64>, ConstU32<20>>::try_from(vec![
296+
candidate_one.clone(),
297+
candidate_two.clone(),
298+
])
299+
.expect("it works");
300+
301+
// Set storage
302+
Candidates::<Test>::put(bounded_candidates.clone());
303+
304+
// Sanity check
305+
assert_eq!(Balances::free_balance(one), 90);
306+
assert_eq!(Balances::free_balance(two), 90);
307+
308+
// Run migration
309+
v2::MigrationToV2::<Test>::on_runtime_upgrade();
310+
311+
let new_storage_version = StorageVersion::get::<Pallet<Test>>();
312+
assert_eq!(new_storage_version, 2);
313+
314+
// Nothing changes deposit-wise
315+
assert_eq!(Balances::free_balance(one), 90);
316+
assert_eq!(Balances::free_balance(two), 90);
317+
// The storage item should be gone
318+
assert!(Candidates::<Test>::get().is_empty());
319+
// The new storage item should have the info now
320+
assert_eq!(CandidateList::<Test>::get(), bounded_candidates);
321+
});
322+
}
323+
}

prdoc/1.7.0/pr_4288.prdoc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
title: "Fix Stuck Collator Funds"
2+
3+
doc:
4+
- audience: Runtime Dev
5+
description: |
6+
Fixes stuck collator funds by providing a migration that should have been in PR 1340.
7+
8+
crates:
9+
- name: pallet-collator-selection
10+
bump: patch

0 commit comments

Comments
 (0)