difftreelog
feat(collator-selection) licenses + onboarding
in: master
8 files changed
node/cli/src/chain_spec.rsdiffbeforeafterboth--- a/node/cli/src/chain_spec.rs
+++ b/node/cli/src/chain_spec.rs
@@ -24,7 +24,7 @@
use serde_json::map::Map;
use up_common::types::opaque::*;
-use up_common::constants::{GENESIS_CANDIDACY_BOND, SESSION_LENGTH};
+use up_common::constants::{GENESIS_LICENSE_BOND, SESSION_LENGTH};
#[cfg(feature = "unique-runtime")]
pub use unique_runtime as default_runtime;
@@ -196,7 +196,7 @@
.cloned()
.map(|(acc, _)| acc)
.collect(),
- candidacy_bond: GENESIS_CANDIDACY_BOND,
+ license_bond: GENESIS_LICENSE_BOND,
kick_threshold: SESSION_LENGTH,
..Default::default()
},
pallets/collator-selection/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/collator-selection/src/benchmarking.rs
+++ b/pallets/collator-selection/src/benchmarking.rs
@@ -116,12 +116,12 @@
.map(|c| account("candidate", c, SEED))
.collect::<Vec<_>>();
assert!(
- <CandidacyBond<T>>::get() > 0u32.into(),
+ <LicenseBond<T>>::get() > 0u32.into(),
"Bond cannot be zero!"
);
for who in candidates {
- T::Currency::make_free_balance_be(&who, <CandidacyBond<T>>::get() * 2u32.into());
+ T::Currency::make_free_balance_be(&who, <LicenseBond<T>>::get() * 2u32.into());
<CollatorSelection<T>>::register_as_candidate(RawOrigin::Signed(who).into()).unwrap();
}
}
@@ -154,16 +154,16 @@
assert_last_event::<T>(Event::NewDesiredCandidates{desired_candidates: max}.into());
}
- set_candidacy_bond {
+ set_license_bond {
let bond_amount: BalanceOf<T> = T::Currency::minimum_balance() * 10u32.into();
let origin = T::UpdateOrigin::successful_origin();
}: {
assert_ok!(
- <CollatorSelection<T>>::set_candidacy_bond(origin, bond_amount.clone())
+ <CollatorSelection<T>>::set_license_bond(origin, bond_amount.clone())
);
}
verify {
- assert_last_event::<T>(Event::NewCandidacyBond{bond_amount}.into());
+ assert_last_event::<T>(Event::NewLicenseBond{bond_amount}.into());
}
// worse case is when we have all the max-candidate slots filled except one, and we fill that
@@ -171,7 +171,7 @@
register_as_candidate {
let c in 1 .. T::MaxCandidates::get();
- <CandidacyBond<T>>::put(T::Currency::minimum_balance());
+ <LicenseBond<T>>::put(T::Currency::minimum_balance());
<DesiredCandidates<T>>::put(c + 1);
register_validators::<T>(c);
@@ -195,7 +195,7 @@
// worse case is the last candidate leaving.
leave_intent {
let c in (T::MinCandidates::get() + 1) .. T::MaxCandidates::get();
- <CandidacyBond<T>>::put(T::Currency::minimum_balance());
+ <LicenseBond<T>>::put(T::Currency::minimum_balance());
<DesiredCandidates<T>>::put(c);
register_validators::<T>(c);
@@ -211,7 +211,7 @@
// worse case is paying a non-existing candidate account.
note_author {
- <CandidacyBond<T>>::put(T::Currency::minimum_balance());
+ <LicenseBond<T>>::put(T::Currency::minimum_balance());
T::Currency::make_free_balance_be(
&<CollatorSelection<T>>::account_id(),
T::Currency::minimum_balance() * 4u32.into(),
@@ -233,7 +233,7 @@
let r in 1 .. T::MaxCandidates::get();
let c in 1 .. T::MaxCandidates::get();
- <CandidacyBond<T>>::put(T::Currency::minimum_balance());
+ <LicenseBond<T>>::put(T::Currency::minimum_balance());
<DesiredCandidates<T>>::put(c);
frame_system::Pallet::<T>::set_block_number(0u32.into());
pallets/collator-selection/src/lib.rsdiffbeforeafterboth--- a/pallets/collator-selection/src/lib.rs
+++ b/pallets/collator-selection/src/lib.rs
@@ -181,7 +181,7 @@
#[derive(
PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen,
)]
- pub struct CandidateInfo<AccountId, Balance> {
+ pub struct LicenseInfo<AccountId, Balance> {
/// Account identifier.
pub who: AccountId,
/// Reserved deposit.
@@ -198,12 +198,18 @@
pub type Invulnerables<T: Config> =
StorageValue<_, BoundedVec<T::AccountId, T::MaxInvulnerables>, ValueQuery>;
+ /// The (community) collation license holders.
+ #[pallet::storage]
+ #[pallet::getter(fn licenses)]
+ pub type Licenses<T: Config> =
+ StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf<T>, ValueQuery>;
+
/// The (community, limited) collation candidates.
#[pallet::storage]
#[pallet::getter(fn candidates)]
pub type Candidates<T: Config> = StorageValue<
_,
- BoundedVec<CandidateInfo<T::AccountId, BalanceOf<T>>, T::MaxCandidates>,
+ BoundedVec<T::AccountId, T::MaxCandidates>, //LicenseInfo<T::AccountId, BalanceOf<T>>, T::MaxCandidates>, // license ID?
ValueQuery,
>;
@@ -231,13 +237,13 @@
///
/// When a collator calls `leave_intent` they immediately receive the deposit back.
#[pallet::storage]
- #[pallet::getter(fn candidacy_bond)]
- pub type CandidacyBond<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
+ #[pallet::getter(fn license_bond)]
+ pub type LicenseBond<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub invulnerables: Vec<T::AccountId>,
- pub candidacy_bond: BalanceOf<T>,
+ pub license_bond: BalanceOf<T>,
pub kick_threshold: T::BlockNumber,
pub desired_candidates: u32,
}
@@ -247,7 +253,7 @@
fn default() -> Self {
Self {
invulnerables: Default::default(),
- candidacy_bond: Default::default(),
+ license_bond: Default::default(),
kick_threshold: T::BlockNumber::one(),
desired_candidates: Default::default(),
}
@@ -275,7 +281,7 @@
);
<DesiredCandidates<T>>::put(&self.desired_candidates);
- <CandidacyBond<T>>::put(&self.candidacy_bond);
+ <LicenseBond<T>>::put(&self.license_bond);
<KickThreshold<T>>::put(&self.kick_threshold);
<Invulnerables<T>>::put(bounded_invulnerables);
}
@@ -287,7 +293,7 @@
NewDesiredCandidates {
desired_candidates: u32,
},
- NewCandidacyBond {
+ NewLicenseBond {
bond_amount: BalanceOf<T>,
},
NewKickThreshold {
@@ -299,13 +305,19 @@
InvulnerableRemoved {
invulnerable: T::AccountId,
},
- CandidateAdded {
+ LicenseObtained {
account_id: T::AccountId,
deposit: BalanceOf<T>,
},
+ LicenseForfeited {
+ account_id: T::AccountId,
+ deposit_returned: BalanceOf<T>,
+ },
+ CandidateAdded {
+ account_id: T::AccountId,
+ },
CandidateRemoved {
account_id: T::AccountId,
- deposit_returned: BalanceOf<T>,
},
}
@@ -320,6 +332,10 @@
Unknown,
/// Permission issue
Permission,
+ /// User already holds license to collate
+ AlreadyLicenseHolder,
+ /// User does not hold a license to collate
+ NoLicense,
/// User is already a candidate
AlreadyCandidate,
/// User is not a candidate
@@ -363,6 +379,9 @@
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)?;
Self::deposit_event(Event::InvulnerableAdded { invulnerable: new });
@@ -419,20 +438,20 @@
}
/// Set the candidacy bond amount.
- #[pallet::weight(T::WeightInfo::set_candidacy_bond())]
- pub fn set_candidacy_bond(
+ #[pallet::weight(T::WeightInfo::set_license_bond())]
+ pub fn set_license_bond(
origin: OriginFor<T>,
bond: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
T::UpdateOrigin::ensure_origin(origin)?;
- <CandidacyBond<T>>::put(&bond);
- Self::deposit_event(Event::NewCandidacyBond { bond_amount: bond });
+ <LicenseBond<T>>::put(&bond);
+ Self::deposit_event(Event::NewLicenseBond { bond_amount: bond });
Ok(().into())
}
/// Set the length of the kick threshold.
/// Note that if the length is not a multiple of the session period, it might get inconsistent.
- #[pallet::weight(T::WeightInfo::set_candidacy_bond())] // todo:collator weight
+ #[pallet::weight(T::WeightInfo::set_license_bond())] // todo:collator weight
pub fn set_kick_threshold(
origin: OriginFor<T>,
kick_threshold: T::BlockNumber,
@@ -446,21 +465,20 @@
Ok(().into())
}
- /// Register this account as a collator candidate. The account must (a) already have
- /// registered session keys and (b) be able to reserve the `CandidacyBond`.
+ /// Purchase a license on block collation for this account.
+ /// It does not make it a collator candidate, use `onboard` afterward. The account must
+ /// (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()))]
- pub fn register_as_candidate(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
+ #[pallet::weight(T::WeightInfo::register_as_candidate(T::MaxCandidates::get()))] // todo:collator weight
+ pub fn get_license(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
+ // register_as_candidate
let who = ensure_signed(origin)?;
- // ensure we are below limit.
- let length = <Candidates<T>>::decode_len().unwrap_or_default();
- ensure!(
- (length as u32) < Self::desired_candidates(),
- Error::<T>::TooManyCandidates
- );
- // todo:collator really need it?
+ if Licenses::<T>::contains_key(&who) {
+ return Ok(().into());
+ }
+
ensure!(
!Self::invulnerables().contains(&who),
Error::<T>::AlreadyInvulnerable
@@ -473,21 +491,81 @@
Error::<T>::ValidatorNotRegistered
);
- let deposit = Self::candidacy_bond();
+ let deposit = Self::license_bond();
// First authored block is current block plus kick threshold to handle session delay
- let incoming = CandidateInfo {
+ /*let incoming = LicenseInfo {
who: who.clone(),
deposit,
- };
+ };*/
+ T::Currency::reserve(&who, deposit)?;
+ Licenses::<T>::insert(who.clone(), deposit);
+
+ /*let current_count =
+ <Licenses<T>>::try_mutate(|licenses| -> Result<usize, DispatchError> {
+ if T::OriginPrivilegeCmp::cmp_privilege(&origin, &scheduled.origin) {
+ return Err(BadOrigin.into());
+ }
+ if candidates.iter().any(|candidate| *candidate == who) {
+ Err(Error::<T>::AlreadyLicenseHolder)?
+ } else {
+ T::Currency::reserve(&who, deposit)?;
+ candidates
+ .try_push(incoming)
+ .map_err(|_| Error::<T>::TooManyCandidates)?;
+ <LastAuthoredBlock<T>>::insert(
+ who.clone(),
+ frame_system::Pallet::<T>::block_number() + Self::kick_threshold(),
+ );
+ Ok(candidates.len())
+ }
+ })?;*/
+
+ Self::deposit_event(Event::LicenseObtained {
+ account_id: who,
+ deposit,
+ });
+ Ok(().into()) // Some(T::WeightInfo::register_as_candidate(current_count as u32)).into())
+ }
+
+ /// Register this account as a candidate for collators for next sessions.
+ /// 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
+ pub fn onboard(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
+ // register_as_candidate
+ let who = ensure_signed(origin)?;
+
+ // 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();
+ ensure!(
+ (length as u32) < Self::desired_candidates(),
+ Error::<T>::TooManyCandidates
+ );
+ // todo:collator really need it?
+ ensure!(
+ !Self::invulnerables().contains(&who),
+ 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,
+ };*/
+
let current_count =
<Candidates<T>>::try_mutate(|candidates| -> Result<usize, DispatchError> {
- if candidates.iter().any(|candidate| candidate.who == who) {
+ if candidates.iter().any(|candidate| *candidate == who) {
Err(Error::<T>::AlreadyCandidate)?
} else {
T::Currency::reserve(&who, deposit)?;
candidates
- .try_push(incoming)
+ .try_push(who.clone())
.map_err(|_| Error::<T>::TooManyCandidates)?;
<LastAuthoredBlock<T>>::insert(
who.clone(),
@@ -497,31 +575,59 @@
}
})?;
- Self::deposit_event(Event::CandidateAdded {
- account_id: who,
- deposit,
- });
+ Self::deposit_event(Event::CandidateAdded { account_id: who });
Ok(Some(T::WeightInfo::register_as_candidate(current_count as u32)).into())
}
/// Deregister `origin` as a collator candidate. Note that the collator can only leave on
- /// session change. The `CandidacyBond` will be unreserved immediately.
- ///
- /// This call will fail if the total number of candidates would drop below `MinCandidates`.
+ /// session change. The license to `onboard` later at any other time will remain.
///
- /// This call is not available to `Invulnerable` collators.
- #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCandidates::get()))]
- pub fn leave_intent(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
+ /// 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
+ 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
ensure!(
Self::candidates().len() as u32 > T::MinCandidates::get(),
Error::<T>::TooFewCandidates
);
- let current_count = Self::try_remove_candidate(&who, false)?;
+ let current_count = Self::try_remove_candidate(&who)?;
Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into())
}
+
+ /// 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
+ 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())
+ }
+
+ /// Force deregister `origin` as a collator candidate as a governing authority, and revoke its license.
+ /// Note that the collator can only leave on session change.
+ /// 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(
+ 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)?;
+
+ Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into()) // todo:collator weight
+ }
}
impl<T: Config> Pallet<T> {
@@ -530,21 +636,29 @@
T::PotId::get().into_account_truncating()
}
- /// Removes a candidate if they exist and sends them back their deposit, optionally slashed.
- fn try_remove_candidate(
- who: &T::AccountId,
- should_slash: bool,
- ) -> Result<usize, DispatchError> {
- let mut deposit_returned = BalanceOf::<T>::default();
+ /// 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 =
<Candidates<T>>::try_mutate(|candidates| -> Result<usize, DispatchError> {
let index = candidates
.iter()
- .position(|candidate| candidate.who == *who)
+ .position(|candidate| *candidate == *who)
.ok_or(Error::<T>::NotCandidate)?;
- let candidate = candidates.remove(index);
- let deposit = candidate.deposit;
+ candidates.remove(index);
+ <LastAuthoredBlock<T>>::remove(who.clone());
+ Ok(candidates.len())
+ })?;
+ Self::deposit_event(Event::CandidateRemoved {
+ account_id: who.clone(),
+ });
+ Ok(current_count)
+ }
+ /// 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 {
+ if let Some(deposit) = deposit.take() {
if should_slash {
let slashed = T::SlashRatio::get() * deposit;
let remaining = deposit - slashed;
@@ -554,23 +668,22 @@
deposit_returned = remaining;
T::Currency::resolve_creating(&T::TreasuryAccountId::get(), imbalance);
-
- // Self::deposit_event(Event::CandidateSlashed(who.clone()));
} else {
//T::Currency::unreserve(who, deposit);
deposit_returned = deposit;
}
T::Currency::unreserve(who, deposit_returned);
- // candidates.remove(index);
- <LastAuthoredBlock<T>>::remove(who.clone());
- Ok(candidates.len())
- })?;
- Self::deposit_event(Event::CandidateRemoved {
+ Ok(())
+ } else {
+ Err(Error::<T>::NoLicense.into())
+ }
+ })?;
+ Self::deposit_event(Event::LicenseForfeited {
account_id: who.clone(),
deposit_returned,
});
- Ok(current_count)
+ Ok(())
}
/// Assemble the current set of candidates and invulnerables into the next collator set.
@@ -587,24 +700,30 @@
/// 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<CandidateInfo<T::AccountId, BalanceOf<T>>, T::MaxCandidates>,
+ candidates: BoundedVec<T::AccountId, T::MaxCandidates>, //LicenseInfo<T::AccountId, BalanceOf<T>>
) -> BoundedVec<T::AccountId, T::MaxCandidates> {
let now = frame_system::Pallet::<T>::block_number();
let kick_threshold = Self::kick_threshold();
candidates
.into_iter()
.filter_map(|c| {
- let last_block = <LastAuthoredBlock<T>>::get(c.who.clone());
+ 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()
{
- Some(c.who)
+ Some(c)
} else {
- let outcome = Self::try_remove_candidate(&c.who, true);
+ let outcome = Self::try_remove_candidate(&c);
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);
}
None
}
pallets/collator-selection/src/mock.rsdiffbeforeafterboth--- a/pallets/collator-selection/src/mock.rs
+++ b/pallets/collator-selection/src/mock.rs
@@ -264,7 +264,7 @@
.collect::<Vec<_>>();
let collator_selection = collator_selection::GenesisConfig::<Test> {
desired_candidates: 2,
- candidacy_bond: 10,
+ license_bond: 10,
kick_threshold: 1,
invulnerables,
};
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::*, CandidateInfo, 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::candidacy_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_candidacy_bond() {137 new_test_ext().execute_with(|| {138 // given139 assert_eq!(CollatorSelection::candidacy_bond(), 10);140141 // can set142 assert_ok!(CollatorSelection::set_candidacy_bond(143 RuntimeOrigin::signed(RootAccount::get()),144 7145 ));146 assert_eq!(CollatorSelection::candidacy_bond(), 7);147148 // rejects bad origin.149 assert_noop!(150 CollatorSelection::set_candidacy_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 = CandidateInfo {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::candidacy_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 = CandidateInfo {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 = CandidateInfo {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 = CandidateInfo {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 = CandidateInfo {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 candidacy_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::*, 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}pallets/collator-selection/src/weights.rsdiffbeforeafterboth--- a/pallets/collator-selection/src/weights.rs
+++ b/pallets/collator-selection/src/weights.rs
@@ -46,7 +46,7 @@
pub trait WeightInfo {
fn set_invulnerables(_b: u32) -> Weight;
fn set_desired_candidates() -> Weight;
- fn set_candidacy_bond() -> Weight;
+ fn set_license_bond() -> Weight;
fn register_as_candidate(_c: u32) -> Weight;
fn leave_intent(_c: u32) -> Weight;
fn note_author() -> Weight;
@@ -65,7 +65,7 @@
fn set_desired_candidates() -> Weight {
Weight::from_ref_time(16_363_000 as u64).saturating_add(T::DbWeight::get().writes(1 as u64))
}
- fn set_candidacy_bond() -> Weight {
+ fn set_license_bond() -> Weight {
Weight::from_ref_time(16_840_000 as u64).saturating_add(T::DbWeight::get().writes(1 as u64))
}
fn register_as_candidate(c: u32) -> Weight {
@@ -112,7 +112,7 @@
Weight::from_ref_time(16_363_000 as u64)
.saturating_add(RocksDbWeight::get().writes(1 as u64))
}
- fn set_candidacy_bond() -> Weight {
+ fn set_license_bond() -> Weight {
Weight::from_ref_time(16_840_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_CANDIDACY_BOND: u128 = EXISTENTIAL_DEPOSIT;
+pub const GENESIS_LICENSE_BOND: u128 = EXISTENTIAL_DEPOSIT;
/// How long a periodic session lasts in blocks.
-pub const SESSION_LENGTH: BlockNumber = HOURS;
+pub const SESSION_LENGTH: BlockNumber = MINUTES;
// Targeting 0.1 UNQ per transfer
pub const WEIGHT_TO_FEE_COEFF: u32 = /*<weight2fee>*/175_199_920/*</weight2fee>*/;
runtime/common/mod.rsdiffbeforeafterboth--- a/runtime/common/mod.rs
+++ b/runtime/common/mod.rs
@@ -191,7 +191,7 @@
RuntimeAppPublic,
};
use pallet_session::SessionManager;
- use up_common::constants::GENESIS_CANDIDACY_BOND;
+ use up_common::constants::GENESIS_LICENSE_BOND;
use crate::config::pallets::collator_selection::MaxInvulnerables;
let mut weight = <Runtime as frame_system::Config>::DbWeight::get().reads(1);
@@ -242,7 +242,7 @@
<pallet_collator_selection::Invulnerables<Runtime>>::put(bounded_invulnerables);
<pallet_collator_selection::DesiredCandidates<Runtime>>::put(0);
- <pallet_collator_selection::CandidacyBond<Runtime>>::put(GENESIS_CANDIDACY_BOND);
+ <pallet_collator_selection::LicenseBond<Runtime>>::put(GENESIS_LICENSE_BOND);
let keys = invulnerables
.into_iter()