Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add simple collator election mechanism #1340

Merged
merged 44 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
e092644
Introduce simple election to `collator-selection`
georgepisaltu Aug 31, 2023
00a3671
Add new weights fn to runtimes
georgepisaltu Sep 1, 2023
dde5418
Merge remote-tracking branch 'origin' into george/collator-election
georgepisaltu Sep 1, 2023
ea0b269
Update `collator-selection` documentation
georgepisaltu Sep 4, 2023
6f1869e
Merge remote-tracking branch 'origin/master' into george/collator-ele…
georgepisaltu Sep 4, 2023
5526ea2
Minor code and docs cleanup
georgepisaltu Sep 12, 2023
06b3c79
Fix doc line wrapping
georgepisaltu Sep 12, 2023
c55c8e2
Fix leftover documentation
georgepisaltu Sep 21, 2023
24cae01
Update docs around unchecked arithmetic
georgepisaltu Sep 21, 2023
21988be
Add docs and test for unwraps
georgepisaltu Sep 21, 2023
5a5010e
Merge remote-tracking branch 'origin/master' into george/collator-ele…
georgepisaltu Sep 21, 2023
a95d831
Allow clippy absurd comparison in integrity test
georgepisaltu Sep 21, 2023
06ac94a
Fix placement of clippy allow statement
georgepisaltu Sep 21, 2023
8d7a635
Address review comments
georgepisaltu Oct 12, 2023
b2985fc
Merge remote-tracking branch 'origin' into george/collator-election
georgepisaltu Oct 12, 2023
ff46963
Add test for identical deposit update
georgepisaltu Oct 12, 2023
a2bcc35
Merge remote-tracking branch 'origin' into george/collator-election
georgepisaltu Oct 13, 2023
e1c458e
Merge remote-tracking branch 'origin' into george/collator-election
georgepisaltu Oct 27, 2023
b0747d5
Disallow lowering bids for top candidates
georgepisaltu Oct 27, 2023
1c7883a
Merge remote-tracking branch 'origin' into george/collator-election
georgepisaltu Oct 30, 2023
967429d
Add weights for asset-hub-rococo
georgepisaltu Oct 30, 2023
dea9465
Refactor list pivots into separate functions
georgepisaltu Nov 6, 2023
fd146da
Remove unnecessary integrity test assertions
georgepisaltu Nov 6, 2023
0fa7fce
Add `try-state` checks
georgepisaltu Nov 6, 2023
76c02a1
Add ordering doc comment to candidate list
georgepisaltu Nov 6, 2023
24c90cd
Merge remote-tracking branch 'origin' into george/collator-election
georgepisaltu Nov 6, 2023
1254351
Add weights for bridge hub westend
georgepisaltu Nov 6, 2023
8ac84da
WIP set desired candidates
georgepisaltu Nov 9, 2023
2ce5275
WIP set candidacy bond v2
georgepisaltu Nov 9, 2023
49c8fe5
Add tests to new `set_candidacy_bond`
georgepisaltu Nov 9, 2023
27788ac
Small refactor in `set_candidacy_bond`
georgepisaltu Nov 9, 2023
43d8251
Update `set_candidacy_bond` documentation
georgepisaltu Nov 9, 2023
0ecc480
Fixup kick candidates comment
georgepisaltu Nov 9, 2023
bb686f9
Refactor out pivot functions
georgepisaltu Nov 9, 2023
58653af
Fix weights for new `set_candidacy_bond`
georgepisaltu Nov 9, 2023
1a54df2
Make `set_candidacy_bond` bench work
georgepisaltu Nov 9, 2023
4a7beba
Refine `set_candidacy_bond` benchmark
georgepisaltu Nov 9, 2023
bba8573
Fix clippy warning in bench
georgepisaltu Nov 10, 2023
772be92
Merge remote-tracking branch 'origin' into george/collator-election
georgepisaltu Nov 10, 2023
4c14458
Remove duplicated `ensure`
georgepisaltu Nov 13, 2023
524a5aa
Merge remote-tracking branch 'origin' into george/collator-election
georgepisaltu Nov 13, 2023
35b4b02
Graceful error instead of panic on `try_insert`
georgepisaltu Nov 13, 2023
12dccf4
Update pallet documentation
georgepisaltu Nov 13, 2023
328888a
Merge remote-tracking branch 'origin' into george/collator-election
georgepisaltu Nov 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 118 additions & 35 deletions cumulus/pallets/collator-selection/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,11 @@ use codec::Decode;
use frame_benchmarking::{
account, impl_benchmark_test_suite, v2::*, whitelisted_caller, BenchmarkError,
};
use frame_support::{
dispatch::DispatchResult,
traits::{Currency, EnsureOrigin, Get, ReservableCurrency},
};
use frame_support::traits::{Currency, EnsureOrigin, Get, ReservableCurrency};
use frame_system::{pallet_prelude::BlockNumberFor, EventRecord, RawOrigin};
use pallet_authorship::EventHandler;
use pallet_session::{self as session, SessionManager};
use sp_std::prelude::*;
use sp_std::{cmp, prelude::*};

pub type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
Expand Down Expand Up @@ -94,7 +91,7 @@ fn register_candidates<T: Config>(count: u32) {
assert!(<CandidacyBond<T>>::get() > 0u32.into(), "Bond cannot be zero!");

for who in candidates {
T::Currency::make_free_balance_be(&who, <CandidacyBond<T>>::get() * 2u32.into());
T::Currency::make_free_balance_be(&who, <CandidacyBond<T>>::get() * 3u32.into());
<CollatorSelection<T>>::register_as_candidate(RawOrigin::Signed(who).into()).unwrap();
}
}
Expand All @@ -107,8 +104,11 @@ fn min_candidates<T: Config>() -> u32 {

fn min_invulnerables<T: Config>() -> u32 {
let min_collators = T::MinEligibleCollators::get();
let candidates_length = <Candidates<T>>::get().len();
min_collators.saturating_sub(candidates_length.try_into().unwrap())
let candidates_length = <CandidateList<T>>::decode_len()
.unwrap_or_default()
.try_into()
.unwrap_or_default();
min_collators.saturating_sub(candidates_length)
}

#[benchmarks(where T: pallet_authorship::Config + session::Config)]
Expand Down Expand Up @@ -160,22 +160,19 @@ mod benchmarks {
.unwrap();
}
// ... and register them.
for (who, _) in candidates {
for (who, _) in candidates.iter() {
let deposit = <CandidacyBond<T>>::get();
T::Currency::make_free_balance_be(&who, deposit * 1000_u32.into());
let incoming = CandidateInfo { who: who.clone(), deposit };
<Candidates<T>>::try_mutate(|candidates| -> DispatchResult {
if !candidates.iter().any(|candidate| candidate.who == who) {
T::Currency::reserve(&who, deposit)?;
candidates.try_push(incoming).expect("we've respected the bounded vec limit");
<LastAuthoredBlock<T>>::insert(
who.clone(),
frame_system::Pallet::<T>::block_number() + T::KickThreshold::get(),
);
}
Ok(())
T::Currency::make_free_balance_be(who, deposit * 1000_u32.into());
<CandidateList<T>>::try_mutate(|list| {
list.try_push(CandidateInfo { who: who.clone(), deposit }).unwrap();
Ok::<(), BenchmarkError>(())
})
.expect("only returns ok");
.unwrap();
T::Currency::reserve(who, deposit)?;
<LastAuthoredBlock<T>>::insert(
who.clone(),
frame_system::Pallet::<T>::block_number() + T::KickThreshold::get(),
);
}

// now we need to fill up invulnerables
Expand Down Expand Up @@ -226,10 +223,27 @@ mod benchmarks {
}

#[benchmark]
fn set_candidacy_bond() -> Result<(), BenchmarkError> {
let bond_amount: BalanceOf<T> = T::Currency::minimum_balance() * 10u32.into();
fn set_candidacy_bond(
c: Linear<0, { T::MaxCandidates::get() }>,
k: Linear<0, { T::MaxCandidates::get() }>,
) -> Result<(), BenchmarkError> {
let initial_bond_amount: BalanceOf<T> = T::Currency::minimum_balance() * 2u32.into();
<CandidacyBond<T>>::put(initial_bond_amount);
register_validators::<T>(c);
register_candidates::<T>(c);
let kicked = cmp::min(k, c);
let origin =
T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
let bond_amount = if k > 0 {
<CandidateList<T>>::mutate(|candidates| {
for info in candidates.iter_mut().skip(kicked as usize) {
info.deposit = T::Currency::minimum_balance() * 3u32.into();
}
});
T::Currency::minimum_balance() * 3u32.into()
} else {
T::Currency::minimum_balance()
};

#[extrinsic_call]
_(origin as T::RuntimeOrigin, bond_amount);
Expand All @@ -238,6 +252,35 @@ mod benchmarks {
Ok(())
}

#[benchmark]
fn update_bond(
c: Linear<{ min_candidates::<T>() + 1 }, { T::MaxCandidates::get() }>,
) -> Result<(), BenchmarkError> {
<CandidacyBond<T>>::put(T::Currency::minimum_balance());
<DesiredCandidates<T>>::put(c);

register_validators::<T>(c);
register_candidates::<T>(c);

let caller = <CandidateList<T>>::get()[0].who.clone();
v2::whitelist!(caller);

let bond_amount: BalanceOf<T> =
T::Currency::minimum_balance() + T::Currency::minimum_balance();

#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), bond_amount);

assert_last_event::<T>(
Event::CandidateBondUpdated { account_id: caller, deposit: bond_amount }.into(),
);
assert!(
<CandidateList<T>>::get().iter().last().unwrap().deposit ==
T::Currency::minimum_balance() * 2u32.into()
);
Ok(())
}

// worse case is when we have all the max-candidate slots filled except one, and we fill that
// one.
#[benchmark]
Expand Down Expand Up @@ -267,6 +310,36 @@ mod benchmarks {
);
}

#[benchmark]
fn take_candidate_slot(c: Linear<{ min_candidates::<T>() + 1 }, { T::MaxCandidates::get() }>) {
<CandidacyBond<T>>::put(T::Currency::minimum_balance());
<DesiredCandidates<T>>::put(1);

register_validators::<T>(c);
register_candidates::<T>(c);

let caller: T::AccountId = whitelisted_caller();
let bond: BalanceOf<T> = T::Currency::minimum_balance() * 10u32.into();
T::Currency::make_free_balance_be(&caller, bond);

<session::Pallet<T>>::set_keys(
RawOrigin::Signed(caller.clone()).into(),
keys::<T>(c + 1),
Vec::new(),
)
.unwrap();

let target = <CandidateList<T>>::get().iter().last().unwrap().who.clone();

#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), bond / 2u32.into(), target.clone());

assert_last_event::<T>(
Event::CandidateReplaced { old: target, new: caller, deposit: bond / 2u32.into() }
.into(),
);
}

// worse case is the last candidate leaving.
#[benchmark]
fn leave_intent(c: Linear<{ min_candidates::<T>() + 1 }, { T::MaxCandidates::get() }>) {
Expand All @@ -276,7 +349,7 @@ mod benchmarks {
register_validators::<T>(c);
register_candidates::<T>(c);

let leaving = <Candidates<T>>::get().last().unwrap().who.clone();
let leaving = <CandidateList<T>>::get().iter().last().unwrap().who.clone();
v2::whitelist!(leaving);

#[extrinsic_call]
Expand Down Expand Up @@ -323,31 +396,37 @@ mod benchmarks {

let new_block: BlockNumberFor<T> = 1800u32.into();
let zero_block: BlockNumberFor<T> = 0u32.into();
let candidates = <Candidates<T>>::get();
let candidates: Vec<T::AccountId> = <CandidateList<T>>::get()
.iter()
.map(|candidate_info| candidate_info.who.clone())
.collect();

let non_removals = c.saturating_sub(r);

for i in 0..c {
<LastAuthoredBlock<T>>::insert(candidates[i as usize].who.clone(), zero_block);
<LastAuthoredBlock<T>>::insert(candidates[i as usize].clone(), zero_block);
}

if non_removals > 0 {
for i in 0..non_removals {
<LastAuthoredBlock<T>>::insert(candidates[i as usize].who.clone(), new_block);
<LastAuthoredBlock<T>>::insert(candidates[i as usize].clone(), new_block);
}
} else {
for i in 0..c {
<LastAuthoredBlock<T>>::insert(candidates[i as usize].who.clone(), new_block);
<LastAuthoredBlock<T>>::insert(candidates[i as usize].clone(), new_block);
}
}

let min_candidates = min_candidates::<T>();
let pre_length = <Candidates<T>>::get().len();
let pre_length = <CandidateList<T>>::decode_len().unwrap_or_default();

frame_system::Pallet::<T>::set_block_number(new_block);

assert!(<Candidates<T>>::get().len() == c as usize);

let current_length: u32 = <CandidateList<T>>::decode_len()
.unwrap_or_default()
.try_into()
.unwrap_or_default();
assert!(c == current_length);
#[block]
{
<CollatorSelection<T> as SessionManager<_>>::new_session(0);
Expand All @@ -357,16 +436,20 @@ mod benchmarks {
// candidates > removals and remaining candidates > min candidates
// => remaining candidates should be shorter than before removal, i.e. some were
// actually removed.
assert!(<Candidates<T>>::get().len() < pre_length);
assert!(<CandidateList<T>>::decode_len().unwrap_or_default() < pre_length);
} else if c > r && non_removals < min_candidates {
// candidates > removals and remaining candidates would be less than min candidates
// => remaining candidates should equal min candidates, i.e. some were removed up to
// the minimum, but then any more were "forced" to stay in candidates.
assert!(<Candidates<T>>::get().len() == min_candidates as usize);
let current_length: u32 = <CandidateList<T>>::decode_len()
.unwrap_or_default()
.try_into()
.unwrap_or_default();
assert!(min_candidates == current_length);
} else {
// removals >= candidates, non removals must == 0
// can't remove more than exist
assert!(<Candidates<T>>::get().len() == pre_length);
assert!(<CandidateList<T>>::decode_len().unwrap_or_default() == pre_length);
}
}

Expand Down
Loading