difftreelog
feat(collator-selection) interaction with configuration pallet
in: master
17 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,6 @@
use serde_json::map::Map;
use up_common::types::opaque::*;
-use up_common::constants::{GENESIS_LICENSE_BOND, SESSION_LENGTH};
#[cfg(feature = "unique-runtime")]
pub use unique_runtime as default_runtime;
@@ -196,9 +195,6 @@
.cloned()
.map(|(acc, _)| acc)
.collect(),
- desired_collators: 10,
- license_bond: GENESIS_LICENSE_BOND,
- kick_threshold: SESSION_LENGTH,
},
session: SessionConfig {
keys: $initial_invulnerables
node/cli/src/service.rsdiffbeforeafterboth--- a/node/cli/src/service.rs
+++ b/node/cli/src/service.rs
@@ -921,7 +921,7 @@
.import_notification_stream()
.map(|_| EngineCommand::SealNewBlock {
create_empty: true,
- finalize: false,
+ finalize: false, // todo:collator finalize true
parent_hash: None,
sender: None,
}),
@@ -932,7 +932,7 @@
dyn Stream<Item = EngineCommand<Hash>> + Send + Sync + Unpin,
> = Box::new(autoseal_interval.map(|_| EngineCommand::SealNewBlock {
create_empty: true,
- finalize: false,
+ finalize: false, // todo:collator finalize true
parent_hash: None,
sender: None,
}));
pallets/collator-selection/Cargo.tomldiffbeforeafterboth--- a/pallets/collator-selection/Cargo.toml
+++ b/pallets/collator-selection/Cargo.toml
@@ -25,6 +25,7 @@
frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.33" }
pallet-authorship = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.33" }
pallet-session = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.33" }
+pallet-configuration = { default-features = false, path = "../configuration" }
frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.33" }
pallets/collator-selection/src/lib.rsdiffbeforeafterboth--- a/pallets/collator-selection/src/lib.rs
+++ b/pallets/collator-selection/src/lib.rs
@@ -105,16 +105,15 @@
},
BoundedVec, PalletId,
};
- use frame_system::{pallet_prelude::*, Config as SystemConfig};
+ use frame_system::pallet_prelude::*;
use pallet_session::SessionManager;
- use sp_runtime::{
- Perbill,
- traits::{One, Convert},
+ use sp_runtime::{Perbill, traits::Convert};
+ use pallet_configuration::{
+ CollatorSelectionDesiredCollatorsOverride as DesiredCollators,
+ CollatorSelectionLicenseBondOverride as LicenseBond,
+ CollatorSelectionKickThresholdOverride as KickThreshold, BalanceOf,
};
use sp_staking::SessionIndex;
-
- type BalanceOf<T> =
- <<T as Config>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
/// A convertor from collators id. Since this pallet does not have stash/controller, this is
/// just identity.
@@ -127,13 +126,10 @@
/// Configure the pallet by specifying the parameters and types on which it depends.
#[pallet::config]
- pub trait Config: frame_system::Config {
+ pub trait Config: frame_system::Config + pallet_configuration::Config {
/// Overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
- /// The currency mechanism.
- type Currency: ReservableCurrency<Self::AccountId>;
-
/// Origin that can dictate updating parameters of this pallet.
type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
@@ -176,8 +172,8 @@
/// The (community) collation license holders.
#[pallet::storage]
- #[pallet::getter(fn licenses)]
- pub type Licenses<T: Config> =
+ #[pallet::getter(fn license_deposit_of)]
+ pub type LicenseDepositOf<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf<T>, ValueQuery>;
/// The (community, limited) collation candidates.
@@ -185,40 +181,16 @@
#[pallet::getter(fn candidates)]
pub type Candidates<T: Config> =
StorageValue<_, BoundedVec<T::AccountId, T::MaxCollators>, ValueQuery>;
-
- /// Collator will be kicked if it does not produce a block within the threshold (does not apply to invulnerables).
- ///
- /// Should be a multiple of session or things will get inconsistent. todo:collator reword?
- #[pallet::storage]
- #[pallet::getter(fn kick_threshold)]
- pub type KickThreshold<T: Config> = StorageValue<_, T::BlockNumber, ValueQuery>;
/// Last block authored by collator.
#[pallet::storage]
#[pallet::getter(fn last_authored_block)]
pub type LastAuthoredBlock<T: Config> =
StorageMap<_, Twox64Concat, T::AccountId, T::BlockNumber, ValueQuery>;
-
- /// Desired number of candidates.
- ///
- /// This should ideally always be less than [`Config::MaxCollators`] for weights to be correct.
- #[pallet::storage]
- #[pallet::getter(fn desired_collators)]
- pub type DesiredCollators<T> = StorageValue<_, u32, ValueQuery>;
- /// Fixed amount to deposit to become a collator.
- ///
- /// When a collator calls `leave_intent` they immediately receive the deposit back.
- #[pallet::storage]
- #[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 license_bond: BalanceOf<T>,
- pub kick_threshold: T::BlockNumber,
- pub desired_collators: u32,
}
#[cfg(feature = "std")]
@@ -226,9 +198,6 @@
fn default() -> Self {
Self {
invulnerables: Default::default(),
- license_bond: Default::default(),
- kick_threshold: T::BlockNumber::one(),
- desired_collators: Default::default(),
}
}
}
@@ -248,14 +217,7 @@
let bounded_invulnerables =
BoundedVec::<_, T::MaxCollators>::try_from(self.invulnerables.clone())
.expect("genesis invulnerables are more than T::MaxCollators");
- assert!(
- T::MaxCollators::get() >= self.desired_collators,
- "genesis desired_collators are more than T::MaxCollators",
- );
-
- <DesiredCollators<T>>::put(self.desired_collators);
- <LicenseBond<T>>::put(self.license_bond);
- <KickThreshold<T>>::put(self.kick_threshold);
+
<Invulnerables<T>>::put(bounded_invulnerables);
}
}
@@ -263,15 +225,6 @@
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
- NewDesiredCollators {
- desired_collators: u32,
- },
- NewLicenseBond {
- bond_amount: BalanceOf<T>,
- },
- NewKickThreshold {
- length_in_blocks: T::BlockNumber,
- },
InvulnerableAdded {
invulnerable: T::AccountId,
},
@@ -383,51 +336,6 @@
Ok(().into())
}
- /// 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_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::MaxCollators::get() {
- log::warn!("max > T::MaxCollators; you might need to run benchmarks again");
- }
- <DesiredCollators<T>>::put(max);
- Self::deposit_event(Event::NewDesiredCollators {
- desired_collators: max,
- });
- Ok(().into())
- }
-
- /// Set the candidacy bond amount.
- #[pallet::weight(T::WeightInfo::set_license_bond())]
- pub fn set_license_bond(
- origin: OriginFor<T>,
- bond: BalanceOf<T>,
- ) -> DispatchResultWithPostInfo {
- T::UpdateOrigin::ensure_origin(origin)?;
- <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_license_bond())] // todo:collator weight
- pub fn set_kick_threshold(
- origin: OriginFor<T>,
- kick_threshold: T::BlockNumber,
- ) -> DispatchResultWithPostInfo {
- T::UpdateOrigin::ensure_origin(origin)?;
- // todo:collator insert something to guarantee consistency?
- <KickThreshold<T>>::put(kick_threshold);
- Self::deposit_event(Event::NewKickThreshold {
- length_in_blocks: kick_threshold,
- });
- Ok(().into())
- }
-
/// 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`.
@@ -438,7 +346,7 @@
// register_as_candidate
let who = ensure_signed(origin)?;
- if Licenses::<T>::contains_key(&who) {
+ if LicenseDepositOf::<T>::contains_key(&who) {
return Err(Error::<T>::AlreadyHoldingLicense.into());
}
@@ -449,10 +357,10 @@
Error::<T>::ValidatorNotRegistered
);
- let deposit = Self::license_bond();
+ let deposit = <LicenseBond<T>>::get();
T::Currency::reserve(&who, deposit)?;
- Licenses::<T>::insert(who.clone(), deposit);
+ LicenseDepositOf::<T>::insert(who.clone(), deposit);
Self::deposit_event(Event::LicenseObtained {
account_id: who,
@@ -471,12 +379,15 @@
let who = ensure_signed(origin)?;
// ensure the user obtained the license.
- ensure!(Licenses::<T>::contains_key(&who), Error::<T>::NoLicense);
+ ensure!(
+ LicenseDepositOf::<T>::contains_key(&who),
+ Error::<T>::NoLicense
+ );
// ensure we are below limit.
let length = <Candidates<T>>::decode_len().unwrap_or_default()
+ <Invulnerables<T>>::decode_len().unwrap_or_default();
ensure!(
- (length as u32) < Self::desired_collators(),
+ (length as u32) < <DesiredCollators<T>>::get(),
Error::<T>::TooManyCandidates
);
ensure!(
@@ -495,7 +406,7 @@
// 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(),
+ frame_system::Pallet::<T>::block_number() + <KickThreshold<T>>::get(),
);
Ok(candidates.len())
}
@@ -594,7 +505,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 {
+ LicenseDepositOf::<T>::try_mutate_exists(who, |deposit| -> DispatchResult {
if let Some(deposit) = deposit.take() {
if should_slash {
let slashed = T::SlashRatio::get() * deposit;
@@ -640,7 +551,7 @@
candidates: BoundedVec<T::AccountId, T::MaxCollators>,
) -> BoundedVec<T::AccountId, T::MaxCollators> {
let now = frame_system::Pallet::<T>::block_number();
- let kick_threshold = Self::kick_threshold();
+ let kick_threshold = <KickThreshold<T>>::get();
candidates
.into_iter()
.filter_map(|c| {
pallets/collator-selection/src/mock.rsdiffbeforeafterboth--- a/pallets/collator-selection/src/mock.rs
+++ b/pallets/collator-selection/src/mock.rs
@@ -63,6 +63,7 @@
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
CollatorSelection: collator_selection::{Pallet, Call, Storage, Event<T>},
Authorship: pallet_authorship::{Pallet, Call, Storage, Inherent},
+ Configuration: pallet_configuration::{Pallet, Call, Storage, Event<T>},
}
);
@@ -200,13 +201,38 @@
type WeightInfo = ();
}
+parameter_types! {
+ pub const MaxCollators: u32 = 5;
+ pub const LicenseBond: u64 = 10;
+ pub const KickThreshold: u64 = 10;
+ // the following values do not matter and are meaningless, etc.
+ pub const DefaultWeightToFeeCoefficient: u32 = 100_000;
+ pub const DefaultMinGasPrice: u64 = 100_000;
+ pub const MaxXcmAllowedLocations: u32 = 16;
+ pub AppPromotionDailyRate: Perbill = Perbill::from_rational(5u32, 10_000);
+ pub const DayRelayBlocks: u32 = 1;
+}
+
+impl pallet_configuration::Config for Test {
+ type RuntimeEvent = RuntimeEvent;
+ type Currency = Balances;
+ type DefaultCollatorSelectionMaxCollators = MaxCollators;
+ type DefaultCollatorSelectionKickThreshold = KickThreshold;
+ type DefaultCollatorSelectionLicenseBond = LicenseBond;
+ // the following we don't care about
+ type DefaultWeightToFeeCoefficient = DefaultWeightToFeeCoefficient;
+ type DefaultMinGasPrice = DefaultMinGasPrice;
+ type MaxXcmAllowedLocations = MaxXcmAllowedLocations;
+ type AppPromotionDailyRate = AppPromotionDailyRate;
+ type DayRelayBlocks = DayRelayBlocks;
+}
+
ord_parameter_types! {
pub const RootAccount: u64 = 777;
}
parameter_types! {
pub const PotId: PalletId = PalletId(*b"PotStake");
- pub const MaxCollators: u32 = 20;
pub const MaxAuthorities: u32 = 100_000;
pub const SlashRatio: Perbill = Perbill::one();
}
@@ -224,7 +250,6 @@
impl Config for Test {
type RuntimeEvent = RuntimeEvent;
- type Currency = Balances;
type UpdateOrigin = EnsureSignedBy<RootAccount, u64>;
type PotId = PotId;
type MaxCollators = MaxCollators;
@@ -257,9 +282,6 @@
})
.collect::<Vec<_>>();
let collator_selection = collator_selection::GenesisConfig::<Test> {
- desired_collators: 5,
- license_bond: 10,
- 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::*, 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#[test]63fn it_should_add_invulnerables() {64 new_test_ext().execute_with(|| {65 assert_ok!(CollatorSelection::add_invulnerable(66 RuntimeOrigin::signed(RootAccount::get()),67 168 ));69 assert_ok!(CollatorSelection::add_invulnerable(70 RuntimeOrigin::signed(RootAccount::get()),71 272 ));73 assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);7475 // cannot set with non-root.76 assert_noop!(77 CollatorSelection::add_invulnerable(RuntimeOrigin::signed(1), 3),78 BadOrigin79 );8081 // cannot set invulnerables without associated validator keys82 assert_noop!(83 CollatorSelection::add_invulnerable(RuntimeOrigin::signed(RootAccount::get()), 7),84 Error::<Test>::ValidatorNotRegistered85 );86 });87}8889#[test]90fn it_should_remove_invulnerables() {91 new_test_ext().execute_with(|| {92 assert_ok!(CollatorSelection::add_invulnerable(93 RuntimeOrigin::signed(RootAccount::get()),94 195 ));96 assert_ok!(CollatorSelection::add_invulnerable(97 RuntimeOrigin::signed(RootAccount::get()),98 299 ));100101 // cannot remove with non-root.102 assert_noop!(103 CollatorSelection::remove_invulnerable(RuntimeOrigin::signed(1), 3),104 BadOrigin105 );106107 assert_ok!(CollatorSelection::remove_invulnerable(108 RuntimeOrigin::signed(RootAccount::get()),109 2110 ));111 assert_eq!(CollatorSelection::invulnerables(), vec![1]);112113 // cannot remove an invulnerable if there would be 0 invulnerables.114 assert_noop!(115 CollatorSelection::remove_invulnerable(RuntimeOrigin::signed(RootAccount::get()), 1),116 Error::<Test>::TooFewInvulnerables117 );118 });119}120121#[test]122fn set_desired_collators_works() {123 new_test_ext().execute_with(|| {124 // given125 assert_eq!(CollatorSelection::desired_collators(), 5);126127 // can set128 assert_ok!(CollatorSelection::set_desired_collators(129 RuntimeOrigin::signed(RootAccount::get()),130 7131 ));132 assert_eq!(CollatorSelection::desired_collators(), 7);133134 // rejects bad origin135 assert_noop!(136 CollatorSelection::set_desired_collators(RuntimeOrigin::signed(1), 8),137 BadOrigin138 );139 });140}141142#[test]143fn set_license_bond() {144 new_test_ext().execute_with(|| {145 // given146 assert_eq!(CollatorSelection::license_bond(), 10);147148 // can set149 assert_ok!(CollatorSelection::set_license_bond(150 RuntimeOrigin::signed(RootAccount::get()),151 7152 ));153 assert_eq!(CollatorSelection::license_bond(), 7);154155 // rejects bad origin.156 assert_noop!(157 CollatorSelection::set_license_bond(RuntimeOrigin::signed(1), 8),158 BadOrigin159 );160 });161}162163#[test]164fn cannot_onboard_candidate_with_no_license() {165 new_test_ext().execute_with(|| {166 // can't onboard a candidate who did not get a license.167 assert_noop!(168 CollatorSelection::onboard(RuntimeOrigin::signed(3)),169 Error::<Test>::NoLicense,170 );171172 // but give it a license and welcome aboard.173 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));174 assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed(3)));175 })176}177178#[test]179fn cannot_onboard_candidate_if_too_many() {180 new_test_ext().execute_with(|| {181 // reset desired candidates182 <crate::DesiredCollators<Test>>::put(0);183184 // can still get a license.185 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(4)));186187 // can't accept anyone anymore.188 assert_noop!(189 CollatorSelection::onboard(RuntimeOrigin::signed(4)),190 Error::<Test>::TooManyCandidates,191 );192193 // reset desired candidates to invulnerables + 1194 <crate::DesiredCollators<Test>>::put(3);195 assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed(4)));196197 // but no more.198 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(5)));199 assert_noop!(200 CollatorSelection::onboard(RuntimeOrigin::signed(5)),201 Error::<Test>::TooManyCandidates,202 );203 })204}205206#[test]207fn cannot_obtain_license_if_keys_not_registered() {208 new_test_ext().execute_with(|| {209 // can't 7 because keys not registered.210 assert_noop!(211 CollatorSelection::get_license(RuntimeOrigin::signed(7)),212 Error::<Test>::ValidatorNotRegistered213 );214 })215}216217#[test]218fn cannot_obtain_license_if_poor() {219 new_test_ext().execute_with(|| {220 assert_eq!(Balances::free_balance(&3), 100);221 assert_eq!(Balances::free_balance(&33), 0);222223 // works224 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));225226 // poor227 assert_noop!(228 CollatorSelection::get_license(RuntimeOrigin::signed(33)),229 BalancesError::<Test>::InsufficientBalance,230 );231 });232}233234#[test]235fn cannot_onboard_dupe_candidate() {236 new_test_ext().execute_with(|| {237 // can add 3 as candidate238 get_license_and_onboard(3);239 assert_eq!(CollatorSelection::licenses(3), 10);240 assert_eq!(CollatorSelection::candidates(), vec![3]);241 assert_eq!(CollatorSelection::last_authored_block(3), 10);242 assert_eq!(Balances::free_balance(3), 90);243244 // but no more245 assert_noop!(246 CollatorSelection::get_license(RuntimeOrigin::signed(3)),247 Error::<Test>::AlreadyHoldingLicense,248 );249 assert_noop!(250 CollatorSelection::onboard(RuntimeOrigin::signed(3)),251 Error::<Test>::AlreadyCandidate,252 );253 })254}255256#[test]257fn becoming_candidate_works() {258 new_test_ext().execute_with(|| {259 // given260 assert_eq!(CollatorSelection::desired_collators(), 5);261 assert_eq!(CollatorSelection::license_bond(), 10);262 assert_eq!(CollatorSelection::candidates(), Vec::new());263 assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);264265 // take two endowed, non-invulnerables accounts.266 assert_eq!(Balances::free_balance(&3), 100);267 assert_eq!(Balances::free_balance(&4), 100);268269 get_license_and_onboard(3);270 get_license_and_onboard(4);271272 assert_eq!(Balances::free_balance(&3), 90);273 assert_eq!(Balances::free_balance(&4), 90);274275 assert_eq!(CollatorSelection::candidates().len(), 2);276 });277}278279#[test]280fn cannot_become_candidate_if_invulnerable() {281 new_test_ext().execute_with(|| {282 assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);283284 // can obtain a license even if is invulnerable.285 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(1)));286 // but cannot onboard287 assert_noop!(288 CollatorSelection::onboard(RuntimeOrigin::signed(1)),289 Error::<Test>::AlreadyInvulnerable,290 );291292 // get a license and then become invulnerable.293 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));294 assert_ok!(CollatorSelection::add_invulnerable(295 RuntimeOrigin::signed(RootAccount::get()),296 3297 ));298 assert_noop!(299 CollatorSelection::onboard(RuntimeOrigin::signed(3)),300 Error::<Test>::AlreadyInvulnerable,301 );302 })303}304305#[test]306fn can_become_invulnerable_if_candidate() {307 new_test_ext().execute_with(|| {308 // become a candidate and then become invulnerable.309 get_license_and_onboard(3);310 assert_eq!(CollatorSelection::candidates(), vec![3]);311312 assert_ok!(CollatorSelection::add_invulnerable(313 RuntimeOrigin::signed(RootAccount::get()),314 3315 ));316 // should exclude from candidates, but not revoke the license317 assert_eq!(CollatorSelection::candidates(), vec![]);318 assert_eq!(CollatorSelection::licenses(3), 10);319 assert_eq!(Balances::free_balance(3), 90);320 });321}322323#[test]324fn offboard() {325 new_test_ext().execute_with(|| {326 // register a candidate.327 get_license_and_onboard(3);328 assert_eq!(Balances::free_balance(3), 90);329330 // cannot leave if holds license but not yet candidate.331 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(4)));332 assert_noop!(333 CollatorSelection::offboard(RuntimeOrigin::signed(4)),334 Error::<Test>::NotCandidate335 );336 // cannot leave if does not hold license.337 assert_noop!(338 CollatorSelection::offboard(RuntimeOrigin::signed(5)),339 Error::<Test>::NotCandidate340 );341342 // bond is returned - only after releasing the license343 assert_ok!(CollatorSelection::offboard(RuntimeOrigin::signed(3)));344 assert_eq!(Balances::free_balance(3), 90);345 assert_eq!(CollatorSelection::last_authored_block(3), 0);346 assert_ok!(CollatorSelection::release_license(RuntimeOrigin::signed(3)));347 assert_eq!(Balances::free_balance(3), 100);348 });349}350351#[test]352fn release_license() {353 new_test_ext().execute_with(|| {354 // obtain a license to collate and reserve the bond.355 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));356 assert_eq!(Balances::free_balance(3), 90);357358 // release the license and get the bond back.359 assert_ok!(CollatorSelection::release_license(RuntimeOrigin::signed(3)));360 assert_eq!(Balances::free_balance(3), 100);361362 // register a candidate.363 get_license_and_onboard(3);364 assert_eq!(Balances::free_balance(3), 90);365366 // can release license even if onboarded.367 assert_ok!(CollatorSelection::release_license(RuntimeOrigin::signed(3)));368 assert_eq!(Balances::free_balance(3), 100);369 assert_eq!(CollatorSelection::candidates(), vec![]);370 });371}372373#[test]374fn force_revoke_license() {375 new_test_ext().execute_with(|| {376 // obtain a license to collate and reserve the bond.377 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));378 assert_eq!(Balances::free_balance(3), 90);379380 // cannot execute the operation as non-root381 assert_noop!(382 CollatorSelection::force_revoke_license(RuntimeOrigin::signed(3), 3),383 BadOrigin384 );385386 // release the license and get the bond back.387 assert_ok!(CollatorSelection::force_revoke_license(388 RuntimeOrigin::signed(RootAccount::get()),389 3390 ));391 assert_eq!(Balances::free_balance(3), 100);392393 // register a candidate.394 get_license_and_onboard(3);395 assert_eq!(Balances::free_balance(3), 90);396397 // can release license even if onboarded.398 assert_ok!(CollatorSelection::force_revoke_license(399 RuntimeOrigin::signed(RootAccount::get()),400 3401 ));402 assert_eq!(Balances::free_balance(3), 100);403 assert_eq!(CollatorSelection::candidates(), vec![]);404 });405}406407#[test]408fn authorship_event_handler() {409 new_test_ext().execute_with(|| {410 // put 100 in the pot + 5 for ED411 Balances::make_free_balance_be(&CollatorSelection::account_id(), 105);412413 // 4 is the default author.414 assert_eq!(Balances::free_balance(4), 100);415 get_license_and_onboard(4);416 // triggers `note_author`417 Authorship::on_initialize(1);418419 assert_eq!(CollatorSelection::candidates(), vec![4]);420 assert_eq!(CollatorSelection::last_authored_block(4), 0);421422 // half of the pot goes to the collator who's the author (4 in tests).423 assert_eq!(Balances::free_balance(4), 140);424 // half + ED stays.425 assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 55);426 });427}428429#[test]430fn fees_edgecases() {431 new_test_ext().execute_with(|| {432 // Nothing panics, no reward when no ED in balance433 Authorship::on_initialize(1);434 // put some money into the pot at ED435 Balances::make_free_balance_be(&CollatorSelection::account_id(), 5);436 // 4 is the default author.437 assert_eq!(Balances::free_balance(4), 100);438 get_license_and_onboard(4);439 // triggers `note_author`440 Authorship::on_initialize(1);441442 assert_eq!(CollatorSelection::candidates(), vec![4]);443 assert_eq!(CollatorSelection::last_authored_block(4), 0);444 // Nothing received445 assert_eq!(Balances::free_balance(4), 90);446 // all fee stays447 assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 5);448 });449}450451#[test]452fn session_management_works() {453 new_test_ext().execute_with(|| {454 initialize_to_block(1);455456 assert_eq!(SessionChangeBlock::get(), 0);457 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);458459 initialize_to_block(4);460461 assert_eq!(SessionChangeBlock::get(), 0);462 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);463464 // add a new collator465 get_license_and_onboard(5);466467 // session won't see this.468 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);469 // but we have a new candidate.470 assert_eq!(CollatorSelection::candidates().len(), 1);471472 initialize_to_block(10);473 assert_eq!(SessionChangeBlock::get(), 10);474 // pallet-session has 1 session delay; current validators are the same.475 assert_eq!(Session::validators(), vec![1, 2]);476 // queued ones are changed, and now we have 3.477 assert_eq!(Session::queued_keys().len(), 3);478 // session handlers (aura, et. al.) cannot see this yet.479 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);480481 initialize_to_block(20);482 assert_eq!(SessionChangeBlock::get(), 20);483 // changed are now reflected to session handlers.484 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 5]);485 });486}487488#[test]489fn kick_mechanism() {490 new_test_ext().execute_with(|| {491 // add a new collator492 get_license_and_onboard(3);493 get_license_and_onboard(4);494495 initialize_to_block(10);496 assert_eq!(CollatorSelection::candidates().len(), 2);497498 initialize_to_block(20);499 assert_eq!(SessionChangeBlock::get(), 20);500 // 4 authored this block, gets to stay 3 was kicked501 assert_eq!(CollatorSelection::candidates().len(), 1);502 // 3 will be kicked after 1 session delay503 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 4]);504505 assert_eq!(CollatorSelection::candidates(), vec![4]);506 assert_eq!(CollatorSelection::kick_threshold(), 10);507 assert_eq!(CollatorSelection::last_authored_block(4), 20);508509 initialize_to_block(30);510 // 3 gets kicked after 1 session delay511 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 4]);512 // kicked collator gets their funds slashed, the deposit going to treasury513 assert_eq!(Balances::free_balance(3), 90);514 });515}516517#[test]518#[should_panic = "duplicate invulnerables in genesis."]519fn cannot_set_genesis_value_twice() {520 sp_tracing::try_init_simple();521 let mut t = frame_system::GenesisConfig::default()522 .build_storage::<Test>()523 .unwrap();524 let invulnerables = vec![1, 1];525526 let collator_selection = collator_selection::GenesisConfig::<Test> {527 desired_collators: 5,528 license_bond: 10,529 kick_threshold: 10,530 invulnerables,531 };532 // collator selection must be initialized before session.533 collator_selection.assimilate_storage(&mut t).unwrap();534}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 frame_system::RawOrigin;40use pallet_balances::Error as BalancesError;41use sp_runtime::traits::BadOrigin;42use pallet_configuration::{43 CollatorSelectionDesiredCollatorsOverride as DesiredCollators,44 CollatorSelectionKickThresholdOverride as KickThreshold,45 CollatorSelectionLicenseBondOverride as LicenseBond,46};4748fn get_license_and_onboard(account_id: <Test as frame_system::Config>::AccountId) {49 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(50 account_id51 )));52 assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed(53 account_id54 )));55}5657#[test]58fn basic_setup_works() {59 new_test_ext().execute_with(|| {60 assert_eq!(<DesiredCollators<Test>>::get(), 5);61 assert_eq!(<LicenseBond<Test>>::get(), 10);6263 assert!(CollatorSelection::candidates().is_empty());64 assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);65 });66}6768#[test]69fn it_should_add_invulnerables() {70 new_test_ext().execute_with(|| {71 assert_ok!(CollatorSelection::add_invulnerable(72 RuntimeOrigin::signed(RootAccount::get()),73 174 ));75 assert_ok!(CollatorSelection::add_invulnerable(76 RuntimeOrigin::signed(RootAccount::get()),77 278 ));79 assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);8081 // cannot set with non-root.82 assert_noop!(83 CollatorSelection::add_invulnerable(RuntimeOrigin::signed(1), 3),84 BadOrigin85 );8687 // cannot set invulnerables without associated validator keys88 assert_noop!(89 CollatorSelection::add_invulnerable(RuntimeOrigin::signed(RootAccount::get()), 7),90 Error::<Test>::ValidatorNotRegistered91 );92 });93}9495#[test]96fn it_should_remove_invulnerables() {97 new_test_ext().execute_with(|| {98 assert_ok!(CollatorSelection::add_invulnerable(99 RuntimeOrigin::signed(RootAccount::get()),100 1101 ));102 assert_ok!(CollatorSelection::add_invulnerable(103 RuntimeOrigin::signed(RootAccount::get()),104 2105 ));106107 // cannot remove with non-root.108 assert_noop!(109 CollatorSelection::remove_invulnerable(RuntimeOrigin::signed(1), 3),110 BadOrigin111 );112113 assert_ok!(CollatorSelection::remove_invulnerable(114 RuntimeOrigin::signed(RootAccount::get()),115 2116 ));117 assert_eq!(CollatorSelection::invulnerables(), vec![1]);118119 // cannot remove an invulnerable if there would be 0 invulnerables.120 assert_noop!(121 CollatorSelection::remove_invulnerable(RuntimeOrigin::signed(RootAccount::get()), 1),122 Error::<Test>::TooFewInvulnerables123 );124 });125}126127#[test]128fn set_desired_collators_works() {129 new_test_ext().execute_with(|| {130 // given131 assert_eq!(<DesiredCollators<Test>>::get(), 5);132133 // can set134 assert_ok!(Configuration::set_collator_selection_desired_collators(135 RawOrigin::Root.into(),136 Some(7)137 ));138 assert_eq!(<DesiredCollators<Test>>::get(), 7);139140 // rejects bad origin141 assert_noop!(142 Configuration::set_collator_selection_desired_collators(143 RuntimeOrigin::signed(1),144 Some(8)145 ),146 BadOrigin147 );148 });149}150151#[test]152fn set_license_bond() {153 new_test_ext().execute_with(|| {154 // given155 assert_eq!(<LicenseBond<Test>>::get(), 10);156157 // can set158 assert_ok!(Configuration::set_collator_selection_license_bond(159 RawOrigin::Root.into(),160 Some(7)161 ));162 assert_eq!(<LicenseBond<Test>>::get(), 7);163164 // rejects bad origin.165 assert_noop!(166 Configuration::set_collator_selection_license_bond(RuntimeOrigin::signed(1), Some(8)),167 BadOrigin168 );169 });170}171172#[test]173fn cannot_onboard_candidate_with_no_license() {174 new_test_ext().execute_with(|| {175 // can't onboard a candidate who did not get a license.176 assert_noop!(177 CollatorSelection::onboard(RuntimeOrigin::signed(3)),178 Error::<Test>::NoLicense,179 );180181 // but give it a license and welcome aboard.182 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));183 assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed(3)));184 })185}186187#[test]188fn cannot_onboard_candidate_if_too_many() {189 new_test_ext().execute_with(|| {190 // reset desired candidates191 <pallet_configuration::CollatorSelectionDesiredCollatorsOverride<Test>>::put(0);192193 // can still get a license.194 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(4)));195196 // can't accept anyone anymore.197 assert_noop!(198 CollatorSelection::onboard(RuntimeOrigin::signed(4)),199 Error::<Test>::TooManyCandidates,200 );201202 // reset desired candidates to invulnerables + 1203 <pallet_configuration::CollatorSelectionDesiredCollatorsOverride<Test>>::put(3);204 assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed(4)));205206 // but no more.207 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(5)));208 assert_noop!(209 CollatorSelection::onboard(RuntimeOrigin::signed(5)),210 Error::<Test>::TooManyCandidates,211 );212 })213}214215#[test]216fn cannot_obtain_license_if_keys_not_registered() {217 new_test_ext().execute_with(|| {218 // can't 7 because keys not registered.219 assert_noop!(220 CollatorSelection::get_license(RuntimeOrigin::signed(7)),221 Error::<Test>::ValidatorNotRegistered222 );223 })224}225226#[test]227fn cannot_obtain_license_if_poor() {228 new_test_ext().execute_with(|| {229 assert_eq!(Balances::free_balance(&3), 100);230 assert_eq!(Balances::free_balance(&33), 0);231232 // works233 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));234235 // poor236 assert_noop!(237 CollatorSelection::get_license(RuntimeOrigin::signed(33)),238 BalancesError::<Test>::InsufficientBalance,239 );240 });241}242243#[test]244fn cannot_onboard_dupe_candidate() {245 new_test_ext().execute_with(|| {246 // can add 3 as candidate247 get_license_and_onboard(3);248 assert_eq!(CollatorSelection::license_deposit_of(3), 10);249 assert_eq!(CollatorSelection::candidates(), vec![3]);250 assert_eq!(CollatorSelection::last_authored_block(3), 10);251 assert_eq!(Balances::free_balance(3), 90);252253 // but no more254 assert_noop!(255 CollatorSelection::get_license(RuntimeOrigin::signed(3)),256 Error::<Test>::AlreadyHoldingLicense,257 );258 assert_noop!(259 CollatorSelection::onboard(RuntimeOrigin::signed(3)),260 Error::<Test>::AlreadyCandidate,261 );262 })263}264265#[test]266fn becoming_candidate_works() {267 new_test_ext().execute_with(|| {268 // given269 assert_eq!(<DesiredCollators<Test>>::get(), 5);270 assert_eq!(<LicenseBond<Test>>::get(), 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 get_license_and_onboard(3);279 get_license_and_onboard(4);280281 assert_eq!(Balances::free_balance(&3), 90);282 assert_eq!(Balances::free_balance(&4), 90);283284 assert_eq!(CollatorSelection::candidates().len(), 2);285 });286}287288#[test]289fn cannot_become_candidate_if_invulnerable() {290 new_test_ext().execute_with(|| {291 assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);292293 // can obtain a license even if is invulnerable.294 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(1)));295 // but cannot onboard296 assert_noop!(297 CollatorSelection::onboard(RuntimeOrigin::signed(1)),298 Error::<Test>::AlreadyInvulnerable,299 );300301 // get a license and then become invulnerable.302 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));303 assert_ok!(CollatorSelection::add_invulnerable(304 RuntimeOrigin::signed(RootAccount::get()),305 3306 ));307 assert_noop!(308 CollatorSelection::onboard(RuntimeOrigin::signed(3)),309 Error::<Test>::AlreadyInvulnerable,310 );311 })312}313314#[test]315fn can_become_invulnerable_if_candidate() {316 new_test_ext().execute_with(|| {317 // become a candidate and then become invulnerable.318 get_license_and_onboard(3);319 assert_eq!(CollatorSelection::candidates(), vec![3]);320321 assert_ok!(CollatorSelection::add_invulnerable(322 RuntimeOrigin::signed(RootAccount::get()),323 3324 ));325 // should exclude from candidates, but not revoke the license326 assert_eq!(CollatorSelection::candidates(), vec![]);327 assert_eq!(CollatorSelection::license_deposit_of(3), 10);328 assert_eq!(Balances::free_balance(3), 90);329 });330}331332#[test]333fn offboard() {334 new_test_ext().execute_with(|| {335 // register a candidate.336 get_license_and_onboard(3);337 assert_eq!(Balances::free_balance(3), 90);338339 // cannot leave if holds license but not yet candidate.340 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(4)));341 assert_noop!(342 CollatorSelection::offboard(RuntimeOrigin::signed(4)),343 Error::<Test>::NotCandidate344 );345 // cannot leave if does not hold license.346 assert_noop!(347 CollatorSelection::offboard(RuntimeOrigin::signed(5)),348 Error::<Test>::NotCandidate349 );350351 // bond is returned - only after releasing the license352 assert_ok!(CollatorSelection::offboard(RuntimeOrigin::signed(3)));353 assert_eq!(Balances::free_balance(3), 90);354 assert_eq!(CollatorSelection::last_authored_block(3), 0);355 assert_ok!(CollatorSelection::release_license(RuntimeOrigin::signed(3)));356 assert_eq!(Balances::free_balance(3), 100);357 });358}359360#[test]361fn release_license() {362 new_test_ext().execute_with(|| {363 // obtain a license to collate and reserve the bond.364 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));365 assert_eq!(Balances::free_balance(3), 90);366367 // release the license and get the bond back.368 assert_ok!(CollatorSelection::release_license(RuntimeOrigin::signed(3)));369 assert_eq!(Balances::free_balance(3), 100);370371 // register a candidate.372 get_license_and_onboard(3);373 assert_eq!(Balances::free_balance(3), 90);374375 // can release license even if onboarded.376 assert_ok!(CollatorSelection::release_license(RuntimeOrigin::signed(3)));377 assert_eq!(Balances::free_balance(3), 100);378 assert_eq!(CollatorSelection::candidates(), vec![]);379 });380}381382#[test]383fn force_revoke_license() {384 new_test_ext().execute_with(|| {385 // obtain a license to collate and reserve the bond.386 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));387 assert_eq!(Balances::free_balance(3), 90);388389 // cannot execute the operation as non-root390 assert_noop!(391 CollatorSelection::force_revoke_license(RuntimeOrigin::signed(3), 3),392 BadOrigin393 );394395 // release the license and get the bond back.396 assert_ok!(CollatorSelection::force_revoke_license(397 RuntimeOrigin::signed(RootAccount::get()),398 3399 ));400 assert_eq!(Balances::free_balance(3), 100);401402 // register a candidate.403 get_license_and_onboard(3);404 assert_eq!(Balances::free_balance(3), 90);405406 // can release license even if onboarded.407 assert_ok!(CollatorSelection::force_revoke_license(408 RuntimeOrigin::signed(RootAccount::get()),409 3410 ));411 assert_eq!(Balances::free_balance(3), 100);412 assert_eq!(CollatorSelection::candidates(), vec![]);413 });414}415416#[test]417fn authorship_event_handler() {418 new_test_ext().execute_with(|| {419 // put 100 in the pot + 5 for ED420 Balances::make_free_balance_be(&CollatorSelection::account_id(), 105);421422 // 4 is the default author.423 assert_eq!(Balances::free_balance(4), 100);424 get_license_and_onboard(4);425 // triggers `note_author`426 Authorship::on_initialize(1);427428 assert_eq!(CollatorSelection::candidates(), vec![4]);429 assert_eq!(CollatorSelection::last_authored_block(4), 0);430431 // half of the pot goes to the collator who's the author (4 in tests).432 assert_eq!(Balances::free_balance(4), 140);433 // half + ED stays.434 assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 55);435 });436}437438#[test]439fn fees_edgecases() {440 new_test_ext().execute_with(|| {441 // Nothing panics, no reward when no ED in balance442 Authorship::on_initialize(1);443 // put some money into the pot at ED444 Balances::make_free_balance_be(&CollatorSelection::account_id(), 5);445 // 4 is the default author.446 assert_eq!(Balances::free_balance(4), 100);447 get_license_and_onboard(4);448 // triggers `note_author`449 Authorship::on_initialize(1);450451 assert_eq!(CollatorSelection::candidates(), vec![4]);452 assert_eq!(CollatorSelection::last_authored_block(4), 0);453 // Nothing received454 assert_eq!(Balances::free_balance(4), 90);455 // all fee stays456 assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 5);457 });458}459460#[test]461fn session_management_works() {462 new_test_ext().execute_with(|| {463 initialize_to_block(1);464465 assert_eq!(SessionChangeBlock::get(), 0);466 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);467468 initialize_to_block(4);469470 assert_eq!(SessionChangeBlock::get(), 0);471 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);472473 // add a new collator474 get_license_and_onboard(5);475476 // session won't see this.477 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);478 // but we have a new candidate.479 assert_eq!(CollatorSelection::candidates().len(), 1);480481 initialize_to_block(10);482 assert_eq!(SessionChangeBlock::get(), 10);483 // pallet-session has 1 session delay; current validators are the same.484 assert_eq!(Session::validators(), vec![1, 2]);485 // queued ones are changed, and now we have 3.486 assert_eq!(Session::queued_keys().len(), 3);487 // session handlers (aura, et. al.) cannot see this yet.488 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);489490 initialize_to_block(20);491 assert_eq!(SessionChangeBlock::get(), 20);492 // changed are now reflected to session handlers.493 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 5]);494 });495}496497#[test]498fn kick_mechanism() {499 new_test_ext().execute_with(|| {500 // add a new collator501 get_license_and_onboard(3);502 get_license_and_onboard(4);503504 initialize_to_block(10);505 assert_eq!(CollatorSelection::candidates().len(), 2);506507 initialize_to_block(20);508 assert_eq!(SessionChangeBlock::get(), 20);509 // 4 authored this block, gets to stay 3 was kicked510 assert_eq!(CollatorSelection::candidates().len(), 1);511 // 3 will be kicked after 1 session delay512 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 4]);513514 assert_eq!(CollatorSelection::candidates(), vec![4]);515 assert_eq!(<KickThreshold<Test>>::get(), 10);516 assert_eq!(CollatorSelection::last_authored_block(4), 20);517518 initialize_to_block(30);519 // 3 gets kicked after 1 session delay520 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 4]);521 // kicked collator gets their funds slashed, the deposit going to treasury522 assert_eq!(Balances::free_balance(3), 90);523 });524}525526#[test]527#[should_panic = "duplicate invulnerables in genesis."]528fn cannot_set_genesis_value_twice() {529 sp_tracing::try_init_simple();530 let mut t = frame_system::GenesisConfig::default()531 .build_storage::<Test>()532 .unwrap();533 let invulnerables = vec![1, 1];534535 let collator_selection = collator_selection::GenesisConfig::<Test> {536 invulnerables,537 };538 // collator selection must be initialized before session.539 collator_selection.assimilate_storage(&mut t).unwrap();540}pallets/configuration/src/lib.rsdiffbeforeafterboth--- a/pallets/configuration/src/lib.rs
+++ b/pallets/configuration/src/lib.rs
@@ -36,15 +36,24 @@
mod pallet {
use super::*;
use frame_support::{
- traits::Get,
- pallet_prelude::{StorageValue, ValueQuery, DispatchResult, OptionQuery},
- BoundedVec,
+ traits::{Get, ReservableCurrency, Currency},
+ pallet_prelude::{StorageValue, ValueQuery, DispatchResult, IsType, OptionQuery},
+ BoundedVec, log,
};
- use frame_system::{pallet_prelude::OriginFor, ensure_root};
+ use frame_system::{pallet_prelude::OriginFor, ensure_root, Config as SystemConfig};
use xcm::v1::MultiLocation;
+ pub type BalanceOf<T> =
+ <<T as Config>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
+
#[pallet::config]
pub trait Config: frame_system::Config {
+ /// Overarching event type.
+ type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
+
+ /// The currency mechanism.
+ type Currency: ReservableCurrency<Self::AccountId>;
+
#[pallet::constant]
type DefaultWeightToFeeCoefficient: Get<u32>;
@@ -57,6 +66,27 @@
type AppPromotionDailyRate: Get<Perbill>;
#[pallet::constant]
type DayRelayBlocks: Get<Self::BlockNumber>;
+
+ #[pallet::constant]
+ type DefaultCollatorSelectionMaxCollators: Get<u32>;
+ #[pallet::constant]
+ type DefaultCollatorSelectionLicenseBond: Get<BalanceOf<Self>>;
+ #[pallet::constant]
+ type DefaultCollatorSelectionKickThreshold: Get<Self::BlockNumber>;
+ }
+
+ #[pallet::event]
+ #[pallet::generate_deposit(pub(super) fn deposit_event)]
+ pub enum Event<T: Config> {
+ NewDesiredCollators {
+ desired_collators: Option<u32>,
+ },
+ NewCollatorLicenseBond {
+ bond_cost: Option<BalanceOf<T>>,
+ },
+ NewCollatorKickThreshold {
+ length_in_blocks: Option<T::BlockNumber>,
+ },
}
#[pallet::error]
@@ -85,6 +115,27 @@
pub type AppPromomotionConfigurationOverride<T: Config> =
StorageValue<Value = AppPromotionConfiguration<T::BlockNumber>, QueryKind = ValueQuery>;
+ #[pallet::storage]
+ pub type CollatorSelectionDesiredCollatorsOverride<T: Config> = StorageValue<
+ Value = u32,
+ QueryKind = ValueQuery,
+ OnEmpty = T::DefaultCollatorSelectionMaxCollators,
+ >;
+
+ #[pallet::storage]
+ pub type CollatorSelectionLicenseBondOverride<T: Config> = StorageValue<
+ Value = BalanceOf<T>,
+ QueryKind = ValueQuery,
+ OnEmpty = T::DefaultCollatorSelectionLicenseBond,
+ >;
+
+ #[pallet::storage]
+ pub type CollatorSelectionKickThresholdOverride<T: Config> = StorageValue<
+ Value = T::BlockNumber,
+ QueryKind = ValueQuery,
+ OnEmpty = T::DefaultCollatorSelectionKickThreshold,
+ >;
+
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(T::DbWeight::get().writes(1))]
@@ -144,6 +195,55 @@
Ok(())
}
+
+ #[pallet::weight(T::DbWeight::get().writes(1))]
+ pub fn set_collator_selection_desired_collators(
+ origin: OriginFor<T>,
+ max: Option<u32>,
+ ) -> DispatchResult {
+ ensure_root(origin)?;
+ if let Some(max) = max {
+ // we trust origin calls, this is just a for more accurate benchmarking
+ if max > T::DefaultCollatorSelectionMaxCollators::get() {
+ log::warn!("max > T::DefaultCollatorSelectionMaxCollators; you might need to run benchmarks again");
+ }
+ <CollatorSelectionDesiredCollatorsOverride<T>>::set(max);
+ } else {
+ <CollatorSelectionDesiredCollatorsOverride<T>>::kill();
+ }
+ Self::deposit_event(Event::NewDesiredCollators { desired_collators: max });
+ Ok(())
+ }
+
+ #[pallet::weight(T::DbWeight::get().writes(1))]
+ pub fn set_collator_selection_license_bond(
+ origin: OriginFor<T>,
+ amount: Option<BalanceOf<T>>,
+ ) -> DispatchResult {
+ ensure_root(origin)?;
+ if let Some(amount) = amount {
+ <CollatorSelectionLicenseBondOverride<T>>::set(amount);
+ } else {
+ <CollatorSelectionLicenseBondOverride<T>>::kill();
+ }
+ Self::deposit_event(Event::NewCollatorLicenseBond { bond_cost: amount });
+ Ok(())
+ }
+
+ #[pallet::weight(T::DbWeight::get().writes(1))]
+ pub fn set_collator_selection_kick_threshold(
+ origin: OriginFor<T>,
+ threshold: Option<T::BlockNumber>,
+ ) -> DispatchResult {
+ ensure_root(origin)?;
+ if let Some(threshold) = threshold {
+ <CollatorSelectionKickThresholdOverride<T>>::set(threshold);
+ } else {
+ <CollatorSelectionKickThresholdOverride<T>>::kill();
+ }
+ Self::deposit_event(Event::NewCollatorKickThreshold { length_in_blocks: threshold });
+ Ok(())
+ }
}
#[pallet::pallet]
primitives/common/src/constants.rsdiffbeforeafterboth--- a/primitives/common/src/constants.rs
+++ b/primitives/common/src/constants.rs
@@ -46,6 +46,8 @@
pub const EXISTENTIAL_DEPOSIT: u128 = 0;
/// Amount of Balance reserved for candidate registration.
pub const GENESIS_LICENSE_BOND: u128 = 1_000_000_000_000 * UNIQUE;
+/// Amount of maximum collators for Collator Selection.
+pub const MAX_COLLATORS: u32 = 10;
/// How long a periodic session lasts in blocks.
pub const SESSION_LENGTH: BlockNumber = HOURS;
runtime/common/config/pallets/collator_selection.rsdiffbeforeafterboth--- a/runtime/common/config/pallets/collator_selection.rs
+++ b/runtime/common/config/pallets/collator_selection.rs
@@ -17,14 +17,14 @@
use frame_support::{parameter_types, PalletId};
use frame_system::EnsureRoot;
use crate::{
- AccountId, BlockNumber, Runtime, RuntimeEvent, Balances, Aura, Session, SessionKeys,
- CollatorSelection, config::pallets::TreasuryAccountId,
+ AccountId, Balance, Balances, BlockNumber, Runtime, RuntimeEvent, Aura, Session, SessionKeys,
+ CollatorSelection, Treasury,
+ config::pallets::{MaxCollators, SessionPeriod, TreasuryAccountId},
};
use sp_runtime::Perbill;
-use up_common::constants::*;
+use up_common::constants::{UNIQUE, MILLIUNIQUE};
parameter_types! {
- pub const SessionPeriod: BlockNumber = SESSION_LENGTH;
pub const SessionOffset: BlockNumber = 0;
}
@@ -55,13 +55,11 @@
parameter_types! {
pub const PotId: PalletId = PalletId(*b"PotStake");
- pub const MaxCollators: u32 = 10;
pub const SlashRatio: Perbill = Perbill::from_percent(100);
}
impl pallet_collator_selection::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
- type Currency = Balances;
// We allow root only to execute privileged collator selection operations.
type UpdateOrigin = EnsureRoot<AccountId>;
type TreasuryAccountId = TreasuryAccountId;
runtime/common/config/pallets/mod.rsdiffbeforeafterboth--- a/runtime/common/config/pallets/mod.rs
+++ b/runtime/common/config/pallets/mod.rs
@@ -25,7 +25,7 @@
},
Runtime, RuntimeEvent, RuntimeCall, Balances,
};
-use frame_support::traits::{ConstU32, ConstU64};
+use frame_support::traits::{ConstU32, ConstU64, ConstU128};
use up_common::{
types::{AccountId, Balance, BlockNumber},
constants::*,
@@ -104,11 +104,20 @@
parameter_types! {
pub AppPromotionDailyRate: Perbill = Perbill::from_rational(5u32, 10_000);
+ pub const MaxCollators: u32 = MAX_COLLATORS;
+ pub const SessionPeriod: BlockNumber = SESSION_LENGTH;
pub const DayRelayBlocks: BlockNumber = RELAY_DAYS;
}
+
impl pallet_configuration::Config for Runtime {
+ type RuntimeEvent = RuntimeEvent;
+ type Currency = Balances;
type DefaultWeightToFeeCoefficient = ConstU32<{ up_common::constants::WEIGHT_TO_FEE_COEFF }>;
type DefaultMinGasPrice = ConstU64<{ up_common::constants::MIN_GAS_PRICE }>;
+ type DefaultCollatorSelectionMaxCollators = MaxCollators;
+ type DefaultCollatorSelectionKickThreshold = SessionPeriod;
+ type DefaultCollatorSelectionLicenseBond =
+ ConstU128<{ up_common::constants::GENESIS_LICENSE_BOND }>;
type MaxXcmAllowedLocations = ConstU32<16>;
type AppPromotionDailyRate = AppPromotionDailyRate;
type DayRelayBlocks = DayRelayBlocks;
runtime/common/construct_runtime/mod.rsdiffbeforeafterboth--- a/runtime/common/construct_runtime/mod.rs
+++ b/runtime/common/construct_runtime/mod.rs
@@ -69,7 +69,7 @@
// #[runtimes(opal)]
// Scheduler: pallet_unique_scheduler_v2::{Pallet, Call, Storage, Event<T>} = 62,
- Configuration: pallet_configuration::{Pallet, Call, Storage} = 63,
+ Configuration: pallet_configuration::{Pallet, Call, Storage, Event<T>} = 63,
Charging: pallet_charge_transaction::{Pallet, Call, Storage } = 64,
// ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage} = 65,
runtime/common/maintenance.rsdiffbeforeafterboth--- a/runtime/common/maintenance.rs
+++ b/runtime/common/maintenance.rs
@@ -85,6 +85,12 @@
Err(TransactionValidityError::Invalid(InvalidTransaction::Call))
}
+ #[cfg(feature = "collator-selection")]
+ RuntimeCall::CollatorSelection(_)
+ | RuntimeCall::Authorship(_)
+ | RuntimeCall::Session(_)
+ | RuntimeCall::Identity(_) => Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
+
#[cfg(feature = "pallet-test-utils")]
RuntimeCall::TestUtils(_) => Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
@@ -110,7 +116,7 @@
) -> TransactionValidity {
if Maintenance::is_enabled() {
match call {
- RuntimeCall::EVM(_) | RuntimeCall::Ethereum(_) | RuntimeCall::EvmMigration(_) => {
+ RuntimeCall::EVM(_) | RuntimeCall::Ethereum(_) | RuntimeCall::DataManagement(_) => {
Err(TransactionValidityError::Invalid(InvalidTransaction::Call))
}
_ => Ok(ValidTransaction::default()),
runtime/common/mod.rsdiffbeforeafterboth--- a/runtime/common/mod.rs
+++ b/runtime/common/mod.rs
@@ -186,13 +186,9 @@
#[cfg(feature = "collator-selection")]
{
use frame_support::{BoundedVec, storage::migration};
- use sp_runtime::{
- traits::{OpaqueKeys, Saturating},
- RuntimeAppPublic,
- };
+ use sp_runtime::{traits::OpaqueKeys, RuntimeAppPublic};
use pallet_session::SessionManager;
- use up_common::constants::{GENESIS_LICENSE_BOND, SESSION_LENGTH};
- use crate::config::pallets::collator_selection::MaxCollators;
+ use crate::config::pallets::MaxCollators;
let mut weight = <Runtime as frame_system::Config>::DbWeight::get().reads(1);
@@ -241,9 +237,6 @@
.expect("Existing collators/invulnerables are more than MaxCollators");
<pallet_collator_selection::Invulnerables<Runtime>>::put(bounded_invulnerables);
- <pallet_collator_selection::KickThreshold<Runtime>>::put(SESSION_LENGTH);
- <pallet_collator_selection::DesiredCollators<Runtime>>::put(MaxCollators::get());
- <pallet_collator_selection::LicenseBond<Runtime>>::put(GENESIS_LICENSE_BOND);
let keys = invulnerables
.into_iter()
runtime/common/tests/mod.rsdiffbeforeafterboth--- a/runtime/common/tests/mod.rs
+++ b/runtime/common/tests/mod.rs
@@ -64,9 +64,6 @@
let cfg = GenesisConfig {
collator_selection: CollatorSelectionConfig {
- desired_collators: 2,
- license_bond: 10,
- kick_threshold: 10,
invulnerables,
},
session: SessionConfig { keys },
tests/src/collatorSelection.seqtest.tsdiffbeforeafterboth--- a/tests/src/collatorSelection.seqtest.ts
+++ b/tests/src/collatorSelection.seqtest.ts
@@ -17,8 +17,6 @@
import {IKeyringPair} from '@polkadot/types/types';
import {usingPlaygrounds, expect, itSub, Pallets, requirePalletsOrSkip} from './util';
-const MAX_INVULNERABLES = 10;
-
async function resetInvulnerables() {
await usingPlaygrounds(async (helper, privateKey) => {
const superuser = await privateKey('//Alice');
@@ -31,7 +29,7 @@
let nonce = await helper.chain.getNonce(alice.address);
// In case there are too many invulnerables already, remove some of them, leaving space for Alice and Bob.
- if (invulnerables.length + 2 >= MAX_INVULNERABLES) {
+ if (invulnerables.length + 2 >= helper.collatorSelection.maxCollators()) {
await Promise.all([
helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerables.pop()], true, {nonce: nonce++}),
helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerables.pop()], true, {nonce: nonce++}),
@@ -409,7 +407,7 @@
// 28 non-functioning collators, teehee.
const invulnerablesLength = (await helper.collatorSelection.getInvulnerables()).length;
- const invulnerablesUntilLimit = MAX_INVULNERABLES - invulnerablesLength;
+ const invulnerablesUntilLimit = helper.collatorSelection.maxCollators() - invulnerablesLength;
const newInvulnerables = await helper.arrange.createAccounts(Array(invulnerablesUntilLimit).fill(10n), superuser);
const [lastInvulnerable] = await helper.arrange.createAccounts([10n], superuser);
tests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -65,6 +65,7 @@
arrange: ArrangeGroup;
wait: WaitGroup;
admin: AdminGroup;
+ session: SessionGroup;
testUtils: TestUtilGroup;
constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
@@ -75,6 +76,7 @@
this.wait = new WaitGroup(this);
this.admin = new AdminGroup(this);
this.testUtils = new TestUtilGroup(this);
+ this.session = new SessionGroup(this);
}
async connect(wsEndpoint: string, _listeners?: any): Promise<void> {
@@ -456,14 +458,14 @@
console.log(`Waiting for ${sessionCount} new session${sessionCount > 1 ? 's' : ''}.`
+ ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');
- const expectedSessionIndex = await this.helper.session.getIndex() + sessionCount;
+ const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;
let currentSessionIndex = -1;
while (currentSessionIndex < expectedSessionIndex) {
// eslint-disable-next-line no-async-promise-executor
currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {
await this.newBlocks(1);
- const res = this.helper.session.getIndex();
+ const res = await (this.helper as DevUniqueHelper).session.getIndex();
resolve(res);
}), blockTimeout, 'The chain has stopped producing blocks!');
}
@@ -552,6 +554,36 @@
}
}
+class SessionGroup {
+ helper: ChainHelperBase;
+
+ constructor(helper: ChainHelperBase) {
+ this.helper = helper;
+ }
+
+ //todo:collator documentation
+ async getIndex(): Promise<number> {
+ return (await this.helper.callRpc('api.query.session.currentIndex')).toNumber();
+ }
+
+ newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {
+ return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);
+ }
+
+ setOwnKeys(signer: TSigner, key: string) {
+ return this.helper.executeExtrinsic(
+ signer,
+ 'api.tx.session.setKeys',
+ [key, '0x0'],
+ true,
+ );
+ }
+
+ setOwnKeysFromAddress(signer: TSigner) {
+ return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));
+ }
+}
+
class TestUtilGroup {
helper: DevUniqueHelper;
tests/src/util/playgrounds/unique.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -45,7 +45,6 @@
import {RuntimeDispatchInfo} from '@polkadot/types/interfaces';
import type {Vec} from '@polkadot/types-codec';
import {FrameSystemEventRecord} from '@polkadot/types/lookup';
-import {DevUniqueHelper} from './unique.dev';
export class CrossAccountId implements ICrossAccountId {
Substrate?: TSubstrateAccount;
@@ -376,7 +375,6 @@
children: ChainHelperBase[];
address: AddressGroup;
chain: ChainGroup;
- session: SessionGroup;
constructor(logger?: ILogger, helperBase?: any) {
this.helperBase = helperBase;
@@ -392,7 +390,6 @@
this.children = [];
this.address = new AddressGroup(this);
this.chain = new ChainGroup(this);
- this.session = new SessionGroup(this);
}
clone(helperCls: ChainHelperBaseConstructor, options: {[key: string]: any} = {}) {
@@ -2642,30 +2639,6 @@
blocksNum,
options,
}) as T;
- }
-}
-
-class SessionGroup extends HelperGroup<ChainHelperBase> {
- //todo:collator documentation
- async getIndex(): Promise<number> {
- return (await this.helper.callRpc('api.query.session.currentIndex')).toNumber();
- }
-
- newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {
- return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);
- }
-
- setOwnKeys(signer: TSigner, key: string) {
- return this.helper.executeExtrinsic(
- signer,
- 'api.tx.session.setKeys',
- [key, '0x0'],
- true,
- );
- }
-
- setOwnKeysFromAddress(signer: TSigner) {
- return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));
}
}
@@ -2683,12 +2656,21 @@
return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman());
}
+ /** and also total max invulnerables */
+ maxCollators(): number {
+ return (this.helper.getApi().consts.configuration.defaultCollatorSelectionMaxCollators.toJSON() as number);
+ }
+
+ async getDesiredCollators(): Promise<number> {
+ return (await this.helper.callRpc('api.query.configuration.collatorSelectionDesiredCollatorsOverride')).toNumber();
+ }
+
setLicenseBond(signer: TSigner, amount: bigint) {
- return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.setLicenseBond', [amount]);
+ return this.helper.executeExtrinsic(signer, 'api.tx.configuration.setCollatorSelectionLicenseBond', [amount]);
}
async getLicenseBond(): Promise<bigint> {
- return (await this.helper.callRpc('api.query.collatorSelection.licenseBond')).toBigInt();
+ return (await this.helper.callRpc('api.query.configuration.collatorSelectionLicenseBondOverride')).toBigInt();
}
obtainLicense(signer: TSigner) {
@@ -2704,7 +2686,7 @@
}
async hasLicense(address: string): Promise<bigint> {
- return (await this.helper.callRpc('api.query.collatorSelection.licenses', [address])).toBigInt();
+ return (await this.helper.callRpc('api.query.collatorSelection.licenseDepositOf', [address])).toBigInt();
}
onboard(signer: TSigner) {