mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 14:41:11 +00:00
Store validator self-vote in bags-list, and allow them to be trimmed for election (#10821)
* Implement the new validator-in-bags-list scenario + migration * Apply suggestions from code review Co-authored-by: Zeke Mostov <z.mostov@gmail.com> * some review comments * guard the migration * some review comments * Fix tests 🤦♂️ * Fix build * fix weight_of_fn * reformat line width * make const * use weight of fn cached * SortedListProvider -> VoterList * Fix all build and docs * check post migration Co-authored-by: Zeke Mostov <z.mostov@gmail.com>
This commit is contained in:
@@ -558,9 +558,7 @@ impl pallet_staking::Config for Runtime {
|
||||
type OffendingValidatorsThreshold = OffendingValidatorsThreshold;
|
||||
type ElectionProvider = ElectionProviderMultiPhase;
|
||||
type GenesisElectionProvider = onchain::OnChainSequentialPhragmen<Self>;
|
||||
// Alternatively, use pallet_staking::UseNominatorsMap<Runtime> to just use the nominators map.
|
||||
// Note that the aforementioned does not scale to a very large number of nominators.
|
||||
type SortedListProvider = BagsList;
|
||||
type VoterList = BagsList;
|
||||
type MaxUnlockingChunks = ConstU32<32>;
|
||||
type WeightInfo = pallet_staking::weights::SubstrateWeight<Runtime>;
|
||||
type BenchmarkingConfig = StakingBenchmarkingConfig;
|
||||
|
||||
@@ -197,7 +197,7 @@ impl pallet_staking::Config for Test {
|
||||
type NextNewSession = Session;
|
||||
type ElectionProvider = onchain::OnChainSequentialPhragmen<Self>;
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
type SortedListProvider = pallet_staking::UseNominatorsMap<Self>;
|
||||
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
||||
type MaxUnlockingChunks = ConstU32<32>;
|
||||
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
|
||||
type WeightInfo = ();
|
||||
|
||||
@@ -48,7 +48,7 @@ pub fn display_and_check_bags<Runtime: RuntimeT>(currency_unit: u64, currency_na
|
||||
let min_nominator_bond = <pallet_staking::MinNominatorBond<Runtime>>::get();
|
||||
log::info!(target: LOG_TARGET, "min nominator bond is {:?}", min_nominator_bond);
|
||||
|
||||
let voter_list_count = <Runtime as pallet_staking::Config>::SortedListProvider::count();
|
||||
let voter_list_count = <Runtime as pallet_staking::Config>::VoterList::count();
|
||||
|
||||
// go through every bag to track the total number of voters within bags and log some info about
|
||||
// how voters are distributed within the bags.
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
//! Test to check the migration of the voter bag.
|
||||
|
||||
use crate::{RuntimeT, LOG_TARGET};
|
||||
use frame_election_provider_support::SortedListProvider;
|
||||
use frame_support::traits::PalletInfoAccess;
|
||||
use pallet_staking::Nominators;
|
||||
use remote_externalities::{Builder, Mode, OnlineConfig};
|
||||
@@ -45,16 +44,16 @@ pub async fn execute<Runtime: RuntimeT, Block: BlockT + DeserializeOwned>(
|
||||
let pre_migrate_nominator_count = <Nominators<Runtime>>::iter().count() as u32;
|
||||
log::info!(target: LOG_TARGET, "Nominator count: {}", pre_migrate_nominator_count);
|
||||
|
||||
// run the actual migration,
|
||||
let moved = <Runtime as pallet_staking::Config>::SortedListProvider::unsafe_regenerate(
|
||||
use frame_election_provider_support::SortedListProvider;
|
||||
// run the actual migration
|
||||
let moved = <Runtime as pallet_staking::Config>::VoterList::unsafe_regenerate(
|
||||
pallet_staking::Nominators::<Runtime>::iter().map(|(n, _)| n),
|
||||
pallet_staking::Pallet::<Runtime>::weight_of_fn(),
|
||||
);
|
||||
log::info!(target: LOG_TARGET, "Moved {} nominators", moved);
|
||||
|
||||
let voter_list_len =
|
||||
<Runtime as pallet_staking::Config>::SortedListProvider::iter().count() as u32;
|
||||
let voter_list_count = <Runtime as pallet_staking::Config>::SortedListProvider::count();
|
||||
let voter_list_len = <Runtime as pallet_staking::Config>::VoterList::iter().count() as u32;
|
||||
let voter_list_count = <Runtime as pallet_staking::Config>::VoterList::count();
|
||||
// and confirm it is equal to the length of the `VoterList`.
|
||||
assert_eq!(pre_migrate_nominator_count, voter_list_len);
|
||||
assert_eq!(pre_migrate_nominator_count, voter_list_count);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
//! Test to execute the snapshot using the voter bag.
|
||||
|
||||
use frame_election_provider_support::SortedListProvider;
|
||||
use frame_support::traits::PalletInfoAccess;
|
||||
use remote_externalities::{Builder, Mode, OnlineConfig};
|
||||
use sp_runtime::{traits::Block as BlockT, DeserializeOwned};
|
||||
@@ -48,11 +49,11 @@ pub async fn execute<Runtime: crate::RuntimeT, Block: BlockT + DeserializeOwned>
|
||||
.unwrap();
|
||||
|
||||
ext.execute_with(|| {
|
||||
use frame_election_provider_support::{ElectionDataProvider, SortedListProvider};
|
||||
use frame_election_provider_support::ElectionDataProvider;
|
||||
log::info!(
|
||||
target: crate::LOG_TARGET,
|
||||
"{} nodes in bags list.",
|
||||
<Runtime as pallet_staking::Config>::SortedListProvider::count(),
|
||||
<Runtime as pallet_staking::Config>::VoterList::count(),
|
||||
);
|
||||
|
||||
let voters =
|
||||
|
||||
@@ -205,7 +205,7 @@ impl pallet_staking::Config for Test {
|
||||
type NextNewSession = Session;
|
||||
type ElectionProvider = onchain::OnChainSequentialPhragmen<Self>;
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
type SortedListProvider = pallet_staking::UseNominatorsMap<Self>;
|
||||
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
||||
type MaxUnlockingChunks = ConstU32<32>;
|
||||
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
|
||||
type WeightInfo = ();
|
||||
|
||||
@@ -175,7 +175,7 @@ impl pallet_staking::Config for Test {
|
||||
type OffendingValidatorsThreshold = ();
|
||||
type ElectionProvider = onchain::OnChainSequentialPhragmen<Self>;
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
type SortedListProvider = pallet_staking::UseNominatorsMap<Self>;
|
||||
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
||||
type MaxUnlockingChunks = ConstU32<32>;
|
||||
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
|
||||
type WeightInfo = ();
|
||||
|
||||
@@ -182,7 +182,7 @@ impl pallet_staking::Config for Test {
|
||||
type ElectionProvider = onchain::OnChainSequentialPhragmen<Self>;
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
type MaxUnlockingChunks = ConstU32<32>;
|
||||
type SortedListProvider = pallet_staking::UseNominatorsMap<Self>;
|
||||
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
||||
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ pub fn migrate<T: pallet_session_historical::Config, P: GetStorageVersion + Pall
|
||||
}
|
||||
|
||||
/// Some checks prior to migration. This can be linked to
|
||||
/// [`frame_support::traits::OnRuntimeUpgrade::pre_upgrade`] for further testing.
|
||||
/// `frame_support::traits::OnRuntimeUpgrade::pre_upgrade` for further testing.
|
||||
///
|
||||
/// Panics if anything goes wrong.
|
||||
pub fn pre_migrate<
|
||||
@@ -123,7 +123,7 @@ pub fn pre_migrate<
|
||||
}
|
||||
|
||||
/// Some checks for after migration. This can be linked to
|
||||
/// [`frame_support::traits::OnRuntimeUpgrade::post_upgrade`] for further testing.
|
||||
/// `frame_support::traits::OnRuntimeUpgrade::post_upgrade` for further testing.
|
||||
///
|
||||
/// Panics if anything goes wrong.
|
||||
pub fn post_migrate<
|
||||
|
||||
@@ -155,8 +155,8 @@ impl<T: Config> ListScenario<T> {
|
||||
/// - the destination bag has at least one node, which will need its next pointer updated.
|
||||
///
|
||||
/// NOTE: while this scenario specifically targets a worst case for the bags-list, it should
|
||||
/// also elicit a worst case for other known `SortedListProvider` implementations; although
|
||||
/// this may not be true against unknown `SortedListProvider` implementations.
|
||||
/// also elicit a worst case for other known `VoterList` implementations; although
|
||||
/// this may not be true against unknown `VoterList` implementations.
|
||||
fn new(origin_weight: BalanceOf<T>, is_increase: bool) -> Result<Self, &'static str> {
|
||||
ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0");
|
||||
|
||||
@@ -189,7 +189,7 @@ impl<T: Config> ListScenario<T> {
|
||||
|
||||
// find a destination weight that will trigger the worst case scenario
|
||||
let dest_weight_as_vote =
|
||||
T::SortedListProvider::score_update_worst_case(&origin_stash1, is_increase);
|
||||
T::VoterList::score_update_worst_case(&origin_stash1, is_increase);
|
||||
|
||||
let total_issuance = T::Currency::total_issuance();
|
||||
|
||||
@@ -316,7 +316,7 @@ benchmarks! {
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
let controller = scenario.origin_controller1.clone();
|
||||
let stash = scenario.origin_stash1.clone();
|
||||
assert!(T::SortedListProvider::contains(&stash));
|
||||
assert!(T::VoterList::contains(&stash));
|
||||
|
||||
let ed = T::Currency::minimum_balance();
|
||||
let mut ledger = Ledger::<T>::get(&controller).unwrap();
|
||||
@@ -328,28 +328,24 @@ benchmarks! {
|
||||
}: withdraw_unbonded(RawOrigin::Signed(controller.clone()), s)
|
||||
verify {
|
||||
assert!(!Ledger::<T>::contains_key(controller));
|
||||
assert!(!T::SortedListProvider::contains(&stash));
|
||||
assert!(!T::VoterList::contains(&stash));
|
||||
}
|
||||
|
||||
validate {
|
||||
// clean up any existing state.
|
||||
clear_validators_and_nominators::<T>();
|
||||
|
||||
let origin_weight = MinNominatorBond::<T>::get().max(T::Currency::minimum_balance());
|
||||
|
||||
// setup a worst case scenario where the user calling validate was formerly a nominator so
|
||||
// they must be removed from the list.
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
let controller = scenario.origin_controller1.clone();
|
||||
let stash = scenario.origin_stash1.clone();
|
||||
assert!(T::SortedListProvider::contains(&stash));
|
||||
let (stash, controller) = create_stash_controller::<T>(
|
||||
T::MaxNominations::get() - 1,
|
||||
100,
|
||||
Default::default(),
|
||||
)?;
|
||||
// because it is chilled.
|
||||
assert!(!T::VoterList::contains(&stash));
|
||||
|
||||
let prefs = ValidatorPrefs::default();
|
||||
whitelist_account!(controller);
|
||||
}: _(RawOrigin::Signed(controller), prefs)
|
||||
verify {
|
||||
assert!(Validators::<T>::contains_key(&stash));
|
||||
assert!(!T::SortedListProvider::contains(&stash));
|
||||
assert!(T::VoterList::contains(&stash));
|
||||
}
|
||||
|
||||
kick {
|
||||
@@ -434,14 +430,14 @@ benchmarks! {
|
||||
).unwrap();
|
||||
|
||||
assert!(!Nominators::<T>::contains_key(&stash));
|
||||
assert!(!T::SortedListProvider::contains(&stash));
|
||||
assert!(!T::VoterList::contains(&stash));
|
||||
|
||||
let validators = create_validators::<T>(n, 100).unwrap();
|
||||
whitelist_account!(controller);
|
||||
}: _(RawOrigin::Signed(controller), validators)
|
||||
verify {
|
||||
assert!(Nominators::<T>::contains_key(&stash));
|
||||
assert!(T::SortedListProvider::contains(&stash))
|
||||
assert!(T::VoterList::contains(&stash))
|
||||
}
|
||||
|
||||
chill {
|
||||
@@ -455,12 +451,12 @@ benchmarks! {
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
let controller = scenario.origin_controller1.clone();
|
||||
let stash = scenario.origin_stash1.clone();
|
||||
assert!(T::SortedListProvider::contains(&stash));
|
||||
assert!(T::VoterList::contains(&stash));
|
||||
|
||||
whitelist_account!(controller);
|
||||
}: _(RawOrigin::Signed(controller))
|
||||
verify {
|
||||
assert!(!T::SortedListProvider::contains(&stash));
|
||||
assert!(!T::VoterList::contains(&stash));
|
||||
}
|
||||
|
||||
set_payee {
|
||||
@@ -523,13 +519,13 @@ benchmarks! {
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
let controller = scenario.origin_controller1.clone();
|
||||
let stash = scenario.origin_stash1.clone();
|
||||
assert!(T::SortedListProvider::contains(&stash));
|
||||
assert!(T::VoterList::contains(&stash));
|
||||
add_slashing_spans::<T>(&stash, s);
|
||||
|
||||
}: _(RawOrigin::Root, stash.clone(), s)
|
||||
verify {
|
||||
assert!(!Ledger::<T>::contains_key(&controller));
|
||||
assert!(!T::SortedListProvider::contains(&stash));
|
||||
assert!(!T::VoterList::contains(&stash));
|
||||
}
|
||||
|
||||
cancel_deferred_slash {
|
||||
@@ -708,13 +704,13 @@ benchmarks! {
|
||||
Ledger::<T>::insert(&controller, l);
|
||||
|
||||
assert!(Bonded::<T>::contains_key(&stash));
|
||||
assert!(T::SortedListProvider::contains(&stash));
|
||||
assert!(T::VoterList::contains(&stash));
|
||||
|
||||
whitelist_account!(controller);
|
||||
}: _(RawOrigin::Signed(controller), stash.clone(), s)
|
||||
verify {
|
||||
assert!(!Bonded::<T>::contains_key(&stash));
|
||||
assert!(!T::SortedListProvider::contains(&stash));
|
||||
assert!(!T::VoterList::contains(&stash));
|
||||
}
|
||||
|
||||
new_era {
|
||||
@@ -899,7 +895,7 @@ benchmarks! {
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
let controller = scenario.origin_controller1.clone();
|
||||
let stash = scenario.origin_stash1.clone();
|
||||
assert!(T::SortedListProvider::contains(&stash));
|
||||
assert!(T::VoterList::contains(&stash));
|
||||
|
||||
Staking::<T>::set_staking_configs(
|
||||
RawOrigin::Root.into(),
|
||||
@@ -914,7 +910,7 @@ benchmarks! {
|
||||
let caller = whitelisted_caller();
|
||||
}: _(RawOrigin::Signed(caller), controller.clone())
|
||||
verify {
|
||||
assert!(!T::SortedListProvider::contains(&stash));
|
||||
assert!(!T::VoterList::contains(&stash));
|
||||
}
|
||||
|
||||
force_apply_min_commission {
|
||||
|
||||
@@ -780,7 +780,8 @@ enum Releases {
|
||||
V5_0_0, // blockable validators.
|
||||
V6_0_0, // removal of all storage associated with offchain phragmen.
|
||||
V7_0_0, // keep track of number of nominators / validators in map
|
||||
V8_0_0, // populate `SortedListProvider`.
|
||||
V8_0_0, // populate `VoterList`.
|
||||
V9_0_0, // inject validators into `VoterList` as well.
|
||||
}
|
||||
|
||||
impl Default for Releases {
|
||||
|
||||
@@ -17,13 +17,83 @@
|
||||
//! Storage migrations for the Staking pallet.
|
||||
|
||||
use super::*;
|
||||
use frame_election_provider_support::SortedListProvider;
|
||||
use frame_support::traits::OnRuntimeUpgrade;
|
||||
|
||||
pub mod v9 {
|
||||
use super::*;
|
||||
|
||||
/// Migration implementation that injects all validators into sorted list.
|
||||
///
|
||||
/// This is only useful for chains that started their `VoterList` just based on nominators.
|
||||
pub struct InjectValidatorsIntoVoterList<T>(sp_std::marker::PhantomData<T>);
|
||||
impl<T: Config> OnRuntimeUpgrade for InjectValidatorsIntoVoterList<T> {
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
if StorageVersion::<T>::get() == Releases::V8_0_0 {
|
||||
let prev_count = T::VoterList::count();
|
||||
let weight_of_cached = Pallet::<T>::weight_of_fn();
|
||||
for (v, _) in Validators::<T>::iter() {
|
||||
let weight = weight_of_cached(&v);
|
||||
let _ = T::VoterList::on_insert(v.clone(), weight).map_err(|err| {
|
||||
log!(warn, "failed to insert {:?} into VoterList: {:?}", v, err)
|
||||
});
|
||||
}
|
||||
|
||||
log!(
|
||||
info,
|
||||
"injected a total of {} new voters, prev count: {} next count: {}, updating to version 9",
|
||||
Validators::<T>::count(),
|
||||
prev_count,
|
||||
T::VoterList::count(),
|
||||
);
|
||||
|
||||
StorageVersion::<T>::put(crate::Releases::V9_0_0);
|
||||
T::BlockWeights::get().max_block
|
||||
} else {
|
||||
log!(
|
||||
warn,
|
||||
"InjectValidatorsIntoVoterList being executed on the wrong storage \
|
||||
version, expected Releases::V8_0_0"
|
||||
);
|
||||
T::DbWeight::get().reads(1)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<(), &'static str> {
|
||||
use frame_support::traits::OnRuntimeUpgradeHelpersExt;
|
||||
frame_support::ensure!(
|
||||
StorageVersion::<T>::get() == crate::Releases::V8_0_0,
|
||||
"must upgrade linearly"
|
||||
);
|
||||
|
||||
let prev_count = T::VoterList::count();
|
||||
Self::set_temp_storage(prev_count, "prev");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade() -> Result<(), &'static str> {
|
||||
use frame_support::traits::OnRuntimeUpgradeHelpersExt;
|
||||
let post_count = T::VoterList::count();
|
||||
let prev_count = Self::get_temp_storage::<u32>("prev").unwrap();
|
||||
let validators = Validators::<T>::count();
|
||||
assert!(post_count == prev_count + validators);
|
||||
|
||||
frame_support::ensure!(
|
||||
StorageVersion::<T>::get() == crate::Releases::V9_0_0,
|
||||
"must upgrade "
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod v8 {
|
||||
use crate::{Config, Nominators, Pallet, StorageVersion, Weight};
|
||||
use frame_election_provider_support::SortedListProvider;
|
||||
use frame_support::traits::Get;
|
||||
|
||||
use crate::{Config, Nominators, Pallet, StorageVersion, Weight};
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
pub fn pre_migrate<T: Config>() -> Result<(), &'static str> {
|
||||
frame_support::ensure!(
|
||||
@@ -35,16 +105,16 @@ pub mod v8 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Migration to sorted [`SortedListProvider`].
|
||||
/// Migration to sorted `VoterList`.
|
||||
pub fn migrate<T: Config>() -> Weight {
|
||||
if StorageVersion::<T>::get() == crate::Releases::V7_0_0 {
|
||||
crate::log!(info, "migrating staking to Releases::V8_0_0");
|
||||
|
||||
let migrated = T::SortedListProvider::unsafe_regenerate(
|
||||
let migrated = T::VoterList::unsafe_regenerate(
|
||||
Nominators::<T>::iter().map(|(id, _)| id),
|
||||
Pallet::<T>::weight_of_fn(),
|
||||
);
|
||||
debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(()));
|
||||
debug_assert_eq!(T::VoterList::sanity_check(), Ok(()));
|
||||
|
||||
StorageVersion::<T>::put(crate::Releases::V8_0_0);
|
||||
crate::log!(
|
||||
@@ -61,8 +131,7 @@ pub mod v8 {
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
pub fn post_migrate<T: Config>() -> Result<(), &'static str> {
|
||||
T::SortedListProvider::sanity_check()
|
||||
.map_err(|_| "SortedListProvider is not in a sane state.")?;
|
||||
T::VoterList::sanity_check().map_err(|_| "VoterList is not in a sane state.")?;
|
||||
crate::log!(info, "👜 staking bags-list migration passes POST migrate checks ✅",);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -270,8 +270,8 @@ impl crate::pallet::pallet::Config for Test {
|
||||
type OffendingValidatorsThreshold = OffendingValidatorsThreshold;
|
||||
type ElectionProvider = onchain::OnChainSequentialPhragmen<Self>;
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
// NOTE: consider a macro and use `UseNominatorsMap<Self>` as well.
|
||||
type SortedListProvider = BagsList;
|
||||
// NOTE: consider a macro and use `UseNominatorsAndValidatorsMap<Self>` as well.
|
||||
type VoterList = BagsList;
|
||||
type MaxUnlockingChunks = ConstU32<32>;
|
||||
type BenchmarkingConfig = TestBenchmarkingConfig;
|
||||
type WeightInfo = ();
|
||||
@@ -541,9 +541,9 @@ fn check_count() {
|
||||
assert_eq!(nominator_count, Nominators::<Test>::count());
|
||||
assert_eq!(validator_count, Validators::<Test>::count());
|
||||
|
||||
// the voters that the `SortedListProvider` list is storing for us.
|
||||
let external_voters = <Test as Config>::SortedListProvider::count();
|
||||
assert_eq!(external_voters, nominator_count);
|
||||
// the voters that the `VoterList` list is storing for us.
|
||||
let external_voters = <Test as Config>::VoterList::count();
|
||||
assert_eq!(external_voters, nominator_count + validator_count);
|
||||
}
|
||||
|
||||
fn check_ledgers() {
|
||||
|
||||
@@ -49,6 +49,14 @@ use crate::{
|
||||
|
||||
use super::{pallet::*, STAKING_ID};
|
||||
|
||||
/// The maximum number of iterations that we do whilst iterating over `T::VoterList` in
|
||||
/// `get_npos_voters`.
|
||||
///
|
||||
/// In most cases, if we want n items, we iterate exactly n times. In rare cases, if a voter is
|
||||
/// invalid (for any reason) the iteration continues. With this constant, we iterate at most 2 * n
|
||||
/// times and then give up.
|
||||
const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2;
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// The total balance that can be slashed from a stash account as of right now.
|
||||
pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf<T> {
|
||||
@@ -649,90 +657,77 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
/// Get all of the voters that are eligible for the npos election.
|
||||
///
|
||||
/// `maybe_max_len` can imposes a cap on the number of voters returned; First all the validator
|
||||
/// are included in no particular order, then remainder is taken from the nominators, as
|
||||
/// returned by [`Config::SortedListProvider`].
|
||||
///
|
||||
/// This will use nominators, and all the validators will inject a self vote.
|
||||
/// `maybe_max_len` can imposes a cap on the number of voters returned;
|
||||
///
|
||||
/// This function is self-weighing as [`DispatchClass::Mandatory`].
|
||||
///
|
||||
/// ### Slashing
|
||||
///
|
||||
/// All nominations that have been submitted before the last non-zero slash of the validator are
|
||||
/// auto-chilled, but still count towards the limit imposed by `maybe_max_len`.
|
||||
/// All votes that have been submitted before the last non-zero slash of the corresponding
|
||||
/// target are *auto-chilled*, but still count towards the limit imposed by `maybe_max_len`.
|
||||
pub fn get_npos_voters(maybe_max_len: Option<usize>) -> Vec<VoterOf<Self>> {
|
||||
let max_allowed_len = {
|
||||
let nominator_count = Nominators::<T>::count() as usize;
|
||||
let validator_count = Validators::<T>::count() as usize;
|
||||
let all_voter_count = validator_count.saturating_add(nominator_count);
|
||||
let all_voter_count = T::VoterList::count() as usize;
|
||||
maybe_max_len.unwrap_or(all_voter_count).min(all_voter_count)
|
||||
};
|
||||
|
||||
let mut all_voters = Vec::<_>::with_capacity(max_allowed_len);
|
||||
|
||||
// first, grab all validators in no particular order, capped by the maximum allowed length.
|
||||
let mut validators_taken = 0u32;
|
||||
for (validator, _) in <Validators<T>>::iter().take(max_allowed_len) {
|
||||
// Append self vote.
|
||||
let self_vote = (
|
||||
validator.clone(),
|
||||
Self::weight_of(&validator),
|
||||
vec![validator.clone()]
|
||||
.try_into()
|
||||
.expect("`MaxVotesPerVoter` must be greater than or equal to 1"),
|
||||
);
|
||||
all_voters.push(self_vote);
|
||||
validators_taken.saturating_inc();
|
||||
}
|
||||
|
||||
// .. and grab whatever we have left from nominators.
|
||||
let nominators_quota = (max_allowed_len as u32).saturating_sub(validators_taken);
|
||||
// cache a few things.
|
||||
let weight_of = Self::weight_of_fn();
|
||||
let slashing_spans = <SlashingSpans<T>>::iter().collect::<BTreeMap<_, _>>();
|
||||
|
||||
// track the count of nominators added to `all_voters
|
||||
let mut voters_seen = 0u32;
|
||||
let mut validators_taken = 0u32;
|
||||
let mut nominators_taken = 0u32;
|
||||
// track every nominator iterated over, but not necessarily added to `all_voters`
|
||||
let mut nominators_seen = 0u32;
|
||||
|
||||
// cache the total-issuance once in this function
|
||||
let weight_of = Self::weight_of_fn();
|
||||
|
||||
let mut nominators_iter = T::SortedListProvider::iter();
|
||||
while nominators_taken < nominators_quota && nominators_seen < nominators_quota * 2 {
|
||||
let nominator = match nominators_iter.next() {
|
||||
Some(nominator) => {
|
||||
nominators_seen.saturating_inc();
|
||||
nominator
|
||||
let mut sorted_voters = T::VoterList::iter();
|
||||
while all_voters.len() < max_allowed_len &&
|
||||
voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * max_allowed_len as u32)
|
||||
{
|
||||
let voter = match sorted_voters.next() {
|
||||
Some(voter) => {
|
||||
voters_seen.saturating_inc();
|
||||
voter
|
||||
},
|
||||
None => break,
|
||||
};
|
||||
|
||||
if let Some(Nominations { submitted_in, mut targets, suppressed: _ }) =
|
||||
<Nominators<T>>::get(&nominator)
|
||||
<Nominators<T>>::get(&voter)
|
||||
{
|
||||
log!(
|
||||
trace,
|
||||
"fetched nominator {:?} with weight {:?}",
|
||||
nominator,
|
||||
weight_of(&nominator)
|
||||
);
|
||||
// if this voter is a nominator:
|
||||
targets.retain(|stash| {
|
||||
slashing_spans
|
||||
.get(stash)
|
||||
.map_or(true, |spans| submitted_in >= spans.last_nonzero_slash())
|
||||
});
|
||||
if !targets.len().is_zero() {
|
||||
all_voters.push((nominator.clone(), weight_of(&nominator), targets));
|
||||
all_voters.push((voter.clone(), weight_of(&voter), targets));
|
||||
nominators_taken.saturating_inc();
|
||||
}
|
||||
} else if Validators::<T>::contains_key(&voter) {
|
||||
// if this voter is a validator:
|
||||
let self_vote = (
|
||||
voter.clone(),
|
||||
weight_of(&voter),
|
||||
vec![voter.clone()]
|
||||
.try_into()
|
||||
.expect("`MaxVotesPerVoter` must be greater than or equal to 1"),
|
||||
);
|
||||
all_voters.push(self_vote);
|
||||
validators_taken.saturating_inc();
|
||||
} else {
|
||||
// this can only happen if: 1. there a pretty bad bug in the bags-list (or whatever
|
||||
// is the sorted list) logic and the state of the two pallets is no longer
|
||||
// compatible, or because the nominators is not decodable since they have more
|
||||
// nomination than `T::MaxNominations`. This can rarely happen, and is not really an
|
||||
// emergency or bug if it does.
|
||||
log!(warn, "DEFENSIVE: invalid item in `SortedListProvider`: {:?}, this nominator probably has too many nominations now", nominator)
|
||||
// this can only happen if: 1. there a bug in the bags-list (or whatever is the
|
||||
// sorted list) logic and the state of the two pallets is no longer compatible, or
|
||||
// because the nominators is not decodable since they have more nomination than
|
||||
// `T::MaxNominations`. The latter can rarely happen, and is not really an emergency
|
||||
// or bug if it does.
|
||||
log!(
|
||||
warn,
|
||||
"DEFENSIVE: invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now",
|
||||
voter
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -752,6 +747,7 @@ impl<T: Config> Pallet<T> {
|
||||
validators_taken,
|
||||
nominators_taken
|
||||
);
|
||||
|
||||
all_voters
|
||||
}
|
||||
|
||||
@@ -773,7 +769,7 @@ impl<T: Config> Pallet<T> {
|
||||
}
|
||||
|
||||
/// This function will add a nominator to the `Nominators` storage map,
|
||||
/// and [`SortedListProvider`].
|
||||
/// and `VoterList`.
|
||||
///
|
||||
/// If the nominator already exists, their nominations will be updated.
|
||||
///
|
||||
@@ -782,18 +778,21 @@ impl<T: Config> Pallet<T> {
|
||||
/// wrong.
|
||||
pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations<T>) {
|
||||
if !Nominators::<T>::contains_key(who) {
|
||||
// maybe update sorted list. Error checking is defensive-only - this should never fail.
|
||||
let _ = T::SortedListProvider::on_insert(who.clone(), Self::weight_of(who))
|
||||
// maybe update sorted list.
|
||||
let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who))
|
||||
.defensive_unwrap_or_default();
|
||||
|
||||
debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(()));
|
||||
}
|
||||
|
||||
Nominators::<T>::insert(who, nominations);
|
||||
|
||||
debug_assert_eq!(
|
||||
Nominators::<T>::count() + Validators::<T>::count(),
|
||||
T::VoterList::count()
|
||||
);
|
||||
debug_assert_eq!(T::VoterList::sanity_check(), Ok(()));
|
||||
}
|
||||
|
||||
/// This function will remove a nominator from the `Nominators` storage map,
|
||||
/// and [`SortedListProvider`].
|
||||
/// and `VoterList`.
|
||||
///
|
||||
/// Returns true if `who` was removed from `Nominators`, otherwise false.
|
||||
///
|
||||
@@ -801,15 +800,21 @@ impl<T: Config> Pallet<T> {
|
||||
/// `Nominators` or `VoterList` outside of this function is almost certainly
|
||||
/// wrong.
|
||||
pub fn do_remove_nominator(who: &T::AccountId) -> bool {
|
||||
if Nominators::<T>::contains_key(who) {
|
||||
let outcome = if Nominators::<T>::contains_key(who) {
|
||||
Nominators::<T>::remove(who);
|
||||
T::SortedListProvider::on_remove(who);
|
||||
debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(()));
|
||||
debug_assert_eq!(Nominators::<T>::count(), T::SortedListProvider::count());
|
||||
T::VoterList::on_remove(who);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
debug_assert_eq!(T::VoterList::sanity_check(), Ok(()));
|
||||
debug_assert_eq!(
|
||||
Nominators::<T>::count() + Validators::<T>::count(),
|
||||
T::VoterList::count()
|
||||
);
|
||||
|
||||
outcome
|
||||
}
|
||||
|
||||
/// This function will add a validator to the `Validators` storage map.
|
||||
@@ -820,7 +825,18 @@ impl<T: Config> Pallet<T> {
|
||||
/// `Validators` or `VoterList` outside of this function is almost certainly
|
||||
/// wrong.
|
||||
pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) {
|
||||
if !Validators::<T>::contains_key(who) {
|
||||
// maybe update sorted list.
|
||||
let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who))
|
||||
.defensive_unwrap_or_default();
|
||||
}
|
||||
Validators::<T>::insert(who, prefs);
|
||||
|
||||
debug_assert_eq!(
|
||||
Nominators::<T>::count() + Validators::<T>::count(),
|
||||
T::VoterList::count()
|
||||
);
|
||||
debug_assert_eq!(T::VoterList::sanity_check(), Ok(()));
|
||||
}
|
||||
|
||||
/// This function will remove a validator from the `Validators` storage map.
|
||||
@@ -831,12 +847,21 @@ impl<T: Config> Pallet<T> {
|
||||
/// `Validators` or `VoterList` outside of this function is almost certainly
|
||||
/// wrong.
|
||||
pub fn do_remove_validator(who: &T::AccountId) -> bool {
|
||||
if Validators::<T>::contains_key(who) {
|
||||
let outcome = if Validators::<T>::contains_key(who) {
|
||||
Validators::<T>::remove(who);
|
||||
T::VoterList::on_remove(who);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
debug_assert_eq!(T::VoterList::sanity_check(), Ok(()));
|
||||
debug_assert_eq!(
|
||||
Nominators::<T>::count() + Validators::<T>::count(),
|
||||
T::VoterList::count()
|
||||
);
|
||||
|
||||
outcome
|
||||
}
|
||||
|
||||
/// Register some amount of weight directly with the system pallet.
|
||||
@@ -963,7 +988,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
|
||||
<Validators<T>>::remove_all();
|
||||
<Nominators<T>>::remove_all();
|
||||
|
||||
T::SortedListProvider::unsafe_clear();
|
||||
T::VoterList::unsafe_clear();
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
@@ -1278,20 +1303,24 @@ impl<T: Config> ScoreProvider<T::AccountId> for Pallet<T> {
|
||||
/// A simple voter list implementation that does not require any additional pallets. Note, this
|
||||
/// does not provided nominators in sorted ordered. If you desire nominators in a sorted order take
|
||||
/// a look at [`pallet-bags-list].
|
||||
pub struct UseNominatorsMap<T>(sp_std::marker::PhantomData<T>);
|
||||
impl<T: Config> SortedListProvider<T::AccountId> for UseNominatorsMap<T> {
|
||||
pub struct UseNominatorsAndValidatorsMap<T>(sp_std::marker::PhantomData<T>);
|
||||
impl<T: Config> SortedListProvider<T::AccountId> for UseNominatorsAndValidatorsMap<T> {
|
||||
type Error = ();
|
||||
type Score = VoteWeight;
|
||||
|
||||
/// Returns iterator over voter list, which can have `take` called on it.
|
||||
fn iter() -> Box<dyn Iterator<Item = T::AccountId>> {
|
||||
Box::new(Nominators::<T>::iter().map(|(n, _)| n))
|
||||
Box::new(
|
||||
Validators::<T>::iter()
|
||||
.map(|(v, _)| v)
|
||||
.chain(Nominators::<T>::iter().map(|(n, _)| n)),
|
||||
)
|
||||
}
|
||||
fn count() -> u32 {
|
||||
Nominators::<T>::count()
|
||||
Nominators::<T>::count().saturating_add(Validators::<T>::count())
|
||||
}
|
||||
fn contains(id: &T::AccountId) -> bool {
|
||||
Nominators::<T>::contains_key(id)
|
||||
Nominators::<T>::contains_key(id) || Validators::<T>::contains_key(id)
|
||||
}
|
||||
fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> {
|
||||
// nothing to do on insert.
|
||||
@@ -1318,5 +1347,6 @@ impl<T: Config> SortedListProvider<T::AccountId> for UseNominatorsMap<T> {
|
||||
// NOTE: Caller must ensure this doesn't lead to too many storage accesses. This is a
|
||||
// condition of SortedListProvider::unsafe_clear.
|
||||
Nominators::<T>::remove_all();
|
||||
Validators::<T>::remove_all();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
//! Staking FRAME Pallet.
|
||||
|
||||
use frame_election_provider_support::SortedListProvider;
|
||||
use frame_election_provider_support::{SortedListProvider, VoteWeight};
|
||||
use frame_support::{
|
||||
dispatch::Codec,
|
||||
pallet_prelude::*,
|
||||
@@ -163,13 +163,12 @@ pub mod pallet {
|
||||
/// After the threshold is reached a new era will be forced.
|
||||
type OffendingValidatorsThreshold: Get<Perbill>;
|
||||
|
||||
/// Something that can provide a sorted list of voters in a somewhat sorted way. The
|
||||
/// original use case for this was designed with `pallet_bags_list::Pallet` in mind. If
|
||||
/// the bags-list is not desired, [`impls::UseNominatorsMap`] is likely the desired option.
|
||||
type SortedListProvider: SortedListProvider<
|
||||
Self::AccountId,
|
||||
Score = frame_election_provider_support::VoteWeight,
|
||||
>;
|
||||
/// Something that provides a best-effort sorted list of voters aka electing nominators,
|
||||
/// used for NPoS election.
|
||||
///
|
||||
/// The changes to nominators are reported to this. Moreover, each validator's self-vote is
|
||||
/// also reported as one independent vote.
|
||||
type VoterList: SortedListProvider<Self::AccountId, Score = VoteWeight>;
|
||||
|
||||
/// The maximum number of `unlocking` chunks a [`StakingLedger`] can have. Effectively
|
||||
/// determines how many unique eras a staker may be unbonding in.
|
||||
@@ -584,10 +583,10 @@ pub mod pallet {
|
||||
});
|
||||
}
|
||||
|
||||
// all voters are reported to the `SortedListProvider`.
|
||||
// all voters are reported to the `VoterList`.
|
||||
assert_eq!(
|
||||
T::SortedListProvider::count(),
|
||||
Nominators::<T>::count(),
|
||||
T::VoterList::count(),
|
||||
Nominators::<T>::count() + Validators::<T>::count(),
|
||||
"not all genesis stakers were inserted into sorted list provider, something is wrong."
|
||||
);
|
||||
}
|
||||
@@ -837,9 +836,9 @@ pub mod pallet {
|
||||
// NOTE: ledger must be updated prior to calling `Self::weight_of`.
|
||||
Self::update_ledger(&controller, &ledger);
|
||||
// update this staker in the sorted list, if they exist in it.
|
||||
if T::SortedListProvider::contains(&stash) {
|
||||
T::SortedListProvider::on_update(&stash, Self::weight_of(&ledger.stash));
|
||||
debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(()));
|
||||
if T::VoterList::contains(&stash) {
|
||||
T::VoterList::on_update(&stash, Self::weight_of(&ledger.stash));
|
||||
debug_assert_eq!(T::VoterList::sanity_check(), Ok(()));
|
||||
}
|
||||
|
||||
Self::deposit_event(Event::<T>::Bonded(stash.clone(), extra));
|
||||
@@ -920,8 +919,8 @@ pub mod pallet {
|
||||
Self::update_ledger(&controller, &ledger);
|
||||
|
||||
// update this staker in the sorted list, if they exist in it.
|
||||
if T::SortedListProvider::contains(&ledger.stash) {
|
||||
T::SortedListProvider::on_update(&ledger.stash, Self::weight_of(&ledger.stash));
|
||||
if T::VoterList::contains(&ledger.stash) {
|
||||
T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash));
|
||||
}
|
||||
|
||||
Self::deposit_event(Event::<T>::Unbonded(ledger.stash, value));
|
||||
@@ -1403,8 +1402,8 @@ pub mod pallet {
|
||||
|
||||
// NOTE: ledger must be updated prior to calling `Self::weight_of`.
|
||||
Self::update_ledger(&controller, &ledger);
|
||||
if T::SortedListProvider::contains(&ledger.stash) {
|
||||
T::SortedListProvider::on_update(&ledger.stash, Self::weight_of(&ledger.stash));
|
||||
if T::VoterList::contains(&ledger.stash) {
|
||||
T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash));
|
||||
}
|
||||
|
||||
let removed_chunks = 1u32 // for the case where the last iterated chunk is not removed
|
||||
|
||||
@@ -38,11 +38,11 @@ const SEED: u32 = 0;
|
||||
pub fn clear_validators_and_nominators<T: Config>() {
|
||||
Validators::<T>::remove_all();
|
||||
|
||||
// whenever we touch nominators counter we should update `T::SortedListProvider` as well.
|
||||
// whenever we touch nominators counter we should update `T::VoterList` as well.
|
||||
Nominators::<T>::remove_all();
|
||||
|
||||
// NOTE: safe to call outside block production
|
||||
T::SortedListProvider::unsafe_clear();
|
||||
T::VoterList::unsafe_clear();
|
||||
}
|
||||
|
||||
/// Grab a funded user.
|
||||
|
||||
@@ -4113,11 +4113,7 @@ mod election_data_provider {
|
||||
.set_status(41, StakerStatus::Validator)
|
||||
.build_and_execute(|| {
|
||||
// sum of all nominators who'd be voters (1), plus the self-votes (4).
|
||||
assert_eq!(
|
||||
<Test as Config>::SortedListProvider::count() +
|
||||
<Validators<Test>>::iter().count() as u32,
|
||||
5
|
||||
);
|
||||
assert_eq!(<Test as Config>::VoterList::count(), 5);
|
||||
|
||||
// if limits is less..
|
||||
assert_eq!(Staking::electing_voters(Some(1)).unwrap().len(), 1);
|
||||
@@ -4140,43 +4136,43 @@ mod election_data_provider {
|
||||
});
|
||||
}
|
||||
|
||||
// Tests the criteria that in `ElectionDataProvider::voters` function, we try to get at most
|
||||
// `maybe_max_len` voters, and if some of them end up being skipped, we iterate at most `2 *
|
||||
// maybe_max_len`.
|
||||
#[test]
|
||||
fn only_iterates_max_2_times_nominators_quota() {
|
||||
fn only_iterates_max_2_times_max_allowed_len() {
|
||||
ExtBuilder::default()
|
||||
.nominate(true) // add nominator 101, who nominates [11, 21]
|
||||
.nominate(false)
|
||||
// the other nominators only nominate 21
|
||||
.add_staker(61, 60, 2_000, StakerStatus::<AccountId>::Nominator(vec![21]))
|
||||
.add_staker(71, 70, 2_000, StakerStatus::<AccountId>::Nominator(vec![21]))
|
||||
.add_staker(81, 80, 2_000, StakerStatus::<AccountId>::Nominator(vec![21]))
|
||||
.build_and_execute(|| {
|
||||
// given our nominators ordered by stake,
|
||||
// all voters ordered by stake,
|
||||
assert_eq!(
|
||||
<Test as Config>::SortedListProvider::iter().collect::<Vec<_>>(),
|
||||
vec![61, 71, 81, 101]
|
||||
<Test as Config>::VoterList::iter().collect::<Vec<_>>(),
|
||||
vec![61, 71, 81, 11, 21, 31]
|
||||
);
|
||||
|
||||
// and total voters
|
||||
assert_eq!(
|
||||
<Test as Config>::SortedListProvider::count() +
|
||||
<Validators<Test>>::iter().count() as u32,
|
||||
7
|
||||
);
|
||||
|
||||
// roll to session 5
|
||||
run_to_block(25);
|
||||
|
||||
// slash 21, the only validator nominated by our first 3 nominators
|
||||
add_slash(&21);
|
||||
|
||||
// we take 4 voters: 2 validators and 2 nominators (so nominators quota = 2)
|
||||
// we want 2 voters now, and in maximum we allow 4 iterations. This is what happens:
|
||||
// 61 is pruned;
|
||||
// 71 is pruned;
|
||||
// 81 is pruned;
|
||||
// 11 is taken;
|
||||
// we finish since the 2x limit is reached.
|
||||
assert_eq!(
|
||||
Staking::electing_voters(Some(3))
|
||||
Staking::electing_voters(Some(2))
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|(stash, _, _)| stash)
|
||||
.copied()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![31, 11], // 2 validators, but no nominators because we hit the quota
|
||||
vec![11],
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -4189,45 +4185,17 @@ mod election_data_provider {
|
||||
#[test]
|
||||
fn get_max_len_voters_even_if_some_nominators_are_slashed() {
|
||||
ExtBuilder::default()
|
||||
.nominate(true) // add nominator 101, who nominates [11, 21]
|
||||
.nominate(false)
|
||||
.add_staker(61, 60, 20, StakerStatus::<AccountId>::Nominator(vec![21]))
|
||||
// 61 only nominates validator 21 ^^
|
||||
.add_staker(71, 70, 10, StakerStatus::<AccountId>::Nominator(vec![11, 21]))
|
||||
.add_staker(81, 80, 10, StakerStatus::<AccountId>::Nominator(vec![11, 21]))
|
||||
.build_and_execute(|| {
|
||||
// given our nominators ordered by stake,
|
||||
// given our voters ordered by stake,
|
||||
assert_eq!(
|
||||
<Test as Config>::SortedListProvider::iter().collect::<Vec<_>>(),
|
||||
vec![101, 61, 71]
|
||||
<Test as Config>::VoterList::iter().collect::<Vec<_>>(),
|
||||
vec![11, 21, 31, 61, 71, 81]
|
||||
);
|
||||
|
||||
// and total voters
|
||||
assert_eq!(
|
||||
<Test as Config>::SortedListProvider::count() +
|
||||
<Validators<Test>>::iter().count() as u32,
|
||||
6
|
||||
);
|
||||
|
||||
// we take 5 voters
|
||||
assert_eq!(
|
||||
Staking::electing_voters(Some(5))
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|(stash, _, _)| stash)
|
||||
.copied()
|
||||
.collect::<Vec<_>>(),
|
||||
// then
|
||||
vec![
|
||||
31, 21, 11, // 3 nominators
|
||||
101, 61 // 2 validators, and 71 is excluded
|
||||
],
|
||||
);
|
||||
|
||||
// roll to session 5
|
||||
run_to_block(25);
|
||||
|
||||
// slash 21, the only validator nominated by 61
|
||||
add_slash(&21);
|
||||
|
||||
// we take 4 voters
|
||||
assert_eq!(
|
||||
Staking::electing_voters(Some(4))
|
||||
@@ -4236,10 +4204,24 @@ mod election_data_provider {
|
||||
.map(|(stash, _, _)| stash)
|
||||
.copied()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
31, 11, // 2 validators (21 was slashed)
|
||||
101, 71 // 2 nominators, excluding 61
|
||||
],
|
||||
vec![11, 21, 31, 61],
|
||||
);
|
||||
|
||||
// roll to session 5
|
||||
run_to_block(25);
|
||||
|
||||
// slash 21, the only validator nominated by 61.
|
||||
add_slash(&21);
|
||||
|
||||
// we take 4 voters; 71 and 81 are replacing the ejected ones.
|
||||
assert_eq!(
|
||||
Staking::electing_voters(Some(4))
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|(stash, _, _)| stash)
|
||||
.copied()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![11, 31, 71, 81],
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -4755,19 +4737,45 @@ mod sorted_list_provider {
|
||||
fn re_nominate_does_not_change_counters_or_list() {
|
||||
ExtBuilder::default().nominate(true).build_and_execute(|| {
|
||||
// given
|
||||
let pre_insert_nominator_count = Nominators::<Test>::iter().count() as u32;
|
||||
assert_eq!(<Test as Config>::SortedListProvider::count(), pre_insert_nominator_count);
|
||||
assert!(Nominators::<Test>::contains_key(101));
|
||||
assert_eq!(<Test as Config>::SortedListProvider::iter().collect::<Vec<_>>(), vec![101]);
|
||||
let pre_insert_voter_count =
|
||||
(Nominators::<Test>::count() + Validators::<Test>::count()) as u32;
|
||||
assert_eq!(<Test as Config>::VoterList::count(), pre_insert_voter_count);
|
||||
|
||||
assert_eq!(
|
||||
<Test as Config>::VoterList::iter().collect::<Vec<_>>(),
|
||||
vec![11, 21, 31, 101]
|
||||
);
|
||||
|
||||
// when account 101 renominates
|
||||
assert_ok!(Staking::nominate(Origin::signed(100), vec![41]));
|
||||
|
||||
// then counts don't change
|
||||
assert_eq!(<Test as Config>::SortedListProvider::count(), pre_insert_nominator_count);
|
||||
assert_eq!(Nominators::<Test>::iter().count() as u32, pre_insert_nominator_count);
|
||||
assert_eq!(<Test as Config>::VoterList::count(), pre_insert_voter_count);
|
||||
// and the list is the same
|
||||
assert_eq!(<Test as Config>::SortedListProvider::iter().collect::<Vec<_>>(), vec![101]);
|
||||
assert_eq!(
|
||||
<Test as Config>::VoterList::iter().collect::<Vec<_>>(),
|
||||
vec![11, 21, 31, 101]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn re_validate_does_not_change_counters_or_list() {
|
||||
ExtBuilder::default().nominate(false).build_and_execute(|| {
|
||||
// given
|
||||
let pre_insert_voter_count =
|
||||
(Nominators::<Test>::count() + Validators::<Test>::count()) as u32;
|
||||
assert_eq!(<Test as Config>::VoterList::count(), pre_insert_voter_count);
|
||||
|
||||
assert_eq!(<Test as Config>::VoterList::iter().collect::<Vec<_>>(), vec![11, 21, 31]);
|
||||
|
||||
// when account 11 re-validates
|
||||
assert_ok!(Staking::validate(Origin::signed(10), Default::default()));
|
||||
|
||||
// then counts don't change
|
||||
assert_eq!(<Test as Config>::VoterList::count(), pre_insert_voter_count);
|
||||
// and the list is the same
|
||||
assert_eq!(<Test as Config>::VoterList::iter().collect::<Vec<_>>(), vec![11, 21, 31]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user