diff --git a/Cargo.lock b/Cargo.lock index d9223cabd7f..e9fdbda5522 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7200,11 +7200,13 @@ name = "pallet-collator-selection" version = "3.0.0" dependencies = [ "frame-benchmarking", + "frame-election-provider-support", "frame-support", "frame-system", "log", "pallet-aura", "pallet-authorship", + "pallet-bags-list", "pallet-balances", "pallet-session", "pallet-timestamp", @@ -8300,6 +8302,7 @@ dependencies = [ "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", + "frame-election-provider-support", "frame-executive", "frame-support", "frame-system", @@ -8310,10 +8313,12 @@ dependencies = [ "log", "pallet-aura", "pallet-authorship", + "pallet-bags-list", "pallet-balances", "pallet-collator-selection", "pallet-parachain-template", "pallet-session", + "pallet-staking", "pallet-sudo", "pallet-timestamp", "pallet-transaction-payment", diff --git a/pallets/collator-selection/Cargo.toml b/pallets/collator-selection/Cargo.toml index dbb7f452162..b3a49854950 100644 --- a/pallets/collator-selection/Cargo.toml +++ b/pallets/collator-selection/Cargo.toml @@ -21,6 +21,7 @@ scale-info = { version = "2.9.0", default-features = false, features = ["derive" sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "master" } sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "master" } sp-staking = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "master" } +frame-election-provider-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "master" } frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "master" } frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "master" } pallet-authorship = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "master" } @@ -35,6 +36,7 @@ sp-tracing = { git = "https://github.com/paritytech/substrate", branch = "master sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-consensus-aura = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-bags-list = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" } pallet-aura = { git = "https://github.com/paritytech/substrate", branch = "master" } @@ -42,6 +44,7 @@ pallet-aura = { git = "https://github.com/paritytech/substrate", branch = "maste default = ["std"] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", + "frame-election-provider-support/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] @@ -53,6 +56,7 @@ std = [ "sp-runtime/std", "sp-staking/std", "sp-std/std", + "frame-election-provider-support/std", "frame-support/std", "frame-system/std", "frame-benchmarking/std", diff --git a/pallets/collator-selection/src/benchmarking.rs b/pallets/collator-selection/src/benchmarking.rs index 5fc92f8a783..2d63eacc9d0 100644 --- a/pallets/collator-selection/src/benchmarking.rs +++ b/pallets/collator-selection/src/benchmarking.rs @@ -24,9 +24,9 @@ use crate::Pallet as CollatorSelection; use frame_benchmarking::{ account, impl_benchmark_test_suite, v2::*, whitelisted_caller, BenchmarkError, }; +use frame_election_provider_support::SortedListProvider; use frame_support::{ codec::Decode, - dispatch::DispatchResult, traits::{Currency, EnsureOrigin, Get, ReservableCurrency}, }; use frame_system::{pallet_prelude::BlockNumberFor, EventRecord, RawOrigin}; @@ -94,7 +94,8 @@ fn register_candidates(count: u32) { assert!(>::get() > 0u32.into(), "Bond cannot be zero!"); for who in candidates { - T::Currency::make_free_balance_be(&who, >::get() * 2u32.into()); + // TODO[GMP] revisit this, need it for Currency reserve in increase_bid + T::Currency::make_free_balance_be(&who, >::get() * 3u32.into()); >::register_as_candidate(RawOrigin::Signed(who).into()).unwrap(); } } @@ -107,8 +108,8 @@ fn min_candidates() -> u32 { fn min_invulnerables() -> u32 { let min_collators = T::MinEligibleCollators::get(); - let candidates_length = >::get().len(); - min_collators.saturating_sub(candidates_length.try_into().unwrap()) + let candidates_length = >::get(); + min_collators.saturating_sub(candidates_length) } #[benchmarks(where T: pallet_authorship::Config + session::Config)] @@ -163,19 +164,13 @@ mod benchmarks { for (who, _) in candidates { let deposit = >::get(); T::Currency::make_free_balance_be(&who, deposit * 1000_u32.into()); - let incoming = CandidateInfo { who: who.clone(), deposit }; - >::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"); - >::insert( - who.clone(), - frame_system::Pallet::::block_number() + T::KickThreshold::get(), - ); - } - Ok(()) - }) - .expect("only returns ok"); + >::insert(who.clone(), deposit); + T::CandidateList::on_insert(who.clone(), deposit).unwrap(); + T::Currency::reserve(&who, deposit)?; + >::insert( + who.clone(), + frame_system::Pallet::::block_number() + T::KickThreshold::get(), + ); } // now we need to fill up invulnerables @@ -238,6 +233,73 @@ mod benchmarks { Ok(()) } + #[benchmark] + fn increase_bond( + c: Linear<{ min_candidates::() + 1 }, { T::MaxCandidates::get() }>, + ) -> Result<(), BenchmarkError> { + >::put(T::Currency::minimum_balance()); + >::put(c); + + register_validators::(c); + register_candidates::(c); + + let caller = >::iter().last().unwrap().0.clone(); + v2::whitelist!(caller); + + let bond_amount: BalanceOf = T::Currency::minimum_balance(); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), bond_amount); + + assert_last_event::( + Event::CandidateBondIncreased { + account_id: caller, + deposit: T::Currency::minimum_balance() + bond_amount, + } + .into(), + ); + assert!( + >::iter().last().unwrap().1 == + T::Currency::minimum_balance() * 2u32.into() + ); + Ok(()) + } + + #[benchmark] + fn decrease_bond( + c: Linear<{ min_candidates::() + 1 }, { T::MaxCandidates::get() }>, + ) -> Result<(), BenchmarkError> { + >::put(T::Currency::minimum_balance()); + >::put(c); + + register_validators::(c); + register_candidates::(c); + + let caller = >::iter().last().unwrap().0.clone(); + v2::whitelist!(caller); + + >::increase_bond( + RawOrigin::Signed(caller.clone()).into(), + >::get(), + ) + .unwrap(); + + let bond_amount: BalanceOf = T::Currency::minimum_balance(); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), bond_amount); + + assert_last_event::( + Event::CandidateBondDecreased { + account_id: caller, + deposit: T::Currency::minimum_balance(), + } + .into(), + ); + assert!(>::iter().last().unwrap().1 == T::Currency::minimum_balance()); + Ok(()) + } + // worse case is when we have all the max-candidate slots filled except one, and we fill that // one. #[benchmark] @@ -267,6 +329,36 @@ mod benchmarks { ); } + #[benchmark] + fn take_candidate_slot(c: Linear<{ min_candidates::() + 1 }, { T::MaxCandidates::get() }>) { + >::put(T::Currency::minimum_balance()); + >::put(1); + + register_validators::(c); + register_candidates::(c); + + let caller: T::AccountId = whitelisted_caller(); + let bond: BalanceOf = T::Currency::minimum_balance() * 10u32.into(); + T::Currency::make_free_balance_be(&caller, bond); + + >::set_keys( + RawOrigin::Signed(caller.clone()).into(), + keys::(c + 1), + Vec::new(), + ) + .unwrap(); + + let target = >::iter().last().unwrap().0.clone(); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), bond / 2u32.into(), target.clone()); + + assert_last_event::( + 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::() + 1 }, { T::MaxCandidates::get() }>) { @@ -276,7 +368,7 @@ mod benchmarks { register_validators::(c); register_candidates::(c); - let leaving = >::get().last().unwrap().who.clone(); + let leaving = >::iter().last().unwrap().0.clone(); v2::whitelist!(leaving); #[extrinsic_call] @@ -323,30 +415,31 @@ mod benchmarks { let new_block: BlockNumberFor = 1800u32.into(); let zero_block: BlockNumberFor = 0u32.into(); - let candidates = >::get(); + let candidates: Vec = >::iter().map(|(who, _)| who).collect(); let non_removals = c.saturating_sub(r); for i in 0..c { - >::insert(candidates[i as usize].who.clone(), zero_block); + >::insert(candidates[i as usize].clone(), zero_block); } if non_removals > 0 { for i in 0..non_removals { - >::insert(candidates[i as usize].who.clone(), new_block); + >::insert(candidates[i as usize].clone(), new_block); } } else { for i in 0..c { - >::insert(candidates[i as usize].who.clone(), new_block); + >::insert(candidates[i as usize].clone(), new_block); } } let min_candidates = min_candidates::(); - let pre_length = >::get().len(); + let pre_length = >::get(); + assert!(pre_length as usize == >::iter().count()); frame_system::Pallet::::set_block_number(new_block); - assert!(>::get().len() == c as usize); + assert!(>::get() == c); #[block] { @@ -357,16 +450,16 @@ mod benchmarks { // candidates > removals and remaining candidates > min candidates // => remaining candidates should be shorter than before removal, i.e. some were // actually removed. - assert!(>::get().len() < pre_length); + assert!(>::get() < 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!(>::get().len() == min_candidates as usize); + assert!(>::get() == min_candidates); } else { // removals >= candidates, non removals must == 0 // can't remove more than exist - assert!(>::get().len() == pre_length); + assert!(>::get() == pre_length); } } diff --git a/pallets/collator-selection/src/lib.rs b/pallets/collator-selection/src/lib.rs index 539a4d8bd95..5cf62b49e57 100644 --- a/pallets/collator-selection/src/lib.rs +++ b/pallets/collator-selection/src/lib.rs @@ -80,6 +80,7 @@ const LOG_TARGET: &str = "runtime::collator-selection"; pub mod pallet { pub use crate::weights::WeightInfo; use core::ops::Div; + use frame_election_provider_support::{ScoreProvider, SortedListProvider}; use frame_support::{ dispatch::{DispatchClass, DispatchResultWithPostInfo}, pallet_prelude::*, @@ -142,6 +143,8 @@ pub mod pallet { /// Maximum number of invulnerables. type MaxInvulnerables: Get; + type CandidateList: SortedListProvider>; + // Will be kicked if block is not produced in threshold. type KickThreshold: Get>; @@ -181,15 +184,19 @@ pub mod pallet { pub type Invulnerables = StorageValue<_, BoundedVec, ValueQuery>; + /// Current number of candidates in the `Candidates` map. + /// + /// This must always be less than [`Config::MaxCandidates`]. + #[pallet::storage] + #[pallet::getter(fn candidate_count)] + pub type CandidateCount = StorageValue<_, u32, ValueQuery>; + /// The (community, limited) collation candidates. `Candidates` and `Invulnerables` should be /// mutually exclusive. #[pallet::storage] #[pallet::getter(fn candidates)] - pub type Candidates = StorageValue< - _, - BoundedVec>, T::MaxCandidates>, - ValueQuery, - >; + pub type Candidates = + StorageMap<_, Identity, T::AccountId, BalanceOf, ValueQuery>; /// Last block authored by collator. #[pallet::storage] @@ -262,8 +269,14 @@ pub mod pallet { NewCandidacyBond { bond_amount: BalanceOf }, /// A new candidate joined. CandidateAdded { account_id: T::AccountId, deposit: BalanceOf }, + /// Bond of a candidate increased. + CandidateBondIncreased { account_id: T::AccountId, deposit: BalanceOf }, + /// Bond of a candidate decreased. + CandidateBondDecreased { account_id: T::AccountId, deposit: BalanceOf }, /// A candidate was removed. CandidateRemoved { account_id: T::AccountId }, + /// An account was replaced in the candidate list by another one. + CandidateReplaced { old: T::AccountId, new: T::AccountId, deposit: BalanceOf }, /// An account was unable to be added to the Invulnerables because they did not have keys /// registered. Other Invulnerables may have been set. InvalidInvulnerableSkipped { account_id: T::AccountId }, @@ -289,6 +302,20 @@ pub mod pallet { NoAssociatedValidatorId, /// Validator ID is not yet registered. ValidatorNotRegistered, + /// Could not insert in the candidate list. + InsertToCandidateListFailed, + /// Could not remove from the candidate list. + RemoveFromCandidateListFailed, + /// New deposit amount would be below the minimum candidacy bond. + DepositTooLow, + /// Could not update the candidate list. + UpdateCandidateListFailed, + /// Deposit amount is too low to take the target's slot in the + /// candidate list. + InsufficientBond, + /// The target account to be replaced in the candidate list is not a + /// candidate. + TargetIsNotCandidate, } #[pallet::hooks] @@ -321,8 +348,7 @@ pub mod pallet { // don't wipe out the collator set if new.is_empty() { ensure!( - Candidates::::decode_len().unwrap_or_default() >= - T::MinEligibleCollators::get() as usize, + CandidateCount::::get() >= T::MinEligibleCollators::get(), Error::::TooFewEligibleCollators ); } @@ -425,8 +451,8 @@ pub mod pallet { let who = ensure_signed(origin)?; // ensure we are below limit. - let length = >::decode_len().unwrap_or_default(); - ensure!((length as u32) < Self::desired_candidates(), Error::::TooManyCandidates); + let length = >::get(); + ensure!(length < T::MaxCandidates::get(), Error::::TooManyCandidates); ensure!(!Self::invulnerables().contains(&who), Error::::AlreadyInvulnerable); let validator_key = T::ValidatorIdOf::convert(who.clone()) @@ -438,25 +464,26 @@ pub mod pallet { let deposit = Self::candidacy_bond(); // First authored block is current block plus kick threshold to handle session delay - let incoming = CandidateInfo { who: who.clone(), deposit }; - - let current_count = - >::try_mutate(|candidates| -> Result { - if candidates.iter().any(|candidate| candidate.who == who) { - Err(Error::::AlreadyCandidate)? - } else { - T::Currency::reserve(&who, deposit)?; - candidates.try_push(incoming).map_err(|_| Error::::TooManyCandidates)?; - >::insert( - who.clone(), - frame_system::Pallet::::block_number() + T::KickThreshold::get(), - ); - Ok(candidates.len()) - } - })?; + let new_length = >::try_mutate_exists( + &who, + |maybe_bond| -> Result { + ensure!(maybe_bond.is_none(), Error::::AlreadyCandidate); + T::Currency::reserve(&who, deposit)?; + *maybe_bond = Some(deposit); + >::insert( + who.clone(), + frame_system::Pallet::::block_number() + T::KickThreshold::get(), + ); + T::CandidateList::on_insert(who.clone(), deposit) + .map_err(|_| Error::::InsertToCandidateListFailed)?; + let new_length = length + 1; + >::put(new_length); + Ok(new_length) + }, + )?; Self::deposit_event(Event::CandidateAdded { account_id: who, deposit }); - Ok(Some(T::WeightInfo::register_as_candidate(current_count as u32)).into()) + Ok(Some(T::WeightInfo::register_as_candidate(new_length)).into()) } /// Deregister `origin` as a collator candidate. Note that the collator can only leave on @@ -469,13 +496,14 @@ pub mod pallet { pub fn leave_intent(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; ensure!( - Self::eligible_collators() > T::MinEligibleCollators::get() as usize, + Self::eligible_collators() > T::MinEligibleCollators::get(), Error::::TooFewEligibleCollators ); + let length = >::get(); // Do remove their last authored block. - let current_count = Self::try_remove_candidate(&who, true)?; + Self::try_remove_candidate(&who, true)?; - Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into()) + Ok(Some(T::WeightInfo::leave_intent(length.saturating_sub(1) as u32)).into()) } /// Add a new account `who` to the list of `Invulnerables` collators. `who` must have @@ -522,10 +550,7 @@ pub mod pallet { .unwrap_or_default() .try_into() .unwrap_or(T::MaxInvulnerables::get().saturating_sub(1)), - Candidates::::decode_len() - .unwrap_or_default() - .try_into() - .unwrap_or(T::MaxCandidates::get()), + >::get(), ); Ok(Some(weight_used).into()) @@ -541,7 +566,7 @@ pub mod pallet { T::UpdateOrigin::ensure_origin(origin)?; ensure!( - Self::eligible_collators() > T::MinEligibleCollators::get() as usize, + Self::eligible_collators() > T::MinEligibleCollators::get(), Error::::TooFewEligibleCollators ); @@ -555,6 +580,134 @@ pub mod pallet { Self::deposit_event(Event::InvulnerableRemoved { account_id: who }); Ok(()) } + + /// Increase the candidacy bond of collator candidate `origin` by an + /// `amount`. + /// + /// This call will fail if `origin` is not a collator candidate and/or + /// the amount cannot be reserved. + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::increase_bond(T::MaxCandidates::get()))] + pub fn increase_bond(origin: OriginFor, amount: BalanceOf) -> DispatchResult { + let who = ensure_signed(origin)?; + // The function below will try to mutate the `Candidates` entry for + // the caller to increase their bond by `amount`. The return value + // is a tuple of the position of the entry in the list, used for + // weight calculation, and the new bonded amount. + let new_amount = >::try_mutate_exists( + &who, + |maybe_deposit| -> Result, DispatchError> { + let new_deposit = maybe_deposit + .ok_or_else(|| Error::::NotCandidate)? + .saturating_add(amount); + *maybe_deposit = Some(new_deposit); + T::Currency::reserve(&who, amount)?; + T::CandidateList::on_increase(&who, amount) + .map_err(|_| Error::::UpdateCandidateListFailed)?; + Ok(new_deposit) + }, + )?; + + Self::deposit_event(Event::CandidateBondIncreased { + account_id: who, + deposit: new_amount, + }); + Ok(()) + } + + /// Decrease the candidacy bond of collator candidate `origin` by an + /// `amount`. + /// + /// This call will fail if `origin` is not a collator candidate and/or + /// the remaining bond is lower than the minimum candidacy bond. + #[pallet::call_index(8)] + #[pallet::weight(T::WeightInfo::decrease_bond(T::MaxCandidates::get()))] + pub fn decrease_bond(origin: OriginFor, amount: BalanceOf) -> DispatchResult { + let who = ensure_signed(origin)?; + // The function below will try to mutate the `Candidates` entry for + // the caller to decrease their bond by `amount`. The return value + // is a tuple of the position of the entry in the list, used for + // weight calculation, and the new bonded amount. + let new_amount = >::try_mutate_exists( + &who, + |maybe_deposit| -> Result, DispatchError> { + let new_deposit = maybe_deposit + .ok_or_else(|| Error::::NotCandidate)? + .saturating_sub(amount); + ensure!(new_deposit >= >::get(), Error::::DepositTooLow); + *maybe_deposit = Some(new_deposit); + T::Currency::unreserve(&who, amount); + T::CandidateList::on_decrease(&who, amount) + .map_err(|_| Error::::UpdateCandidateListFailed)?; + Ok(new_deposit) + }, + )?; + + Self::deposit_event(Event::CandidateBondDecreased { + account_id: who, + deposit: new_amount, + }); + Ok(()) + } + + /// The caller `origin` replaces a candidate `target` in the collator + /// candidate list by reserving `deposit`. The amount `deposit` + /// reserved by the caller must be greater than the existing bond of + /// the target it is trying to replace. + /// + /// This call will fail if the caller is already a collator candidate + /// or invulnerable, the caller does not have registered session keys, + /// the target is not a collator candidate, and/or the `deposit` amount + /// cannot be reserved. + #[pallet::call_index(9)] + #[pallet::weight(T::WeightInfo::take_candidate_slot(T::MaxCandidates::get()))] + pub fn take_candidate_slot( + origin: OriginFor, + deposit: BalanceOf, + target: T::AccountId, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + ensure!(!Self::invulnerables().contains(&who), Error::::AlreadyInvulnerable); + ensure!(deposit >= Self::candidacy_bond(), Error::::InsufficientBond); + ensure!(!>::contains_key(&who), Error::::AlreadyCandidate); + + let validator_key = T::ValidatorIdOf::convert(who.clone()) + .ok_or(Error::::NoAssociatedValidatorId)?; + ensure!( + T::ValidatorRegistration::is_registered(&validator_key), + Error::::ValidatorNotRegistered + ); + + ensure!(deposit >= Self::candidacy_bond(), Error::::InsufficientBond); + + let length = >::get(); + let target_deposit = >::try_mutate_exists( + &target, + |maybe_target_deposit| -> Result, DispatchError> { + let target_deposit = + maybe_target_deposit.ok_or(Error::::TargetIsNotCandidate)?; + T::Currency::unreserve(&target, target_deposit); + T::CandidateList::on_remove(&target) + .map_err(|_| Error::::RemoveFromCandidateListFailed)?; + >::remove(target.clone()); + *maybe_target_deposit = None; + Ok(target_deposit) + }, + )?; + ensure!(deposit > target_deposit, Error::::InsufficientBond); + T::Currency::reserve(&who, deposit)?; + >::insert(&who, deposit); + >::insert( + who.clone(), + frame_system::Pallet::::block_number() + T::KickThreshold::get(), + ); + T::CandidateList::on_insert(who.clone(), deposit) + .map_err(|_| Error::::InsertToCandidateListFailed)?; + + Self::deposit_event(Event::CandidateReplaced { old: target, new: who, deposit }); + Ok(Some(T::WeightInfo::take_candidate_slot(length)).into()) + } } impl Pallet { @@ -565,81 +718,86 @@ pub mod pallet { /// Return the total number of accounts that are eligible collators (candidates and /// invulnerables). - fn eligible_collators() -> usize { - Candidates::::decode_len() - .unwrap_or_default() - .saturating_add(Invulnerables::::decode_len().unwrap_or_default()) + fn eligible_collators() -> u32 { + >::get().saturating_add( + Invulnerables::::decode_len() + .unwrap_or_default() + .try_into() + .unwrap_or(u32::MAX), + ) } /// Removes a candidate if they exist and sends them back their deposit. fn try_remove_candidate( who: &T::AccountId, remove_last_authored: bool, - ) -> Result { - let current_count = - >::try_mutate(|candidates| -> Result { - let index = candidates - .iter() - .position(|candidate| candidate.who == *who) - .ok_or(Error::::NotCandidate)?; - let candidate = candidates.remove(index); - T::Currency::unreserve(who, candidate.deposit); + ) -> Result<(), DispatchError> { + >::try_mutate_exists( + &who, + |maybe_deposit| -> Result<(), DispatchError> { + let deposit = maybe_deposit.ok_or(Error::::NotCandidate)?; + T::Currency::unreserve(who, deposit); + *maybe_deposit = None; + T::CandidateList::on_remove(&who) + .map_err(|_| Error::::RemoveFromCandidateListFailed)?; if remove_last_authored { >::remove(who.clone()) }; - Ok(candidates.len()) - })?; + >::mutate(|length| *length = length.saturating_sub(1)); + Ok(()) + }, + )?; Self::deposit_event(Event::CandidateRemoved { account_id: who.clone() }); - Ok(current_count) + Ok(()) } /// Assemble the current set of candidates and invulnerables into the next collator set. /// /// This is done on the fly, as frequent as we are told to do so, as the session manager. - pub fn assemble_collators( - candidates: BoundedVec, - ) -> Vec { + pub fn assemble_collators() -> Vec { + let desired_candidates: usize = + >::get().try_into().unwrap_or(usize::MAX); let mut collators = Self::invulnerables().to_vec(); - collators.extend(candidates); + collators.extend(T::CandidateList::iter().take(desired_candidates)); collators } /// Kicks out candidates that did not produce a block in the kick threshold and refunds /// their deposits. - pub fn kick_stale_candidates( - candidates: BoundedVec>, T::MaxCandidates>, - ) -> BoundedVec { + /// + /// Return value is the number of candidates left in the list. + pub fn kick_stale_candidates(candidates: impl IntoIterator) -> u32 { let now = frame_system::Pallet::::block_number(); let kick_threshold = T::KickThreshold::get(); let min_collators = T::MinEligibleCollators::get(); candidates .into_iter() .filter_map(|c| { - let last_block = >::get(c.who.clone()); + let last_block = >::get(c.clone()); let since_last = now.saturating_sub(last_block); - let is_invulnerable = Self::invulnerables().contains(&c.who); + let is_invulnerable = Self::invulnerables().contains(&c); let is_lazy = since_last >= kick_threshold; if is_invulnerable { // They are invulnerable. No reason for them to be in Candidates also. // We don't even care about the min collators here, because an Account // should not be a collator twice. - let _ = Self::try_remove_candidate(&c.who, false); + let _ = Self::try_remove_candidate(&c, false); None } else { - if Self::eligible_collators() <= min_collators as usize || !is_lazy { + if Self::eligible_collators() <= min_collators || !is_lazy { // Either this is a good collator (not lazy) or we are at the minimum // that the system needs. They get to stay. - Some(c.who) + Some(c) } else { // This collator has not produced a block recently enough. Bye bye. - let _ = Self::try_remove_candidate(&c.who, true); + let _ = Self::try_remove_candidate(&c, true); None } } }) - .collect::>() + .count() .try_into() .expect("filter_map operation can't result in a bounded vec larger than its original; qed") } @@ -678,11 +836,11 @@ pub mod pallet { >::block_number(), ); - let candidates = Self::candidates(); - let candidates_len_before = candidates.len(); - let active_candidates = Self::kick_stale_candidates(candidates); - let removed = candidates_len_before - active_candidates.len(); - let result = Self::assemble_collators(active_candidates); + let candidates_len_before = >::get(); + let active_candidates_count = + Self::kick_stale_candidates(>::iter().map(|(who, _)| who)); + let removed = candidates_len_before.saturating_sub(active_candidates_count); + let result = Self::assemble_collators(); frame_system::Pallet::::register_extra_weight_unchecked( T::WeightInfo::new_session(candidates_len_before as u32, removed as u32), @@ -697,4 +855,20 @@ pub mod pallet { // we don't care. } } + + impl ScoreProvider for Pallet { + type Score = BalanceOf; + + fn score(who: &T::AccountId) -> Self::Score { + >::get(who) + } + + #[cfg(feature = "runtime-benchmarks")] + fn set_score_of(who: &T::AccountId, weight: Self::Score) { + let active: BalanceOf = weight.try_into().map_err(|_| ()).unwrap(); + Candidates::::mutate(who, |deposit| { + *deposit = active; + }); + } + } } diff --git a/pallets/collator-selection/src/mock.rs b/pallets/collator-selection/src/mock.rs index 7e8b1595d2c..ff8c23278eb 100644 --- a/pallets/collator-selection/src/mock.rs +++ b/pallets/collator-selection/src/mock.rs @@ -40,6 +40,7 @@ frame_support::construct_runtime!( Session: pallet_session, Aura: pallet_aura, Balances: pallet_balances, + CandidateList: pallet_bags_list::, CollatorSelection: collator_selection, Authorship: pallet_authorship, } @@ -177,12 +178,24 @@ impl pallet_session::Config for Test { type WeightInfo = (); } +type CandidateBagsListInstance = pallet_bags_list::Instance1; +impl pallet_bags_list::Config for Test { + type RuntimeEvent = RuntimeEvent; + /// The candidate bags-list is loosely kept up to date, and the real source + /// of truth for the score of each node is the collator-selection pallet. + type ScoreProvider = CollatorSelection; + type BagThresholds = BagThresholds; + type Score = u64; + type WeightInfo = pallet_bags_list::weights::SubstrateWeight; +} + ord_parameter_types! { pub const RootAccount: u64 = 777; } parameter_types! { pub const PotId: PalletId = PalletId(*b"PotStake"); + pub const BagThresholds: &'static [u64] = &[]; } pub struct IsRegistered; @@ -201,6 +214,7 @@ impl Config for Test { type MinEligibleCollators = ConstU32<1>; type MaxInvulnerables = ConstU32<20>; type KickThreshold = Period; + type CandidateList = CandidateList; type ValidatorId = ::AccountId; type ValidatorIdOf = IdentityCollator; type ValidatorRegistration = IsRegistered; diff --git a/pallets/collator-selection/src/tests.rs b/pallets/collator-selection/src/tests.rs index cbfbde743f0..380fa2841cd 100644 --- a/pallets/collator-selection/src/tests.rs +++ b/pallets/collator-selection/src/tests.rs @@ -14,11 +14,13 @@ // limitations under the License. use crate as collator_selection; -use crate::{mock::*, CandidateInfo, Error}; +use crate::{mock::*, Error}; +use frame_election_provider_support::SortedListProvider; use frame_support::{ assert_noop, assert_ok, traits::{Currency, OnInitialize}, }; +use pallet_bags_list::{Error as BagsListError, ListError}; use pallet_balances::Error as BalancesError; use sp_runtime::{testing::UintAuthorityId, traits::BadOrigin, BuildStorage}; @@ -28,7 +30,7 @@ fn basic_setup_works() { assert_eq!(CollatorSelection::desired_candidates(), 2); assert_eq!(CollatorSelection::candidacy_bond(), 10); - assert!(CollatorSelection::candidates().is_empty()); + assert_eq!(>::iter().count(), 0); // genesis should sort input assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); }); @@ -202,7 +204,7 @@ fn candidate_to_invulnerable_works() { initialize_to_block(1); assert_eq!(CollatorSelection::desired_candidates(), 2); assert_eq!(CollatorSelection::candidacy_bond(), 10); - assert_eq!(CollatorSelection::candidates(), Vec::new()); + assert_eq!(>::iter().count(), 0); assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); assert_eq!(Balances::free_balance(3), 100); @@ -226,7 +228,7 @@ fn candidate_to_invulnerable_works() { )); assert!(CollatorSelection::invulnerables().to_vec().contains(&3)); assert_eq!(Balances::free_balance(3), 100); - assert_eq!(CollatorSelection::candidates().len(), 1); + assert_eq!(>::iter().count(), 1); assert_ok!(CollatorSelection::add_invulnerable( RuntimeOrigin::signed(RootAccount::get()), @@ -240,7 +242,7 @@ fn candidate_to_invulnerable_works() { )); assert!(CollatorSelection::invulnerables().to_vec().contains(&4)); assert_eq!(Balances::free_balance(4), 100); - assert_eq!(CollatorSelection::candidates().len(), 0); + assert_eq!(>::iter().count(), 0); }); } @@ -286,22 +288,23 @@ fn set_candidacy_bond() { #[test] fn cannot_register_candidate_if_too_many() { new_test_ext().execute_with(|| { - // reset desired candidates: - >::put(0); + >::put(1); - // can't accept anyone anymore. - assert_noop!( - CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)), - Error::::TooManyCandidates, - ); + // MaxCandidates: u32 = 20 + // Aside from 3, 4, and 5, create enough accounts to have 21 potential + // candidates. + for i in 6..=23 { + Balances::make_free_balance_be(&i, 100); + let key = MockSessionKeys { aura: UintAuthorityId(i) }; + Session::set_keys(RuntimeOrigin::signed(i).into(), key, Vec::new()).unwrap(); + } - // reset desired candidates: - >::put(1); - assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + for c in 3..=22 { + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(c))); + } - // but no more assert_noop!( - CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)), + CollatorSelection::register_as_candidate(RuntimeOrigin::signed(23)), Error::::TooManyCandidates, ); }) @@ -310,7 +313,7 @@ fn cannot_register_candidate_if_too_many() { #[test] fn cannot_unregister_candidate_if_too_few() { new_test_ext().execute_with(|| { - assert_eq!(CollatorSelection::candidates(), Vec::new()); + assert_eq!(>::iter().count(), 0); assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); assert_ok!(CollatorSelection::remove_invulnerable( RuntimeOrigin::signed(RootAccount::get()), @@ -368,8 +371,9 @@ fn cannot_register_dupe_candidate() { new_test_ext().execute_with(|| { // can add 3 as candidate assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); - let addition = CandidateInfo { who: 3, deposit: 10 }; - assert_eq!(CollatorSelection::candidates(), vec![addition]); + // tuple of (id, deposit). + let addition = (3, 10); + assert_eq!(>::iter().collect::>(), vec![addition]); assert_eq!(CollatorSelection::last_authored_block(3), 10); assert_eq!(Balances::free_balance(3), 90); @@ -404,20 +408,412 @@ fn register_as_candidate_works() { // given assert_eq!(CollatorSelection::desired_candidates(), 2); assert_eq!(CollatorSelection::candidacy_bond(), 10); - assert_eq!(CollatorSelection::candidates(), Vec::new()); + assert_eq!(>::iter().count(), 0); + assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + + // take two endowed, non-invulnerables accounts. + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(Balances::free_balance(4), 100); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + + assert_eq!(Balances::free_balance(3), 90); + assert_eq!(Balances::free_balance(4), 90); + + assert_eq!(>::iter().count(), 2); + }); +} + +#[test] +fn cannot_take_candidate_slot_if_invulnerable() { + new_test_ext().execute_with(|| { + assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + + // can't 1 because it is invulnerable. + assert_noop!( + CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(1), 50u64.into(), 2), + Error::::AlreadyInvulnerable, + ); + }) +} + +#[test] +fn cannot_take_candidate_slot_if_keys_not_registered() { + new_test_ext().execute_with(|| { + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_noop!( + CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(42), 50u64.into(), 3), + Error::::ValidatorNotRegistered + ); + }) +} + +#[test] +fn cannot_take_candidate_slot_if_duplicate() { + new_test_ext().execute_with(|| { + // can add 3 as candidate + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + // tuple of (id, deposit). + let candidate_3 = (3, 10); + let candidate_4 = (4, 10); + let mut actual_candidates = >::iter().collect::>(); + actual_candidates.sort(); + assert_eq!(actual_candidates, vec![candidate_3, candidate_4]); + assert_eq!(CollatorSelection::last_authored_block(3), 10); + assert_eq!(CollatorSelection::last_authored_block(4), 10); + assert_eq!(Balances::free_balance(3), 90); + + // but no more + assert_noop!( + CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(3), 50u64.into(), 4), + Error::::AlreadyCandidate, + ); + }) +} + +#[test] +fn cannot_take_candidate_slot_if_target_invalid() { + new_test_ext().execute_with(|| { + // can add 3 as candidate + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + // tuple of (id, deposit). + let candidate_3 = (3, 10); + assert_eq!(>::iter().collect::>(), vec![candidate_3]); + assert_eq!(CollatorSelection::last_authored_block(3), 10); + assert_eq!(Balances::free_balance(3), 90); + assert_eq!(Balances::free_balance(4), 100); + + assert_noop!( + CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(4), 50u64.into(), 5), + Error::::TargetIsNotCandidate, + ); + }) +} + +#[test] +fn cannot_take_candidate_slot_if_poor() { + new_test_ext().execute_with(|| { + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(Balances::free_balance(33), 0); + + // works + assert_ok!(CollatorSelection::take_candidate_slot( + RuntimeOrigin::signed(3), + 20u64.into(), + 4 + )); + + // poor + assert_noop!( + CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(33), 30u64.into(), 3), + BalancesError::::InsufficientBalance, + ); + }); +} + +#[test] +fn cannot_take_candidate_slot_if_insufficient_deposit() { + new_test_ext().execute_with(|| { + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(3), 50u64.into())); + assert_noop!( + CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(4), 5u64.into(), 3), + Error::::InsufficientBond, + ); + }); +} + +#[test] +fn cannot_take_candidate_slot_if_deposit_less_than_target() { + new_test_ext().execute_with(|| { + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(3), 50u64.into())); + assert_noop!( + CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(4), 20u64.into(), 3), + Error::::InsufficientBond, + ); + }); +} + +#[test] +fn take_candidate_slot_works() { + new_test_ext().execute_with(|| { + // given + assert_eq!(CollatorSelection::desired_candidates(), 2); + assert_eq!(CollatorSelection::candidacy_bond(), 10); + assert_eq!(>::iter().count(), 0); assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); // take two endowed, non-invulnerables accounts. assert_eq!(Balances::free_balance(3), 100); assert_eq!(Balances::free_balance(4), 100); + assert_eq!(Balances::free_balance(5), 100); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + + assert_eq!(Balances::free_balance(3), 90); + assert_eq!(Balances::free_balance(4), 90); + assert_eq!(Balances::free_balance(5), 90); + + assert_eq!(>::iter().count(), 3); + + Balances::make_free_balance_be(&6, 100); + let key = MockSessionKeys { aura: UintAuthorityId(6) }; + Session::set_keys(RuntimeOrigin::signed(6).into(), key, Vec::new()).unwrap(); + + assert_ok!(CollatorSelection::take_candidate_slot( + RuntimeOrigin::signed(6), + 50u64.into(), + 4 + )); + + assert_eq!(Balances::free_balance(3), 90); + assert_eq!(Balances::free_balance(4), 100); + assert_eq!(Balances::free_balance(5), 90); + assert_eq!(Balances::free_balance(6), 50); + + // tuple of (id, deposit). + let candidate_3 = (3, 10); + let candidate_6 = (6, 50); + let candidate_5 = (5, 10); + let mut actual_candidates = >::iter().collect::>(); + actual_candidates.sort(); + assert_eq!( + >::iter().collect::>(), + vec![candidate_3, candidate_5, candidate_6] + ); + assert_eq!(CandidateList::iter().collect::>(), vec![3, 5, 6]); + }); +} + +#[test] +fn increase_candidacy_bond_non_candidate_account() { + new_test_ext().execute_with(|| { + // given + assert_eq!(CollatorSelection::desired_candidates(), 2); + assert_eq!(CollatorSelection::candidacy_bond(), 10); + assert_eq!(>::iter().count(), 0); + assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + + assert_noop!( + CollatorSelection::increase_bond(RuntimeOrigin::signed(5), 10), + Error::::NotCandidate + ); + }); +} + +#[test] +fn increase_candidacy_bond_insufficient_balance() { + new_test_ext().execute_with(|| { + // given + assert_eq!(CollatorSelection::desired_candidates(), 2); + assert_eq!(CollatorSelection::candidacy_bond(), 10); + assert_eq!(>::iter().count(), 0); + assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + + // take two endowed, non-invulnerables accounts. + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(Balances::free_balance(4), 100); + assert_eq!(Balances::free_balance(5), 100); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + + assert_eq!(Balances::free_balance(3), 90); + assert_eq!(Balances::free_balance(4), 90); + assert_eq!(Balances::free_balance(5), 90); + + assert_noop!( + CollatorSelection::increase_bond(RuntimeOrigin::signed(3), 100), + BalancesError::::InsufficientBalance + ); + }); +} + +#[test] +fn increase_candidacy_bond_works() { + new_test_ext().execute_with(|| { + // given + assert_eq!(CollatorSelection::desired_candidates(), 2); + assert_eq!(CollatorSelection::candidacy_bond(), 10); + assert_eq!(>::iter().count(), 0); + assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + + // take three endowed, non-invulnerables accounts. + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(Balances::free_balance(4), 100); + assert_eq!(Balances::free_balance(5), 100); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + + assert_eq!(Balances::free_balance(3), 90); + assert_eq!(Balances::free_balance(4), 90); + assert_eq!(Balances::free_balance(5), 90); + + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(3), 10)); + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(4), 20)); + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(5), 30)); + + assert_eq!(>::iter().count(), 3); + assert_eq!(Balances::free_balance(3), 80); + assert_eq!(Balances::free_balance(4), 70); + assert_eq!(Balances::free_balance(5), 60); + + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(3), 20)); + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(4), 30)); + + assert_eq!(>::iter().count(), 3); + assert_eq!(Balances::free_balance(3), 60); + assert_eq!(Balances::free_balance(4), 40); + assert_eq!(Balances::free_balance(5), 60); + }); +} + +#[test] +fn decrease_candidacy_bond_non_candidate_account() { + new_test_ext().execute_with(|| { + // given + assert_eq!(CollatorSelection::desired_candidates(), 2); + assert_eq!(CollatorSelection::candidacy_bond(), 10); + assert_eq!(>::iter().count(), 0); + assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + + assert_noop!( + CollatorSelection::decrease_bond(RuntimeOrigin::signed(5), 10), + Error::::NotCandidate + ); + }); +} + +#[test] +fn decrease_candidacy_bond_insufficient_funds() { + new_test_ext().execute_with(|| { + // given + assert_eq!(CollatorSelection::desired_candidates(), 2); + assert_eq!(CollatorSelection::candidacy_bond(), 10); + assert_eq!(>::iter().count(), 0); + assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + + // take two endowed, non-invulnerables accounts. + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(Balances::free_balance(4), 100); + assert_eq!(Balances::free_balance(5), 100); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(3), 50)); + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(4), 50)); + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(5), 50)); + + assert_eq!(Balances::free_balance(3), 40); + assert_eq!(Balances::free_balance(4), 40); + assert_eq!(Balances::free_balance(5), 40); + + assert_noop!( + CollatorSelection::decrease_bond(RuntimeOrigin::signed(3), 100), + Error::::DepositTooLow + ); + + assert_noop!( + CollatorSelection::decrease_bond(RuntimeOrigin::signed(4), 60), + Error::::DepositTooLow + ); + + assert_noop!( + CollatorSelection::decrease_bond(RuntimeOrigin::signed(5), 51), + Error::::DepositTooLow + ); + }); +} + +#[test] +fn decrease_candidacy_bond_works() { + new_test_ext().execute_with(|| { + // given + assert_eq!(CollatorSelection::desired_candidates(), 2); + assert_eq!(CollatorSelection::candidacy_bond(), 10); + assert_eq!(>::iter().count(), 0); + assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + + // take three endowed, non-invulnerables accounts. + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(Balances::free_balance(4), 100); + assert_eq!(Balances::free_balance(5), 100); assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); assert_eq!(Balances::free_balance(3), 90); assert_eq!(Balances::free_balance(4), 90); + assert_eq!(Balances::free_balance(5), 90); + + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(3), 10)); + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(4), 20)); + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(5), 30)); + + assert_eq!(>::iter().count(), 3); + assert_eq!(Balances::free_balance(3), 80); + assert_eq!(Balances::free_balance(4), 70); + assert_eq!(Balances::free_balance(5), 60); - assert_eq!(CollatorSelection::candidates().len(), 2); + assert_ok!(CollatorSelection::decrease_bond(RuntimeOrigin::signed(3), 10)); + assert_ok!(CollatorSelection::decrease_bond(RuntimeOrigin::signed(4), 10)); + + assert_eq!(>::iter().count(), 3); + assert_eq!(Balances::free_balance(3), 90); + assert_eq!(Balances::free_balance(4), 80); + assert_eq!(Balances::free_balance(5), 60); + }); +} + +#[test] +fn candidate_list_works() { + new_test_ext().execute_with(|| { + // given + assert_eq!(CollatorSelection::desired_candidates(), 2); + assert_eq!(CollatorSelection::candidacy_bond(), 10); + assert_eq!(>::iter().count(), 0); + assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + + // take three endowed, non-invulnerables accounts. + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(Balances::free_balance(4), 100); + assert_eq!(Balances::free_balance(5), 100); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + + assert_noop!( + CandidateList::put_in_front_of(RuntimeOrigin::signed(5), 3), + BagsListError::::List(ListError::NotHeavier) + ); + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(5), 10)); + assert_ok!(CandidateList::put_in_front_of(RuntimeOrigin::signed(5), 4)); + assert_ok!(CandidateList::put_in_front_of(RuntimeOrigin::signed(5), 3)); + + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(3), 20)); + assert_ok!(CandidateList::put_in_front_of(RuntimeOrigin::signed(3), 5)); + + assert_ok!(CollatorSelection::decrease_bond(RuntimeOrigin::signed(3), 20)); + assert_ok!(CandidateList::put_in_front_of(RuntimeOrigin::signed(5), 3)); }); } @@ -457,9 +853,10 @@ fn authorship_event_handler() { // triggers `note_author` Authorship::on_initialize(1); - let collator = CandidateInfo { who: 4, deposit: 10 }; + // tuple of (id, deposit). + let collator = (4, 10); - assert_eq!(CollatorSelection::candidates(), vec![collator]); + assert_eq!(>::iter().collect::>(), vec![collator]); assert_eq!(CollatorSelection::last_authored_block(4), 0); // half of the pot goes to the collator who's the author (4 in tests). @@ -482,9 +879,10 @@ fn fees_edgecases() { // triggers `note_author` Authorship::on_initialize(1); - let collator = CandidateInfo { who: 4, deposit: 10 }; + // tuple of (id, deposit). + let collator = (4, 10); - assert_eq!(CollatorSelection::candidates(), vec![collator]); + assert_eq!(>::iter().collect::>(), vec![collator]); assert_eq!(CollatorSelection::last_authored_block(4), 0); // Nothing received assert_eq!(Balances::free_balance(4), 90); @@ -494,7 +892,7 @@ fn fees_edgecases() { } #[test] -fn session_management_works() { +fn session_management_single_candidate() { new_test_ext().execute_with(|| { initialize_to_block(1); @@ -512,7 +910,7 @@ fn session_management_works() { // session won't see this. assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); // but we have a new candidate. - assert_eq!(CollatorSelection::candidates().len(), 1); + assert_eq!(>::iter().count(), 1); initialize_to_block(10); assert_eq!(SessionChangeBlock::get(), 10); @@ -530,6 +928,271 @@ fn session_management_works() { }); } +#[test] +fn session_management_max_candidates() { + new_test_ext().execute_with(|| { + initialize_to_block(1); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(4); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + + // session won't see this. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + // but we have a new candidate. + assert_eq!(>::iter().count(), 3); + + initialize_to_block(10); + assert_eq!(SessionChangeBlock::get(), 10); + // pallet-session has 1 session delay; current validators are the same. + assert_eq!(Session::validators(), vec![1, 2]); + // queued ones are changed, and now we have 4. + assert_eq!(Session::queued_keys().len(), 4); + // session handlers (aura, et. al.) cannot see this yet. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(20); + assert_eq!(SessionChangeBlock::get(), 20); + // changed are now reflected to session handlers. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 4]); + }); +} + +#[test] +fn session_management_increase_bid_without_list_update() { + new_test_ext().execute_with(|| { + initialize_to_block(1); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(4); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(5), 50)); + + // session won't see this. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + // but we have a new candidate. + assert_eq!(>::iter().count(), 3); + + initialize_to_block(10); + assert_eq!(SessionChangeBlock::get(), 10); + // pallet-session has 1 session delay; current validators are the same. + assert_eq!(Session::validators(), vec![1, 2]); + // queued ones are changed, and now we have 4. + assert_eq!(Session::queued_keys().len(), 4); + // session handlers (aura, et. al.) cannot see this yet. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(20); + assert_eq!(SessionChangeBlock::get(), 20); + // changed are now reflected to session handlers. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 4]); + }); +} + +#[test] +fn session_management_increase_bid_with_list_update() { + new_test_ext().execute_with(|| { + initialize_to_block(1); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(4); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(5), 50)); + assert_ok!(CandidateList::put_in_front_of(RuntimeOrigin::signed(5), 3)); + + // session won't see this. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + // but we have a new candidate. + assert_eq!(>::iter().count(), 3); + + initialize_to_block(10); + assert_eq!(SessionChangeBlock::get(), 10); + // pallet-session has 1 session delay; current validators are the same. + assert_eq!(Session::validators(), vec![1, 2]); + // queued ones are changed, and now we have 4. + assert_eq!(Session::queued_keys().len(), 4); + // session handlers (aura, et. al.) cannot see this yet. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(20); + assert_eq!(SessionChangeBlock::get(), 20); + // changed are now reflected to session handlers. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 5, 3]); + }); +} + +#[test] +fn session_management_candidate_list_lazy_sort() { + new_test_ext().execute_with(|| { + initialize_to_block(1); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(4); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(5), 50)); + // even though candidate 5 has a higher bid than both 3 and 4, we try + // to put it in front of 4 and not 3; the list should be lazily sorted + // and not reorder candidate 5 ahead of 4. + assert_ok!(CandidateList::put_in_front_of(RuntimeOrigin::signed(5), 4)); + + // session won't see this. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + // but we have a new candidate. + assert_eq!(>::iter().count(), 3); + + initialize_to_block(10); + assert_eq!(SessionChangeBlock::get(), 10); + // pallet-session has 1 session delay; current validators are the same. + assert_eq!(Session::validators(), vec![1, 2]); + // queued ones are changed, and now we have 4. + assert_eq!(Session::queued_keys().len(), 4); + // session handlers (aura, et. al.) cannot see this yet. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(20); + assert_eq!(SessionChangeBlock::get(), 20); + // changed are now reflected to session handlers. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 5]); + }); +} + +#[test] +fn session_management_reciprocal_outbidding() { + new_test_ext().execute_with(|| { + initialize_to_block(1); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(4); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(5), 50)); + assert_ok!(CandidateList::put_in_front_of(RuntimeOrigin::signed(5), 3)); + + initialize_to_block(5); + + // candidates 3 and 4 saw they were outbid and preemptively bid more + // than 5 in the next block. + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(4), 60)); + assert_ok!(CandidateList::put_in_front_of(RuntimeOrigin::signed(4), 5)); + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(3), 60)); + assert_ok!(CandidateList::put_in_front_of(RuntimeOrigin::signed(3), 5)); + + // session won't see this. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + // but we have a new candidate. + assert_eq!(>::iter().count(), 3); + + initialize_to_block(10); + assert_eq!(SessionChangeBlock::get(), 10); + // pallet-session has 1 session delay; current validators are the same. + assert_eq!(Session::validators(), vec![1, 2]); + // queued ones are changed, and now we have 4. + assert_eq!(Session::queued_keys().len(), 4); + // session handlers (aura, et. al.) cannot see this yet. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(20); + assert_eq!(SessionChangeBlock::get(), 20); + // changed are now reflected to session handlers. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 4, 3]); + }); +} + +#[test] +fn session_management_decrease_bid_after_auction() { + new_test_ext().execute_with(|| { + initialize_to_block(1); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(4); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(5), 50)); + assert_ok!(CandidateList::put_in_front_of(RuntimeOrigin::signed(5), 3)); + + initialize_to_block(5); + + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(4), 60)); + assert_ok!(CandidateList::put_in_front_of(RuntimeOrigin::signed(4), 5)); + assert_ok!(CollatorSelection::increase_bond(RuntimeOrigin::signed(3), 60)); + assert_ok!(CandidateList::put_in_front_of(RuntimeOrigin::signed(3), 5)); + + initialize_to_block(5); + + // candidate 5 saw it was outbid and wants to take back its bid, but + // not entirely so they still keep their place in the candidate list + // in case there is an opportunity in the future. + assert_ok!(CollatorSelection::decrease_bond(RuntimeOrigin::signed(5), 50)); + + // session won't see this. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + // but we have a new candidate. + assert_eq!(>::iter().count(), 3); + + initialize_to_block(10); + assert_eq!(SessionChangeBlock::get(), 10); + // pallet-session has 1 session delay; current validators are the same. + assert_eq!(Session::validators(), vec![1, 2]); + // queued ones are changed, and now we have 4. + assert_eq!(Session::queued_keys().len(), 4); + // session handlers (aura, et. al.) cannot see this yet. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(20); + assert_eq!(SessionChangeBlock::get(), 20); + // changed are now reflected to session handlers. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 4, 3]); + }); +} + #[test] fn kick_mechanism() { new_test_ext().execute_with(|| { @@ -537,15 +1200,16 @@ fn kick_mechanism() { assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); initialize_to_block(10); - assert_eq!(CollatorSelection::candidates().len(), 2); + assert_eq!(>::iter().count(), 2); initialize_to_block(20); assert_eq!(SessionChangeBlock::get(), 20); // 4 authored this block, gets to stay 3 was kicked - assert_eq!(CollatorSelection::candidates().len(), 1); + assert_eq!(>::iter().count(), 1); // 3 will be kicked after 1 session delay assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 4]); - let collator = CandidateInfo { who: 4, deposit: 10 }; - assert_eq!(CollatorSelection::candidates(), vec![collator]); + // tuple of (id, deposit). + let collator = (4, 10); + assert_eq!(>::iter().collect::>(), vec![collator]); assert_eq!(CollatorSelection::last_authored_block(4), 20); initialize_to_block(30); // 3 gets kicked after 1 session delay @@ -559,7 +1223,7 @@ fn kick_mechanism() { fn should_not_kick_mechanism_too_few() { new_test_ext().execute_with(|| { // remove the invulnerables and add new collators 3 and 5 - assert_eq!(CollatorSelection::candidates(), Vec::new()); + assert_eq!(>::iter().count(), 0); assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); assert_ok!(CollatorSelection::remove_invulnerable( RuntimeOrigin::signed(RootAccount::get()), @@ -573,16 +1237,17 @@ fn should_not_kick_mechanism_too_few() { )); initialize_to_block(10); - assert_eq!(CollatorSelection::candidates().len(), 2); + assert_eq!(>::iter().count(), 2); initialize_to_block(20); assert_eq!(SessionChangeBlock::get(), 20); // 4 authored this block, 3 is kicked, 5 stays because of too few collators - assert_eq!(CollatorSelection::candidates().len(), 1); + assert_eq!(>::iter().count(), 1); // 3 will be kicked after 1 session delay assert_eq!(SessionHandlerCollators::get(), vec![3, 5]); - let collator = CandidateInfo { who: 5, deposit: 10 }; - assert_eq!(CollatorSelection::candidates(), vec![collator]); + // tuple of (id, deposit). + let collator = (5, 10); + assert_eq!(>::iter().collect::>(), vec![collator]); assert_eq!(CollatorSelection::last_authored_block(4), 20); initialize_to_block(30); @@ -596,7 +1261,7 @@ fn should_not_kick_mechanism_too_few() { #[test] fn should_kick_invulnerables_from_candidates_on_session_change() { new_test_ext().execute_with(|| { - assert_eq!(CollatorSelection::candidates(), Vec::new()); + assert_eq!(>::iter().count(), 0); assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); assert_eq!(Balances::free_balance(3), 90); @@ -606,16 +1271,19 @@ fn should_kick_invulnerables_from_candidates_on_session_change() { vec![1, 2, 3] )); - let collator_3 = CandidateInfo { who: 3, deposit: 10 }; - let collator_4 = CandidateInfo { who: 4, deposit: 10 }; + // tuple of (id, deposit). + let collator_3 = (3, 10); + let collator_4 = (4, 10); - assert_eq!(CollatorSelection::candidates(), vec![collator_3, collator_4.clone()]); + let mut actual_candidates = >::iter().collect::>(); + actual_candidates.sort(); + assert_eq!(actual_candidates, vec![collator_3, collator_4.clone()]); assert_eq!(CollatorSelection::invulnerables(), vec![1, 2, 3]); // session change initialize_to_block(10); // 3 is removed from candidates - assert_eq!(CollatorSelection::candidates(), vec![collator_4]); + assert_eq!(>::iter().collect::>(), vec![collator_4]); // but not from invulnerables assert_eq!(CollatorSelection::invulnerables(), vec![1, 2, 3]); // and it got its deposit back diff --git a/pallets/collator-selection/src/weights.rs b/pallets/collator-selection/src/weights.rs index 7d227da291a..878906863ad 100644 --- a/pallets/collator-selection/src/weights.rs +++ b/pallets/collator-selection/src/weights.rs @@ -33,6 +33,9 @@ pub trait WeightInfo { fn set_candidacy_bond() -> Weight; fn register_as_candidate(_c: u32) -> Weight; fn leave_intent(_c: u32) -> Weight; + fn increase_bond(_c: u32) -> Weight; + fn decrease_bond(_c: u32) -> Weight; + fn take_candidate_slot(_c: u32) -> Weight; fn note_author() -> Weight; fn new_session(_c: u32, _r: u32) -> Weight; } @@ -66,6 +69,27 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } + fn increase_bond(c: u32) -> Weight { + Weight::from_parts(55_336_000_u64, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(151_000_u64, 0).saturating_mul(c as u64)) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + fn decrease_bond(c: u32) -> Weight { + Weight::from_parts(55_336_000_u64, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(151_000_u64, 0).saturating_mul(c as u64)) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + fn take_candidate_slot(c: u32) -> Weight { + Weight::from_parts(71_196_000_u64, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(198_000_u64, 0).saturating_mul(c as u64)) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } fn note_author() -> Weight { Weight::from_parts(71_461_000_u64, 0) .saturating_add(T::DbWeight::get().reads(3_u64)) @@ -153,6 +177,27 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } + fn increase_bond(c: u32) -> Weight { + Weight::from_parts(55_336_000_u64, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(151_000_u64, 0).saturating_mul(c as u64)) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + fn decrease_bond(c: u32) -> Weight { + Weight::from_parts(55_336_000_u64, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(151_000_u64, 0).saturating_mul(c as u64)) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + fn take_candidate_slot(c: u32) -> Weight { + Weight::from_parts(71_196_000_u64, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(198_000_u64, 0).saturating_mul(c as u64)) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } fn note_author() -> Weight { Weight::from_parts(71_461_000_u64, 0) .saturating_add(RocksDbWeight::get().reads(3_u64)) diff --git a/parachain-template/runtime/Cargo.toml b/parachain-template/runtime/Cargo.toml index 59bd61124a2..311df7ae0e0 100644 --- a/parachain-template/runtime/Cargo.toml +++ b/parachain-template/runtime/Cargo.toml @@ -26,6 +26,8 @@ pallet-parachain-template = { path = "../pallets/template", default-features = f # Substrate frame-benchmarking = { git = "https://github.com/paritytech/substrate", default-features = false, optional = true, branch = "master" } +# TODO[GMP] maybe unnecessary +frame-election-provider-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "master" } frame-executive = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } @@ -34,9 +36,11 @@ frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/substrate" frame-try-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, optional = true, branch = "master" } pallet-aura = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } pallet-authorship = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +pallet-bags-list = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } pallet-balances = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } pallet-session = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } pallet-sudo = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +pallet-staking = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } pallet-timestamp = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } @@ -90,15 +94,18 @@ std = [ "cumulus-primitives-timestamp/std", "cumulus-primitives-utility/std", "frame-executive/std", + "frame-election-provider-support/std", "frame-support/std", "frame-system-rpc-runtime-api/std", "frame-system/std", "pallet-aura/std", "pallet-authorship/std", + "pallet-bags-list/std", "pallet-balances/std", "pallet-collator-selection/std", "pallet-session/std", "pallet-sudo/std", + "pallet-staking/std", "pallet-parachain-template/std", "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", @@ -128,13 +135,16 @@ runtime-benchmarks = [ "hex-literal", "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", + "frame-election-provider-support/runtime-benchmarks", "frame-system-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-bags-list/runtime-benchmarks", "pallet-collator-selection/runtime-benchmarks", "pallet-parachain-template/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-sudo/runtime-benchmarks", + "pallet-staking/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", @@ -149,15 +159,18 @@ try-runtime = [ "cumulus-pallet-parachain-system/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", + "frame-election-provider-support/try-runtime", "frame-executive/try-runtime", "frame-system/try-runtime", "frame-try-runtime/try-runtime", "pallet-aura/try-runtime", "pallet-authorship/try-runtime", "pallet-balances/try-runtime", + "pallet-bags-list/try-runtime", "pallet-collator-selection/try-runtime", "pallet-session/try-runtime", "pallet-sudo/try-runtime", + "pallet-staking/try-runtime", "pallet-parachain-template/try-runtime", "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", diff --git a/parachain-template/runtime/src/lib.rs b/parachain-template/runtime/src/lib.rs index 761a3944afb..f63bb538ef7 100644 --- a/parachain-template/runtime/src/lib.rs +++ b/parachain-template/runtime/src/lib.rs @@ -450,6 +450,7 @@ impl pallet_collator_selection::Config for Runtime { type MaxCandidates = ConstU32<100>; type MinEligibleCollators = ConstU32<4>; type MaxInvulnerables = ConstU32<20>; + type CandidateList = CandidateList; // should be a multiple of session or things will get inconsistent type KickThreshold = Period; type ValidatorId = ::AccountId; @@ -458,6 +459,21 @@ impl pallet_collator_selection::Config for Runtime { type WeightInfo = (); } +parameter_types! { + pub const BagThresholds: &'static [u128] = &[]; +} + +type CandidateBagsListInstance = pallet_bags_list::Instance1; +impl pallet_bags_list::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + /// The candidate bags-list is loosely kept up to date, and the real source + /// of truth for the score of each node is the collator-selection pallet. + type ScoreProvider = CollatorSelection; + type BagThresholds = BagThresholds; + type Score = Balance; + type WeightInfo = pallet_bags_list::weights::SubstrateWeight; +} + /// Configure the pallet template in pallets/template. impl pallet_parachain_template::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -486,6 +502,7 @@ construct_runtime!( Session: pallet_session = 22, Aura: pallet_aura = 23, AuraExt: cumulus_pallet_aura_ext = 24, + CandidateList: pallet_bags_list:: = 25, // XCM helpers. XcmpQueue: cumulus_pallet_xcmp_queue = 30,