difftreelog
feat(collator-selection) method refactoring + unit tests complete
in: master
9 files changed
pallets/collator-selection/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/collator-selection/src/benchmarking.rs
+++ b/pallets/collator-selection/src/benchmarking.rs
@@ -130,7 +130,7 @@
where_clause { where T: pallet_authorship::Config + session::Config }
set_invulnerables {
- let b in 1 .. T::MaxInvulnerables::get();
+ let b in 1 .. T::MaxCollators::get();
let new_invulnerables = register_validators::<T>(b);
let origin = T::UpdateOrigin::successful_origin();
}: {
@@ -142,16 +142,16 @@
assert_last_event::<T>(Event::NewInvulnerables{invulnerables: new_invulnerables}.into());
}
- set_desired_candidates {
+ set_desired_collators {
let max: u32 = 999;
let origin = T::UpdateOrigin::successful_origin();
}: {
assert_ok!(
- <CollatorSelection<T>>::set_desired_candidates(origin, max.clone())
+ <CollatorSelection<T>>::set_desired_collators(origin, max.clone())
);
}
verify {
- assert_last_event::<T>(Event::NewDesiredCandidates{desired_candidates: max}.into());
+ assert_last_event::<T>(Event::NewDesiredCollators{desired_collators: max}.into());
}
set_license_bond {
@@ -169,10 +169,10 @@
// worse case is when we have all the max-candidate slots filled except one, and we fill that
// one.
register_as_candidate {
- let c in 1 .. T::MaxCandidates::get();
+ let c in 1 .. T::MaxCollators::get();
<LicenseBond<T>>::put(T::Currency::minimum_balance());
- <DesiredCandidates<T>>::put(c + 1);
+ <DesiredCollators<T>>::put(c + 1);
register_validators::<T>(c);
register_candidates::<T>(c);
@@ -194,9 +194,9 @@
// worse case is the last candidate leaving.
leave_intent {
- let c in (T::MinCandidates::get() + 1) .. T::MaxCandidates::get();
+ let c in (T::MinCandidates::get() + 1) .. T::MaxCollators::get();
<LicenseBond<T>>::put(T::Currency::minimum_balance());
- <DesiredCandidates<T>>::put(c);
+ <DesiredCollators<T>>::put(c);
register_validators::<T>(c);
register_candidates::<T>(c);
@@ -230,11 +230,11 @@
// worst case for new session.
new_session {
- let r in 1 .. T::MaxCandidates::get();
- let c in 1 .. T::MaxCandidates::get();
+ let r in 1 .. T::MaxCollators::get();
+ let c in 1 .. T::MaxCollators::get();
<LicenseBond<T>>::put(T::Currency::minimum_balance());
- <DesiredCandidates<T>>::put(c);
+ <DesiredCollators<T>>::put(c);
frame_system::Pallet::<T>::set_block_number(0u32.into());
register_validators::<T>(c);
pallets/collator-selection/src/lib.rsdiffbeforeafterboth--- a/pallets/collator-selection/src/lib.rs
+++ b/pallets/collator-selection/src/lib.rs
@@ -98,10 +98,7 @@
dispatch::{DispatchClass, DispatchResultWithPostInfo},
inherent::Vec,
pallet_prelude::*,
- sp_runtime::{
- traits::{AccountIdConversion, CheckedSub, Saturating, Zero},
- RuntimeDebug,
- },
+ sp_runtime::traits::{AccountIdConversion, CheckedSub, Saturating, Zero},
traits::{
Currency, EnsureOrigin, ExistenceRequirement::KeepAlive, ReservableCurrency,
ValidatorRegistration,
@@ -145,19 +142,9 @@
/// Account Identifier from which the internal Pot is generated.
type PotId: Get<PalletId>;
-
- /// Maximum number of candidates that we should have. This is enforced in code.
- ///
- /// This does not take into account the invulnerables.
- type MaxCandidates: Get<u32>;
- /// Minimum number of candidates that we should have. This is used for disaster recovery.
- ///
- /// This does not take into account the invulnerables.
- type MinCandidates: Get<u32>;
-
- /// Maximum number of invulnerables. This is enforced in code.
- type MaxInvulnerables: Get<u32>;
+ /// Maximum number of candidates and invulnerables that we should have. This is enforced in code.
+ type MaxCollators: Get<u32>;
/// If kicked, how much of the collator's deposit will be slashed and sent to the slash destination.
type SlashRatio: Get<Perbill>;
@@ -175,17 +162,6 @@
/// The weight information of this pallet.
type WeightInfo: WeightInfo;
- }
-
- /// Basic information about a collation candidate.
- #[derive(
- PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen,
- )]
- pub struct LicenseInfo<AccountId, Balance> {
- /// Account identifier.
- pub who: AccountId,
- /// Reserved deposit.
- pub deposit: Balance,
}
#[pallet::pallet]
@@ -196,7 +172,7 @@
#[pallet::storage]
#[pallet::getter(fn invulnerables)]
pub type Invulnerables<T: Config> =
- StorageValue<_, BoundedVec<T::AccountId, T::MaxInvulnerables>, ValueQuery>;
+ StorageValue<_, BoundedVec<T::AccountId, T::MaxCollators>, ValueQuery>;
/// The (community) collation license holders.
#[pallet::storage]
@@ -209,7 +185,7 @@
#[pallet::getter(fn candidates)]
pub type Candidates<T: Config> = StorageValue<
_,
- BoundedVec<T::AccountId, T::MaxCandidates>, //LicenseInfo<T::AccountId, BalanceOf<T>>, T::MaxCandidates>, // license ID?
+ BoundedVec<T::AccountId, T::MaxCollators>, //LicenseInfo<T::AccountId, BalanceOf<T>>, T::MaxCollators>, // license ID?
ValueQuery,
>;
@@ -228,10 +204,10 @@
/// Desired number of candidates.
///
- /// This should ideally always be less than [`Config::MaxCandidates`] for weights to be correct.
+ /// This should ideally always be less than [`Config::MaxCollators`] for weights to be correct.
#[pallet::storage]
- #[pallet::getter(fn desired_candidates)]
- pub type DesiredCandidates<T> = StorageValue<_, u32, ValueQuery>;
+ #[pallet::getter(fn desired_collators)]
+ pub type DesiredCollators<T> = StorageValue<_, u32, ValueQuery>;
/// Fixed amount to deposit to become a collator.
///
@@ -245,7 +221,7 @@
pub invulnerables: Vec<T::AccountId>,
pub license_bond: BalanceOf<T>,
pub kick_threshold: T::BlockNumber,
- pub desired_candidates: u32,
+ pub desired_collators: u32,
}
#[cfg(feature = "std")]
@@ -255,7 +231,7 @@
invulnerables: Default::default(),
license_bond: Default::default(),
kick_threshold: T::BlockNumber::one(),
- desired_candidates: Default::default(),
+ desired_collators: Default::default(),
}
}
}
@@ -273,16 +249,16 @@
);
let bounded_invulnerables =
- BoundedVec::<_, T::MaxInvulnerables>::try_from(self.invulnerables.clone())
- .expect("genesis invulnerables are more than T::MaxInvulnerables");
+ BoundedVec::<_, T::MaxCollators>::try_from(self.invulnerables.clone())
+ .expect("genesis invulnerables are more than T::MaxCollators");
assert!(
- T::MaxCandidates::get() >= self.desired_candidates,
- "genesis desired_candidates are more than T::MaxCandidates",
+ T::MaxCollators::get() >= self.desired_collators,
+ "genesis desired_collators are more than T::MaxCollators",
);
- <DesiredCandidates<T>>::put(&self.desired_candidates);
- <LicenseBond<T>>::put(&self.license_bond);
- <KickThreshold<T>>::put(&self.kick_threshold);
+ <DesiredCollators<T>>::put(self.desired_collators);
+ <LicenseBond<T>>::put(self.license_bond);
+ <KickThreshold<T>>::put(self.kick_threshold);
<Invulnerables<T>>::put(bounded_invulnerables);
}
}
@@ -290,8 +266,8 @@
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
- NewDesiredCandidates {
- desired_candidates: u32,
+ NewDesiredCollators {
+ desired_collators: u32,
},
NewLicenseBond {
bond_amount: BalanceOf<T>,
@@ -326,14 +302,12 @@
pub enum Error<T> {
/// Too many candidates
TooManyCandidates,
- /// Too few candidates
- TooFewCandidates,
/// Unknown error
Unknown,
/// Permission issue
Permission,
/// User already holds license to collate
- AlreadyLicenseHolder,
+ AlreadyHoldingLicense,
/// User does not hold a license to collate
NoLicense,
/// User is already a candidate
@@ -360,7 +334,7 @@
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Add a collator to the list of invulnerable (fixed) collators.
- #[pallet::weight(T::WeightInfo::set_invulnerables(1 as u32))] // todo:collator weight
+ #[pallet::weight(T::WeightInfo::set_invulnerables(1u32))] // todo:collator weight
pub fn add_invulnerable(
origin: OriginFor<T>,
new: T::AccountId,
@@ -378,12 +352,13 @@
if Self::invulnerables().contains(&new) {
return Ok(().into());
}
-
- // todo:collator check license holders, release moneys, promotion!
- // force_release_license? Error::<T>::lreadyLicenseHolder?
<Invulnerables<T>>::try_append(new.clone())
.map_err(|_| Error::<T>::TooManyInvulnerables)?;
+
+ // try to offboard the new invulnerable if it was a collator candidate before
+ let _ = Self::try_remove_candidate(&new);
+
Self::deposit_event(Event::InvulnerableAdded { invulnerable: new });
Ok(().into())
}
@@ -417,22 +392,19 @@
Ok(().into())
}
- /// Set the ideal number of collators (not including the invulnerables).
- /// If lowering this number, then the number of running collators could be higher than this figure.
+ /// Set the ideal number of collators. If lowering this number,
+ /// then the number of running collators could be higher than this figure.
/// Aside from that edge case, there should be no other way to have more collators than the desired number.
- #[pallet::weight(T::WeightInfo::set_desired_candidates())]
- pub fn set_desired_candidates(
- origin: OriginFor<T>,
- max: u32,
- ) -> DispatchResultWithPostInfo {
+ #[pallet::weight(T::WeightInfo::set_desired_collators())]
+ pub fn set_desired_collators(origin: OriginFor<T>, max: u32) -> DispatchResultWithPostInfo {
T::UpdateOrigin::ensure_origin(origin)?;
// we trust origin calls, this is just a for more accurate benchmarking
- if max > T::MaxCandidates::get() {
- log::warn!("max > T::MaxCandidates; you might need to run benchmarks again");
+ if max > T::MaxCollators::get() {
+ log::warn!("max > T::MaxCollators; you might need to run benchmarks again");
}
- <DesiredCandidates<T>>::put(&max);
- Self::deposit_event(Event::NewDesiredCandidates {
- desired_candidates: max,
+ <DesiredCollators<T>>::put(max);
+ Self::deposit_event(Event::NewDesiredCollators {
+ desired_collators: max,
});
Ok(().into())
}
@@ -444,7 +416,7 @@
bond: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
T::UpdateOrigin::ensure_origin(origin)?;
- <LicenseBond<T>>::put(&bond);
+ <LicenseBond<T>>::put(bond);
Self::deposit_event(Event::NewLicenseBond { bond_amount: bond });
Ok(().into())
}
@@ -470,19 +442,19 @@
/// (a) already have registered session keys and (b) be able to reserve the `LicenseBond`.
///
/// This call is not available to `Invulnerable` collators.
- #[pallet::weight(T::WeightInfo::register_as_candidate(T::MaxCandidates::get()))] // todo:collator weight
+ #[pallet::weight(T::WeightInfo::register_as_candidate(T::MaxCollators::get()))] // todo:collator weight
pub fn get_license(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
// register_as_candidate
let who = ensure_signed(origin)?;
if Licenses::<T>::contains_key(&who) {
- return Ok(().into());
+ return Err(Error::<T>::AlreadyHoldingLicense.into());
}
- ensure!(
+ /*ensure!(
!Self::invulnerables().contains(&who),
Error::<T>::AlreadyInvulnerable
- );
+ );*/
let validator_key = T::ValidatorIdOf::convert(who.clone())
.ok_or(Error::<T>::NoAssociatedValidatorId)?;
@@ -507,7 +479,7 @@
return Err(BadOrigin.into());
}
if candidates.iter().any(|candidate| *candidate == who) {
- Err(Error::<T>::AlreadyLicenseHolder)?
+ Err(Error::<T>::AlreadyHoldingLicense)?
} else {
T::Currency::reserve(&who, deposit)?;
candidates
@@ -532,7 +504,7 @@
/// The account must already hold a license, and cannot offboard immediately during a session.
///
/// This call is not available to `Invulnerable` collators.
- #[pallet::weight(T::WeightInfo::register_as_candidate(T::MaxCandidates::get()))] // todo:collator weight
+ #[pallet::weight(T::WeightInfo::register_as_candidate(T::MaxCollators::get()))] // todo:collator weight
pub fn onboard(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
// register_as_candidate
let who = ensure_signed(origin)?;
@@ -540,9 +512,10 @@
// ensure the user obtained the license.
ensure!(Licenses::<T>::contains_key(&who), Error::<T>::NoLicense);
// ensure we are below limit.
- let length = <Candidates<T>>::decode_len().unwrap_or_default();
+ let length = <Candidates<T>>::decode_len().unwrap_or_default()
+ + <Invulnerables<T>>::decode_len().unwrap_or_default();
ensure!(
- (length as u32) < Self::desired_candidates(),
+ (length as u32) < Self::desired_collators(),
Error::<T>::TooManyCandidates
);
// todo:collator really need it?
@@ -551,8 +524,6 @@
Error::<T>::AlreadyInvulnerable
);
- let deposit = Self::license_bond();
- // First authored block is current block plus kick threshold to handle session delay
/*let incoming = LicenseInfo {
who: who.clone(),
deposit,
@@ -563,10 +534,10 @@
if candidates.iter().any(|candidate| *candidate == who) {
Err(Error::<T>::AlreadyCandidate)?
} else {
- T::Currency::reserve(&who, deposit)?;
candidates
.try_push(who.clone())
.map_err(|_| Error::<T>::TooManyCandidates)?;
+ // First authored block is current block plus kick threshold to handle session delay
<LastAuthoredBlock<T>>::insert(
who.clone(),
frame_system::Pallet::<T>::block_number() + Self::kick_threshold(),
@@ -583,31 +554,31 @@
/// session change. The license to `onboard` later at any other time will remain.
///
/// This call will fail if the total number of candidates would drop below `MinCandidates`. todo:collator maybe not
- #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCandidates::get()))] // todo:collator weight
+ #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCollators::get()))] // todo:collator weight
pub fn offboard(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
// leave_intent
let who = ensure_signed(origin)?;
- // todo:collator invulnerables and candidates should count against min candidates together
+ /* todo:collator invulnerables and candidates should count against min candidates together
ensure!(
Self::candidates().len() as u32 > T::MinCandidates::get(),
Error::<T>::TooFewCandidates
- );
+ );*/
let current_count = Self::try_remove_candidate(&who)?;
- Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into())
+ Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into()) // todo:collator weight
}
/// Forfeit `origin`'s own license. The `LicenseBond` will be unreserved immediately.
///
/// This call is not available to `Invulnerable` collators.
- #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCandidates::get()))] // todo:collator weight
+ #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCollators::get()))] // todo:collator weight
pub fn release_license(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
// leave_intent
let who = ensure_signed(origin)?;
- // let current_count = Self::try_remove_candidate(&who, false)?;
- Self::try_release_license(&who, false)?;
- Ok(().into())
+ let current_count = Self::try_remove_candidate_and_release_license(&who, false, true)?;
+
+ Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into()) // todo:collator weight
}
/// Force deregister `origin` as a collator candidate as a governing authority, and revoke its license.
@@ -615,16 +586,15 @@
/// The `LicenseBond` will be unreserved and returned immediately.
///
/// This call is not available to `Invulnerable` collators.
- #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCandidates::get()))] // todo:collator weight
- pub fn force_release_license(
+ #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCollators::get()))] // todo:collator weight
+ pub fn force_revoke_license(
origin: OriginFor<T>,
who: T::AccountId,
) -> DispatchResultWithPostInfo {
// leave_intent
T::UpdateOrigin::ensure_origin(origin)?;
- let current_count = Self::try_remove_candidate(&who)?;
- Self::try_release_license(&who, false)?;
+ let current_count = Self::try_remove_candidate_and_release_license(&who, false, true)?;
Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into()) // todo:collator weight
}
@@ -636,6 +606,23 @@
T::PotId::get().into_account_truncating()
}
+ fn try_remove_candidate_and_release_license(
+ who: &T::AccountId,
+ should_slash: bool,
+ ignore_if_not_candidate: bool,
+ ) -> Result<usize, DispatchError> {
+ let current_count = Self::try_remove_candidate(who);
+ let current_count = if ignore_if_not_candidate
+ && current_count == Err(Error::<T>::NotCandidate.into())
+ {
+ <Candidates<T>>::decode_len().unwrap_or_default()
+ } else {
+ current_count?
+ };
+ Self::try_release_license(who, should_slash)?;
+ Ok(current_count)
+ }
+
/// Removes a candidate from the collator pool for the next session if they exist.
fn try_remove_candidate(who: &T::AccountId) -> Result<usize, DispatchError> {
let current_count =
@@ -657,7 +644,7 @@
/// Removes a candidate if they exist and sends them back their deposit, optionally slashed.
fn try_release_license(who: &T::AccountId, should_slash: bool) -> DispatchResult {
let mut deposit_returned = BalanceOf::<T>::default();
- Licenses::<T>::try_mutate_exists(&who, |deposit| -> DispatchResult {
+ Licenses::<T>::try_mutate_exists(who, |deposit| -> DispatchResult {
if let Some(deposit) = deposit.take() {
if should_slash {
let slashed = T::SlashRatio::get() * deposit;
@@ -690,7 +677,7 @@
///
/// 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<T::AccountId, T::MaxCandidates>,
+ candidates: BoundedVec<T::AccountId, T::MaxCollators>,
) -> Vec<T::AccountId> {
let mut collators = Self::invulnerables().to_vec();
collators.extend(candidates);
@@ -700,8 +687,8 @@
/// Kicks out candidates that did not produce a block in the kick threshold
/// and **confiscates** their deposits to the treasury.
pub fn kick_stale_candidates(
- candidates: BoundedVec<T::AccountId, T::MaxCandidates>, //LicenseInfo<T::AccountId, BalanceOf<T>>
- ) -> BoundedVec<T::AccountId, T::MaxCandidates> {
+ candidates: BoundedVec<T::AccountId, T::MaxCollators>, //LicenseInfo<T::AccountId, BalanceOf<T>>
+ ) -> BoundedVec<T::AccountId, T::MaxCollators> {
let now = frame_system::Pallet::<T>::block_number();
let kick_threshold = Self::kick_threshold();
candidates
@@ -709,21 +696,13 @@
.filter_map(|c| {
let last_block = <LastAuthoredBlock<T>>::get(c.clone());
let since_last = now.saturating_sub(last_block);
- if since_last < kick_threshold ||
- Self::candidates().len() as u32 <= T::MinCandidates::get()
- {
+ if since_last < kick_threshold {
Some(c)
} else {
- let outcome = Self::try_remove_candidate(&c);
+ let outcome = Self::try_remove_candidate_and_release_license(&c, true, false);
if let Err(why) = outcome {
- log::warn!("Failed to remove candidate {:?}", why);
- debug_assert!(false, "failed to remove candidate {:?}", why);
- return None;
- }
- let outcome = Self::try_release_license(&c, true);
- if let Err(why) = outcome {
- log::warn!("Failed to release license {:?}", why);
- debug_assert!(false, "failed to release license {:?}", why);
+ log::warn!("Failed to kick collator and release license {:?}", why);
+ debug_assert!(false, "failed to kick collator and release license {why:?}");
}
None
}
pallets/collator-selection/src/mock.rsdiffbeforeafterboth--- a/pallets/collator-selection/src/mock.rs
+++ b/pallets/collator-selection/src/mock.rs
@@ -206,9 +206,7 @@
parameter_types! {
pub const PotId: PalletId = PalletId(*b"PotStake");
- pub const MaxCandidates: u32 = 20;
- pub const MaxInvulnerables: u32 = 20;
- pub const MinCandidates: u32 = 1;
+ pub const MaxCollators: u32 = 20;
pub const MaxAuthorities: u32 = 100_000;
pub const SlashRatio: Perbill = Perbill::one();
}
@@ -230,9 +228,7 @@
type Currency = Balances;
type UpdateOrigin = EnsureSignedBy<RootAccount, u64>;
type PotId = PotId;
- type MaxCandidates = MaxCandidates;
- type MinCandidates = MinCandidates;
- type MaxInvulnerables = MaxInvulnerables;
+ type MaxCollators = MaxCollators;
// type KickThreshold = Period;
type SlashRatio = SlashRatio;
type TreasuryAccountId = ();
@@ -263,9 +259,9 @@
})
.collect::<Vec<_>>();
let collator_selection = collator_selection::GenesisConfig::<Test> {
- desired_candidates: 2,
+ desired_collators: 5,
license_bond: 10,
- kick_threshold: 1,
+ kick_threshold: 10,
invulnerables,
};
let session = pallet_session::GenesisConfig::<Test> { keys };
pallets/collator-selection/src/tests.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617// Original license:18// Copyright (C) 2021 Parity Technologies (UK) Ltd.19// SPDX-License-Identifier: Apache-2.02021// Licensed under the Apache License, Version 2.0 (the "License");22// you may not use this file except in compliance with the License.23// You may obtain a copy of the License at24//25// http://www.apache.org/licenses/LICENSE-2.026//27// Unless required by applicable law or agreed to in writing, software28// distributed under the License is distributed on an "AS IS" BASIS,29// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.30// See the License for the specific language governing permissions and31// limitations under the License.3233use crate as collator_selection;34use crate::{mock::*, LicenseInfo, Error};35use frame_support::{36 assert_noop, assert_ok,37 traits::{Currency, GenesisBuild, OnInitialize},38};39use pallet_balances::Error as BalancesError;40use sp_runtime::traits::BadOrigin;4142#[test]43fn basic_setup_works() {44 new_test_ext().execute_with(|| {45 assert_eq!(CollatorSelection::desired_candidates(), 2);46 assert_eq!(CollatorSelection::license_bond(), 10);4748 assert!(CollatorSelection::candidates().is_empty());49 assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);50 });51}5253// todo:collator add more tests later5455#[test]56fn it_should_add_invulnerables() {57 new_test_ext().execute_with(|| {58 assert_ok!(CollatorSelection::add_invulnerable(59 RuntimeOrigin::signed(RootAccount::get()),60 161 ));62 assert_ok!(CollatorSelection::add_invulnerable(63 RuntimeOrigin::signed(RootAccount::get()),64 265 ));66 assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);6768 // cannot set with non-root.69 assert_noop!(70 CollatorSelection::add_invulnerable(RuntimeOrigin::signed(1), 3),71 BadOrigin72 );7374 // cannot set invulnerables without associated validator keys75 assert_noop!(76 CollatorSelection::add_invulnerable(RuntimeOrigin::signed(RootAccount::get()), 7),77 Error::<Test>::ValidatorNotRegistered78 );79 });80}8182#[test]83fn it_should_remove_invulnerables() {84 new_test_ext().execute_with(|| {85 assert_ok!(CollatorSelection::add_invulnerable(86 RuntimeOrigin::signed(RootAccount::get()),87 188 ));89 assert_ok!(CollatorSelection::add_invulnerable(90 RuntimeOrigin::signed(RootAccount::get()),91 292 ));9394 // cannot remove with non-root.95 assert_noop!(96 CollatorSelection::remove_invulnerable(RuntimeOrigin::signed(1), 3),97 BadOrigin98 );99100 assert_ok!(CollatorSelection::remove_invulnerable(101 RuntimeOrigin::signed(RootAccount::get()),102 2103 ));104 assert_eq!(CollatorSelection::invulnerables(), vec![1]);105106 // cannot remove an invulnerable if there would be 0 invulnerables.107 assert_noop!(108 CollatorSelection::remove_invulnerable(RuntimeOrigin::signed(RootAccount::get()), 1),109 Error::<Test>::TooFewInvulnerables110 );111 });112}113114#[test]115fn set_desired_candidates_works() {116 new_test_ext().execute_with(|| {117 // given118 assert_eq!(CollatorSelection::desired_candidates(), 2);119120 // can set121 assert_ok!(CollatorSelection::set_desired_candidates(122 RuntimeOrigin::signed(RootAccount::get()),123 7124 ));125 assert_eq!(CollatorSelection::desired_candidates(), 7);126127 // rejects bad origin128 assert_noop!(129 CollatorSelection::set_desired_candidates(RuntimeOrigin::signed(1), 8),130 BadOrigin131 );132 });133}134135#[test]136fn set_license_bond() {137 new_test_ext().execute_with(|| {138 // given139 assert_eq!(CollatorSelection::license_bond(), 10);140141 // can set142 assert_ok!(CollatorSelection::set_license_bond(143 RuntimeOrigin::signed(RootAccount::get()),144 7145 ));146 assert_eq!(CollatorSelection::license_bond(), 7);147148 // rejects bad origin.149 assert_noop!(150 CollatorSelection::set_license_bond(RuntimeOrigin::signed(1), 8),151 BadOrigin152 );153 });154}155156#[test]157fn cannot_register_candidate_if_too_many() {158 new_test_ext().execute_with(|| {159 // reset desired candidates:160 <crate::DesiredCandidates<Test>>::put(0);161162 // can't accept anyone anymore.163 assert_noop!(164 CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)),165 Error::<Test>::TooManyCandidates,166 );167168 // reset desired candidates:169 <crate::DesiredCandidates<Test>>::put(1);170 assert_ok!(CollatorSelection::register_as_candidate(171 RuntimeOrigin::signed(4)172 ));173174 // but no more175 assert_noop!(176 CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)),177 Error::<Test>::TooManyCandidates,178 );179 })180}181182#[test]183fn cannot_unregister_candidate_if_too_few() {184 new_test_ext().execute_with(|| {185 // reset desired candidates:186 <crate::DesiredCandidates<Test>>::put(1);187 assert_ok!(CollatorSelection::register_as_candidate(188 RuntimeOrigin::signed(4)189 ));190191 // can not remove too few192 assert_noop!(193 CollatorSelection::leave_intent(RuntimeOrigin::signed(4)),194 Error::<Test>::TooFewCandidates,195 );196 })197}198199#[test]200fn cannot_register_as_candidate_if_invulnerable() {201 new_test_ext().execute_with(|| {202 assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);203204 // can't 1 because it is invulnerable.205 assert_noop!(206 CollatorSelection::register_as_candidate(RuntimeOrigin::signed(1)),207 Error::<Test>::AlreadyInvulnerable,208 );209 })210}211212#[test]213fn cannot_register_as_candidate_if_keys_not_registered() {214 new_test_ext().execute_with(|| {215 // can't 7 because keys not registered.216 assert_noop!(217 CollatorSelection::register_as_candidate(RuntimeOrigin::signed(7)),218 Error::<Test>::ValidatorNotRegistered219 );220 })221}222223#[test]224fn cannot_register_dupe_candidate() {225 new_test_ext().execute_with(|| {226 // can add 3 as candidate227 assert_ok!(CollatorSelection::register_as_candidate(228 RuntimeOrigin::signed(3)229 ));230 let addition = LicenseInfo {231 who: 3,232 deposit: 10,233 };234 assert_eq!(CollatorSelection::candidates(), vec![addition]);235 assert_eq!(CollatorSelection::last_authored_block(3), 10);236 assert_eq!(Balances::free_balance(3), 90);237238 // but no more239 assert_noop!(240 CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)),241 Error::<Test>::AlreadyCandidate,242 );243 })244}245246#[test]247fn cannot_register_as_candidate_if_poor() {248 new_test_ext().execute_with(|| {249 assert_eq!(Balances::free_balance(&3), 100);250 assert_eq!(Balances::free_balance(&33), 0);251252 // works253 assert_ok!(CollatorSelection::register_as_candidate(254 RuntimeOrigin::signed(3)255 ));256257 // poor258 assert_noop!(259 CollatorSelection::register_as_candidate(RuntimeOrigin::signed(33)),260 BalancesError::<Test>::InsufficientBalance,261 );262 });263}264265#[test]266fn register_as_candidate_works() {267 new_test_ext().execute_with(|| {268 // given269 assert_eq!(CollatorSelection::desired_candidates(), 2);270 assert_eq!(CollatorSelection::license_bond(), 10);271 assert_eq!(CollatorSelection::candidates(), Vec::new());272 assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);273274 // take two endowed, non-invulnerables accounts.275 assert_eq!(Balances::free_balance(&3), 100);276 assert_eq!(Balances::free_balance(&4), 100);277278 assert_ok!(CollatorSelection::register_as_candidate(279 RuntimeOrigin::signed(3)280 ));281 assert_ok!(CollatorSelection::register_as_candidate(282 RuntimeOrigin::signed(4)283 ));284285 assert_eq!(Balances::free_balance(&3), 90);286 assert_eq!(Balances::free_balance(&4), 90);287288 assert_eq!(CollatorSelection::candidates().len(), 2);289 });290}291292#[test]293fn leave_intent() {294 new_test_ext().execute_with(|| {295 // register a candidate.296 assert_ok!(CollatorSelection::register_as_candidate(297 RuntimeOrigin::signed(3)298 ));299 assert_eq!(Balances::free_balance(3), 90);300301 // register too so can leave above min candidates302 assert_ok!(CollatorSelection::register_as_candidate(303 RuntimeOrigin::signed(5)304 ));305 assert_eq!(Balances::free_balance(5), 90);306307 // cannot leave if not candidate.308 assert_noop!(309 CollatorSelection::leave_intent(RuntimeOrigin::signed(4)),310 Error::<Test>::NotCandidate311 );312313 // bond is returned314 assert_ok!(CollatorSelection::leave_intent(RuntimeOrigin::signed(3)));315 assert_eq!(Balances::free_balance(3), 100);316 assert_eq!(CollatorSelection::last_authored_block(3), 0);317 });318}319320#[test]321fn authorship_event_handler() {322 new_test_ext().execute_with(|| {323 // put 100 in the pot + 5 for ED324 Balances::make_free_balance_be(&CollatorSelection::account_id(), 105);325326 // 4 is the default author.327 assert_eq!(Balances::free_balance(4), 100);328 assert_ok!(CollatorSelection::register_as_candidate(329 RuntimeOrigin::signed(4)330 ));331 // triggers `note_author`332 Authorship::on_initialize(1);333334 let collator = LicenseInfo {335 who: 4,336 deposit: 10,337 };338339 assert_eq!(CollatorSelection::candidates(), vec![collator]);340 assert_eq!(CollatorSelection::last_authored_block(4), 0);341342 // half of the pot goes to the collator who's the author (4 in tests).343 assert_eq!(Balances::free_balance(4), 140);344 // half + ED stays.345 assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 55);346 });347}348349#[test]350fn fees_edgecases() {351 new_test_ext().execute_with(|| {352 // Nothing panics, no reward when no ED in balance353 Authorship::on_initialize(1);354 // put some money into the pot at ED355 Balances::make_free_balance_be(&CollatorSelection::account_id(), 5);356 // 4 is the default author.357 assert_eq!(Balances::free_balance(4), 100);358 assert_ok!(CollatorSelection::register_as_candidate(359 RuntimeOrigin::signed(4)360 ));361 // triggers `note_author`362 Authorship::on_initialize(1);363364 let collator = LicenseInfo {365 who: 4,366 deposit: 10,367 };368369 assert_eq!(CollatorSelection::candidates(), vec![collator]);370 assert_eq!(CollatorSelection::last_authored_block(4), 0);371 // Nothing received372 assert_eq!(Balances::free_balance(4), 90);373 // all fee stays374 assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 5);375 });376}377378#[test]379fn session_management_works() {380 new_test_ext().execute_with(|| {381 initialize_to_block(1);382383 assert_eq!(SessionChangeBlock::get(), 0);384 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);385386 initialize_to_block(4);387388 assert_eq!(SessionChangeBlock::get(), 0);389 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);390391 // add a new collator392 assert_ok!(CollatorSelection::register_as_candidate(393 RuntimeOrigin::signed(3)394 ));395396 // session won't see this.397 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);398 // but we have a new candidate.399 assert_eq!(CollatorSelection::candidates().len(), 1);400401 initialize_to_block(10);402 assert_eq!(SessionChangeBlock::get(), 10);403 // pallet-session has 1 session delay; current validators are the same.404 assert_eq!(Session::validators(), vec![1, 2]);405 // queued ones are changed, and now we have 3.406 assert_eq!(Session::queued_keys().len(), 3);407 // session handlers (aura, et. al.) cannot see this yet.408 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);409410 initialize_to_block(20);411 assert_eq!(SessionChangeBlock::get(), 20);412 // changed are now reflected to session handlers.413 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3]);414 });415}416417#[test]418fn kick_mechanism() {419 new_test_ext().execute_with(|| {420 // add a new collator421 assert_ok!(CollatorSelection::register_as_candidate(422 RuntimeOrigin::signed(3)423 ));424 assert_ok!(CollatorSelection::register_as_candidate(425 RuntimeOrigin::signed(4)426 ));427 initialize_to_block(10);428 assert_eq!(CollatorSelection::candidates().len(), 2);429 initialize_to_block(20);430 assert_eq!(SessionChangeBlock::get(), 20);431 // 4 authored this block, gets to stay 3 was kicked432 assert_eq!(CollatorSelection::candidates().len(), 1);433 // 3 will be kicked after 1 session delay434 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 4]);435 let collator = LicenseInfo {436 who: 4,437 deposit: 10,438 };439 assert_eq!(CollatorSelection::candidates(), vec![collator]);440 assert_eq!(CollatorSelection::kick_threshold(), 1);441 assert_eq!(CollatorSelection::last_authored_block(4), 20);442 initialize_to_block(30);443 // 3 gets kicked after 1 session delay444 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 4]);445 // kicked collator gets funds back446 assert_eq!(Balances::free_balance(3), 100);447 });448}449450#[test]451fn should_not_kick_mechanism_too_few() {452 new_test_ext().execute_with(|| {453 // add a new collator454 assert_ok!(CollatorSelection::register_as_candidate(455 RuntimeOrigin::signed(3)456 ));457 assert_ok!(CollatorSelection::register_as_candidate(458 RuntimeOrigin::signed(5)459 ));460 initialize_to_block(10);461 assert_eq!(CollatorSelection::candidates().len(), 2);462 initialize_to_block(20);463 assert_eq!(SessionChangeBlock::get(), 20);464 // 4 authored this block, 5 gets to stay too few 3 was kicked465 assert_eq!(CollatorSelection::candidates().len(), 1);466 // 3 will be kicked after 1 session delay467 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 5]);468 let collator = LicenseInfo {469 who: 5,470 deposit: 10,471 };472 assert_eq!(CollatorSelection::candidates(), vec![collator]);473 assert_eq!(CollatorSelection::last_authored_block(4), 20);474 initialize_to_block(30);475 // 3 gets kicked after 1 session delay476 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 5]);477 // kicked collator gets funds back478 assert_eq!(Balances::free_balance(3), 100);479 });480}481482#[test]483#[should_panic = "duplicate invulnerables in genesis."]484fn cannot_set_genesis_value_twice() {485 sp_tracing::try_init_simple();486 let mut t = frame_system::GenesisConfig::default()487 .build_storage::<Test>()488 .unwrap();489 let invulnerables = vec![1, 1];490491 let collator_selection = collator_selection::GenesisConfig::<Test> {492 desired_candidates: 2,493 license_bond: 10,494 kick_threshold: 1,495 invulnerables,496 };497 // collator selection must be initialized before session.498 collator_selection.assimilate_storage(&mut t).unwrap();499}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617// Original license:18// Copyright (C) 2021 Parity Technologies (UK) Ltd.19// SPDX-License-Identifier: Apache-2.02021// Licensed under the Apache License, Version 2.0 (the "License");22// you may not use this file except in compliance with the License.23// You may obtain a copy of the License at24//25// http://www.apache.org/licenses/LICENSE-2.026//27// Unless required by applicable law or agreed to in writing, software28// distributed under the License is distributed on an "AS IS" BASIS,29// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.30// See the License for the specific language governing permissions and31// limitations under the License.3233use crate as collator_selection;34use crate::{mock::*, Error};35use frame_support::{36 assert_noop, assert_ok,37 traits::{Currency, GenesisBuild, OnInitialize},38};39use pallet_balances::Error as BalancesError;40use sp_runtime::traits::BadOrigin;4142fn get_license_and_onboard(account_id: <Test as frame_system::Config>::AccountId) {43 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(44 account_id45 )));46 assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed(47 account_id48 )));49}5051#[test]52fn basic_setup_works() {53 new_test_ext().execute_with(|| {54 assert_eq!(CollatorSelection::desired_collators(), 5);55 assert_eq!(CollatorSelection::license_bond(), 10);5657 assert!(CollatorSelection::candidates().is_empty());58 assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);59 });60}6162// todo:collator add more tests later63// invulnerable after onboard + invulnerables can bypass desired_candidates6465#[test]66fn it_should_add_invulnerables() {67 new_test_ext().execute_with(|| {68 assert_ok!(CollatorSelection::add_invulnerable(69 RuntimeOrigin::signed(RootAccount::get()),70 171 ));72 assert_ok!(CollatorSelection::add_invulnerable(73 RuntimeOrigin::signed(RootAccount::get()),74 275 ));76 assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);7778 // cannot set with non-root.79 assert_noop!(80 CollatorSelection::add_invulnerable(RuntimeOrigin::signed(1), 3),81 BadOrigin82 );8384 // cannot set invulnerables without associated validator keys85 assert_noop!(86 CollatorSelection::add_invulnerable(RuntimeOrigin::signed(RootAccount::get()), 7),87 Error::<Test>::ValidatorNotRegistered88 );89 });90}9192#[test]93fn it_should_remove_invulnerables() {94 new_test_ext().execute_with(|| {95 assert_ok!(CollatorSelection::add_invulnerable(96 RuntimeOrigin::signed(RootAccount::get()),97 198 ));99 assert_ok!(CollatorSelection::add_invulnerable(100 RuntimeOrigin::signed(RootAccount::get()),101 2102 ));103104 // cannot remove with non-root.105 assert_noop!(106 CollatorSelection::remove_invulnerable(RuntimeOrigin::signed(1), 3),107 BadOrigin108 );109110 assert_ok!(CollatorSelection::remove_invulnerable(111 RuntimeOrigin::signed(RootAccount::get()),112 2113 ));114 assert_eq!(CollatorSelection::invulnerables(), vec![1]);115116 // cannot remove an invulnerable if there would be 0 invulnerables.117 assert_noop!(118 CollatorSelection::remove_invulnerable(RuntimeOrigin::signed(RootAccount::get()), 1),119 Error::<Test>::TooFewInvulnerables120 );121 });122}123124#[test]125fn set_desired_collators_works() {126 new_test_ext().execute_with(|| {127 // given128 assert_eq!(CollatorSelection::desired_collators(), 5);129130 // can set131 assert_ok!(CollatorSelection::set_desired_collators(132 RuntimeOrigin::signed(RootAccount::get()),133 7134 ));135 assert_eq!(CollatorSelection::desired_collators(), 7);136137 // rejects bad origin138 assert_noop!(139 CollatorSelection::set_desired_collators(RuntimeOrigin::signed(1), 8),140 BadOrigin141 );142 });143}144145#[test]146fn set_license_bond() {147 new_test_ext().execute_with(|| {148 // given149 assert_eq!(CollatorSelection::license_bond(), 10);150151 // can set152 assert_ok!(CollatorSelection::set_license_bond(153 RuntimeOrigin::signed(RootAccount::get()),154 7155 ));156 assert_eq!(CollatorSelection::license_bond(), 7);157158 // rejects bad origin.159 assert_noop!(160 CollatorSelection::set_license_bond(RuntimeOrigin::signed(1), 8),161 BadOrigin162 );163 });164}165166#[test]167fn cannot_onboard_candidate_with_no_license() {168 new_test_ext().execute_with(|| {169 // can't onboard a candidate who did not get a license.170 assert_noop!(171 CollatorSelection::onboard(RuntimeOrigin::signed(3)),172 Error::<Test>::NoLicense,173 );174175 // but give it a license and welcome aboard.176 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));177 assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed(3)));178 })179}180181#[test]182fn cannot_onboard_candidate_if_too_many() {183 new_test_ext().execute_with(|| {184 // reset desired candidates185 <crate::DesiredCollators<Test>>::put(0);186187 // can still get a license.188 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(4)));189190 // can't accept anyone anymore.191 assert_noop!(192 CollatorSelection::onboard(RuntimeOrigin::signed(4)),193 Error::<Test>::TooManyCandidates,194 );195196 // reset desired candidates to invulnerables + 1197 <crate::DesiredCollators<Test>>::put(3);198 assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed(4)));199200 // but no more.201 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(5)));202 assert_noop!(203 CollatorSelection::onboard(RuntimeOrigin::signed(5)),204 Error::<Test>::TooManyCandidates,205 );206 })207}208209#[test]210fn cannot_obtain_license_if_keys_not_registered() {211 new_test_ext().execute_with(|| {212 // can't 7 because keys not registered.213 assert_noop!(214 CollatorSelection::get_license(RuntimeOrigin::signed(7)),215 Error::<Test>::ValidatorNotRegistered216 );217 })218}219220#[test]221fn cannot_obtain_license_if_poor() {222 new_test_ext().execute_with(|| {223 assert_eq!(Balances::free_balance(&3), 100);224 assert_eq!(Balances::free_balance(&33), 0);225226 // works227 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));228229 // poor230 assert_noop!(231 CollatorSelection::get_license(RuntimeOrigin::signed(33)),232 BalancesError::<Test>::InsufficientBalance,233 );234 });235}236237#[test]238fn cannot_onboard_dupe_candidate() {239 new_test_ext().execute_with(|| {240 // can add 3 as candidate241 get_license_and_onboard(3);242 assert_eq!(CollatorSelection::licenses(3), 10);243 assert_eq!(CollatorSelection::candidates(), vec![3]);244 assert_eq!(CollatorSelection::last_authored_block(3), 10);245 assert_eq!(Balances::free_balance(3), 90);246247 // but no more248 assert_noop!(249 CollatorSelection::get_license(RuntimeOrigin::signed(3)),250 Error::<Test>::AlreadyHoldingLicense,251 );252 assert_noop!(253 CollatorSelection::onboard(RuntimeOrigin::signed(3)),254 Error::<Test>::AlreadyCandidate,255 );256 })257}258259#[test]260fn becoming_candidate_works() {261 new_test_ext().execute_with(|| {262 // given263 assert_eq!(CollatorSelection::desired_collators(), 5);264 assert_eq!(CollatorSelection::license_bond(), 10);265 assert_eq!(CollatorSelection::candidates(), Vec::new());266 assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);267268 // take two endowed, non-invulnerables accounts.269 assert_eq!(Balances::free_balance(&3), 100);270 assert_eq!(Balances::free_balance(&4), 100);271272 get_license_and_onboard(3);273 get_license_and_onboard(4);274275 assert_eq!(Balances::free_balance(&3), 90);276 assert_eq!(Balances::free_balance(&4), 90);277278 assert_eq!(CollatorSelection::candidates().len(), 2);279 });280}281282#[test]283fn cannot_become_candidate_if_invulnerable() {284 new_test_ext().execute_with(|| {285 assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);286287 // can obtain a license even if is invulnerable.288 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(1)));289 // but cannot onboard290 assert_noop!(291 CollatorSelection::onboard(RuntimeOrigin::signed(1)),292 Error::<Test>::AlreadyInvulnerable,293 );294295 // get a license and then become invulnerable.296 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));297 assert_ok!(CollatorSelection::add_invulnerable(298 RuntimeOrigin::signed(RootAccount::get()),299 3300 ));301 assert_noop!(302 CollatorSelection::onboard(RuntimeOrigin::signed(3)),303 Error::<Test>::AlreadyInvulnerable,304 );305 })306}307308#[test]309fn can_become_invulnerable_if_candidate() {310 new_test_ext().execute_with(|| {311 // become a candidate and then become invulnerable.312 get_license_and_onboard(3);313 assert_eq!(CollatorSelection::candidates(), vec![3]);314315 assert_ok!(CollatorSelection::add_invulnerable(316 RuntimeOrigin::signed(RootAccount::get()),317 3318 ));319 // should exclude from candidates, but not revoke the license320 assert_eq!(CollatorSelection::candidates(), vec![]);321 assert_eq!(CollatorSelection::licenses(3), 10);322 assert_eq!(Balances::free_balance(3), 90);323 });324}325326#[test]327fn offboard() {328 new_test_ext().execute_with(|| {329 // register a candidate.330 get_license_and_onboard(3);331 assert_eq!(Balances::free_balance(3), 90);332333 // cannot leave if holds license but not yet candidate.334 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(4)));335 assert_noop!(336 CollatorSelection::offboard(RuntimeOrigin::signed(4)),337 Error::<Test>::NotCandidate338 );339 // cannot leave if does not hold license.340 assert_noop!(341 CollatorSelection::offboard(RuntimeOrigin::signed(5)),342 Error::<Test>::NotCandidate343 );344345 // bond is returned - only after releasing the license346 assert_ok!(CollatorSelection::offboard(RuntimeOrigin::signed(3)));347 assert_eq!(Balances::free_balance(3), 90);348 assert_eq!(CollatorSelection::last_authored_block(3), 0);349 assert_ok!(CollatorSelection::release_license(RuntimeOrigin::signed(3)));350 assert_eq!(Balances::free_balance(3), 100);351 });352}353354#[test]355fn release_license() {356 new_test_ext().execute_with(|| {357 // obtain a license to collate and reserve the bond.358 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));359 assert_eq!(Balances::free_balance(3), 90);360361 // release the license and get the bond back.362 assert_ok!(CollatorSelection::release_license(RuntimeOrigin::signed(3)));363 assert_eq!(Balances::free_balance(3), 100);364365 // register a candidate.366 get_license_and_onboard(3);367 assert_eq!(Balances::free_balance(3), 90);368369 // can release license even if onboarded.370 assert_ok!(CollatorSelection::release_license(RuntimeOrigin::signed(3)));371 assert_eq!(Balances::free_balance(3), 100);372 assert_eq!(CollatorSelection::candidates(), vec![]);373 });374}375376#[test]377fn force_revoke_license() {378 new_test_ext().execute_with(|| {379 // obtain a license to collate and reserve the bond.380 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));381 assert_eq!(Balances::free_balance(3), 90);382383 // cannot execute the operation as non-root384 assert_noop!(385 CollatorSelection::force_revoke_license(RuntimeOrigin::signed(3), 3),386 BadOrigin387 );388389 // release the license and get the bond back.390 assert_ok!(CollatorSelection::force_revoke_license(391 RuntimeOrigin::signed(RootAccount::get()),392 3393 ));394 assert_eq!(Balances::free_balance(3), 100);395396 // register a candidate.397 get_license_and_onboard(3);398 assert_eq!(Balances::free_balance(3), 90);399400 // can release license even if onboarded.401 assert_ok!(CollatorSelection::force_revoke_license(402 RuntimeOrigin::signed(RootAccount::get()),403 3404 ));405 assert_eq!(Balances::free_balance(3), 100);406 assert_eq!(CollatorSelection::candidates(), vec![]);407 });408}409410#[test]411fn authorship_event_handler() {412 new_test_ext().execute_with(|| {413 // put 100 in the pot + 5 for ED414 Balances::make_free_balance_be(&CollatorSelection::account_id(), 105);415416 // 4 is the default author.417 assert_eq!(Balances::free_balance(4), 100);418 get_license_and_onboard(4);419 // triggers `note_author`420 Authorship::on_initialize(1);421422 assert_eq!(CollatorSelection::candidates(), vec![4]);423 assert_eq!(CollatorSelection::last_authored_block(4), 0);424425 // half of the pot goes to the collator who's the author (4 in tests).426 assert_eq!(Balances::free_balance(4), 140);427 // half + ED stays.428 assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 55);429 });430}431432#[test]433fn fees_edgecases() {434 new_test_ext().execute_with(|| {435 // Nothing panics, no reward when no ED in balance436 Authorship::on_initialize(1);437 // put some money into the pot at ED438 Balances::make_free_balance_be(&CollatorSelection::account_id(), 5);439 // 4 is the default author.440 assert_eq!(Balances::free_balance(4), 100);441 get_license_and_onboard(4);442 // triggers `note_author`443 Authorship::on_initialize(1);444445 assert_eq!(CollatorSelection::candidates(), vec![4]);446 assert_eq!(CollatorSelection::last_authored_block(4), 0);447 // Nothing received448 assert_eq!(Balances::free_balance(4), 90);449 // all fee stays450 assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 5);451 });452}453454#[test]455fn session_management_works() {456 new_test_ext().execute_with(|| {457 initialize_to_block(1);458459 assert_eq!(SessionChangeBlock::get(), 0);460 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);461462 initialize_to_block(4);463464 assert_eq!(SessionChangeBlock::get(), 0);465 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);466467 // add a new collator468 get_license_and_onboard(5);469470 // session won't see this.471 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);472 // but we have a new candidate.473 assert_eq!(CollatorSelection::candidates().len(), 1);474475 initialize_to_block(10);476 assert_eq!(SessionChangeBlock::get(), 10);477 // pallet-session has 1 session delay; current validators are the same.478 assert_eq!(Session::validators(), vec![1, 2]);479 // queued ones are changed, and now we have 3.480 assert_eq!(Session::queued_keys().len(), 3);481 // session handlers (aura, et. al.) cannot see this yet.482 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);483484 initialize_to_block(20);485 assert_eq!(SessionChangeBlock::get(), 20);486 // changed are now reflected to session handlers.487 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 5]);488 });489}490491#[test]492fn kick_mechanism() {493 new_test_ext().execute_with(|| {494 // add a new collator495 get_license_and_onboard(3);496 get_license_and_onboard(4);497498 initialize_to_block(10);499 assert_eq!(CollatorSelection::candidates().len(), 2);500501 initialize_to_block(20);502 assert_eq!(SessionChangeBlock::get(), 20);503 // 4 authored this block, gets to stay 3 was kicked504 assert_eq!(CollatorSelection::candidates().len(), 1);505 // 3 will be kicked after 1 session delay506 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 4]);507508 assert_eq!(CollatorSelection::candidates(), vec![4]);509 assert_eq!(CollatorSelection::kick_threshold(), 10);510 assert_eq!(CollatorSelection::last_authored_block(4), 20);511512 initialize_to_block(30);513 // 3 gets kicked after 1 session delay514 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 4]);515 // kicked collator gets their funds slashed, the deposit going to treasury516 assert_eq!(Balances::free_balance(3), 90);517 });518}519520#[test]521#[should_panic = "duplicate invulnerables in genesis."]522fn cannot_set_genesis_value_twice() {523 sp_tracing::try_init_simple();524 let mut t = frame_system::GenesisConfig::default()525 .build_storage::<Test>()526 .unwrap();527 let invulnerables = vec![1, 1];528529 let collator_selection = collator_selection::GenesisConfig::<Test> {530 desired_collators: 5,531 license_bond: 10,532 kick_threshold: 10,533 invulnerables,534 };535 // collator selection must be initialized before session.536 collator_selection.assimilate_storage(&mut t).unwrap();537}pallets/collator-selection/src/weights.rsdiffbeforeafterboth--- a/pallets/collator-selection/src/weights.rs
+++ b/pallets/collator-selection/src/weights.rs
@@ -45,7 +45,7 @@
// The weight info trait for `pallet_collator_selection`.
pub trait WeightInfo {
fn set_invulnerables(_b: u32) -> Weight;
- fn set_desired_candidates() -> Weight;
+ fn set_desired_collators() -> Weight;
fn set_license_bond() -> Weight;
fn register_as_candidate(_c: u32) -> Weight;
fn leave_intent(_c: u32) -> Weight;
@@ -62,7 +62,7 @@
.saturating_add(Weight::from_ref_time(68_000 as u64).saturating_mul(b as u64))
.saturating_add(T::DbWeight::get().writes(1 as u64))
}
- fn set_desired_candidates() -> Weight {
+ fn set_desired_collators() -> Weight {
Weight::from_ref_time(16_363_000 as u64).saturating_add(T::DbWeight::get().writes(1 as u64))
}
fn set_license_bond() -> Weight {
@@ -108,7 +108,7 @@
.saturating_add(Weight::from_ref_time(68_000 as u64).saturating_mul(b as u64))
.saturating_add(RocksDbWeight::get().writes(1 as u64))
}
- fn set_desired_candidates() -> Weight {
+ fn set_desired_collators() -> Weight {
Weight::from_ref_time(16_363_000 as u64)
.saturating_add(RocksDbWeight::get().writes(1 as u64))
}
primitives/common/src/constants.rsdiffbeforeafterboth--- a/primitives/common/src/constants.rs
+++ b/primitives/common/src/constants.rs
@@ -45,9 +45,9 @@
/// Minimum balance required to create or keep an account open.
pub const EXISTENTIAL_DEPOSIT: u128 = 0;
/// Amount of Balance reserved for candidate registration.
-pub const GENESIS_LICENSE_BOND: u128 = EXISTENTIAL_DEPOSIT;
+pub const GENESIS_LICENSE_BOND: u128 = 1_000_000_000_000 * UNIQUE;
/// How long a periodic session lasts in blocks.
-pub const SESSION_LENGTH: BlockNumber = MINUTES;
+pub const SESSION_LENGTH: BlockNumber = HOURS;
// Targeting 0.1 UNQ per transfer
pub const WEIGHT_TO_FEE_COEFF: u32 = /*<weight2fee>*/175_199_920/*</weight2fee>*/;
runtime/common/config/pallets/collator_selection.rsdiffbeforeafterboth--- a/runtime/common/config/pallets/collator_selection.rs
+++ b/runtime/common/config/pallets/collator_selection.rs
@@ -55,9 +55,7 @@
parameter_types! {
pub const PotId: PalletId = PalletId(*b"PotStake");
- pub const MaxCandidates: u32 = 30; // todo:collator 30 collator slots - 3 planned invulnerables
- pub const MinCandidates: u32 = 1;
- pub const MaxInvulnerables: u32 = 30;
+ pub const MaxCollators: u32 = 10;
pub const SlashRatio: Perbill = Perbill::from_percent(100);
}
@@ -68,9 +66,7 @@
type UpdateOrigin = EnsureRoot<AccountId>;
type TreasuryAccountId = TreasuryAccountId;
type PotId = PotId;
- type MaxCandidates = MaxCandidates;
- type MinCandidates = MinCandidates;
- type MaxInvulnerables = MaxInvulnerables;
+ type MaxCollators = MaxCollators;
// todo:collator kick threshold should be in storage and configured only by root -- or rather UpdateOrigin
type SlashRatio = SlashRatio;
type ValidatorId = <Self as frame_system::Config>::AccountId;
runtime/common/mod.rsdiffbeforeafterboth--- a/runtime/common/mod.rs
+++ b/runtime/common/mod.rs
@@ -192,7 +192,7 @@
};
use pallet_session::SessionManager;
use up_common::constants::GENESIS_LICENSE_BOND;
- use crate::config::pallets::collator_selection::MaxInvulnerables;
+ use crate::config::pallets::collator_selection::MaxCollators;
let mut weight = <Runtime as frame_system::Config>::DbWeight::get().reads(1);
@@ -231,17 +231,17 @@
})
.collect::<Vec<_>>();
- let bounded_invulnerables = BoundedVec::<_, MaxInvulnerables>::try_from(
+ let bounded_invulnerables = BoundedVec::<_, MaxCollators>::try_from(
invulnerables
.iter()
.cloned()
.map(|(acc, _)| acc)
.collect::<Vec<_>>(),
)
- .expect("Existing collators/invulnerables are more than MaxInvulnerables");
+ .expect("Existing collators/invulnerables are more than MaxCollators");
<pallet_collator_selection::Invulnerables<Runtime>>::put(bounded_invulnerables);
- <pallet_collator_selection::DesiredCandidates<Runtime>>::put(0);
+ <pallet_collator_selection::DesiredCollators<Runtime>>::put(MaxCollators::get());
<pallet_collator_selection::LicenseBond<Runtime>>::put(GENESIS_LICENSE_BOND);
let keys = invulnerables
runtime/common/tests/mod.rsdiffbeforeafterboth--- a/runtime/common/tests/mod.rs
+++ b/runtime/common/tests/mod.rs
@@ -18,7 +18,7 @@
use sp_core::{Public, Pair};
use sp_std::vec;
use up_common::types::AuraId;
-use crate::{GenesisConfig, ParachainInfoConfig, AuraConfig};
+use crate::{GenesisConfig, ParachainInfoConfig};
pub mod xcm;
@@ -28,7 +28,61 @@
.public()
}
+#[cfg(feature = "collator-selection")]
+fn new_test_ext(para_id: u32) -> sp_io::TestExternalities {
+ use sp_core::{sr25519};
+ use sp_runtime::traits::{IdentifyAccount, Verify};
+ use crate::{AccountId, Signature, SessionKeys, CollatorSelectionConfig, SessionConfig};
+
+ type AccountPublic = <Signature as Verify>::Signer;
+
+ fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId
+ where
+ AccountPublic: From<<TPublic::Pair as Pair>::Public>,
+ {
+ AccountPublic::from(get_from_seed::<TPublic>(seed)).into_account()
+ }
+
+ let accounts = vec!["Alice", "Bob"];
+ let keys = accounts
+ .iter()
+ .map(|&acc| {
+ let account_id = get_account_id_from_seed::<sr25519::Public>(acc);
+ (
+ account_id.clone(),
+ account_id,
+ SessionKeys {
+ aura: get_from_seed::<AuraId>(acc),
+ },
+ )
+ })
+ .collect::<Vec<_>>();
+ let invulnerables = accounts
+ .iter()
+ .map(|acc| get_account_id_from_seed::<sr25519::Public>(acc))
+ .collect::<Vec<_>>();
+
+ let cfg = GenesisConfig {
+ collator_selection: CollatorSelectionConfig {
+ desired_collators: 2,
+ license_bond: 10,
+ kick_threshold: 10,
+ invulnerables,
+ },
+ session: SessionConfig { keys },
+ parachain_info: ParachainInfoConfig {
+ parachain_id: para_id.into(),
+ },
+ ..GenesisConfig::default()
+ };
+
+ cfg.build_storage().unwrap().into()
+}
+
+#[cfg(not(feature = "collator-selection"))]
fn new_test_ext(para_id: u32) -> sp_io::TestExternalities {
+ use crate::AuraConfig;
+
let cfg = GenesisConfig {
aura: AuraConfig {
authorities: vec![