Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 560579d

Browse files
authored
Better Handling of Candidates Who Become Invulnerable (#2801)
* remove candidate when to invulnerable * fix * candidates to collators * make parameters consistent and more reasonable * add call to kick invulnerable candidates * factor removal into weight * fix: use accrue instead of add * make set_invulnerables non-atomic * benchmark add_invulnerable to account for candidate removal * don't remove from candidates with set_invulnerables * fix bounds on benchmarking * protect against zero min invulnerables underflow * extra event and tests * make candidates/invulnerables self-cleaning on session change * add integrity test * unused imports * make rococo-contracts have 1 collator
1 parent 77a6904 commit 560579d

23 files changed

Lines changed: 593 additions & 260 deletions

File tree

pallets/collator-selection/src/benchmarking.rs

Lines changed: 71 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ use frame_benchmarking::{
2525
use frame_support::{
2626
assert_ok,
2727
codec::Decode,
28-
traits::{Currency, EnsureOrigin, Get},
28+
dispatch::DispatchResult,
29+
traits::{Currency, EnsureOrigin, Get, ReservableCurrency},
2930
};
3031
use frame_system::{EventRecord, RawOrigin};
3132
use pallet_authorship::EventHandler;
@@ -106,6 +107,18 @@ fn register_candidates<T: Config>(count: u32) {
106107
}
107108
}
108109

110+
fn min_candidates<T: Config>() -> u32 {
111+
let min_collators = T::MinEligibleCollators::get();
112+
let invulnerable_length = <Invulnerables<T>>::get().len();
113+
min_collators.saturating_sub(invulnerable_length.try_into().unwrap())
114+
}
115+
116+
fn min_invulnerables<T: Config>() -> u32 {
117+
let min_collators = T::MinEligibleCollators::get();
118+
let candidates_length = <Candidates<T>>::get().len();
119+
min_collators.saturating_sub(candidates_length.try_into().unwrap())
120+
}
121+
109122
benchmarks! {
110123
where_clause { where T: pallet_authorship::Config + session::Config }
111124

@@ -128,34 +141,67 @@ benchmarks! {
128141
}
129142

130143
add_invulnerable {
144+
let b in 1 .. T::MaxInvulnerables::get() - 1;
145+
let c in 1 .. T::MaxCandidates::get() - 1;
146+
131147
let origin =
132148
T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
133-
// we're going to add one, so need one less than max set as invulnerables to start
134-
let b in 1 .. T::MaxInvulnerables::get() - 1;
149+
150+
// need to fill up candidates
151+
<CandidacyBond<T>>::put(T::Currency::minimum_balance());
152+
<DesiredCandidates<T>>::put(c);
153+
// get accounts and keys for the `c` candidates
154+
let mut candidates = (0..c).map(|cc| validator::<T>(cc)).collect::<Vec<_>>();
155+
// add one more to the list. should not be in `b` (invulnerables) because it's the account
156+
// we will _add_ to invulnerables. we want it to be in `candidates` because we need the
157+
// weight associated with removing it.
158+
let (new_invulnerable, new_invulnerable_keys) = validator::<T>(b.max(c) + 1);
159+
candidates.push((new_invulnerable.clone(), new_invulnerable_keys));
160+
// set their keys ...
161+
for (who, keys) in candidates.clone() {
162+
<session::Pallet<T>>::set_keys(RawOrigin::Signed(who).into(), keys, Vec::new()).unwrap();
163+
}
164+
// ... and register them.
165+
for (who, _) in candidates {
166+
let deposit = <CandidacyBond<T>>::get();
167+
T::Currency::make_free_balance_be(&who, deposit * 1000_u32.into());
168+
let incoming = CandidateInfo { who: who.clone(), deposit };
169+
<Candidates<T>>::try_mutate(|candidates| -> DispatchResult {
170+
if !candidates.iter().any(|candidate| candidate.who == who) {
171+
T::Currency::reserve(&who, deposit)?;
172+
candidates.try_push(incoming).expect("we've respected the bounded vec limit");
173+
<LastAuthoredBlock<T>>::insert(
174+
who.clone(),
175+
frame_system::Pallet::<T>::block_number() + T::KickThreshold::get(),
176+
);
177+
}
178+
Ok(())
179+
}).expect("only returns ok");
180+
}
181+
182+
// now we need to fill up invulnerables
135183
let mut invulnerables = register_validators::<T>(b);
136184
invulnerables.sort();
137-
let invulnerables: frame_support::BoundedVec<_, T::MaxInvulnerables> = frame_support::BoundedVec::try_from(invulnerables).unwrap();
185+
let invulnerables: frame_support::BoundedVec<_, T::MaxInvulnerables> =
186+
frame_support::BoundedVec::try_from(invulnerables).unwrap();
138187
<Invulnerables<T>>::put(invulnerables);
139-
140-
// now let's set up a new one to add
141-
let (new, keys) = validator::<T>(b + 1);
142-
<session::Pallet<T>>::set_keys(RawOrigin::Signed(new.clone()).into(), keys, Vec::new()).unwrap();
143188
}: {
144189
assert_ok!(
145-
<CollatorSelection<T>>::add_invulnerable(origin, new.clone())
190+
<CollatorSelection<T>>::add_invulnerable(origin, new_invulnerable.clone())
146191
);
147192
}
148193
verify {
149-
assert_last_event::<T>(Event::InvulnerableAdded{account_id: new}.into());
194+
assert_last_event::<T>(Event::InvulnerableAdded{account_id: new_invulnerable}.into());
150195
}
151196

152197
remove_invulnerable {
153198
let origin =
154199
T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
155-
let b in 1 .. T::MaxInvulnerables::get();
200+
let b in (min_invulnerables::<T>() + 1) .. T::MaxInvulnerables::get();
156201
let mut invulnerables = register_validators::<T>(b);
157202
invulnerables.sort();
158-
let invulnerables: frame_support::BoundedVec<_, T::MaxInvulnerables> = frame_support::BoundedVec::try_from(invulnerables).unwrap();
203+
let invulnerables: frame_support::BoundedVec<_, T::MaxInvulnerables> =
204+
frame_support::BoundedVec::try_from(invulnerables).unwrap();
159205
<Invulnerables<T>>::put(invulnerables);
160206
let to_remove = <Invulnerables<T>>::get().first().unwrap().clone();
161207
}: {
@@ -221,7 +267,7 @@ benchmarks! {
221267

222268
// worse case is the last candidate leaving.
223269
leave_intent {
224-
let c in (T::MinCandidates::get() + 1) .. T::MaxCandidates::get();
270+
let c in (min_candidates::<T>() + 1) .. T::MaxCandidates::get();
225271
<CandidacyBond<T>>::put(T::Currency::minimum_balance());
226272
<DesiredCandidates<T>>::put(c);
227273

@@ -286,6 +332,7 @@ benchmarks! {
286332
}
287333
}
288334

335+
let min_candidates = min_candidates::<T>();
289336
let pre_length = <Candidates<T>>::get().len();
290337

291338
frame_system::Pallet::<T>::set_block_number(new_block);
@@ -294,11 +341,19 @@ benchmarks! {
294341
}: {
295342
<CollatorSelection<T> as SessionManager<_>>::new_session(0)
296343
} verify {
297-
if c > r && non_removals >= T::MinCandidates::get() {
344+
if c > r && non_removals >= min_candidates {
345+
// candidates > removals and remaining candidates > min candidates
346+
// => remaining candidates should be shorter than before removal, i.e. some were
347+
// actually removed.
298348
assert!(<Candidates<T>>::get().len() < pre_length);
299-
} else if c > r && non_removals < T::MinCandidates::get() {
300-
assert!(<Candidates<T>>::get().len() == T::MinCandidates::get() as usize);
349+
} else if c > r && non_removals < min_candidates {
350+
// candidates > removals and remaining candidates would be less than min candidates
351+
// => remaining candidates should equal min candidates, i.e. some were removed up to
352+
// the minimum, but then any more were "forced" to stay in candidates.
353+
assert!(<Candidates<T>>::get().len() == min_candidates as usize);
301354
} else {
355+
// removals >= candidates, non removals must == 0
356+
// can't remove more than exist
302357
assert!(<Candidates<T>>::get().len() == pre_length);
303358
}
304359
}

0 commit comments

Comments
 (0)