mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 10:31:03 +00:00
[NPoS] Implements dynamic number of nominators (#12970)
* Implements dynamic nominations per nominator * Adds SnapshotBounds and ElectionSizeTracker * Changes the ElectionDataProvider interface to receive ElectionBounds as input * Implements get_npos_voters with ElectionBounds * Implements get_npos_targets with ElectionBounds * Adds comments * tests * Truncates nomninations that exceed nominations quota; Old tests passing * Uses DataProviderBounds and ElectionBounds (to continue) * Finishes conversions - tests passing * Refactor staking in babe mocks * Replaces MaxElectableTargets and MaxElectingVoters with ElectionBounds; Adds more tests * Fixes nits; node compiling * bechmarks * removes nomination_quota extrinsic to request the nomination quota * Lazy quota check, ie. at nominate time only * remove non-working test (for now) * tests lazy nominations quota when quota is lower than current number of nominated targets * Adds runtime API and custom RPC call for clients to query the nominations quota for a given balance * removes old rpc * Cosmetic touches * All mocks working * Fixes benchmarking mocks * nits * more tests * renames trait methods * nit * ".git/.scripts/commands/fmt/fmt.sh" * Fix V2 PoV benchmarking (#13485) * Bump default 'additional_trie_layers' to two The default here only works for extremely small runtimes, which have no more than 16 storage prefices. This is changed to a "sane" default of 2, which is save for runtimes with up to 4096 storage prefices (eg StorageValue). Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Update tests and test weights Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix PoV weights Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_balances * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_message_queue * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_glutton * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_glutton * Fix sanity check >0 would also do as a check, but let's try this. Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> --------- Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: command-bot <> * Move BEEFY code to consensus (#13484) * Move beefy primitives to consensus dir * Move beefy gadget to client consensus folder * Rename beefy crates * chore: move genesis block builder to chain-spec crate. (#13427) * chore: move genesis block builder to block builder crate. * add missing file * chore: move genesis block builder to sc-chain-spec * Update client/chain-spec/src/genesis.rs Co-authored-by: Bastian Köcher <git@kchr.de> * Update test-utils/runtime/src/genesismap.rs Co-authored-by: Bastian Köcher <git@kchr.de> * Update test-utils/runtime/client/src/lib.rs * fix warnings * fix warnings --------- Co-authored-by: Bastian Köcher <git@kchr.de> * Speed up storage iteration from within the runtime (#13479) * Speed up storage iteration from within the runtime * Move the cached iterator into an `Option` * Use `RefCell` in no_std * Simplify the code slightly * Use `Option::replace` * Update doc comment for `next_storage_key_slow` * Make unbounded channels size warning exact (part 1) (#13490) * Replace `futures-channel` with `async-channel` in `out_events` * Apply suggestions from code review Co-authored-by: Koute <koute@users.noreply.github.com> * Also print the backtrace of `send()` call * Switch from `backtrace` crate to `std::backtrace` * Remove outdated `backtrace` dependency * Remove `backtrace` from `Cargo.lock` --------- Co-authored-by: Koute <koute@users.noreply.github.com> * Removal of Prometheus alerting rules deployment in cloud-infra (#13499) * sp-consensus: remove unused error variants (#13495) * Expose `ChargedAmount` (#13488) * Expose `ChargedAmount` * Fix imports * sc-consensus-beefy: fix metrics: use correct names (#13494) Signed-off-by: acatangiu <adrian@parity.io> * clippy fix * removes NominationsQuotaExceeded event * Update frame/staking/src/lib.rs Co-authored-by: Ross Bulat <ross@parity.io> * adds back the npos_max_iter * remove duplicate imports added after merge * fmt * Adds comment in public struct; Refactors CountBound and SizeCount to struct * addresses various pr comments * PR comment reviews * Fixes on-chain election bounds and related code * EPM checks the size of the voter list returned by the data provider * cosmetic changes * updates e2e tests mock * Adds more tests for size tracker and refactors code * Adds back only_iterates_max_2_times_max_allowed_len test * Refactor * removes unecessary dependency * empty commit -- restart all stuck CI jobs * restarts ci jobs * Renames ElectionBounds -> Bounds in benchmarking mocks et al * updates mocks * Update frame/election-provider-support/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update frame/staking/src/pallet/impls.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update frame/election-provider-support/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update frame/staking/src/tests.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * more checks in api_nominations_quota in tests * Improves docs * fixes e2e tests * Uses size_hint rather than mem::size_of in size tracker; Refactor size tracker to own module * nits from reviews * Refactors bounds to own module; improves docs * More tests and docs * fixes docs * Fixes benchmarks * Fixes rust docs * fixes bags-list remote-ext-tests * Simplify bound checks in create_snapshot_external * Adds target size check in get_npos_targets * ".git/.scripts/commands/fmt/fmt.sh" * restart ci * rust doc fixes and cosmetic nits * rollback upgrade on parity-scale-codec version (unecessary) * reset cargo lock, no need to update it --------- Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Signed-off-by: acatangiu <adrian@parity.io> Co-authored-by: command-bot <> Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: Davide Galassi <davxy@datawok.net> Co-authored-by: yjh <yjh465402634@gmail.com> Co-authored-by: Bastian Köcher <git@kchr.de> Co-authored-by: Koute <koute@users.noreply.github.com> Co-authored-by: Dmitry Markin <dmitry@markin.tech> Co-authored-by: Anthony Lazam <lazam@users.noreply.github.com> Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> Co-authored-by: Piotr Mikołajczyk <piomiko41@gmail.com> Co-authored-by: Adrian Catangiu <adrian@parity.io> Co-authored-by: Ross Bulat <ross@parity.io> Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
This commit is contained in:
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
use codec::{Decode, Encode, MaxEncodedLen};
|
use codec::{Decode, Encode, MaxEncodedLen};
|
||||||
use frame_election_provider_support::{
|
use frame_election_provider_support::{
|
||||||
|
bounds::{ElectionBounds, ElectionBoundsBuilder},
|
||||||
onchain, BalancingConfig, ElectionDataProvider, SequentialPhragmen, VoteWeight,
|
onchain, BalancingConfig, ElectionDataProvider, SequentialPhragmen, VoteWeight,
|
||||||
};
|
};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
@@ -562,6 +563,9 @@ parameter_types! {
|
|||||||
pub HistoryDepth: u32 = 84;
|
pub HistoryDepth: u32 = 84;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Upper limit on the number of NPOS nominations.
|
||||||
|
const MAX_QUOTA_NOMINATIONS: u32 = 16;
|
||||||
|
|
||||||
pub struct StakingBenchmarkingConfig;
|
pub struct StakingBenchmarkingConfig;
|
||||||
impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig {
|
impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig {
|
||||||
type MaxNominators = ConstU32<1000>;
|
type MaxNominators = ConstU32<1000>;
|
||||||
@@ -569,7 +573,6 @@ impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl pallet_staking::Config for Runtime {
|
impl pallet_staking::Config for Runtime {
|
||||||
type MaxNominations = MaxNominations;
|
|
||||||
type Currency = Balances;
|
type Currency = Balances;
|
||||||
type CurrencyBalance = Balance;
|
type CurrencyBalance = Balance;
|
||||||
type UnixTime = Timestamp;
|
type UnixTime = Timestamp;
|
||||||
@@ -594,6 +597,7 @@ impl pallet_staking::Config for Runtime {
|
|||||||
type ElectionProvider = ElectionProviderMultiPhase;
|
type ElectionProvider = ElectionProviderMultiPhase;
|
||||||
type GenesisElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
|
type GenesisElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
|
||||||
type VoterList = VoterList;
|
type VoterList = VoterList;
|
||||||
|
type NominationsQuota = pallet_staking::FixedNominationsQuota<MAX_QUOTA_NOMINATIONS>;
|
||||||
// This a placeholder, to be introduced in the next PR as an instance of bags-list
|
// This a placeholder, to be introduced in the next PR as an instance of bags-list
|
||||||
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
||||||
type MaxUnlockingChunks = ConstU32<32>;
|
type MaxUnlockingChunks = ConstU32<32>;
|
||||||
@@ -647,17 +651,20 @@ frame_election_provider_support::generate_solution_type!(
|
|||||||
VoterIndex = u32,
|
VoterIndex = u32,
|
||||||
TargetIndex = u16,
|
TargetIndex = u16,
|
||||||
Accuracy = sp_runtime::PerU16,
|
Accuracy = sp_runtime::PerU16,
|
||||||
MaxVoters = MaxElectingVoters,
|
MaxVoters = MaxElectingVotersSolution,
|
||||||
>(16)
|
>(16)
|
||||||
);
|
);
|
||||||
|
|
||||||
parameter_types! {
|
parameter_types! {
|
||||||
|
// Note: the EPM in this runtime runs the election on-chain. The election bounds must be
|
||||||
|
// carefully set so that an election round fits in one block.
|
||||||
|
pub ElectionBoundsMultiPhase: ElectionBounds = ElectionBoundsBuilder::default()
|
||||||
|
.voters_count(10_000.into()).targets_count(1_500.into()).build();
|
||||||
|
pub ElectionBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::default()
|
||||||
|
.voters_count(5_000.into()).targets_count(1_250.into()).build();
|
||||||
|
|
||||||
pub MaxNominations: u32 = <NposSolution16 as frame_election_provider_support::NposSolution>::LIMIT as u32;
|
pub MaxNominations: u32 = <NposSolution16 as frame_election_provider_support::NposSolution>::LIMIT as u32;
|
||||||
pub MaxElectingVoters: u32 = 40_000;
|
pub MaxElectingVotersSolution: u32 = 40_000;
|
||||||
pub MaxElectableTargets: u16 = 10_000;
|
|
||||||
// OnChain values are lower.
|
|
||||||
pub MaxOnChainElectingVoters: u32 = 5000;
|
|
||||||
pub MaxOnChainElectableTargets: u16 = 1250;
|
|
||||||
// The maximum winners that can be elected by the Election pallet which is equivalent to the
|
// The maximum winners that can be elected by the Election pallet which is equivalent to the
|
||||||
// maximum active validators the staking pallet can have.
|
// maximum active validators the staking pallet can have.
|
||||||
pub MaxActiveValidators: u32 = 1000;
|
pub MaxActiveValidators: u32 = 1000;
|
||||||
@@ -712,8 +719,7 @@ impl onchain::Config for OnChainSeqPhragmen {
|
|||||||
type DataProvider = <Runtime as pallet_election_provider_multi_phase::Config>::DataProvider;
|
type DataProvider = <Runtime as pallet_election_provider_multi_phase::Config>::DataProvider;
|
||||||
type WeightInfo = frame_election_provider_support::weights::SubstrateWeight<Runtime>;
|
type WeightInfo = frame_election_provider_support::weights::SubstrateWeight<Runtime>;
|
||||||
type MaxWinners = <Runtime as pallet_election_provider_multi_phase::Config>::MaxWinners;
|
type MaxWinners = <Runtime as pallet_election_provider_multi_phase::Config>::MaxWinners;
|
||||||
type VotersBound = MaxOnChainElectingVoters;
|
type Bounds = ElectionBoundsOnChain;
|
||||||
type TargetsBound = MaxOnChainElectableTargets;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl pallet_election_provider_multi_phase::MinerConfig for Runtime {
|
impl pallet_election_provider_multi_phase::MinerConfig for Runtime {
|
||||||
@@ -761,9 +767,8 @@ impl pallet_election_provider_multi_phase::Config for Runtime {
|
|||||||
type GovernanceFallback = onchain::OnChainExecution<OnChainSeqPhragmen>;
|
type GovernanceFallback = onchain::OnChainExecution<OnChainSeqPhragmen>;
|
||||||
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Self>, OffchainRandomBalancing>;
|
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Self>, OffchainRandomBalancing>;
|
||||||
type ForceOrigin = EnsureRootOrHalfCouncil;
|
type ForceOrigin = EnsureRootOrHalfCouncil;
|
||||||
type MaxElectableTargets = MaxElectableTargets;
|
|
||||||
type MaxWinners = MaxActiveValidators;
|
type MaxWinners = MaxActiveValidators;
|
||||||
type MaxElectingVoters = MaxElectingVoters;
|
type ElectionBounds = ElectionBoundsMultiPhase;
|
||||||
type BenchmarkingConfig = ElectionProviderBenchmarkConfig;
|
type BenchmarkingConfig = ElectionProviderBenchmarkConfig;
|
||||||
type WeightInfo = pallet_election_provider_multi_phase::weights::SubstrateWeight<Self>;
|
type WeightInfo = pallet_election_provider_multi_phase::weights::SubstrateWeight<Self>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,16 @@
|
|||||||
|
|
||||||
use crate::{self as pallet_babe, Config, CurrentSlot};
|
use crate::{self as pallet_babe, Config, CurrentSlot};
|
||||||
use codec::Encode;
|
use codec::Encode;
|
||||||
use frame_election_provider_support::{onchain, SequentialPhragmen};
|
use frame_election_provider_support::{
|
||||||
|
bounds::{ElectionBounds, ElectionBoundsBuilder},
|
||||||
|
onchain, SequentialPhragmen,
|
||||||
|
};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
parameter_types,
|
parameter_types,
|
||||||
traits::{ConstU128, ConstU32, ConstU64, KeyOwnerProofSystem, OnInitialize},
|
traits::{ConstU128, ConstU32, ConstU64, KeyOwnerProofSystem, OnInitialize},
|
||||||
};
|
};
|
||||||
use pallet_session::historical as pallet_session_historical;
|
use pallet_session::historical as pallet_session_historical;
|
||||||
|
use pallet_staking::FixedNominationsQuota;
|
||||||
use sp_consensus_babe::{AuthorityId, AuthorityPair, Randomness, Slot, VrfSignature};
|
use sp_consensus_babe::{AuthorityId, AuthorityPair, Randomness, Slot, VrfSignature};
|
||||||
use sp_core::{
|
use sp_core::{
|
||||||
crypto::{KeyTypeId, Pair, VrfSecret},
|
crypto::{KeyTypeId, Pair, VrfSecret},
|
||||||
@@ -161,6 +165,7 @@ parameter_types! {
|
|||||||
pub const SlashDeferDuration: EraIndex = 0;
|
pub const SlashDeferDuration: EraIndex = 0;
|
||||||
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
|
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
|
||||||
pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(16);
|
pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(16);
|
||||||
|
pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OnChainSeqPhragmen;
|
pub struct OnChainSeqPhragmen;
|
||||||
@@ -170,12 +175,10 @@ impl onchain::Config for OnChainSeqPhragmen {
|
|||||||
type DataProvider = Staking;
|
type DataProvider = Staking;
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
type MaxWinners = ConstU32<100>;
|
type MaxWinners = ConstU32<100>;
|
||||||
type VotersBound = ConstU32<{ u32::MAX }>;
|
type Bounds = ElectionsBounds;
|
||||||
type TargetsBound = ConstU32<{ u32::MAX }>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl pallet_staking::Config for Test {
|
impl pallet_staking::Config for Test {
|
||||||
type MaxNominations = ConstU32<16>;
|
|
||||||
type RewardRemainder = ();
|
type RewardRemainder = ();
|
||||||
type CurrencyToVote = ();
|
type CurrencyToVote = ();
|
||||||
type RuntimeEvent = RuntimeEvent;
|
type RuntimeEvent = RuntimeEvent;
|
||||||
@@ -197,6 +200,7 @@ impl pallet_staking::Config for Test {
|
|||||||
type GenesisElectionProvider = Self::ElectionProvider;
|
type GenesisElectionProvider = Self::ElectionProvider;
|
||||||
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
||||||
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
||||||
|
type NominationsQuota = FixedNominationsQuota<16>;
|
||||||
type MaxUnlockingChunks = ConstU32<32>;
|
type MaxUnlockingChunks = ConstU32<32>;
|
||||||
type HistoryDepth = ConstU32<84>;
|
type HistoryDepth = ConstU32<84>;
|
||||||
type EventListeners = ();
|
type EventListeners = ();
|
||||||
|
|||||||
@@ -16,7 +16,10 @@
|
|||||||
|
|
||||||
//! Test to execute the snapshot using the voter bag.
|
//! Test to execute the snapshot using the voter bag.
|
||||||
|
|
||||||
use frame_election_provider_support::SortedListProvider;
|
use frame_election_provider_support::{
|
||||||
|
bounds::{CountBound, DataProviderBounds},
|
||||||
|
SortedListProvider,
|
||||||
|
};
|
||||||
use frame_support::traits::PalletInfoAccess;
|
use frame_support::traits::PalletInfoAccess;
|
||||||
use remote_externalities::{Builder, Mode, OnlineConfig};
|
use remote_externalities::{Builder, Mode, OnlineConfig};
|
||||||
use sp_runtime::{traits::Block as BlockT, DeserializeOwned};
|
use sp_runtime::{traits::Block as BlockT, DeserializeOwned};
|
||||||
@@ -62,8 +65,13 @@ where
|
|||||||
<Runtime as pallet_staking::Config>::VoterList::count(),
|
<Runtime as pallet_staking::Config>::VoterList::count(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let bounds = match voter_limit {
|
||||||
|
None => DataProviderBounds::default(),
|
||||||
|
Some(v) => DataProviderBounds { count: Some(CountBound(v as u32)), size: None },
|
||||||
|
};
|
||||||
|
|
||||||
let voters =
|
let voters =
|
||||||
<pallet_staking::Pallet<Runtime> as ElectionDataProvider>::electing_voters(voter_limit)
|
<pallet_staking::Pallet<Runtime> as ElectionDataProvider>::electing_voters(bounds)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut voters_nominator_only = voters
|
let mut voters_nominator_only = voters
|
||||||
|
|||||||
@@ -17,7 +17,10 @@
|
|||||||
|
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
|
||||||
use frame_election_provider_support::{onchain, SequentialPhragmen};
|
use frame_election_provider_support::{
|
||||||
|
bounds::{ElectionBounds, ElectionBoundsBuilder},
|
||||||
|
onchain, SequentialPhragmen,
|
||||||
|
};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
construct_runtime, parameter_types,
|
construct_runtime, parameter_types,
|
||||||
sp_io::TestExternalities,
|
sp_io::TestExternalities,
|
||||||
@@ -184,6 +187,7 @@ parameter_types! {
|
|||||||
pub const BondingDuration: EraIndex = 3;
|
pub const BondingDuration: EraIndex = 3;
|
||||||
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
|
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
|
||||||
pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17);
|
pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17);
|
||||||
|
pub static ElectionsBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::default().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OnChainSeqPhragmen;
|
pub struct OnChainSeqPhragmen;
|
||||||
@@ -193,12 +197,10 @@ impl onchain::Config for OnChainSeqPhragmen {
|
|||||||
type DataProvider = Staking;
|
type DataProvider = Staking;
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
type MaxWinners = ConstU32<100>;
|
type MaxWinners = ConstU32<100>;
|
||||||
type VotersBound = ConstU32<{ u32::MAX }>;
|
type Bounds = ElectionsBoundsOnChain;
|
||||||
type TargetsBound = ConstU32<{ u32::MAX }>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl pallet_staking::Config for Test {
|
impl pallet_staking::Config for Test {
|
||||||
type MaxNominations = ConstU32<16>;
|
|
||||||
type RewardRemainder = ();
|
type RewardRemainder = ();
|
||||||
type CurrencyToVote = ();
|
type CurrencyToVote = ();
|
||||||
type RuntimeEvent = RuntimeEvent;
|
type RuntimeEvent = RuntimeEvent;
|
||||||
@@ -220,6 +222,7 @@ impl pallet_staking::Config for Test {
|
|||||||
type GenesisElectionProvider = Self::ElectionProvider;
|
type GenesisElectionProvider = Self::ElectionProvider;
|
||||||
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
||||||
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
||||||
|
type NominationsQuota = pallet_staking::FixedNominationsQuota<16>;
|
||||||
type MaxUnlockingChunks = ConstU32<32>;
|
type MaxUnlockingChunks = ConstU32<32>;
|
||||||
type HistoryDepth = ConstU32<84>;
|
type HistoryDepth = ConstU32<84>;
|
||||||
type EventListeners = ();
|
type EventListeners = ();
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{unsigned::IndexAssignmentOf, Pallet as MultiPhase};
|
use crate::{unsigned::IndexAssignmentOf, Pallet as MultiPhase};
|
||||||
use frame_benchmarking::account;
|
use frame_benchmarking::account;
|
||||||
|
use frame_election_provider_support::bounds::DataProviderBounds;
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
assert_ok,
|
assert_ok,
|
||||||
traits::{Hooks, TryCollect},
|
traits::{Hooks, TryCollect},
|
||||||
@@ -270,8 +271,9 @@ frame_benchmarking::benchmarks! {
|
|||||||
|
|
||||||
// we don't directly need the data-provider to be populated, but it is just easy to use it.
|
// we don't directly need the data-provider to be populated, but it is just easy to use it.
|
||||||
set_up_data_provider::<T>(v, t);
|
set_up_data_provider::<T>(v, t);
|
||||||
let targets = T::DataProvider::electable_targets(None)?;
|
// default bounds are unbounded.
|
||||||
let voters = T::DataProvider::electing_voters(None)?;
|
let targets = T::DataProvider::electable_targets(DataProviderBounds::default())?;
|
||||||
|
let voters = T::DataProvider::electing_voters(DataProviderBounds::default())?;
|
||||||
let desired_targets = T::DataProvider::desired_targets()?;
|
let desired_targets = T::DataProvider::desired_targets()?;
|
||||||
assert!(<MultiPhase<T>>::snapshot().is_none());
|
assert!(<MultiPhase<T>>::snapshot().is_none());
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@@ -231,8 +231,9 @@
|
|||||||
|
|
||||||
use codec::{Decode, Encode};
|
use codec::{Decode, Encode};
|
||||||
use frame_election_provider_support::{
|
use frame_election_provider_support::{
|
||||||
BoundedSupportsOf, ElectionDataProvider, ElectionProvider, ElectionProviderBase,
|
bounds::{CountBound, ElectionBounds, ElectionBoundsBuilder, SizeBound},
|
||||||
InstantElectionProvider, NposSolution,
|
BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider,
|
||||||
|
ElectionProviderBase, InstantElectionProvider, NposSolution,
|
||||||
};
|
};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
dispatch::DispatchClass,
|
dispatch::DispatchClass,
|
||||||
@@ -659,16 +660,6 @@ pub mod pallet {
|
|||||||
#[pallet::constant]
|
#[pallet::constant]
|
||||||
type SignedDepositWeight: Get<BalanceOf<Self>>;
|
type SignedDepositWeight: Get<BalanceOf<Self>>;
|
||||||
|
|
||||||
/// The maximum number of electing voters to put in the snapshot. At the moment, snapshots
|
|
||||||
/// are only over a single block, but once multi-block elections are introduced they will
|
|
||||||
/// take place over multiple blocks.
|
|
||||||
#[pallet::constant]
|
|
||||||
type MaxElectingVoters: Get<SolutionVoterIndexOf<Self::MinerConfig>>;
|
|
||||||
|
|
||||||
/// The maximum number of electable targets to put in the snapshot.
|
|
||||||
#[pallet::constant]
|
|
||||||
type MaxElectableTargets: Get<SolutionTargetIndexOf<Self::MinerConfig>>;
|
|
||||||
|
|
||||||
/// The maximum number of winners that can be elected by this `ElectionProvider`
|
/// The maximum number of winners that can be elected by this `ElectionProvider`
|
||||||
/// implementation.
|
/// implementation.
|
||||||
///
|
///
|
||||||
@@ -676,6 +667,11 @@ pub mod pallet {
|
|||||||
#[pallet::constant]
|
#[pallet::constant]
|
||||||
type MaxWinners: Get<u32>;
|
type MaxWinners: Get<u32>;
|
||||||
|
|
||||||
|
/// The maximum number of electing voters and electable targets to put in the snapshot.
|
||||||
|
/// At the moment, snapshots are only over a single block, but once multi-block elections
|
||||||
|
/// are introduced they will take place over multiple blocks.
|
||||||
|
type ElectionBounds: Get<ElectionBounds>;
|
||||||
|
|
||||||
/// Handler for the slashed deposits.
|
/// Handler for the slashed deposits.
|
||||||
type SlashHandler: OnUnbalanced<NegativeImbalanceOf<Self>>;
|
type SlashHandler: OnUnbalanced<NegativeImbalanceOf<Self>>;
|
||||||
|
|
||||||
@@ -1097,13 +1093,19 @@ pub mod pallet {
|
|||||||
T::ForceOrigin::ensure_origin(origin)?;
|
T::ForceOrigin::ensure_origin(origin)?;
|
||||||
ensure!(Self::current_phase().is_emergency(), <Error<T>>::CallNotAllowed);
|
ensure!(Self::current_phase().is_emergency(), <Error<T>>::CallNotAllowed);
|
||||||
|
|
||||||
let supports =
|
let election_bounds = ElectionBoundsBuilder::default()
|
||||||
T::GovernanceFallback::instant_elect(maybe_max_voters, maybe_max_targets).map_err(
|
.voters_count(maybe_max_voters.unwrap_or(u32::MAX).into())
|
||||||
|e| {
|
.targets_count(maybe_max_targets.unwrap_or(u32::MAX).into())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let supports = T::GovernanceFallback::instant_elect(
|
||||||
|
election_bounds.voters,
|
||||||
|
election_bounds.targets,
|
||||||
|
)
|
||||||
|
.map_err(|e| {
|
||||||
log!(error, "GovernanceFallback failed: {:?}", e);
|
log!(error, "GovernanceFallback failed: {:?}", e);
|
||||||
Error::<T>::FallbackFailed
|
Error::<T>::FallbackFailed
|
||||||
},
|
})?;
|
||||||
)?;
|
|
||||||
|
|
||||||
// transform BoundedVec<_, T::GovernanceFallback::MaxWinners> into
|
// transform BoundedVec<_, T::GovernanceFallback::MaxWinners> into
|
||||||
// `BoundedVec<_, T::MaxWinners>`
|
// `BoundedVec<_, T::MaxWinners>`
|
||||||
@@ -1426,19 +1428,28 @@ impl<T: Config> Pallet<T> {
|
|||||||
/// Extracted for easier weight calculation.
|
/// Extracted for easier weight calculation.
|
||||||
fn create_snapshot_external(
|
fn create_snapshot_external(
|
||||||
) -> Result<(Vec<T::AccountId>, Vec<VoterOf<T>>, u32), ElectionError<T>> {
|
) -> Result<(Vec<T::AccountId>, Vec<VoterOf<T>>, u32), ElectionError<T>> {
|
||||||
let target_limit = T::MaxElectableTargets::get().saturated_into::<usize>();
|
let election_bounds = T::ElectionBounds::get();
|
||||||
let voter_limit = T::MaxElectingVoters::get().saturated_into::<usize>();
|
|
||||||
|
|
||||||
let targets = T::DataProvider::electable_targets(Some(target_limit))
|
let targets = T::DataProvider::electable_targets(election_bounds.targets)
|
||||||
|
.and_then(|t| {
|
||||||
|
election_bounds.ensure_targets_limits(
|
||||||
|
CountBound(t.len() as u32),
|
||||||
|
SizeBound(t.encoded_size() as u32),
|
||||||
|
)?;
|
||||||
|
Ok(t)
|
||||||
|
})
|
||||||
.map_err(ElectionError::DataProvider)?;
|
.map_err(ElectionError::DataProvider)?;
|
||||||
|
|
||||||
let voters = T::DataProvider::electing_voters(Some(voter_limit))
|
let voters = T::DataProvider::electing_voters(election_bounds.voters)
|
||||||
|
.and_then(|v| {
|
||||||
|
election_bounds.ensure_voters_limits(
|
||||||
|
CountBound(v.len() as u32),
|
||||||
|
SizeBound(v.encoded_size() as u32),
|
||||||
|
)?;
|
||||||
|
Ok(v)
|
||||||
|
})
|
||||||
.map_err(ElectionError::DataProvider)?;
|
.map_err(ElectionError::DataProvider)?;
|
||||||
|
|
||||||
if targets.len() > target_limit || voters.len() > voter_limit {
|
|
||||||
return Err(ElectionError::DataProvider("Snapshot too big for submission."))
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut desired_targets = <Pallet<T> as ElectionProviderBase>::desired_targets_checked()
|
let mut desired_targets = <Pallet<T> as ElectionProviderBase>::desired_targets_checked()
|
||||||
.map_err(|e| ElectionError::DataProvider(e))?;
|
.map_err(|e| ElectionError::DataProvider(e))?;
|
||||||
|
|
||||||
@@ -1544,10 +1555,17 @@ impl<T: Config> Pallet<T> {
|
|||||||
// - signed phase was complete or not started, in which case finalization is idempotent and
|
// - signed phase was complete or not started, in which case finalization is idempotent and
|
||||||
// inexpensive (1 read of an empty vector).
|
// inexpensive (1 read of an empty vector).
|
||||||
let _ = Self::finalize_signed_phase();
|
let _ = Self::finalize_signed_phase();
|
||||||
|
|
||||||
<QueuedSolution<T>>::take()
|
<QueuedSolution<T>>::take()
|
||||||
.ok_or(ElectionError::<T>::NothingQueued)
|
.ok_or(ElectionError::<T>::NothingQueued)
|
||||||
.or_else(|_| {
|
.or_else(|_| {
|
||||||
T::Fallback::instant_elect(None, None)
|
// default data provider bounds are unbounded. calling `instant_elect` with
|
||||||
|
// unbounded data provider bounds means that the on-chain `T:Bounds` configs will
|
||||||
|
// *not* be overwritten.
|
||||||
|
T::Fallback::instant_elect(
|
||||||
|
DataProviderBounds::default(),
|
||||||
|
DataProviderBounds::default(),
|
||||||
|
)
|
||||||
.map_err(|fe| ElectionError::Fallback(fe))
|
.map_err(|fe| ElectionError::Fallback(fe))
|
||||||
.and_then(|supports| {
|
.and_then(|supports| {
|
||||||
Ok(ReadySolution {
|
Ok(ReadySolution {
|
||||||
@@ -1925,8 +1943,8 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
mock::{
|
mock::{
|
||||||
multi_phase_events, raw_solution, roll_to, roll_to_signed, roll_to_unsigned, AccountId,
|
multi_phase_events, raw_solution, roll_to, roll_to_signed, roll_to_unsigned, AccountId,
|
||||||
ExtBuilder, MockWeightInfo, MockedWeightInfo, MultiPhase, Runtime, RuntimeOrigin,
|
ElectionsBounds, ExtBuilder, MockWeightInfo, MockedWeightInfo, MultiPhase, Runtime,
|
||||||
SignedMaxSubmissions, System, TargetIndex, Targets,
|
RuntimeOrigin, SignedMaxSubmissions, System, TargetIndex, Targets, Voters,
|
||||||
},
|
},
|
||||||
Phase,
|
Phase,
|
||||||
};
|
};
|
||||||
@@ -2529,7 +2547,11 @@ mod tests {
|
|||||||
fn snapshot_too_big_failure_onchain_fallback() {
|
fn snapshot_too_big_failure_onchain_fallback() {
|
||||||
// the `MockStaking` is designed such that if it has too many targets, it simply fails.
|
// the `MockStaking` is designed such that if it has too many targets, it simply fails.
|
||||||
ExtBuilder::default().build_and_execute(|| {
|
ExtBuilder::default().build_and_execute(|| {
|
||||||
Targets::set((0..(TargetIndex::max_value() as AccountId) + 1).collect::<Vec<_>>());
|
// sets bounds on number of targets.
|
||||||
|
let new_bounds = ElectionBoundsBuilder::default().targets_count(1_000.into()).build();
|
||||||
|
ElectionsBounds::set(new_bounds);
|
||||||
|
|
||||||
|
Targets::set((0..(1_000 as AccountId) + 1).collect::<Vec<_>>());
|
||||||
|
|
||||||
// Signed phase failed to open.
|
// Signed phase failed to open.
|
||||||
roll_to(15);
|
roll_to(15);
|
||||||
@@ -2564,9 +2586,11 @@ mod tests {
|
|||||||
fn snapshot_too_big_failure_no_fallback() {
|
fn snapshot_too_big_failure_no_fallback() {
|
||||||
// and if the backup mode is nothing, we go into the emergency mode..
|
// and if the backup mode is nothing, we go into the emergency mode..
|
||||||
ExtBuilder::default().onchain_fallback(false).build_and_execute(|| {
|
ExtBuilder::default().onchain_fallback(false).build_and_execute(|| {
|
||||||
crate::mock::Targets::set(
|
// sets bounds on number of targets.
|
||||||
(0..(TargetIndex::max_value() as AccountId) + 1).collect::<Vec<_>>(),
|
let new_bounds = ElectionBoundsBuilder::default().targets_count(1_000.into()).build();
|
||||||
);
|
ElectionsBounds::set(new_bounds);
|
||||||
|
|
||||||
|
Targets::set((0..(TargetIndex::max_value() as AccountId) + 1).collect::<Vec<_>>());
|
||||||
|
|
||||||
// Signed phase failed to open.
|
// Signed phase failed to open.
|
||||||
roll_to(15);
|
roll_to(15);
|
||||||
@@ -2596,9 +2620,10 @@ mod tests {
|
|||||||
// but if there are too many voters, we simply truncate them.
|
// but if there are too many voters, we simply truncate them.
|
||||||
ExtBuilder::default().build_and_execute(|| {
|
ExtBuilder::default().build_and_execute(|| {
|
||||||
// we have 8 voters in total.
|
// we have 8 voters in total.
|
||||||
assert_eq!(crate::mock::Voters::get().len(), 8);
|
assert_eq!(Voters::get().len(), 8);
|
||||||
// but we want to take 2.
|
// but we want to take 2.
|
||||||
crate::mock::MaxElectingVoters::set(2);
|
let new_bounds = ElectionBoundsBuilder::default().voters_count(2.into()).build();
|
||||||
|
ElectionsBounds::set(new_bounds);
|
||||||
|
|
||||||
// Signed phase opens just fine.
|
// Signed phase opens just fine.
|
||||||
roll_to_signed();
|
roll_to_signed();
|
||||||
|
|||||||
@@ -18,9 +18,8 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{self as multi_phase, unsigned::MinerConfig};
|
use crate::{self as multi_phase, unsigned::MinerConfig};
|
||||||
use frame_election_provider_support::{
|
use frame_election_provider_support::{
|
||||||
data_provider,
|
bounds::{DataProviderBounds, ElectionBounds},
|
||||||
onchain::{self},
|
data_provider, onchain, ElectionDataProvider, NposSolution, SequentialPhragmen,
|
||||||
ElectionDataProvider, NposSolution, SequentialPhragmen,
|
|
||||||
};
|
};
|
||||||
pub use frame_support::{assert_noop, assert_ok, pallet_prelude::GetDefault};
|
pub use frame_support::{assert_noop, assert_ok, pallet_prelude::GetDefault};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
@@ -300,7 +299,9 @@ parameter_types! {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub static MaxWinners: u32 = 200;
|
pub static MaxWinners: u32 = 200;
|
||||||
|
// `ElectionBounds` and `OnChainElectionsBounds` are defined separately to set them independently in the tests.
|
||||||
|
pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build();
|
||||||
|
pub static OnChainElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build();
|
||||||
pub static EpochLength: u64 = 30;
|
pub static EpochLength: u64 = 30;
|
||||||
pub static OnChainFallback: bool = true;
|
pub static OnChainFallback: bool = true;
|
||||||
}
|
}
|
||||||
@@ -312,8 +313,7 @@ impl onchain::Config for OnChainSeqPhragmen {
|
|||||||
type DataProvider = StakingMock;
|
type DataProvider = StakingMock;
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
type MaxWinners = MaxWinners;
|
type MaxWinners = MaxWinners;
|
||||||
type VotersBound = ConstU32<{ u32::MAX }>;
|
type Bounds = OnChainElectionsBounds;
|
||||||
type TargetsBound = ConstU32<{ u32::MAX }>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MockFallback;
|
pub struct MockFallback;
|
||||||
@@ -327,11 +327,14 @@ impl ElectionProviderBase for MockFallback {
|
|||||||
|
|
||||||
impl InstantElectionProvider for MockFallback {
|
impl InstantElectionProvider for MockFallback {
|
||||||
fn instant_elect(
|
fn instant_elect(
|
||||||
max_voters: Option<u32>,
|
voters_bounds: DataProviderBounds,
|
||||||
max_targets: Option<u32>,
|
targets_bounds: DataProviderBounds,
|
||||||
) -> Result<BoundedSupportsOf<Self>, Self::Error> {
|
) -> Result<BoundedSupportsOf<Self>, Self::Error> {
|
||||||
if OnChainFallback::get() {
|
if OnChainFallback::get() {
|
||||||
onchain::OnChainExecution::<OnChainSeqPhragmen>::instant_elect(max_voters, max_targets)
|
onchain::OnChainExecution::<OnChainSeqPhragmen>::instant_elect(
|
||||||
|
voters_bounds,
|
||||||
|
targets_bounds,
|
||||||
|
)
|
||||||
.map_err(|_| "onchain::OnChainExecution failed.")
|
.map_err(|_| "onchain::OnChainExecution failed.")
|
||||||
} else {
|
} else {
|
||||||
Err("NoFallback.")
|
Err("NoFallback.")
|
||||||
@@ -404,11 +407,10 @@ impl crate::Config for Runtime {
|
|||||||
type GovernanceFallback =
|
type GovernanceFallback =
|
||||||
frame_election_provider_support::onchain::OnChainExecution<OnChainSeqPhragmen>;
|
frame_election_provider_support::onchain::OnChainExecution<OnChainSeqPhragmen>;
|
||||||
type ForceOrigin = frame_system::EnsureRoot<AccountId>;
|
type ForceOrigin = frame_system::EnsureRoot<AccountId>;
|
||||||
type MaxElectingVoters = MaxElectingVoters;
|
|
||||||
type MaxElectableTargets = MaxElectableTargets;
|
|
||||||
type MaxWinners = MaxWinners;
|
type MaxWinners = MaxWinners;
|
||||||
type MinerConfig = Self;
|
type MinerConfig = Self;
|
||||||
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, Balancing>;
|
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, Balancing>;
|
||||||
|
type ElectionBounds = ElectionsBounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<LocalCall> frame_system::offchain::SendTransactionTypes<LocalCall> for Runtime
|
impl<LocalCall> frame_system::offchain::SendTransactionTypes<LocalCall> for Runtime
|
||||||
@@ -436,11 +438,11 @@ impl ElectionDataProvider for StakingMock {
|
|||||||
type AccountId = AccountId;
|
type AccountId = AccountId;
|
||||||
type MaxVotesPerVoter = MaxNominations;
|
type MaxVotesPerVoter = MaxNominations;
|
||||||
|
|
||||||
fn electable_targets(maybe_max_len: Option<usize>) -> data_provider::Result<Vec<AccountId>> {
|
fn electable_targets(bounds: DataProviderBounds) -> data_provider::Result<Vec<AccountId>> {
|
||||||
let targets = Targets::get();
|
let targets = Targets::get();
|
||||||
|
|
||||||
if !DataProviderAllowBadData::get() &&
|
if !DataProviderAllowBadData::get() &&
|
||||||
maybe_max_len.map_or(false, |max_len| targets.len() > max_len)
|
bounds.count.map_or(false, |max_len| targets.len() > max_len.0 as usize)
|
||||||
{
|
{
|
||||||
return Err("Targets too big")
|
return Err("Targets too big")
|
||||||
}
|
}
|
||||||
@@ -448,13 +450,12 @@ impl ElectionDataProvider for StakingMock {
|
|||||||
Ok(targets)
|
Ok(targets)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn electing_voters(
|
fn electing_voters(bounds: DataProviderBounds) -> data_provider::Result<Vec<VoterOf<Runtime>>> {
|
||||||
maybe_max_len: Option<usize>,
|
|
||||||
) -> data_provider::Result<Vec<VoterOf<Runtime>>> {
|
|
||||||
let mut voters = Voters::get();
|
let mut voters = Voters::get();
|
||||||
|
|
||||||
if !DataProviderAllowBadData::get() {
|
if !DataProviderAllowBadData::get() {
|
||||||
if let Some(max_len) = maybe_max_len {
|
if let Some(max_len) = bounds.count {
|
||||||
voters.truncate(max_len)
|
voters.truncate(max_len.0 as usize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -536,7 +536,10 @@ impl<T: Config> Pallet<T> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{mock::*, ElectionCompute, ElectionError, Error, Event, Perbill, Phase};
|
use crate::{
|
||||||
|
mock::*, ElectionBoundsBuilder, ElectionCompute, ElectionError, Error, Event, Perbill,
|
||||||
|
Phase,
|
||||||
|
};
|
||||||
use frame_support::{assert_noop, assert_ok, assert_storage_noop};
|
use frame_support::{assert_noop, assert_ok, assert_storage_noop};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -565,13 +568,14 @@ mod tests {
|
|||||||
fn data_provider_should_respect_target_limits() {
|
fn data_provider_should_respect_target_limits() {
|
||||||
ExtBuilder::default().build_and_execute(|| {
|
ExtBuilder::default().build_and_execute(|| {
|
||||||
// given a reduced expectation of maximum electable targets
|
// given a reduced expectation of maximum electable targets
|
||||||
MaxElectableTargets::set(2);
|
let new_bounds = ElectionBoundsBuilder::default().targets_count(2.into()).build();
|
||||||
|
ElectionsBounds::set(new_bounds);
|
||||||
// and a data provider that does not respect limits
|
// and a data provider that does not respect limits
|
||||||
DataProviderAllowBadData::set(true);
|
DataProviderAllowBadData::set(true);
|
||||||
|
|
||||||
assert_noop!(
|
assert_noop!(
|
||||||
MultiPhase::create_snapshot(),
|
MultiPhase::create_snapshot(),
|
||||||
ElectionError::DataProvider("Snapshot too big for submission."),
|
ElectionError::DataProvider("Ensure targets bounds: bounds exceeded."),
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -580,13 +584,14 @@ mod tests {
|
|||||||
fn data_provider_should_respect_voter_limits() {
|
fn data_provider_should_respect_voter_limits() {
|
||||||
ExtBuilder::default().build_and_execute(|| {
|
ExtBuilder::default().build_and_execute(|| {
|
||||||
// given a reduced expectation of maximum electing voters
|
// given a reduced expectation of maximum electing voters
|
||||||
MaxElectingVoters::set(2);
|
let new_bounds = ElectionBoundsBuilder::default().voters_count(2.into()).build();
|
||||||
|
ElectionsBounds::set(new_bounds);
|
||||||
// and a data provider that does not respect limits
|
// and a data provider that does not respect limits
|
||||||
DataProviderAllowBadData::set(true);
|
DataProviderAllowBadData::set(true);
|
||||||
|
|
||||||
assert_noop!(
|
assert_noop!(
|
||||||
MultiPhase::create_snapshot(),
|
MultiPhase::create_snapshot(),
|
||||||
ElectionError::DataProvider("Snapshot too big for submission."),
|
ElectionError::DataProvider("Ensure voters bounds: bounds exceeded."),
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use _feps::ExtendedBalance;
|
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
assert_ok, dispatch::UnfilteredDispatchable, parameter_types, traits, traits::Hooks,
|
assert_ok, dispatch::UnfilteredDispatchable, parameter_types, traits, traits::Hooks,
|
||||||
weights::constants,
|
weights::constants,
|
||||||
@@ -42,7 +41,10 @@ use sp_std::prelude::*;
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use codec::Decode;
|
use codec::Decode;
|
||||||
use frame_election_provider_support::{onchain, ElectionDataProvider, SequentialPhragmen, Weight};
|
use frame_election_provider_support::{
|
||||||
|
bounds::ElectionBoundsBuilder, onchain, ElectionDataProvider, ExtendedBalance,
|
||||||
|
SequentialPhragmen, Weight,
|
||||||
|
};
|
||||||
use pallet_election_provider_multi_phase::{
|
use pallet_election_provider_multi_phase::{
|
||||||
unsigned::MinerConfig, Call, ElectionCompute, QueuedSolution, SolutionAccuracyOf,
|
unsigned::MinerConfig, Call, ElectionCompute, QueuedSolution, SolutionAccuracyOf,
|
||||||
};
|
};
|
||||||
@@ -172,8 +174,6 @@ parameter_types! {
|
|||||||
// we expect a minimum of 3 blocks in signed phase and unsigned phases before trying
|
// we expect a minimum of 3 blocks in signed phase and unsigned phases before trying
|
||||||
// enetering in emergency phase after the election failed.
|
// enetering in emergency phase after the election failed.
|
||||||
pub static MinBlocksBeforeEmergency: BlockNumber = 3;
|
pub static MinBlocksBeforeEmergency: BlockNumber = 3;
|
||||||
pub static MaxElectingVoters: VoterIndex = 1000;
|
|
||||||
pub static MaxElectableTargets: TargetIndex = 1000;
|
|
||||||
pub static MaxActiveValidators: u32 = 1000;
|
pub static MaxActiveValidators: u32 = 1000;
|
||||||
pub static OffchainRepeat: u32 = 5;
|
pub static OffchainRepeat: u32 = 5;
|
||||||
pub static MinerMaxLength: u32 = 256;
|
pub static MinerMaxLength: u32 = 256;
|
||||||
@@ -181,8 +181,8 @@ parameter_types! {
|
|||||||
pub static TransactionPriority: transaction_validity::TransactionPriority = 1;
|
pub static TransactionPriority: transaction_validity::TransactionPriority = 1;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub static MaxWinners: u32 = 100;
|
pub static MaxWinners: u32 = 100;
|
||||||
pub static MaxVotesPerVoter: u32 = 16;
|
pub static ElectionBounds: frame_election_provider_support::bounds::ElectionBounds = ElectionBoundsBuilder::default()
|
||||||
pub static MaxNominations: u32 = 16;
|
.voters_count(1_000.into()).targets_count(1_000.into()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
impl pallet_election_provider_multi_phase::Config for Runtime {
|
impl pallet_election_provider_multi_phase::Config for Runtime {
|
||||||
@@ -211,9 +211,8 @@ impl pallet_election_provider_multi_phase::Config for Runtime {
|
|||||||
type GovernanceFallback = onchain::OnChainExecution<OnChainSeqPhragmen>;
|
type GovernanceFallback = onchain::OnChainExecution<OnChainSeqPhragmen>;
|
||||||
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, ()>;
|
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, ()>;
|
||||||
type ForceOrigin = EnsureRoot<AccountId>;
|
type ForceOrigin = EnsureRoot<AccountId>;
|
||||||
type MaxElectableTargets = MaxElectableTargets;
|
|
||||||
type MaxElectingVoters = MaxElectingVoters;
|
|
||||||
type MaxWinners = MaxWinners;
|
type MaxWinners = MaxWinners;
|
||||||
|
type ElectionBounds = ElectionBounds;
|
||||||
type BenchmarkingConfig = NoopElectionProviderBenchmarkConfig;
|
type BenchmarkingConfig = NoopElectionProviderBenchmarkConfig;
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
}
|
}
|
||||||
@@ -252,8 +251,10 @@ impl pallet_bags_list::Config for Runtime {
|
|||||||
type Score = VoteWeight;
|
type Score = VoteWeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Upper limit on the number of NPOS nominations.
|
||||||
|
const MAX_QUOTA_NOMINATIONS: u32 = 16;
|
||||||
|
|
||||||
impl pallet_staking::Config for Runtime {
|
impl pallet_staking::Config for Runtime {
|
||||||
type MaxNominations = MaxNominations;
|
|
||||||
type Currency = Balances;
|
type Currency = Balances;
|
||||||
type CurrencyBalance = Balance;
|
type CurrencyBalance = Balance;
|
||||||
type UnixTime = Timestamp;
|
type UnixTime = Timestamp;
|
||||||
@@ -274,6 +275,7 @@ impl pallet_staking::Config for Runtime {
|
|||||||
type ElectionProvider = ElectionProviderMultiPhase;
|
type ElectionProvider = ElectionProviderMultiPhase;
|
||||||
type GenesisElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
|
type GenesisElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
|
||||||
type VoterList = BagsList;
|
type VoterList = BagsList;
|
||||||
|
type NominationsQuota = pallet_staking::FixedNominationsQuota<MAX_QUOTA_NOMINATIONS>;
|
||||||
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
||||||
type MaxUnlockingChunks = ConstU32<32>;
|
type MaxUnlockingChunks = ConstU32<32>;
|
||||||
type HistoryDepth = HistoryDepth;
|
type HistoryDepth = HistoryDepth;
|
||||||
@@ -306,8 +308,7 @@ impl onchain::Config for OnChainSeqPhragmen {
|
|||||||
type DataProvider = Staking;
|
type DataProvider = Staking;
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
type MaxWinners = MaxWinners;
|
type MaxWinners = MaxWinners;
|
||||||
type VotersBound = VotersBound;
|
type Bounds = ElectionBounds;
|
||||||
type TargetsBound = TargetsBound;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NoopElectionProviderBenchmarkConfig;
|
pub struct NoopElectionProviderBenchmarkConfig;
|
||||||
|
|||||||
@@ -0,0 +1,460 @@
|
|||||||
|
// This file is part of Substrate.
|
||||||
|
|
||||||
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//! Types and helpers to define and handle election bounds.
|
||||||
|
//!
|
||||||
|
//! ### Overview
|
||||||
|
//!
|
||||||
|
//! This module defines and implements types that help creating and handling election bounds.
|
||||||
|
//! [`DataProviderBounds`] encapsulates the upper limits for the results provided by `DataProvider`
|
||||||
|
//! implementors. Those limits can be defined over two axis: number of elements returned (`count`)
|
||||||
|
//! and/or the size of the returned SCALE encoded structure (`size`).
|
||||||
|
//!
|
||||||
|
//! [`ElectionBoundsBuilder`] is a helper to construct data election bounds and it aims at
|
||||||
|
//! preventing the caller from mistake the order of size and count limits.
|
||||||
|
//!
|
||||||
|
//! ### Examples
|
||||||
|
//!
|
||||||
|
//! [`ElectionBoundsBuilder`] helps defining the size and count bounds for both voters and targets.
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use frame_election_provider_support::bounds::*;
|
||||||
|
//!
|
||||||
|
//! // unbounded limits are never exhausted.
|
||||||
|
//! let unbounded = ElectionBoundsBuilder::default().build();
|
||||||
|
//! assert!(!unbounded.targets.exhausted(SizeBound(1_000_000_000).into(), None));
|
||||||
|
//!
|
||||||
|
//! let bounds = ElectionBoundsBuilder::default()
|
||||||
|
//! .voters_count(100.into())
|
||||||
|
//! .voters_size(1_000.into())
|
||||||
|
//! .targets_count(200.into())
|
||||||
|
//! .targets_size(2_000.into())
|
||||||
|
//! .build();
|
||||||
|
//!
|
||||||
|
//! assert!(!bounds.targets.exhausted(SizeBound(1).into(), CountBound(1).into()));
|
||||||
|
//! assert!(bounds.targets.exhausted(SizeBound(1).into(), CountBound(100_000).into()));
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Implementation details
|
||||||
|
//!
|
||||||
|
//! A default or `None` bound means that no bounds are enforced (i.e. unlimited result size). In
|
||||||
|
//! general, be careful when using unbounded election bounds in production.
|
||||||
|
|
||||||
|
use core::ops::Add;
|
||||||
|
use sp_runtime::traits::Zero;
|
||||||
|
|
||||||
|
/// Count type for data provider bounds.
|
||||||
|
///
|
||||||
|
/// Encapsulates the counting of things that can be bounded in an election, such as voters,
|
||||||
|
/// targets or anything else.
|
||||||
|
///
|
||||||
|
/// This struct is defined mostly to prevent callers from mistankingly using `CountBound` instead of
|
||||||
|
/// `SizeBound` and vice-versa.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
|
pub struct CountBound(pub u32);
|
||||||
|
|
||||||
|
impl From<u32> for CountBound {
|
||||||
|
fn from(value: u32) -> Self {
|
||||||
|
CountBound(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for CountBound {
|
||||||
|
type Output = Self;
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
CountBound(self.0.saturating_add(rhs.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zero for CountBound {
|
||||||
|
fn is_zero(&self) -> bool {
|
||||||
|
self.0 == 0u32
|
||||||
|
}
|
||||||
|
fn zero() -> Self {
|
||||||
|
CountBound(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Size type for data provider bounds.
|
||||||
|
///
|
||||||
|
/// Encapsulates the size limit of things that can be bounded in an election, such as voters,
|
||||||
|
/// targets or anything else. The size unit can represent anything depending on the election
|
||||||
|
/// logic and implementation, but it most likely will represent bytes in SCALE encoding in this
|
||||||
|
/// context.
|
||||||
|
///
|
||||||
|
/// This struct is defined mostly to prevent callers from mistankingly using `CountBound` instead of
|
||||||
|
/// `SizeBound` and vice-versa.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
|
pub struct SizeBound(pub u32);
|
||||||
|
|
||||||
|
impl From<u32> for SizeBound {
|
||||||
|
fn from(value: u32) -> Self {
|
||||||
|
SizeBound(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zero for SizeBound {
|
||||||
|
fn is_zero(&self) -> bool {
|
||||||
|
self.0 == 0u32
|
||||||
|
}
|
||||||
|
fn zero() -> Self {
|
||||||
|
SizeBound(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for SizeBound {
|
||||||
|
type Output = Self;
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
SizeBound(self.0.saturating_add(rhs.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Data bounds for election data.
|
||||||
|
///
|
||||||
|
/// Limits the data returned by `DataProvider` implementors, defined over two axis: `count`,
|
||||||
|
/// defining the maximum number of elements returned, and `size`, defining the limit in size
|
||||||
|
/// (bytes) of the SCALE encoded result.
|
||||||
|
///
|
||||||
|
/// `None` represents unlimited bounds in both `count` and `size` axis.
|
||||||
|
#[derive(Clone, Copy, Default, Debug, Eq, PartialEq)]
|
||||||
|
pub struct DataProviderBounds {
|
||||||
|
pub count: Option<CountBound>,
|
||||||
|
pub size: Option<SizeBound>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataProviderBounds {
|
||||||
|
/// Returns true if `given_count` exhausts `self.count`.
|
||||||
|
pub fn count_exhausted(self, given_count: CountBound) -> bool {
|
||||||
|
self.count.map_or(false, |count| given_count > count)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if `given_size` exhausts `self.size`.
|
||||||
|
pub fn size_exhausted(self, given_size: SizeBound) -> bool {
|
||||||
|
self.size.map_or(false, |size| given_size > size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if `given_size` or `given_count` exhausts `self.size` or `self_count`,
|
||||||
|
/// respectively.
|
||||||
|
pub fn exhausted(self, given_size: Option<SizeBound>, given_count: Option<CountBound>) -> bool {
|
||||||
|
self.count_exhausted(given_count.unwrap_or(CountBound::zero())) ||
|
||||||
|
self.size_exhausted(given_size.unwrap_or(SizeBound::zero()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an instance of `Self` that is constructed by capping both the `count` and `size`
|
||||||
|
/// fields. If `self` is None, overwrite it with the provided bounds.
|
||||||
|
pub fn max(self, bounds: DataProviderBounds) -> Self {
|
||||||
|
DataProviderBounds {
|
||||||
|
count: self
|
||||||
|
.count
|
||||||
|
.map(|c| {
|
||||||
|
c.clamp(CountBound::zero(), bounds.count.unwrap_or(CountBound(u32::MAX))).into()
|
||||||
|
})
|
||||||
|
.or(bounds.count),
|
||||||
|
size: self
|
||||||
|
.size
|
||||||
|
.map(|c| {
|
||||||
|
c.clamp(SizeBound::zero(), bounds.size.unwrap_or(SizeBound(u32::MAX))).into()
|
||||||
|
})
|
||||||
|
.or(bounds.size),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The voter and target bounds of an election.
|
||||||
|
///
|
||||||
|
/// The bounds are defined over two axis: `count` of element of the election (voters or targets) and
|
||||||
|
/// the `size` of the SCALE encoded result snapshot.
|
||||||
|
#[derive(Clone, Debug, Copy)]
|
||||||
|
pub struct ElectionBounds {
|
||||||
|
pub voters: DataProviderBounds,
|
||||||
|
pub targets: DataProviderBounds,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElectionBounds {
|
||||||
|
/// Returns an error if the provided `count` and `size` do not fit in the voter's election
|
||||||
|
/// bounds.
|
||||||
|
pub fn ensure_voters_limits(
|
||||||
|
self,
|
||||||
|
count: CountBound,
|
||||||
|
size: SizeBound,
|
||||||
|
) -> Result<(), &'static str> {
|
||||||
|
match self.voters.exhausted(Some(size), Some(count)) {
|
||||||
|
true => Err("Ensure voters bounds: bounds exceeded."),
|
||||||
|
false => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an error if the provided `count` and `size` do not fit in the target's election
|
||||||
|
/// bounds.
|
||||||
|
pub fn ensure_targets_limits(
|
||||||
|
self,
|
||||||
|
count: CountBound,
|
||||||
|
size: SizeBound,
|
||||||
|
) -> Result<(), &'static str> {
|
||||||
|
match self.targets.exhausted(Some(size), Some(count).into()) {
|
||||||
|
true => Err("Ensure targets bounds: bounds exceeded."),
|
||||||
|
false => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Utility builder for [`ElectionBounds`].
|
||||||
|
#[derive(Copy, Clone, Default)]
|
||||||
|
pub struct ElectionBoundsBuilder {
|
||||||
|
voters: Option<DataProviderBounds>,
|
||||||
|
targets: Option<DataProviderBounds>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ElectionBounds> for ElectionBoundsBuilder {
|
||||||
|
fn from(bounds: ElectionBounds) -> Self {
|
||||||
|
ElectionBoundsBuilder { voters: Some(bounds.voters), targets: Some(bounds.targets) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElectionBoundsBuilder {
|
||||||
|
/// Sets the voters count bounds.
|
||||||
|
pub fn voters_count(mut self, count: CountBound) -> Self {
|
||||||
|
self.voters = self.voters.map_or(
|
||||||
|
Some(DataProviderBounds { count: Some(count), size: None }),
|
||||||
|
|mut bounds| {
|
||||||
|
bounds.count = Some(count);
|
||||||
|
Some(bounds)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the voters size bounds.
|
||||||
|
pub fn voters_size(mut self, size: SizeBound) -> Self {
|
||||||
|
self.voters = self.voters.map_or(
|
||||||
|
Some(DataProviderBounds { count: None, size: Some(size) }),
|
||||||
|
|mut bounds| {
|
||||||
|
bounds.size = Some(size);
|
||||||
|
Some(bounds)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the targets count bounds.
|
||||||
|
pub fn targets_count(mut self, count: CountBound) -> Self {
|
||||||
|
self.targets = self.targets.map_or(
|
||||||
|
Some(DataProviderBounds { count: Some(count), size: None }),
|
||||||
|
|mut bounds| {
|
||||||
|
bounds.count = Some(count);
|
||||||
|
Some(bounds)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the targets size bounds.
|
||||||
|
pub fn targets_size(mut self, size: SizeBound) -> Self {
|
||||||
|
self.targets = self.targets.map_or(
|
||||||
|
Some(DataProviderBounds { count: None, size: Some(size) }),
|
||||||
|
|mut bounds| {
|
||||||
|
bounds.size = Some(size);
|
||||||
|
Some(bounds)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the voters bounds.
|
||||||
|
pub fn voters(mut self, bounds: Option<DataProviderBounds>) -> Self {
|
||||||
|
self.voters = bounds;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the targets bounds.
|
||||||
|
pub fn targets(mut self, bounds: Option<DataProviderBounds>) -> Self {
|
||||||
|
self.targets = bounds;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Caps the number of the voters bounds in self to `voters` bounds. If `voters` bounds are
|
||||||
|
/// higher than the self bounds, keeps it. Note that `None` bounds are equivalent to maximum
|
||||||
|
/// and should be treated as such.
|
||||||
|
pub fn voters_or_lower(mut self, voters: DataProviderBounds) -> Self {
|
||||||
|
self.voters = match self.voters {
|
||||||
|
None => Some(voters),
|
||||||
|
Some(v) => Some(v.max(voters)),
|
||||||
|
};
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Caps the number of the target bounds in self to `voters` bounds. If `voters` bounds are
|
||||||
|
/// higher than the self bounds, keeps it. Note that `None` bounds are equivalent to maximum
|
||||||
|
/// and should be treated as such.
|
||||||
|
pub fn targets_or_lower(mut self, targets: DataProviderBounds) -> Self {
|
||||||
|
self.targets = match self.targets {
|
||||||
|
None => Some(targets),
|
||||||
|
Some(t) => Some(t.max(targets)),
|
||||||
|
};
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an instance of `ElectionBounds` from the current state.
|
||||||
|
pub fn build(self) -> ElectionBounds {
|
||||||
|
ElectionBounds {
|
||||||
|
voters: self.voters.unwrap_or_default(),
|
||||||
|
targets: self.targets.unwrap_or_default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use frame_support::{assert_err, assert_ok};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn data_provider_bounds_unbounded_works() {
|
||||||
|
let bounds = DataProviderBounds::default();
|
||||||
|
assert!(!bounds.exhausted(None, None));
|
||||||
|
assert!(!bounds.exhausted(SizeBound(u32::MAX).into(), CountBound(u32::MAX).into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn election_bounds_builder_and_exhausted_bounds_work() {
|
||||||
|
// voter bounds exhausts if count > 100 or size > 1_000; target bounds exhausts if count >
|
||||||
|
// 200 or size > 2_000.
|
||||||
|
let bounds = ElectionBoundsBuilder::default()
|
||||||
|
.voters_count(100.into())
|
||||||
|
.voters_size(1_000.into())
|
||||||
|
.targets_count(200.into())
|
||||||
|
.targets_size(2_000.into())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert!(!bounds.voters.exhausted(None, None));
|
||||||
|
assert!(!bounds.voters.exhausted(SizeBound(10).into(), CountBound(10).into()));
|
||||||
|
assert!(!bounds.voters.exhausted(None, CountBound(100).into()));
|
||||||
|
assert!(!bounds.voters.exhausted(SizeBound(1_000).into(), None));
|
||||||
|
// exhausts bounds.
|
||||||
|
assert!(bounds.voters.exhausted(None, CountBound(101).into()));
|
||||||
|
assert!(bounds.voters.exhausted(SizeBound(1_001).into(), None));
|
||||||
|
|
||||||
|
assert!(!bounds.targets.exhausted(None, None));
|
||||||
|
assert!(!bounds.targets.exhausted(SizeBound(20).into(), CountBound(20).into()));
|
||||||
|
assert!(!bounds.targets.exhausted(None, CountBound(200).into()));
|
||||||
|
assert!(!bounds.targets.exhausted(SizeBound(2_000).into(), None));
|
||||||
|
// exhausts bounds.
|
||||||
|
assert!(bounds.targets.exhausted(None, CountBound(201).into()));
|
||||||
|
assert!(bounds.targets.exhausted(SizeBound(2_001).into(), None));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn election_bounds_ensure_limits_works() {
|
||||||
|
let bounds = ElectionBounds {
|
||||||
|
voters: DataProviderBounds { count: Some(CountBound(10)), size: Some(SizeBound(10)) },
|
||||||
|
targets: DataProviderBounds { count: Some(CountBound(10)), size: Some(SizeBound(10)) },
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_ok!(bounds.ensure_voters_limits(CountBound(1), SizeBound(1)));
|
||||||
|
assert_ok!(bounds.ensure_voters_limits(CountBound(1), SizeBound(1)));
|
||||||
|
assert_ok!(bounds.ensure_voters_limits(CountBound(10), SizeBound(10)));
|
||||||
|
assert_err!(
|
||||||
|
bounds.ensure_voters_limits(CountBound(1), SizeBound(11)),
|
||||||
|
"Ensure voters bounds: bounds exceeded."
|
||||||
|
);
|
||||||
|
assert_err!(
|
||||||
|
bounds.ensure_voters_limits(CountBound(11), SizeBound(10)),
|
||||||
|
"Ensure voters bounds: bounds exceeded."
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_ok!(bounds.ensure_targets_limits(CountBound(1), SizeBound(1)));
|
||||||
|
assert_ok!(bounds.ensure_targets_limits(CountBound(1), SizeBound(1)));
|
||||||
|
assert_ok!(bounds.ensure_targets_limits(CountBound(10), SizeBound(10)));
|
||||||
|
assert_err!(
|
||||||
|
bounds.ensure_targets_limits(CountBound(1), SizeBound(11)),
|
||||||
|
"Ensure targets bounds: bounds exceeded."
|
||||||
|
);
|
||||||
|
assert_err!(
|
||||||
|
bounds.ensure_targets_limits(CountBound(11), SizeBound(10)),
|
||||||
|
"Ensure targets bounds: bounds exceeded."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn data_provider_max_unbounded_works() {
|
||||||
|
let unbounded = DataProviderBounds::default();
|
||||||
|
|
||||||
|
// max of some bounds with unbounded data provider bounds will always return the defined
|
||||||
|
// bounds.
|
||||||
|
let bounds = DataProviderBounds { count: CountBound(5).into(), size: SizeBound(10).into() };
|
||||||
|
assert_eq!(unbounded.max(bounds), bounds);
|
||||||
|
|
||||||
|
let bounds = DataProviderBounds { count: None, size: SizeBound(10).into() };
|
||||||
|
assert_eq!(unbounded.max(bounds), bounds);
|
||||||
|
|
||||||
|
let bounds = DataProviderBounds { count: CountBound(5).into(), size: None };
|
||||||
|
assert_eq!(unbounded.max(bounds), bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn data_provider_max_bounded_works() {
|
||||||
|
let bounds_one =
|
||||||
|
DataProviderBounds { count: CountBound(10).into(), size: SizeBound(100).into() };
|
||||||
|
let bounds_two =
|
||||||
|
DataProviderBounds { count: CountBound(100).into(), size: SizeBound(10).into() };
|
||||||
|
let max_bounds_expected =
|
||||||
|
DataProviderBounds { count: CountBound(10).into(), size: SizeBound(10).into() };
|
||||||
|
|
||||||
|
assert_eq!(bounds_one.max(bounds_two), max_bounds_expected);
|
||||||
|
assert_eq!(bounds_two.max(bounds_one), max_bounds_expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn election_bounds_clamp_works() {
|
||||||
|
let bounds = ElectionBoundsBuilder::default()
|
||||||
|
.voters_count(10.into())
|
||||||
|
.voters_size(10.into())
|
||||||
|
.voters_or_lower(DataProviderBounds {
|
||||||
|
count: CountBound(5).into(),
|
||||||
|
size: SizeBound(20).into(),
|
||||||
|
})
|
||||||
|
.targets_count(20.into())
|
||||||
|
.targets_or_lower(DataProviderBounds {
|
||||||
|
count: CountBound(30).into(),
|
||||||
|
size: SizeBound(30).into(),
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(bounds.voters.count.unwrap(), CountBound(5));
|
||||||
|
assert_eq!(bounds.voters.size.unwrap(), SizeBound(10));
|
||||||
|
assert_eq!(bounds.targets.count.unwrap(), CountBound(20));
|
||||||
|
assert_eq!(bounds.targets.size.unwrap(), SizeBound(30));
|
||||||
|
|
||||||
|
// note that unbounded bounds (None) are equivalent to maximum value.
|
||||||
|
let bounds = ElectionBoundsBuilder::default()
|
||||||
|
.voters_or_lower(DataProviderBounds {
|
||||||
|
count: CountBound(5).into(),
|
||||||
|
size: SizeBound(20).into(),
|
||||||
|
})
|
||||||
|
.targets_or_lower(DataProviderBounds {
|
||||||
|
count: CountBound(10).into(),
|
||||||
|
size: SizeBound(10).into(),
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(bounds.voters.count.unwrap(), CountBound(5));
|
||||||
|
assert_eq!(bounds.voters.size.unwrap(), SizeBound(20));
|
||||||
|
assert_eq!(bounds.targets.count.unwrap(), CountBound(10));
|
||||||
|
assert_eq!(bounds.targets.size.unwrap(), SizeBound(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -109,12 +109,12 @@
|
|||||||
//! fn desired_targets() -> data_provider::Result<u32> {
|
//! fn desired_targets() -> data_provider::Result<u32> {
|
||||||
//! Ok(1)
|
//! Ok(1)
|
||||||
//! }
|
//! }
|
||||||
//! fn electing_voters(maybe_max_len: Option<usize>)
|
//! fn electing_voters(bounds: DataProviderBounds)
|
||||||
//! -> data_provider::Result<Vec<VoterOf<Self>>>
|
//! -> data_provider::Result<Vec<VoterOf<Self>>>
|
||||||
//! {
|
//! {
|
||||||
//! Ok(Default::default())
|
//! Ok(Default::default())
|
||||||
//! }
|
//! }
|
||||||
//! fn electable_targets(maybe_max_len: Option<usize>) -> data_provider::Result<Vec<AccountId>> {
|
//! fn electable_targets(bounds: DataProviderBounds) -> data_provider::Result<Vec<AccountId>> {
|
||||||
//! Ok(vec![10, 20, 30])
|
//! Ok(vec![10, 20, 30])
|
||||||
//! }
|
//! }
|
||||||
//! fn next_election_prediction(now: BlockNumber) -> BlockNumber {
|
//! fn next_election_prediction(now: BlockNumber) -> BlockNumber {
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
//! impl<T: Config> ElectionProvider for GenericElectionProvider<T> {
|
//! impl<T: Config> ElectionProvider for GenericElectionProvider<T> {
|
||||||
//! fn ongoing() -> bool { false }
|
//! fn ongoing() -> bool { false }
|
||||||
//! fn elect() -> Result<BoundedSupportsOf<Self>, Self::Error> {
|
//! fn elect() -> Result<BoundedSupportsOf<Self>, Self::Error> {
|
||||||
//! Self::DataProvider::electable_targets(None)
|
//! Self::DataProvider::electable_targets(DataProviderBounds::default())
|
||||||
//! .map_err(|_| "failed to elect")
|
//! .map_err(|_| "failed to elect")
|
||||||
//! .map(|t| bounded_vec![(t[0], Support::default())])
|
//! .map(|t| bounded_vec![(t[0], Support::default())])
|
||||||
//! }
|
//! }
|
||||||
@@ -173,11 +173,15 @@
|
|||||||
|
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
|
pub mod bounds;
|
||||||
pub mod onchain;
|
pub mod onchain;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
|
|
||||||
use sp_runtime::traits::{Bounded, Saturating, Zero};
|
use sp_runtime::traits::{Bounded, Saturating, Zero};
|
||||||
use sp_std::{fmt::Debug, prelude::*};
|
use sp_std::{fmt::Debug, prelude::*};
|
||||||
|
|
||||||
|
pub use bounds::DataProviderBounds;
|
||||||
|
pub use codec::{Decode, Encode};
|
||||||
/// Re-export the solution generation macro.
|
/// Re-export the solution generation macro.
|
||||||
pub use frame_election_provider_solution_type::generate_solution_type;
|
pub use frame_election_provider_solution_type::generate_solution_type;
|
||||||
pub use frame_support::{traits::Get, weights::Weight, BoundedVec, RuntimeDebug};
|
pub use frame_support::{traits::Get, weights::Weight, BoundedVec, RuntimeDebug};
|
||||||
@@ -231,7 +235,7 @@ mod tests;
|
|||||||
/// making it fast to repeatedly encode into a `SolutionOf<T>`. This property turns out
|
/// making it fast to repeatedly encode into a `SolutionOf<T>`. This property turns out
|
||||||
/// to be important when trimming for solution length.
|
/// to be important when trimming for solution length.
|
||||||
#[derive(RuntimeDebug, Clone, Default)]
|
#[derive(RuntimeDebug, Clone, Default)]
|
||||||
#[cfg_attr(feature = "std", derive(PartialEq, Eq, codec::Encode, codec::Decode))]
|
#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))]
|
||||||
pub struct IndexAssignment<VoterIndex, TargetIndex, P: PerThing> {
|
pub struct IndexAssignment<VoterIndex, TargetIndex, P: PerThing> {
|
||||||
/// Index of the voter among the voters list.
|
/// Index of the voter among the voters list.
|
||||||
pub who: VoterIndex,
|
pub who: VoterIndex,
|
||||||
@@ -275,7 +279,7 @@ pub mod data_provider {
|
|||||||
/// Something that can provide the data to an [`ElectionProvider`].
|
/// Something that can provide the data to an [`ElectionProvider`].
|
||||||
pub trait ElectionDataProvider {
|
pub trait ElectionDataProvider {
|
||||||
/// The account identifier type.
|
/// The account identifier type.
|
||||||
type AccountId;
|
type AccountId: Encode;
|
||||||
|
|
||||||
/// The block number type.
|
/// The block number type.
|
||||||
type BlockNumber;
|
type BlockNumber;
|
||||||
@@ -286,25 +290,18 @@ pub trait ElectionDataProvider {
|
|||||||
/// All possible targets for the election, i.e. the targets that could become elected, thus
|
/// All possible targets for the election, i.e. the targets that could become elected, thus
|
||||||
/// "electable".
|
/// "electable".
|
||||||
///
|
///
|
||||||
/// If `maybe_max_len` is `Some(v)` then the resulting vector MUST NOT be longer than `v` items
|
|
||||||
/// long.
|
|
||||||
///
|
|
||||||
/// This should be implemented as a self-weighing function. The implementor should register its
|
/// This should be implemented as a self-weighing function. The implementor should register its
|
||||||
/// appropriate weight at the end of execution with the system pallet directly.
|
/// appropriate weight at the end of execution with the system pallet directly.
|
||||||
fn electable_targets(
|
fn electable_targets(bounds: DataProviderBounds)
|
||||||
maybe_max_len: Option<usize>,
|
-> data_provider::Result<Vec<Self::AccountId>>;
|
||||||
) -> data_provider::Result<Vec<Self::AccountId>>;
|
|
||||||
|
|
||||||
/// All the voters that participate in the election, thus "electing".
|
/// All the voters that participate in the election, thus "electing".
|
||||||
///
|
///
|
||||||
/// Note that if a notion of self-vote exists, it should be represented here.
|
/// Note that if a notion of self-vote exists, it should be represented here.
|
||||||
///
|
///
|
||||||
/// If `maybe_max_len` is `Some(v)` then the resulting vector MUST NOT be longer than `v` items
|
|
||||||
/// long.
|
|
||||||
///
|
|
||||||
/// This should be implemented as a self-weighing function. The implementor should register its
|
/// This should be implemented as a self-weighing function. The implementor should register its
|
||||||
/// appropriate weight at the end of execution with the system pallet directly.
|
/// appropriate weight at the end of execution with the system pallet directly.
|
||||||
fn electing_voters(maybe_max_len: Option<usize>) -> data_provider::Result<Vec<VoterOf<Self>>>;
|
fn electing_voters(bounds: DataProviderBounds) -> data_provider::Result<Vec<VoterOf<Self>>>;
|
||||||
|
|
||||||
/// The number of targets to elect.
|
/// The number of targets to elect.
|
||||||
///
|
///
|
||||||
@@ -425,8 +422,8 @@ pub trait ElectionProvider: ElectionProviderBase {
|
|||||||
/// data provider at runtime via `forced_input_voters_bound` and `forced_input_target_bound`.
|
/// data provider at runtime via `forced_input_voters_bound` and `forced_input_target_bound`.
|
||||||
pub trait InstantElectionProvider: ElectionProviderBase {
|
pub trait InstantElectionProvider: ElectionProviderBase {
|
||||||
fn instant_elect(
|
fn instant_elect(
|
||||||
forced_input_voters_bound: Option<u32>,
|
forced_input_voters_bound: DataProviderBounds,
|
||||||
forced_input_target_bound: Option<u32>,
|
forced_input_target_bound: DataProviderBounds,
|
||||||
) -> Result<BoundedSupportsOf<Self>, Self::Error>;
|
) -> Result<BoundedSupportsOf<Self>, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,8 +465,8 @@ where
|
|||||||
MaxWinners: Get<u32>,
|
MaxWinners: Get<u32>,
|
||||||
{
|
{
|
||||||
fn instant_elect(
|
fn instant_elect(
|
||||||
_: Option<u32>,
|
_: DataProviderBounds,
|
||||||
_: Option<u32>,
|
_: DataProviderBounds,
|
||||||
) -> Result<BoundedSupportsOf<Self>, Self::Error> {
|
) -> Result<BoundedSupportsOf<Self>, Self::Error> {
|
||||||
Err("`NoElection` cannot do anything.")
|
Err("`NoElection` cannot do anything.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
//! careful when using it onchain.
|
//! careful when using it onchain.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
bounds::{DataProviderBounds, ElectionBounds, ElectionBoundsBuilder},
|
||||||
BoundedSupportsOf, Debug, ElectionDataProvider, ElectionProvider, ElectionProviderBase,
|
BoundedSupportsOf, Debug, ElectionDataProvider, ElectionProvider, ElectionProviderBase,
|
||||||
InstantElectionProvider, NposSolver, WeightInfo,
|
InstantElectionProvider, NposSolver, WeightInfo,
|
||||||
};
|
};
|
||||||
@@ -52,8 +53,7 @@ impl From<sp_npos_elections::Error> for Error {
|
|||||||
/// This implements both `ElectionProvider` and `InstantElectionProvider`.
|
/// This implements both `ElectionProvider` and `InstantElectionProvider`.
|
||||||
///
|
///
|
||||||
/// This type has some utilities to make it safe. Nonetheless, it should be used with utmost care. A
|
/// This type has some utilities to make it safe. Nonetheless, it should be used with utmost care. A
|
||||||
/// thoughtful value must be set as [`Config::VotersBound`] and [`Config::TargetsBound`] to ensure
|
/// thoughtful value must be set as [`Config::Bounds`] to ensure the size of the input is sensible.
|
||||||
/// the size of the input is sensible.
|
|
||||||
pub struct OnChainExecution<T: Config>(PhantomData<T>);
|
pub struct OnChainExecution<T: Config>(PhantomData<T>);
|
||||||
|
|
||||||
#[deprecated(note = "use OnChainExecution, which is bounded by default")]
|
#[deprecated(note = "use OnChainExecution, which is bounded by default")]
|
||||||
@@ -85,13 +85,9 @@ pub trait Config {
|
|||||||
/// always be more than `DataProvider::desired_target`.
|
/// always be more than `DataProvider::desired_target`.
|
||||||
type MaxWinners: Get<u32>;
|
type MaxWinners: Get<u32>;
|
||||||
|
|
||||||
/// Bounds the number of voters, when calling into [`Config::DataProvider`]. It might be
|
/// Elections bounds, to use when calling into [`Config::DataProvider`]. It might be overwritten
|
||||||
/// overwritten in the `InstantElectionProvider` impl.
|
/// in the `InstantElectionProvider` impl.
|
||||||
type VotersBound: Get<u32>;
|
type Bounds: Get<ElectionBounds>;
|
||||||
|
|
||||||
/// Bounds the number of targets, when calling into [`Config::DataProvider`]. It might be
|
|
||||||
/// overwritten in the `InstantElectionProvider` impl.
|
|
||||||
type TargetsBound: Get<u32>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as `BoundedSupportsOf` but for `onchain::Config`.
|
/// Same as `BoundedSupportsOf` but for `onchain::Config`.
|
||||||
@@ -101,12 +97,12 @@ pub type OnChainBoundedSupportsOf<E> = BoundedSupports<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
fn elect_with_input_bounds<T: Config>(
|
fn elect_with_input_bounds<T: Config>(
|
||||||
maybe_max_voters: Option<usize>,
|
bounds: ElectionBounds,
|
||||||
maybe_max_targets: Option<usize>,
|
|
||||||
) -> Result<OnChainBoundedSupportsOf<T>, Error> {
|
) -> Result<OnChainBoundedSupportsOf<T>, Error> {
|
||||||
let voters = T::DataProvider::electing_voters(maybe_max_voters).map_err(Error::DataProvider)?;
|
let (voters, targets) = T::DataProvider::electing_voters(bounds.voters)
|
||||||
let targets =
|
.and_then(|voters| Ok((voters, T::DataProvider::electable_targets(bounds.targets)?)))
|
||||||
T::DataProvider::electable_targets(maybe_max_targets).map_err(Error::DataProvider)?;
|
.map_err(Error::DataProvider)?;
|
||||||
|
|
||||||
let desired_targets = T::DataProvider::desired_targets().map_err(Error::DataProvider)?;
|
let desired_targets = T::DataProvider::desired_targets().map_err(Error::DataProvider)?;
|
||||||
|
|
||||||
if desired_targets > T::MaxWinners::get() {
|
if desired_targets > T::MaxWinners::get() {
|
||||||
@@ -159,13 +155,15 @@ impl<T: Config> ElectionProviderBase for OnChainExecution<T> {
|
|||||||
|
|
||||||
impl<T: Config> InstantElectionProvider for OnChainExecution<T> {
|
impl<T: Config> InstantElectionProvider for OnChainExecution<T> {
|
||||||
fn instant_elect(
|
fn instant_elect(
|
||||||
forced_input_voters_bound: Option<u32>,
|
forced_input_voters_bounds: DataProviderBounds,
|
||||||
forced_input_target_bound: Option<u32>,
|
forced_input_targets_bounds: DataProviderBounds,
|
||||||
) -> Result<BoundedSupportsOf<Self>, Self::Error> {
|
) -> Result<BoundedSupportsOf<Self>, Self::Error> {
|
||||||
elect_with_input_bounds::<T>(
|
let elections_bounds = ElectionBoundsBuilder::from(T::Bounds::get())
|
||||||
Some(T::VotersBound::get().min(forced_input_voters_bound.unwrap_or(u32::MAX)) as usize),
|
.voters_or_lower(forced_input_voters_bounds)
|
||||||
Some(T::TargetsBound::get().min(forced_input_target_bound.unwrap_or(u32::MAX)) as usize),
|
.targets_or_lower(forced_input_targets_bounds)
|
||||||
)
|
.build();
|
||||||
|
|
||||||
|
elect_with_input_bounds::<T>(elections_bounds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,10 +173,8 @@ impl<T: Config> ElectionProvider for OnChainExecution<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn elect() -> Result<BoundedSupportsOf<Self>, Self::Error> {
|
fn elect() -> Result<BoundedSupportsOf<Self>, Self::Error> {
|
||||||
elect_with_input_bounds::<T>(
|
let election_bounds = ElectionBoundsBuilder::from(T::Bounds::get()).build();
|
||||||
Some(T::VotersBound::get() as usize),
|
elect_with_input_bounds::<T>(election_bounds)
|
||||||
Some(T::TargetsBound::get() as usize),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +182,7 @@ impl<T: Config> ElectionProvider for OnChainExecution<T> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{ElectionProvider, PhragMMS, SequentialPhragmen};
|
use crate::{ElectionProvider, PhragMMS, SequentialPhragmen};
|
||||||
use frame_support::{assert_noop, parameter_types, traits::ConstU32};
|
use frame_support::{assert_noop, parameter_types};
|
||||||
use sp_npos_elections::Support;
|
use sp_npos_elections::Support;
|
||||||
use sp_runtime::Perbill;
|
use sp_runtime::Perbill;
|
||||||
type AccountId = u64;
|
type AccountId = u64;
|
||||||
@@ -236,6 +232,7 @@ mod tests {
|
|||||||
parameter_types! {
|
parameter_types! {
|
||||||
pub static MaxWinners: u32 = 10;
|
pub static MaxWinners: u32 = 10;
|
||||||
pub static DesiredTargets: u32 = 2;
|
pub static DesiredTargets: u32 = 2;
|
||||||
|
pub static Bounds: ElectionBounds = ElectionBoundsBuilder::default().voters_count(600.into()).targets_count(400.into()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config for PhragmenParams {
|
impl Config for PhragmenParams {
|
||||||
@@ -244,8 +241,7 @@ mod tests {
|
|||||||
type DataProvider = mock_data_provider::DataProvider;
|
type DataProvider = mock_data_provider::DataProvider;
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
type MaxWinners = MaxWinners;
|
type MaxWinners = MaxWinners;
|
||||||
type VotersBound = ConstU32<600>;
|
type Bounds = Bounds;
|
||||||
type TargetsBound = ConstU32<400>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config for PhragMMSParams {
|
impl Config for PhragMMSParams {
|
||||||
@@ -254,8 +250,7 @@ mod tests {
|
|||||||
type DataProvider = mock_data_provider::DataProvider;
|
type DataProvider = mock_data_provider::DataProvider;
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
type MaxWinners = MaxWinners;
|
type MaxWinners = MaxWinners;
|
||||||
type VotersBound = ConstU32<600>;
|
type Bounds = Bounds;
|
||||||
type TargetsBound = ConstU32<400>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mod mock_data_provider {
|
mod mock_data_provider {
|
||||||
@@ -269,7 +264,7 @@ mod tests {
|
|||||||
type AccountId = AccountId;
|
type AccountId = AccountId;
|
||||||
type BlockNumber = BlockNumber;
|
type BlockNumber = BlockNumber;
|
||||||
type MaxVotesPerVoter = ConstU32<2>;
|
type MaxVotesPerVoter = ConstU32<2>;
|
||||||
fn electing_voters(_: Option<usize>) -> data_provider::Result<Vec<VoterOf<Self>>> {
|
fn electing_voters(_: DataProviderBounds) -> data_provider::Result<Vec<VoterOf<Self>>> {
|
||||||
Ok(vec![
|
Ok(vec![
|
||||||
(1, 10, bounded_vec![10, 20]),
|
(1, 10, bounded_vec![10, 20]),
|
||||||
(2, 20, bounded_vec![30, 20]),
|
(2, 20, bounded_vec![30, 20]),
|
||||||
@@ -277,7 +272,7 @@ mod tests {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn electable_targets(_: Option<usize>) -> data_provider::Result<Vec<AccountId>> {
|
fn electable_targets(_: DataProviderBounds) -> data_provider::Result<Vec<AccountId>> {
|
||||||
Ok(vec![10, 20, 30])
|
Ok(vec![10, 20, 30])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,6 @@ impl frame_election_provider_support::ElectionProvider for MockElection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl pallet_staking::Config for Runtime {
|
impl pallet_staking::Config for Runtime {
|
||||||
type MaxNominations = ConstU32<16>;
|
|
||||||
type Currency = Balances;
|
type Currency = Balances;
|
||||||
type CurrencyBalance = Balance;
|
type CurrencyBalance = Balance;
|
||||||
type UnixTime = pallet_timestamp::Pallet<Self>;
|
type UnixTime = pallet_timestamp::Pallet<Self>;
|
||||||
@@ -158,6 +157,7 @@ impl pallet_staking::Config for Runtime {
|
|||||||
type GenesisElectionProvider = Self::ElectionProvider;
|
type GenesisElectionProvider = Self::ElectionProvider;
|
||||||
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
||||||
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
||||||
|
type NominationsQuota = pallet_staking::FixedNominationsQuota<16>;
|
||||||
type MaxUnlockingChunks = ConstU32<32>;
|
type MaxUnlockingChunks = ConstU32<32>;
|
||||||
type EventListeners = ();
|
type EventListeners = ();
|
||||||
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
|
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
|
||||||
|
|||||||
@@ -22,7 +22,10 @@
|
|||||||
use crate::{self as pallet_grandpa, AuthorityId, AuthorityList, Config, ConsensusLog};
|
use crate::{self as pallet_grandpa, AuthorityId, AuthorityList, Config, ConsensusLog};
|
||||||
use ::grandpa as finality_grandpa;
|
use ::grandpa as finality_grandpa;
|
||||||
use codec::Encode;
|
use codec::Encode;
|
||||||
use frame_election_provider_support::{onchain, SequentialPhragmen};
|
use frame_election_provider_support::{
|
||||||
|
bounds::{ElectionBounds, ElectionBoundsBuilder},
|
||||||
|
onchain, SequentialPhragmen,
|
||||||
|
};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
parameter_types,
|
parameter_types,
|
||||||
traits::{ConstU128, ConstU32, ConstU64, KeyOwnerProofSystem, OnFinalize, OnInitialize},
|
traits::{ConstU128, ConstU32, ConstU64, KeyOwnerProofSystem, OnFinalize, OnInitialize},
|
||||||
@@ -164,6 +167,7 @@ parameter_types! {
|
|||||||
pub const BondingDuration: EraIndex = 3;
|
pub const BondingDuration: EraIndex = 3;
|
||||||
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
|
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
|
||||||
pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17);
|
pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17);
|
||||||
|
pub static ElectionsBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::default().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OnChainSeqPhragmen;
|
pub struct OnChainSeqPhragmen;
|
||||||
@@ -173,12 +177,10 @@ impl onchain::Config for OnChainSeqPhragmen {
|
|||||||
type DataProvider = Staking;
|
type DataProvider = Staking;
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
type MaxWinners = ConstU32<100>;
|
type MaxWinners = ConstU32<100>;
|
||||||
type VotersBound = ConstU32<{ u32::MAX }>;
|
type Bounds = ElectionsBoundsOnChain;
|
||||||
type TargetsBound = ConstU32<{ u32::MAX }>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl pallet_staking::Config for Test {
|
impl pallet_staking::Config for Test {
|
||||||
type MaxNominations = ConstU32<16>;
|
|
||||||
type RewardRemainder = ();
|
type RewardRemainder = ();
|
||||||
type CurrencyToVote = ();
|
type CurrencyToVote = ();
|
||||||
type RuntimeEvent = RuntimeEvent;
|
type RuntimeEvent = RuntimeEvent;
|
||||||
@@ -200,6 +202,7 @@ impl pallet_staking::Config for Test {
|
|||||||
type GenesisElectionProvider = Self::ElectionProvider;
|
type GenesisElectionProvider = Self::ElectionProvider;
|
||||||
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
||||||
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
||||||
|
type NominationsQuota = pallet_staking::FixedNominationsQuota<16>;
|
||||||
type MaxUnlockingChunks = ConstU32<32>;
|
type MaxUnlockingChunks = ConstU32<32>;
|
||||||
type HistoryDepth = ConstU32<84>;
|
type HistoryDepth = ConstU32<84>;
|
||||||
type EventListeners = ();
|
type EventListeners = ();
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ use pallet_nomination_pools::{
|
|||||||
MaxPoolMembersPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond, Pallet as Pools,
|
MaxPoolMembersPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond, Pallet as Pools,
|
||||||
PoolMembers, PoolRoles, PoolState, RewardPools, SubPoolsStorage,
|
PoolMembers, PoolRoles, PoolState, RewardPools, SubPoolsStorage,
|
||||||
};
|
};
|
||||||
|
use pallet_staking::MaxNominationsOf;
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
traits::{Bounded, StaticLookup, Zero},
|
traits::{Bounded, StaticLookup, Zero},
|
||||||
Perbill,
|
Perbill,
|
||||||
@@ -564,7 +565,7 @@ frame_benchmarking::benchmarks! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nominate {
|
nominate {
|
||||||
let n in 1 .. T::MaxNominations::get();
|
let n in 1 .. MaxNominationsOf::<T>::get();
|
||||||
|
|
||||||
// Create a pool
|
// Create a pool
|
||||||
let min_create_bond = Pools::<T>::depositor_min_bond() * 2u32.into();
|
let min_create_bond = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||||
@@ -679,7 +680,7 @@ frame_benchmarking::benchmarks! {
|
|||||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||||
|
|
||||||
// Nominate with the pool.
|
// Nominate with the pool.
|
||||||
let validators: Vec<_> = (0..T::MaxNominations::get())
|
let validators: Vec<_> = (0..MaxNominationsOf::<T>::get())
|
||||||
.map(|i| account("stash", USER_SEED, i))
|
.map(|i| account("stash", USER_SEED, i))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ parameter_types! {
|
|||||||
pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS;
|
pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS;
|
||||||
}
|
}
|
||||||
impl pallet_staking::Config for Runtime {
|
impl pallet_staking::Config for Runtime {
|
||||||
type MaxNominations = ConstU32<16>;
|
|
||||||
type Currency = Balances;
|
type Currency = Balances;
|
||||||
type CurrencyBalance = Balance;
|
type CurrencyBalance = Balance;
|
||||||
type UnixTime = pallet_timestamp::Pallet<Self>;
|
type UnixTime = pallet_timestamp::Pallet<Self>;
|
||||||
@@ -117,6 +116,7 @@ impl pallet_staking::Config for Runtime {
|
|||||||
type GenesisElectionProvider = Self::ElectionProvider;
|
type GenesisElectionProvider = Self::ElectionProvider;
|
||||||
type VoterList = VoterList;
|
type VoterList = VoterList;
|
||||||
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
||||||
|
type NominationsQuota = pallet_staking::FixedNominationsQuota<16>;
|
||||||
type MaxUnlockingChunks = ConstU32<32>;
|
type MaxUnlockingChunks = ConstU32<32>;
|
||||||
type HistoryDepth = ConstU32<84>;
|
type HistoryDepth = ConstU32<84>;
|
||||||
type EventListeners = Pools;
|
type EventListeners = Pools;
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ parameter_types! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl pallet_staking::Config for Runtime {
|
impl pallet_staking::Config for Runtime {
|
||||||
type MaxNominations = ConstU32<16>;
|
|
||||||
type Currency = Balances;
|
type Currency = Balances;
|
||||||
type CurrencyBalance = Balance;
|
type CurrencyBalance = Balance;
|
||||||
type UnixTime = pallet_timestamp::Pallet<Self>;
|
type UnixTime = pallet_timestamp::Pallet<Self>;
|
||||||
@@ -131,6 +130,7 @@ impl pallet_staking::Config for Runtime {
|
|||||||
type GenesisElectionProvider = Self::ElectionProvider;
|
type GenesisElectionProvider = Self::ElectionProvider;
|
||||||
type VoterList = VoterList;
|
type VoterList = VoterList;
|
||||||
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
||||||
|
type NominationsQuota = pallet_staking::FixedNominationsQuota<16>;
|
||||||
type MaxUnlockingChunks = ConstU32<32>;
|
type MaxUnlockingChunks = ConstU32<32>;
|
||||||
type HistoryDepth = ConstU32<84>;
|
type HistoryDepth = ConstU32<84>;
|
||||||
type EventListeners = Pools;
|
type EventListeners = Pools;
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ use pallet_session::{
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use pallet_staking::Event as StakingEvent;
|
use pallet_staking::Event as StakingEvent;
|
||||||
use pallet_staking::{
|
use pallet_staking::{
|
||||||
Config as StakingConfig, Exposure, IndividualExposure, Pallet as Staking, RewardDestination,
|
Config as StakingConfig, Exposure, IndividualExposure, MaxNominationsOf, Pallet as Staking,
|
||||||
ValidatorPrefs,
|
RewardDestination, ValidatorPrefs,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SEED: u32 = 0;
|
const SEED: u32 = 0;
|
||||||
@@ -283,7 +283,7 @@ benchmarks! {
|
|||||||
let r in 1 .. MAX_REPORTERS;
|
let r in 1 .. MAX_REPORTERS;
|
||||||
// we skip 1 offender, because in such case there is no slashing
|
// we skip 1 offender, because in such case there is no slashing
|
||||||
let o in 2 .. MAX_OFFENDERS;
|
let o in 2 .. MAX_OFFENDERS;
|
||||||
let n in 0 .. MAX_NOMINATORS.min(<T as pallet_staking::Config>::MaxNominations::get());
|
let n in 0 .. MAX_NOMINATORS.min(MaxNominationsOf::<T>::get());
|
||||||
|
|
||||||
// Make r reporters
|
// Make r reporters
|
||||||
let mut reporters = vec![];
|
let mut reporters = vec![];
|
||||||
@@ -399,7 +399,7 @@ benchmarks! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
report_offence_grandpa {
|
report_offence_grandpa {
|
||||||
let n in 0 .. MAX_NOMINATORS.min(<T as pallet_staking::Config>::MaxNominations::get());
|
let n in 0 .. MAX_NOMINATORS.min(MaxNominationsOf::<T>::get());
|
||||||
|
|
||||||
// for grandpa equivocation reports the number of reporters
|
// for grandpa equivocation reports the number of reporters
|
||||||
// and offenders is always 1
|
// and offenders is always 1
|
||||||
@@ -436,7 +436,7 @@ benchmarks! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
report_offence_babe {
|
report_offence_babe {
|
||||||
let n in 0 .. MAX_NOMINATORS.min(<T as pallet_staking::Config>::MaxNominations::get());
|
let n in 0 .. MAX_NOMINATORS.min(MaxNominationsOf::<T>::get());
|
||||||
|
|
||||||
// for babe equivocation reports the number of reporters
|
// for babe equivocation reports the number of reporters
|
||||||
// and offenders is always 1
|
// and offenders is always 1
|
||||||
|
|||||||
@@ -20,7 +20,10 @@
|
|||||||
#![cfg(test)]
|
#![cfg(test)]
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use frame_election_provider_support::{onchain, SequentialPhragmen};
|
use frame_election_provider_support::{
|
||||||
|
bounds::{ElectionBounds, ElectionBoundsBuilder},
|
||||||
|
onchain, SequentialPhragmen,
|
||||||
|
};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
parameter_types,
|
parameter_types,
|
||||||
traits::{ConstU32, ConstU64},
|
traits::{ConstU32, ConstU64},
|
||||||
@@ -141,6 +144,7 @@ pallet_staking_reward_curve::build! {
|
|||||||
}
|
}
|
||||||
parameter_types! {
|
parameter_types! {
|
||||||
pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS;
|
pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS;
|
||||||
|
pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Extrinsic = sp_runtime::testing::TestXt<RuntimeCall, ()>;
|
pub type Extrinsic = sp_runtime::testing::TestXt<RuntimeCall, ()>;
|
||||||
@@ -152,12 +156,10 @@ impl onchain::Config for OnChainSeqPhragmen {
|
|||||||
type DataProvider = Staking;
|
type DataProvider = Staking;
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
type MaxWinners = ConstU32<100>;
|
type MaxWinners = ConstU32<100>;
|
||||||
type VotersBound = ConstU32<{ u32::MAX }>;
|
type Bounds = ElectionsBounds;
|
||||||
type TargetsBound = ConstU32<{ u32::MAX }>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl pallet_staking::Config for Test {
|
impl pallet_staking::Config for Test {
|
||||||
type MaxNominations = ConstU32<16>;
|
|
||||||
type Currency = Balances;
|
type Currency = Balances;
|
||||||
type CurrencyBalance = <Self as pallet_balances::Config>::Balance;
|
type CurrencyBalance = <Self as pallet_balances::Config>::Balance;
|
||||||
type UnixTime = pallet_timestamp::Pallet<Self>;
|
type UnixTime = pallet_timestamp::Pallet<Self>;
|
||||||
@@ -179,6 +181,7 @@ impl pallet_staking::Config for Test {
|
|||||||
type GenesisElectionProvider = Self::ElectionProvider;
|
type GenesisElectionProvider = Self::ElectionProvider;
|
||||||
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
||||||
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
||||||
|
type NominationsQuota = pallet_staking::FixedNominationsQuota<16>;
|
||||||
type MaxUnlockingChunks = ConstU32<32>;
|
type MaxUnlockingChunks = ConstU32<32>;
|
||||||
type HistoryDepth = ConstU32<84>;
|
type HistoryDepth = ConstU32<84>;
|
||||||
type EventListeners = ();
|
type EventListeners = ();
|
||||||
|
|||||||
@@ -18,7 +18,10 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate as root_offences;
|
use crate as root_offences;
|
||||||
|
|
||||||
use frame_election_provider_support::{onchain, SequentialPhragmen};
|
use frame_election_provider_support::{
|
||||||
|
bounds::{ElectionBounds, ElectionBoundsBuilder},
|
||||||
|
onchain, SequentialPhragmen,
|
||||||
|
};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
parameter_types,
|
parameter_types,
|
||||||
traits::{ConstU32, ConstU64, Hooks, OneSessionHandler},
|
traits::{ConstU32, ConstU64, Hooks, OneSessionHandler},
|
||||||
@@ -134,6 +137,10 @@ pallet_staking_reward_curve::build! {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parameter_types! {
|
||||||
|
pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build();
|
||||||
|
}
|
||||||
|
|
||||||
pub struct OnChainSeqPhragmen;
|
pub struct OnChainSeqPhragmen;
|
||||||
impl onchain::Config for OnChainSeqPhragmen {
|
impl onchain::Config for OnChainSeqPhragmen {
|
||||||
type System = Test;
|
type System = Test;
|
||||||
@@ -141,8 +148,7 @@ impl onchain::Config for OnChainSeqPhragmen {
|
|||||||
type DataProvider = Staking;
|
type DataProvider = Staking;
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
type MaxWinners = ConstU32<100>;
|
type MaxWinners = ConstU32<100>;
|
||||||
type VotersBound = ConstU32<{ u32::MAX }>;
|
type Bounds = ElectionsBounds;
|
||||||
type TargetsBound = ConstU32<{ u32::MAX }>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parameter_types! {
|
parameter_types! {
|
||||||
@@ -157,7 +163,6 @@ parameter_types! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl pallet_staking::Config for Test {
|
impl pallet_staking::Config for Test {
|
||||||
type MaxNominations = ConstU32<16>;
|
|
||||||
type Currency = Balances;
|
type Currency = Balances;
|
||||||
type CurrencyBalance = <Self as pallet_balances::Config>::Balance;
|
type CurrencyBalance = <Self as pallet_balances::Config>::Balance;
|
||||||
type UnixTime = Timestamp;
|
type UnixTime = Timestamp;
|
||||||
@@ -178,6 +183,7 @@ impl pallet_staking::Config for Test {
|
|||||||
type ElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
|
type ElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
|
||||||
type GenesisElectionProvider = Self::ElectionProvider;
|
type GenesisElectionProvider = Self::ElectionProvider;
|
||||||
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
||||||
|
type NominationsQuota = pallet_staking::FixedNominationsQuota<16>;
|
||||||
type MaxUnlockingChunks = ConstU32<32>;
|
type MaxUnlockingChunks = ConstU32<32>;
|
||||||
type HistoryDepth = ConstU32<84>;
|
type HistoryDepth = ConstU32<84>;
|
||||||
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin};
|
|||||||
use pallet_session::{historical::Pallet as Historical, Pallet as Session, *};
|
use pallet_session::{historical::Pallet as Historical, Pallet as Session, *};
|
||||||
use pallet_staking::{
|
use pallet_staking::{
|
||||||
benchmarking::create_validator_with_nominators, testing_utils::create_validators,
|
benchmarking::create_validator_with_nominators, testing_utils::create_validators,
|
||||||
RewardDestination,
|
MaxNominationsOf, RewardDestination,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_VALIDATORS: u32 = 1000;
|
const MAX_VALIDATORS: u32 = 1000;
|
||||||
@@ -54,10 +54,10 @@ impl<T: Config> OnInitialize<BlockNumberFor<T>> for Pallet<T> {
|
|||||||
|
|
||||||
benchmarks! {
|
benchmarks! {
|
||||||
set_keys {
|
set_keys {
|
||||||
let n = <T as pallet_staking::Config>::MaxNominations::get();
|
let n = MaxNominationsOf::<T>::get();
|
||||||
let (v_stash, _) = create_validator_with_nominators::<T>(
|
let (v_stash, _) = create_validator_with_nominators::<T>(
|
||||||
n,
|
n,
|
||||||
<T as pallet_staking::Config>::MaxNominations::get(),
|
MaxNominationsOf::<T>::get(),
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
RewardDestination::Staked,
|
RewardDestination::Staked,
|
||||||
@@ -72,10 +72,10 @@ benchmarks! {
|
|||||||
}: _(RawOrigin::Signed(v_controller), keys, proof)
|
}: _(RawOrigin::Signed(v_controller), keys, proof)
|
||||||
|
|
||||||
purge_keys {
|
purge_keys {
|
||||||
let n = <T as pallet_staking::Config>::MaxNominations::get();
|
let n = MaxNominationsOf::<T>::get();
|
||||||
let (v_stash, _) = create_validator_with_nominators::<T>(
|
let (v_stash, _) = create_validator_with_nominators::<T>(
|
||||||
n,
|
n,
|
||||||
<T as pallet_staking::Config>::MaxNominations::get(),
|
MaxNominationsOf::<T>::get(),
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
RewardDestination::Staked,
|
RewardDestination::Staked,
|
||||||
|
|||||||
@@ -19,7 +19,10 @@
|
|||||||
|
|
||||||
#![cfg(test)]
|
#![cfg(test)]
|
||||||
|
|
||||||
use frame_election_provider_support::{onchain, SequentialPhragmen};
|
use frame_election_provider_support::{
|
||||||
|
bounds::{ElectionBounds, ElectionBoundsBuilder},
|
||||||
|
onchain, SequentialPhragmen,
|
||||||
|
};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
parameter_types,
|
parameter_types,
|
||||||
traits::{ConstU32, ConstU64},
|
traits::{ConstU32, ConstU64},
|
||||||
@@ -140,6 +143,7 @@ pallet_staking_reward_curve::build! {
|
|||||||
}
|
}
|
||||||
parameter_types! {
|
parameter_types! {
|
||||||
pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS;
|
pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS;
|
||||||
|
pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OnChainSeqPhragmen;
|
pub struct OnChainSeqPhragmen;
|
||||||
@@ -149,12 +153,10 @@ impl onchain::Config for OnChainSeqPhragmen {
|
|||||||
type DataProvider = Staking;
|
type DataProvider = Staking;
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
type MaxWinners = ConstU32<100>;
|
type MaxWinners = ConstU32<100>;
|
||||||
type VotersBound = ConstU32<{ u32::MAX }>;
|
type Bounds = ElectionsBounds;
|
||||||
type TargetsBound = ConstU32<{ u32::MAX }>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl pallet_staking::Config for Test {
|
impl pallet_staking::Config for Test {
|
||||||
type MaxNominations = ConstU32<16>;
|
|
||||||
type Currency = Balances;
|
type Currency = Balances;
|
||||||
type CurrencyBalance = <Self as pallet_balances::Config>::Balance;
|
type CurrencyBalance = <Self as pallet_balances::Config>::Balance;
|
||||||
type UnixTime = pallet_timestamp::Pallet<Self>;
|
type UnixTime = pallet_timestamp::Pallet<Self>;
|
||||||
@@ -178,6 +180,7 @@ impl pallet_staking::Config for Test {
|
|||||||
type HistoryDepth = ConstU32<84>;
|
type HistoryDepth = ConstU32<84>;
|
||||||
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
||||||
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
||||||
|
type NominationsQuota = pallet_staking::FixedNominationsQuota<16>;
|
||||||
type EventListeners = ();
|
type EventListeners = ();
|
||||||
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
|
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use crate::{ConfigOp, Pallet as Staking};
|
|||||||
use testing_utils::*;
|
use testing_utils::*;
|
||||||
|
|
||||||
use codec::Decode;
|
use codec::Decode;
|
||||||
use frame_election_provider_support::SortedListProvider;
|
use frame_election_provider_support::{bounds::DataProviderBounds, SortedListProvider};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
dispatch::UnfilteredDispatchable,
|
dispatch::UnfilteredDispatchable,
|
||||||
pallet_prelude::*,
|
pallet_prelude::*,
|
||||||
@@ -338,7 +338,7 @@ benchmarks! {
|
|||||||
|
|
||||||
validate {
|
validate {
|
||||||
let (stash, controller) = create_stash_controller::<T>(
|
let (stash, controller) = create_stash_controller::<T>(
|
||||||
T::MaxNominations::get() - 1,
|
MaxNominationsOf::<T>::get() - 1,
|
||||||
100,
|
100,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
)?;
|
)?;
|
||||||
@@ -362,11 +362,11 @@ benchmarks! {
|
|||||||
|
|
||||||
// these are the other validators; there are `T::MaxNominations::get() - 1` of them, so
|
// these are the other validators; there are `T::MaxNominations::get() - 1` of them, so
|
||||||
// there are a total of `T::MaxNominations::get()` validators in the system.
|
// there are a total of `T::MaxNominations::get()` validators in the system.
|
||||||
let rest_of_validators = create_validators_with_seed::<T>(T::MaxNominations::get() - 1, 100, 415)?;
|
let rest_of_validators = create_validators_with_seed::<T>(MaxNominationsOf::<T>::get() - 1, 100, 415)?;
|
||||||
|
|
||||||
// this is the validator that will be kicking.
|
// this is the validator that will be kicking.
|
||||||
let (stash, controller) = create_stash_controller::<T>(
|
let (stash, controller) = create_stash_controller::<T>(
|
||||||
T::MaxNominations::get() - 1,
|
MaxNominationsOf::<T>::get() - 1,
|
||||||
100,
|
100,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
)?;
|
)?;
|
||||||
@@ -381,7 +381,7 @@ benchmarks! {
|
|||||||
for i in 0 .. k {
|
for i in 0 .. k {
|
||||||
// create a nominator stash.
|
// create a nominator stash.
|
||||||
let (n_stash, n_controller) = create_stash_controller::<T>(
|
let (n_stash, n_controller) = create_stash_controller::<T>(
|
||||||
T::MaxNominations::get() + i,
|
MaxNominationsOf::<T>::get() + i,
|
||||||
100,
|
100,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
)?;
|
)?;
|
||||||
@@ -418,7 +418,7 @@ benchmarks! {
|
|||||||
|
|
||||||
// Worst case scenario, T::MaxNominations::get()
|
// Worst case scenario, T::MaxNominations::get()
|
||||||
nominate {
|
nominate {
|
||||||
let n in 1 .. T::MaxNominations::get();
|
let n in 1 .. MaxNominationsOf::<T>::get();
|
||||||
|
|
||||||
// clean up any existing state.
|
// clean up any existing state.
|
||||||
clear_validators_and_nominators::<T>();
|
clear_validators_and_nominators::<T>();
|
||||||
@@ -429,7 +429,7 @@ benchmarks! {
|
|||||||
// we are just doing an insert into the origin position.
|
// we are just doing an insert into the origin position.
|
||||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||||
let (stash, controller) = create_stash_controller_with_balance::<T>(
|
let (stash, controller) = create_stash_controller_with_balance::<T>(
|
||||||
SEED + T::MaxNominations::get() + 1, // make sure the account does not conflict with others
|
SEED + MaxNominationsOf::<T>::get() + 1, // make sure the account does not conflict with others
|
||||||
origin_weight,
|
origin_weight,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
).unwrap();
|
).unwrap();
|
||||||
@@ -711,7 +711,7 @@ benchmarks! {
|
|||||||
create_validators_with_nominators_for_era::<T>(
|
create_validators_with_nominators_for_era::<T>(
|
||||||
v,
|
v,
|
||||||
n,
|
n,
|
||||||
<T as Config>::MaxNominations::get() as usize,
|
MaxNominationsOf::<T>::get() as usize,
|
||||||
false,
|
false,
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
@@ -729,7 +729,7 @@ benchmarks! {
|
|||||||
create_validators_with_nominators_for_era::<T>(
|
create_validators_with_nominators_for_era::<T>(
|
||||||
v,
|
v,
|
||||||
n,
|
n,
|
||||||
<T as Config>::MaxNominations::get() as usize,
|
MaxNominationsOf::<T>::get() as usize,
|
||||||
false,
|
false,
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
@@ -808,7 +808,7 @@ benchmarks! {
|
|||||||
let n in (MaxNominators::<T>::get() / 2) .. MaxNominators::<T>::get();
|
let n in (MaxNominators::<T>::get() / 2) .. MaxNominators::<T>::get();
|
||||||
|
|
||||||
let validators = create_validators_with_nominators_for_era::<T>(
|
let validators = create_validators_with_nominators_for_era::<T>(
|
||||||
v, n, T::MaxNominations::get() as usize, false, None
|
v, n, MaxNominationsOf::<T>::get() as usize, false, None
|
||||||
)?
|
)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|v| T::Lookup::lookup(v).unwrap())
|
.map(|v| T::Lookup::lookup(v).unwrap())
|
||||||
@@ -819,7 +819,8 @@ benchmarks! {
|
|||||||
|
|
||||||
let num_voters = (v + n) as usize;
|
let num_voters = (v + n) as usize;
|
||||||
}: {
|
}: {
|
||||||
let voters = <Staking<T>>::get_npos_voters(None);
|
// default bounds are unbounded.
|
||||||
|
let voters = <Staking<T>>::get_npos_voters(DataProviderBounds::default());
|
||||||
assert_eq!(voters.len(), num_voters);
|
assert_eq!(voters.len(), num_voters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -830,10 +831,11 @@ benchmarks! {
|
|||||||
let n = MaxNominators::<T>::get();
|
let n = MaxNominators::<T>::get();
|
||||||
|
|
||||||
let _ = create_validators_with_nominators_for_era::<T>(
|
let _ = create_validators_with_nominators_for_era::<T>(
|
||||||
v, n, T::MaxNominations::get() as usize, false, None
|
v, n, MaxNominationsOf::<T>::get() as usize, false, None
|
||||||
)?;
|
)?;
|
||||||
}: {
|
}: {
|
||||||
let targets = <Staking<T>>::get_npos_targets(None);
|
// default bounds are unbounded.
|
||||||
|
let targets = <Staking<T>>::get_npos_targets(DataProviderBounds::default());
|
||||||
assert_eq!(targets.len() as u32, v);
|
assert_eq!(targets.len() as u32, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -961,7 +963,7 @@ mod tests {
|
|||||||
create_validators_with_nominators_for_era::<Test>(
|
create_validators_with_nominators_for_era::<Test>(
|
||||||
v,
|
v,
|
||||||
n,
|
n,
|
||||||
<Test as Config>::MaxNominations::get() as usize,
|
MaxNominationsOf::<Test>::get() as usize,
|
||||||
false,
|
false,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,259 @@
|
|||||||
|
// This file is part of Substrate.
|
||||||
|
|
||||||
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//! ## A static size tracker for the election snapshot data.
|
||||||
|
//!
|
||||||
|
//! ### Overview
|
||||||
|
//!
|
||||||
|
//! The goal of the size tracker is to provide a static, no-allocation byte tracker to be
|
||||||
|
//! used by the election data provider when preparing the results of
|
||||||
|
//! [`ElectionDataProvider::electing_voters`]. The [`StaticTracker`] implementation uses
|
||||||
|
//! [`codec::Encode::size_hint`] to estimate the SCALE encoded size of the snapshot voters struct
|
||||||
|
//! as it is being constructed without requiring extra stack allocations.
|
||||||
|
//!
|
||||||
|
//! The [`StaticTracker::try_register_voter`] is called to update the static tracker internal
|
||||||
|
//! state, if It will return an error if the resulting SCALE encoded size (in bytes) is larger than
|
||||||
|
//! the provided `DataProviderBounds`.
|
||||||
|
//!
|
||||||
|
//! ### Example
|
||||||
|
//!
|
||||||
|
//! ```ignore
|
||||||
|
//! use pallet_staking::election_size_tracker::*;
|
||||||
|
//!
|
||||||
|
//! // instantiates a new tracker.
|
||||||
|
//! let mut size_tracker = StaticTracker::<Staking>::default();
|
||||||
|
//!
|
||||||
|
//! let voter_bounds = ElectionBoundsBuilder::default().voter_size(1_00.into()).build().voters;
|
||||||
|
//!
|
||||||
|
//! let mut sorted_voters = T::VoterList.iter();
|
||||||
|
//! let mut selected_voters = vec![];
|
||||||
|
//!
|
||||||
|
//! // fit as many voters in the vec as the bounds permit.
|
||||||
|
//! for v in sorted_voters {
|
||||||
|
//! let voter = (v, weight_of(&v), targets_of(&v));
|
||||||
|
//! if size_tracker.try_register_voter(&voter, &voter_bounds).is_err() {
|
||||||
|
//! // voter bounds size exhausted
|
||||||
|
//! break;
|
||||||
|
//! }
|
||||||
|
//! selected_voters.push(voter);
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // The SCALE encoded size in bytes of `selected_voters` is guaranteed to be below
|
||||||
|
//! // `voter_bounds`.
|
||||||
|
//! debug_assert!(
|
||||||
|
//! selected_voters.encoded_size() <=
|
||||||
|
//! SizeTracker::<Staking>::final_byte_size_of(size_tracker.num_voters, size_tracker.size)
|
||||||
|
//! );
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Implementation Details
|
||||||
|
//!
|
||||||
|
//! The current implementation of the static tracker is tightly coupled with the staking pallet
|
||||||
|
//! implementation, namely the representation of a voter ([`VoterOf`]). The SCALE encoded byte size
|
||||||
|
//! is calculated using [`Encode::size_hint`] of each type in the voter tuple. Each voter's byte
|
||||||
|
//! size is the sum of:
|
||||||
|
//! - 1 * [`Encode::size_hint`] of the `AccountId` type;
|
||||||
|
//! - 1 * [`Encode::size_hint`] of the `VoteWeight` type;
|
||||||
|
//! - `num_votes` * [`Encode::size_hint`] of the `AccountId` type.
|
||||||
|
|
||||||
|
use codec::Encode;
|
||||||
|
use frame_election_provider_support::{
|
||||||
|
bounds::{DataProviderBounds, SizeBound},
|
||||||
|
ElectionDataProvider, VoterOf,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Keeps track of the SCALE encoded byte length of the snapshot's voters or targets.
|
||||||
|
///
|
||||||
|
/// The tracker calculates the bytes used based on static rules, without requiring any actual
|
||||||
|
/// encoding or extra allocations.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct StaticTracker<DataProvider> {
|
||||||
|
pub size: usize,
|
||||||
|
pub counter: usize,
|
||||||
|
_marker: sp_std::marker::PhantomData<DataProvider>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<DataProvider> Default for StaticTracker<DataProvider> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { size: 0, counter: 0, _marker: Default::default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<DataProvider> StaticTracker<DataProvider>
|
||||||
|
where
|
||||||
|
DataProvider: ElectionDataProvider,
|
||||||
|
{
|
||||||
|
/// Tries to register a new voter.
|
||||||
|
///
|
||||||
|
/// If the new voter exhausts the provided bounds, return an error. Otherwise, the internal
|
||||||
|
/// state of the tracker is updated with the new registered voter.
|
||||||
|
pub fn try_register_voter(
|
||||||
|
&mut self,
|
||||||
|
voter: &VoterOf<DataProvider>,
|
||||||
|
bounds: &DataProviderBounds,
|
||||||
|
) -> Result<(), ()> {
|
||||||
|
let tracker_size_after = {
|
||||||
|
let voter_hint = Self::voter_size_hint(voter);
|
||||||
|
Self::final_byte_size_of(self.counter + 1, self.size.saturating_add(voter_hint))
|
||||||
|
};
|
||||||
|
|
||||||
|
match bounds.size_exhausted(SizeBound(tracker_size_after as u32)) {
|
||||||
|
true => Err(()),
|
||||||
|
false => {
|
||||||
|
self.size = tracker_size_after;
|
||||||
|
self.counter += 1;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates the size of the voter to register based on [`Encode::size_hint`].
|
||||||
|
fn voter_size_hint(voter: &VoterOf<DataProvider>) -> usize {
|
||||||
|
let (voter_account, vote_weight, targets) = voter;
|
||||||
|
|
||||||
|
voter_account
|
||||||
|
.size_hint()
|
||||||
|
.saturating_add(vote_weight.size_hint())
|
||||||
|
.saturating_add(voter_account.size_hint().saturating_mul(targets.len()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to register a new target.
|
||||||
|
///
|
||||||
|
/// If the new target exhausts the provided bounds, return an error. Otherwise, the internal
|
||||||
|
/// state of the tracker is updated with the new registered target.
|
||||||
|
pub fn try_register_target(
|
||||||
|
&mut self,
|
||||||
|
target: DataProvider::AccountId,
|
||||||
|
bounds: &DataProviderBounds,
|
||||||
|
) -> Result<(), ()> {
|
||||||
|
let tracker_size_after = Self::final_byte_size_of(
|
||||||
|
self.counter + 1,
|
||||||
|
self.size.saturating_add(target.size_hint()),
|
||||||
|
);
|
||||||
|
|
||||||
|
match bounds.size_exhausted(SizeBound(tracker_size_after as u32)) {
|
||||||
|
true => Err(()),
|
||||||
|
false => {
|
||||||
|
self.size = tracker_size_after;
|
||||||
|
self.counter += 1;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Size of the SCALE encoded prefix with a given length.
|
||||||
|
#[inline]
|
||||||
|
fn length_prefix(len: usize) -> usize {
|
||||||
|
use codec::{Compact, CompactLen};
|
||||||
|
Compact::<u32>::compact_len(&(len as u32))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates the final size in bytes of the SCALE encoded snapshot voter struct.
|
||||||
|
fn final_byte_size_of(num_voters: usize, size: usize) -> usize {
|
||||||
|
Self::length_prefix(num_voters).saturating_add(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
mock::{AccountId, Staking, Test},
|
||||||
|
BoundedVec, MaxNominationsOf,
|
||||||
|
};
|
||||||
|
use frame_election_provider_support::bounds::ElectionBoundsBuilder;
|
||||||
|
use sp_core::bounded_vec;
|
||||||
|
|
||||||
|
type Voters = BoundedVec<AccountId, MaxNominationsOf<Test>>;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn election_size_tracker_works() {
|
||||||
|
let mut voters: Vec<(u64, u64, Voters)> = vec![];
|
||||||
|
let mut size_tracker = StaticTracker::<Staking>::default();
|
||||||
|
let voter_bounds = ElectionBoundsBuilder::default().voters_size(1_50.into()).build().voters;
|
||||||
|
|
||||||
|
// register 1 voter with 1 vote.
|
||||||
|
let voter = (1, 10, bounded_vec![2]);
|
||||||
|
assert!(size_tracker.try_register_voter(&voter, &voter_bounds).is_ok());
|
||||||
|
voters.push(voter);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
StaticTracker::<Staking>::final_byte_size_of(size_tracker.counter, size_tracker.size),
|
||||||
|
voters.encoded_size()
|
||||||
|
);
|
||||||
|
|
||||||
|
// register another voter, now with 3 votes.
|
||||||
|
let voter = (2, 20, bounded_vec![3, 4, 5]);
|
||||||
|
assert!(size_tracker.try_register_voter(&voter, &voter_bounds).is_ok());
|
||||||
|
voters.push(voter);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
StaticTracker::<Staking>::final_byte_size_of(size_tracker.counter, size_tracker.size),
|
||||||
|
voters.encoded_size()
|
||||||
|
);
|
||||||
|
|
||||||
|
// register noop vote (unlikely to happen).
|
||||||
|
let voter = (3, 30, bounded_vec![]);
|
||||||
|
assert!(size_tracker.try_register_voter(&voter, &voter_bounds).is_ok());
|
||||||
|
voters.push(voter);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
StaticTracker::<Staking>::final_byte_size_of(size_tracker.counter, size_tracker.size),
|
||||||
|
voters.encoded_size()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn election_size_tracker_bounds_works() {
|
||||||
|
let mut voters: Vec<(u64, u64, Voters)> = vec![];
|
||||||
|
let mut size_tracker = StaticTracker::<Staking>::default();
|
||||||
|
let voter_bounds = ElectionBoundsBuilder::default().voters_size(1_00.into()).build().voters;
|
||||||
|
|
||||||
|
let voter = (1, 10, bounded_vec![2]);
|
||||||
|
assert!(size_tracker.try_register_voter(&voter, &voter_bounds).is_ok());
|
||||||
|
voters.push(voter);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
StaticTracker::<Staking>::final_byte_size_of(size_tracker.counter, size_tracker.size),
|
||||||
|
voters.encoded_size()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(size_tracker.size > 0 && size_tracker.size < 1_00);
|
||||||
|
let size_before_overflow = size_tracker.size;
|
||||||
|
|
||||||
|
// try many voters that will overflow the tracker's buffer.
|
||||||
|
let voter = (2, 10, bounded_vec![2, 3, 4, 5, 6, 7, 8, 9]);
|
||||||
|
voters.push(voter.clone());
|
||||||
|
|
||||||
|
assert!(size_tracker.try_register_voter(&voter, &voter_bounds).is_err());
|
||||||
|
assert!(size_tracker.size > 0 && size_tracker.size < 1_00);
|
||||||
|
|
||||||
|
// size of the tracker did not update when trying to register votes failed.
|
||||||
|
assert_eq!(size_tracker.size, size_before_overflow);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn len_prefix_works() {
|
||||||
|
let length_samples =
|
||||||
|
vec![0usize, 1, 62, 63, 64, 16383, 16384, 16385, 1073741822, 1073741823, 1073741824];
|
||||||
|
|
||||||
|
for s in length_samples {
|
||||||
|
// the encoded size of a vector of n bytes should be n + the length prefix
|
||||||
|
assert_eq!(vec![1u8; s].encoded_size(), StaticTracker::<Staking>::length_prefix(s) + s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -292,6 +292,7 @@ pub(crate) mod mock;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
pub mod election_size_tracker;
|
||||||
pub mod inflation;
|
pub mod inflation;
|
||||||
pub mod migrations;
|
pub mod migrations;
|
||||||
pub mod slashing;
|
pub mod slashing;
|
||||||
@@ -301,7 +302,7 @@ mod pallet;
|
|||||||
|
|
||||||
use codec::{Decode, Encode, HasCompact, MaxEncodedLen};
|
use codec::{Decode, Encode, HasCompact, MaxEncodedLen};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
traits::{Currency, Defensive, Get},
|
traits::{ConstU32, Currency, Defensive, Get},
|
||||||
weights::Weight,
|
weights::Weight,
|
||||||
BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
|
BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
|
||||||
};
|
};
|
||||||
@@ -338,6 +339,10 @@ macro_rules! log {
|
|||||||
/// pallet.
|
/// pallet.
|
||||||
pub type MaxWinnersOf<T> = <<T as Config>::ElectionProvider as frame_election_provider_support::ElectionProviderBase>::MaxWinners;
|
pub type MaxWinnersOf<T> = <<T as Config>::ElectionProvider as frame_election_provider_support::ElectionProviderBase>::MaxWinners;
|
||||||
|
|
||||||
|
/// Maximum number of nominations per nominator.
|
||||||
|
pub type MaxNominationsOf<T> =
|
||||||
|
<<T as Config>::NominationsQuota as NominationsQuota<BalanceOf<T>>>::MaxNominations;
|
||||||
|
|
||||||
/// Counter for the number of "reward" points earned by a given validator.
|
/// Counter for the number of "reward" points earned by a given validator.
|
||||||
pub type RewardPoint = u32;
|
pub type RewardPoint = u32;
|
||||||
|
|
||||||
@@ -679,7 +684,7 @@ impl<T: Config> StakingLedger<T> {
|
|||||||
#[scale_info(skip_type_params(T))]
|
#[scale_info(skip_type_params(T))]
|
||||||
pub struct Nominations<T: Config> {
|
pub struct Nominations<T: Config> {
|
||||||
/// The targets of nomination.
|
/// The targets of nomination.
|
||||||
pub targets: BoundedVec<T::AccountId, T::MaxNominations>,
|
pub targets: BoundedVec<T::AccountId, MaxNominationsOf<T>>,
|
||||||
/// The era the nominations were submitted.
|
/// The era the nominations were submitted.
|
||||||
///
|
///
|
||||||
/// Except for initial nominations which are considered submitted at era 0.
|
/// Except for initial nominations which are considered submitted at era 0.
|
||||||
@@ -749,6 +754,36 @@ impl<AccountId, Balance: HasCompact + Zero> UnappliedSlash<AccountId, Balance> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Something that defines the maximum number of nominations per nominator based on a curve.
|
||||||
|
///
|
||||||
|
/// The method `curve` implements the nomination quota curve and should not be used directly.
|
||||||
|
/// However, `get_quota` returns the bounded maximum number of nominations based on `fn curve` and
|
||||||
|
/// the nominator's balance.
|
||||||
|
pub trait NominationsQuota<Balance> {
|
||||||
|
/// Strict maximum number of nominations that caps the nominations curve. This value can be
|
||||||
|
/// used as the upper bound of the number of votes per nominator.
|
||||||
|
type MaxNominations: Get<u32>;
|
||||||
|
|
||||||
|
/// Returns the voter's nomination quota within reasonable bounds [`min`, `max`], where `min`
|
||||||
|
/// is 1 and `max` is `Self::MaxNominations`.
|
||||||
|
fn get_quota(balance: Balance) -> u32 {
|
||||||
|
Self::curve(balance).clamp(1, Self::MaxNominations::get())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the voter's nomination quota based on its balance and a curve.
|
||||||
|
fn curve(balance: Balance) -> u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A nomination quota that allows up to MAX nominations for all validators.
|
||||||
|
pub struct FixedNominationsQuota<const MAX: u32>;
|
||||||
|
impl<Balance, const MAX: u32> NominationsQuota<Balance> for FixedNominationsQuota<MAX> {
|
||||||
|
type MaxNominations = ConstU32<MAX>;
|
||||||
|
|
||||||
|
fn curve(_: Balance) -> u32 {
|
||||||
|
MAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Means for interacting with a specialized version of the `session` trait.
|
/// Means for interacting with a specialized version of the `session` trait.
|
||||||
///
|
///
|
||||||
/// This is needed because `Staking` sets the `ValidatorIdOf` of the `pallet_session::Config`
|
/// This is needed because `Staking` sets the `ValidatorIdOf` of the `pallet_session::Config`
|
||||||
|
|||||||
@@ -18,7 +18,10 @@
|
|||||||
//! Test utilities
|
//! Test utilities
|
||||||
|
|
||||||
use crate::{self as pallet_staking, *};
|
use crate::{self as pallet_staking, *};
|
||||||
use frame_election_provider_support::{onchain, SequentialPhragmen, VoteWeight};
|
use frame_election_provider_support::{
|
||||||
|
bounds::{ElectionBounds, ElectionBoundsBuilder},
|
||||||
|
onchain, SequentialPhragmen, VoteWeight,
|
||||||
|
};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
assert_ok, ord_parameter_types, parameter_types,
|
assert_ok, ord_parameter_types, parameter_types,
|
||||||
traits::{
|
traits::{
|
||||||
@@ -228,11 +231,12 @@ const THRESHOLDS: [sp_npos_elections::VoteWeight; 9] =
|
|||||||
|
|
||||||
parameter_types! {
|
parameter_types! {
|
||||||
pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS;
|
pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS;
|
||||||
pub static MaxNominations: u32 = 16;
|
|
||||||
pub static HistoryDepth: u32 = 80;
|
pub static HistoryDepth: u32 = 80;
|
||||||
pub static MaxUnlockingChunks: u32 = 32;
|
pub static MaxUnlockingChunks: u32 = 32;
|
||||||
pub static RewardOnUnbalanceWasCalled: bool = false;
|
pub static RewardOnUnbalanceWasCalled: bool = false;
|
||||||
pub static MaxWinners: u32 = 100;
|
pub static MaxWinners: u32 = 100;
|
||||||
|
pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build();
|
||||||
|
pub static AbsoluteMaxNominations: u32 = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
type VoterBagsListInstance = pallet_bags_list::Instance1;
|
type VoterBagsListInstance = pallet_bags_list::Instance1;
|
||||||
@@ -252,8 +256,7 @@ impl onchain::Config for OnChainSeqPhragmen {
|
|||||||
type DataProvider = Staking;
|
type DataProvider = Staking;
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
type MaxWinners = MaxWinners;
|
type MaxWinners = MaxWinners;
|
||||||
type VotersBound = ConstU32<{ u32::MAX }>;
|
type Bounds = ElectionsBounds;
|
||||||
type TargetsBound = ConstU32<{ u32::MAX }>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MockReward {}
|
pub struct MockReward {}
|
||||||
@@ -281,7 +284,6 @@ impl OnStakingUpdate<AccountId, Balance> for EventListenerMock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl crate::pallet::pallet::Config for Test {
|
impl crate::pallet::pallet::Config for Test {
|
||||||
type MaxNominations = MaxNominations;
|
|
||||||
type Currency = Balances;
|
type Currency = Balances;
|
||||||
type CurrencyBalance = <Self as pallet_balances::Config>::Balance;
|
type CurrencyBalance = <Self as pallet_balances::Config>::Balance;
|
||||||
type UnixTime = Timestamp;
|
type UnixTime = Timestamp;
|
||||||
@@ -304,6 +306,7 @@ impl crate::pallet::pallet::Config for Test {
|
|||||||
// NOTE: consider a macro and use `UseNominatorsAndValidatorsMap<Self>` as well.
|
// NOTE: consider a macro and use `UseNominatorsAndValidatorsMap<Self>` as well.
|
||||||
type VoterList = VoterBagsList;
|
type VoterList = VoterBagsList;
|
||||||
type TargetList = UseValidatorsMap<Self>;
|
type TargetList = UseValidatorsMap<Self>;
|
||||||
|
type NominationsQuota = WeightedNominationsQuota<16>;
|
||||||
type MaxUnlockingChunks = MaxUnlockingChunks;
|
type MaxUnlockingChunks = MaxUnlockingChunks;
|
||||||
type HistoryDepth = HistoryDepth;
|
type HistoryDepth = HistoryDepth;
|
||||||
type EventListeners = EventListenerMock;
|
type EventListeners = EventListenerMock;
|
||||||
@@ -311,6 +314,25 @@ impl crate::pallet::pallet::Config for Test {
|
|||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct WeightedNominationsQuota<const MAX: u32>;
|
||||||
|
impl<Balance, const MAX: u32> NominationsQuota<Balance> for WeightedNominationsQuota<MAX>
|
||||||
|
where
|
||||||
|
u128: From<Balance>,
|
||||||
|
{
|
||||||
|
type MaxNominations = AbsoluteMaxNominations;
|
||||||
|
|
||||||
|
fn curve(balance: Balance) -> u32 {
|
||||||
|
match balance.into() {
|
||||||
|
// random curve for testing.
|
||||||
|
0..=110 => MAX,
|
||||||
|
111 => 0,
|
||||||
|
222 => 2,
|
||||||
|
333 => MAX + 10,
|
||||||
|
_ => MAX,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) type StakingCall = crate::Call<Test>;
|
pub(crate) type StakingCall = crate::Call<Test>;
|
||||||
pub(crate) type TestCall = <Test as frame_system::Config>::RuntimeCall;
|
pub(crate) type TestCall = <Test as frame_system::Config>::RuntimeCall;
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,9 @@
|
|||||||
//! Implementations for the Staking FRAME Pallet.
|
//! Implementations for the Staking FRAME Pallet.
|
||||||
|
|
||||||
use frame_election_provider_support::{
|
use frame_election_provider_support::{
|
||||||
data_provider, BoundedSupportsOf, ElectionDataProvider, ElectionProvider, ScoreProvider,
|
bounds::{CountBound, SizeBound},
|
||||||
SortedListProvider, VoteWeight, VoterOf,
|
data_provider, BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider,
|
||||||
|
ScoreProvider, SortedListProvider, VoteWeight, VoterOf,
|
||||||
};
|
};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
defensive,
|
defensive,
|
||||||
@@ -45,8 +46,9 @@ use sp_staking::{
|
|||||||
use sp_std::prelude::*;
|
use sp_std::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraPayout, Exposure, ExposureOf,
|
election_size_tracker::StaticTracker, log, slashing, weights::WeightInfo, ActiveEraInfo,
|
||||||
Forcing, IndividualExposure, MaxWinnersOf, Nominations, PositiveImbalanceOf, RewardDestination,
|
BalanceOf, EraPayout, Exposure, ExposureOf, Forcing, IndividualExposure, MaxNominationsOf,
|
||||||
|
MaxWinnersOf, Nominations, NominationsQuota, PositiveImbalanceOf, RewardDestination,
|
||||||
SessionInterface, StakingLedger, ValidatorPrefs,
|
SessionInterface, StakingLedger, ValidatorPrefs,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -761,13 +763,15 @@ impl<T: Config> Pallet<T> {
|
|||||||
/// nominators.
|
/// nominators.
|
||||||
///
|
///
|
||||||
/// This function is self-weighing as [`DispatchClass::Mandatory`].
|
/// This function is self-weighing as [`DispatchClass::Mandatory`].
|
||||||
pub fn get_npos_voters(maybe_max_len: Option<usize>) -> Vec<VoterOf<Self>> {
|
pub fn get_npos_voters(bounds: DataProviderBounds) -> Vec<VoterOf<Self>> {
|
||||||
let max_allowed_len = {
|
let mut voters_size_tracker: StaticTracker<Self> = StaticTracker::default();
|
||||||
let all_voter_count = T::VoterList::count() as usize;
|
|
||||||
maybe_max_len.unwrap_or(all_voter_count).min(all_voter_count)
|
let final_predicted_len = {
|
||||||
|
let all_voter_count = T::VoterList::count();
|
||||||
|
bounds.count.unwrap_or(all_voter_count.into()).min(all_voter_count.into()).0
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut all_voters = Vec::<_>::with_capacity(max_allowed_len);
|
let mut all_voters = Vec::<_>::with_capacity(final_predicted_len as usize);
|
||||||
|
|
||||||
// cache a few things.
|
// cache a few things.
|
||||||
let weight_of = Self::weight_of_fn();
|
let weight_of = Self::weight_of_fn();
|
||||||
@@ -778,8 +782,8 @@ impl<T: Config> Pallet<T> {
|
|||||||
let mut min_active_stake = u64::MAX;
|
let mut min_active_stake = u64::MAX;
|
||||||
|
|
||||||
let mut sorted_voters = T::VoterList::iter();
|
let mut sorted_voters = T::VoterList::iter();
|
||||||
while all_voters.len() < max_allowed_len &&
|
while all_voters.len() < final_predicted_len as usize &&
|
||||||
voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * max_allowed_len as u32)
|
voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32)
|
||||||
{
|
{
|
||||||
let voter = match sorted_voters.next() {
|
let voter = match sorted_voters.next() {
|
||||||
Some(voter) => {
|
Some(voter) => {
|
||||||
@@ -798,10 +802,23 @@ impl<T: Config> Pallet<T> {
|
|||||||
|
|
||||||
if let Some(Nominations { targets, .. }) = <Nominators<T>>::get(&voter) {
|
if let Some(Nominations { targets, .. }) = <Nominators<T>>::get(&voter) {
|
||||||
if !targets.is_empty() {
|
if !targets.is_empty() {
|
||||||
all_voters.push((voter.clone(), voter_weight, targets));
|
// Note on lazy nomination quota: we do not check the nomination quota of the
|
||||||
|
// voter at this point and accept all the current nominations. The nomination
|
||||||
|
// quota is only enforced at `nominate` time.
|
||||||
|
|
||||||
|
let voter = (voter, voter_weight, targets);
|
||||||
|
if voters_size_tracker.try_register_voter(&voter, &bounds).is_err() {
|
||||||
|
// no more space left for the election result, stop iterating.
|
||||||
|
Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
|
||||||
|
size: voters_size_tracker.size as u32,
|
||||||
|
});
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
all_voters.push(voter);
|
||||||
nominators_taken.saturating_inc();
|
nominators_taken.saturating_inc();
|
||||||
} else {
|
} else {
|
||||||
// Technically should never happen, but not much we can do about it.
|
// technically should never happen, but not much we can do about it.
|
||||||
}
|
}
|
||||||
min_active_stake =
|
min_active_stake =
|
||||||
if voter_weight < min_active_stake { voter_weight } else { min_active_stake };
|
if voter_weight < min_active_stake { voter_weight } else { min_active_stake };
|
||||||
@@ -814,24 +831,31 @@ impl<T: Config> Pallet<T> {
|
|||||||
.try_into()
|
.try_into()
|
||||||
.expect("`MaxVotesPerVoter` must be greater than or equal to 1"),
|
.expect("`MaxVotesPerVoter` must be greater than or equal to 1"),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if voters_size_tracker.try_register_voter(&self_vote, &bounds).is_err() {
|
||||||
|
// no more space left for the election snapshot, stop iterating.
|
||||||
|
Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
|
||||||
|
size: voters_size_tracker.size as u32,
|
||||||
|
});
|
||||||
|
break
|
||||||
|
}
|
||||||
all_voters.push(self_vote);
|
all_voters.push(self_vote);
|
||||||
validators_taken.saturating_inc();
|
validators_taken.saturating_inc();
|
||||||
} else {
|
} else {
|
||||||
// this can only happen if: 1. there a bug in the bags-list (or whatever is the
|
// 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
|
// 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
|
// 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
|
// `T::NominationsQuota::get_quota`. The latter can rarely happen, and is not
|
||||||
// or bug if it does.
|
// really an emergency or bug if it does.
|
||||||
log!(
|
defensive!(
|
||||||
warn,
|
|
||||||
"DEFENSIVE: invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now",
|
"DEFENSIVE: invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now",
|
||||||
voter
|
voter,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// all_voters should have not re-allocated.
|
// all_voters should have not re-allocated.
|
||||||
debug_assert!(all_voters.capacity() == max_allowed_len);
|
debug_assert!(all_voters.capacity() == final_predicted_len as usize);
|
||||||
|
|
||||||
Self::register_weight(T::WeightInfo::get_npos_voters(validators_taken, nominators_taken));
|
Self::register_weight(T::WeightInfo::get_npos_voters(validators_taken, nominators_taken));
|
||||||
|
|
||||||
@@ -854,14 +878,20 @@ impl<T: Config> Pallet<T> {
|
|||||||
/// Get the targets for an upcoming npos election.
|
/// Get the targets for an upcoming npos election.
|
||||||
///
|
///
|
||||||
/// This function is self-weighing as [`DispatchClass::Mandatory`].
|
/// This function is self-weighing as [`DispatchClass::Mandatory`].
|
||||||
pub fn get_npos_targets(maybe_max_len: Option<usize>) -> Vec<T::AccountId> {
|
pub fn get_npos_targets(bounds: DataProviderBounds) -> Vec<T::AccountId> {
|
||||||
let max_allowed_len = maybe_max_len.unwrap_or_else(|| T::TargetList::count() as usize);
|
let mut targets_size_tracker: StaticTracker<Self> = StaticTracker::default();
|
||||||
let mut all_targets = Vec::<T::AccountId>::with_capacity(max_allowed_len);
|
|
||||||
|
let final_predicted_len = {
|
||||||
|
let all_target_count = T::TargetList::count();
|
||||||
|
bounds.count.unwrap_or(all_target_count.into()).min(all_target_count.into()).0
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut all_targets = Vec::<T::AccountId>::with_capacity(final_predicted_len as usize);
|
||||||
let mut targets_seen = 0;
|
let mut targets_seen = 0;
|
||||||
|
|
||||||
let mut targets_iter = T::TargetList::iter();
|
let mut targets_iter = T::TargetList::iter();
|
||||||
while all_targets.len() < max_allowed_len &&
|
while all_targets.len() < final_predicted_len as usize &&
|
||||||
targets_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * max_allowed_len as u32)
|
targets_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32)
|
||||||
{
|
{
|
||||||
let target = match targets_iter.next() {
|
let target = match targets_iter.next() {
|
||||||
Some(target) => {
|
Some(target) => {
|
||||||
@@ -871,6 +901,14 @@ impl<T: Config> Pallet<T> {
|
|||||||
None => break,
|
None => break,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if targets_size_tracker.try_register_target(target.clone(), &bounds).is_err() {
|
||||||
|
// no more space left for the election snapshot, stop iterating.
|
||||||
|
Self::deposit_event(Event::<T>::SnapshotTargetsSizeExceeded {
|
||||||
|
size: targets_size_tracker.size as u32,
|
||||||
|
});
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
if Validators::<T>::contains_key(&target) {
|
if Validators::<T>::contains_key(&target) {
|
||||||
all_targets.push(target);
|
all_targets.push(target);
|
||||||
}
|
}
|
||||||
@@ -989,43 +1027,48 @@ impl<T: Config> Pallet<T> {
|
|||||||
/// Returns the current nominations quota for nominators.
|
/// Returns the current nominations quota for nominators.
|
||||||
///
|
///
|
||||||
/// Used by the runtime API.
|
/// Used by the runtime API.
|
||||||
/// Note: for now, this api runtime will always return value of `T::MaxNominations` and thus it
|
pub fn api_nominations_quota(balance: BalanceOf<T>) -> u32 {
|
||||||
/// is redundant. However, with the upcoming changes in
|
T::NominationsQuota::get_quota(balance)
|
||||||
/// <https://github.com/paritytech/substrate/pull/12970>, the nominations quota will change
|
|
||||||
/// depending on the nominators balance. We're introducing this runtime API now to prepare the
|
|
||||||
/// community to use it before rolling out PR#12970.
|
|
||||||
pub fn api_nominations_quota(_balance: BalanceOf<T>) -> u32 {
|
|
||||||
T::MaxNominations::get()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config> ElectionDataProvider for Pallet<T> {
|
impl<T: Config> ElectionDataProvider for Pallet<T> {
|
||||||
type AccountId = T::AccountId;
|
type AccountId = T::AccountId;
|
||||||
type BlockNumber = BlockNumberFor<T>;
|
type BlockNumber = BlockNumberFor<T>;
|
||||||
type MaxVotesPerVoter = T::MaxNominations;
|
type MaxVotesPerVoter = MaxNominationsOf<T>;
|
||||||
|
|
||||||
fn desired_targets() -> data_provider::Result<u32> {
|
fn desired_targets() -> data_provider::Result<u32> {
|
||||||
Self::register_weight(T::DbWeight::get().reads(1));
|
Self::register_weight(T::DbWeight::get().reads(1));
|
||||||
Ok(Self::validator_count())
|
Ok(Self::validator_count())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn electing_voters(maybe_max_len: Option<usize>) -> data_provider::Result<Vec<VoterOf<Self>>> {
|
fn electing_voters(bounds: DataProviderBounds) -> data_provider::Result<Vec<VoterOf<Self>>> {
|
||||||
// This can never fail -- if `maybe_max_len` is `Some(_)` we handle it.
|
// This can never fail -- if `maybe_max_len` is `Some(_)` we handle it.
|
||||||
let voters = Self::get_npos_voters(maybe_max_len);
|
let voters = Self::get_npos_voters(bounds);
|
||||||
debug_assert!(maybe_max_len.map_or(true, |max| voters.len() <= max));
|
|
||||||
|
debug_assert!(!bounds.exhausted(
|
||||||
|
SizeBound(voters.encoded_size() as u32).into(),
|
||||||
|
CountBound(voters.len() as u32).into()
|
||||||
|
));
|
||||||
|
|
||||||
Ok(voters)
|
Ok(voters)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn electable_targets(maybe_max_len: Option<usize>) -> data_provider::Result<Vec<T::AccountId>> {
|
fn electable_targets(bounds: DataProviderBounds) -> data_provider::Result<Vec<T::AccountId>> {
|
||||||
let target_count = T::TargetList::count();
|
let targets = Self::get_npos_targets(bounds);
|
||||||
|
|
||||||
// We can't handle this case yet -- return an error.
|
// We can't handle this case yet -- return an error. WIP to improve handling this case in
|
||||||
if maybe_max_len.map_or(false, |max_len| target_count > max_len as u32) {
|
// <https://github.com/paritytech/substrate/pull/13195>.
|
||||||
|
if bounds.exhausted(None, CountBound(T::TargetList::count() as u32).into()) {
|
||||||
return Err("Target snapshot too big")
|
return Err("Target snapshot too big")
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self::get_npos_targets(None))
|
debug_assert!(!bounds.exhausted(
|
||||||
|
SizeBound(targets.encoded_size() as u32).into(),
|
||||||
|
CountBound(targets.len() as u32).into()
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(targets)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_election_prediction(now: BlockNumberFor<T>) -> BlockNumberFor<T> {
|
fn next_election_prediction(now: BlockNumberFor<T>) -> BlockNumberFor<T> {
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ pub use impls::*;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, EraPayout,
|
slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, EraPayout,
|
||||||
EraRewardPoints, Exposure, Forcing, NegativeImbalanceOf, Nominations, PositiveImbalanceOf,
|
EraRewardPoints, Exposure, Forcing, MaxNominationsOf, NegativeImbalanceOf, Nominations,
|
||||||
RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk,
|
NominationsQuota, PositiveImbalanceOf, RewardDestination, SessionInterface, StakingLedger,
|
||||||
ValidatorPrefs,
|
UnappliedSlash, UnlockChunk, ValidatorPrefs,
|
||||||
};
|
};
|
||||||
|
|
||||||
const STAKING_ID: LockIdentifier = *b"staking ";
|
const STAKING_ID: LockIdentifier = *b"staking ";
|
||||||
@@ -129,9 +129,8 @@ pub mod pallet {
|
|||||||
DataProvider = Pallet<Self>,
|
DataProvider = Pallet<Self>,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/// Maximum number of nominations per nominator.
|
/// Something that defines the maximum number of nominations per nominator.
|
||||||
#[pallet::constant]
|
type NominationsQuota: NominationsQuota<BalanceOf<Self>>;
|
||||||
type MaxNominations: Get<u32>;
|
|
||||||
|
|
||||||
/// Number of eras to keep in history.
|
/// Number of eras to keep in history.
|
||||||
///
|
///
|
||||||
@@ -348,7 +347,8 @@ pub mod pallet {
|
|||||||
/// they wish to support.
|
/// they wish to support.
|
||||||
///
|
///
|
||||||
/// Note that the keys of this storage map might become non-decodable in case the
|
/// Note that the keys of this storage map might become non-decodable in case the
|
||||||
/// [`Config::MaxNominations`] configuration is decreased. In this rare case, these nominators
|
/// account's [`NominationsQuota::MaxNominations`] configuration is decreased.
|
||||||
|
/// In this rare case, these nominators
|
||||||
/// are still existent in storage, their key is correct and retrievable (i.e. `contains_key`
|
/// are still existent in storage, their key is correct and retrievable (i.e. `contains_key`
|
||||||
/// indicates that they exist), but their value cannot be decoded. Therefore, the non-decodable
|
/// indicates that they exist), but their value cannot be decoded. Therefore, the non-decodable
|
||||||
/// nominators will effectively not-exist, until they re-submit their preferences such that it
|
/// nominators will effectively not-exist, until they re-submit their preferences such that it
|
||||||
@@ -696,6 +696,10 @@ pub mod pallet {
|
|||||||
PayoutStarted { era_index: EraIndex, validator_stash: T::AccountId },
|
PayoutStarted { era_index: EraIndex, validator_stash: T::AccountId },
|
||||||
/// A validator has set their preferences.
|
/// A validator has set their preferences.
|
||||||
ValidatorPrefsSet { stash: T::AccountId, prefs: ValidatorPrefs },
|
ValidatorPrefsSet { stash: T::AccountId, prefs: ValidatorPrefs },
|
||||||
|
/// Voters size limit reached.
|
||||||
|
SnapshotVotersSizeExceeded { size: u32 },
|
||||||
|
/// Targets size limit reached.
|
||||||
|
SnapshotTargetsSizeExceeded { size: u32 },
|
||||||
/// A new force era mode was set.
|
/// A new force era mode was set.
|
||||||
ForceEra { mode: Forcing },
|
ForceEra { mode: Forcing },
|
||||||
}
|
}
|
||||||
@@ -782,11 +786,11 @@ pub mod pallet {
|
|||||||
fn integrity_test() {
|
fn integrity_test() {
|
||||||
// ensure that we funnel the correct value to the `DataProvider::MaxVotesPerVoter`;
|
// ensure that we funnel the correct value to the `DataProvider::MaxVotesPerVoter`;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
T::MaxNominations::get(),
|
MaxNominationsOf::<T>::get(),
|
||||||
<Self as ElectionDataProvider>::MaxVotesPerVoter::get()
|
<Self as ElectionDataProvider>::MaxVotesPerVoter::get()
|
||||||
);
|
);
|
||||||
// and that MaxNominations is always greater than 1, since we count on this.
|
// and that MaxNominations is always greater than 1, since we count on this.
|
||||||
assert!(!T::MaxNominations::get().is_zero());
|
assert!(!MaxNominationsOf::<T>::get().is_zero());
|
||||||
|
|
||||||
// ensure election results are always bounded with the same value
|
// ensure election results are always bounded with the same value
|
||||||
assert!(
|
assert!(
|
||||||
@@ -1145,7 +1149,10 @@ pub mod pallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ensure!(!targets.is_empty(), Error::<T>::EmptyTargets);
|
ensure!(!targets.is_empty(), Error::<T>::EmptyTargets);
|
||||||
ensure!(targets.len() <= T::MaxNominations::get() as usize, Error::<T>::TooManyTargets);
|
ensure!(
|
||||||
|
targets.len() <= T::NominationsQuota::get_quota(ledger.active) as usize,
|
||||||
|
Error::<T>::TooManyTargets
|
||||||
|
);
|
||||||
|
|
||||||
let old = Nominators::<T>::get(stash).map_or_else(Vec::new, |x| x.targets.into_inner());
|
let old = Nominators::<T>::get(stash).map_or_else(Vec::new, |x| x.targets.into_inner());
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,10 @@
|
|||||||
//! Tests for the module.
|
//! Tests for the module.
|
||||||
|
|
||||||
use super::{ConfigOp, Event, *};
|
use super::{ConfigOp, Event, *};
|
||||||
use frame_election_provider_support::{ElectionProvider, SortedListProvider, Support};
|
use frame_election_provider_support::{
|
||||||
|
bounds::{DataProviderBounds, ElectionBoundsBuilder},
|
||||||
|
ElectionProvider, SortedListProvider, Support,
|
||||||
|
};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
assert_noop, assert_ok, assert_storage_noop, bounded_vec,
|
assert_noop, assert_ok, assert_storage_noop, bounded_vec,
|
||||||
dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo},
|
dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo},
|
||||||
@@ -4508,12 +4511,16 @@ mod election_data_provider {
|
|||||||
.add_staker(71, 71, 10, StakerStatus::<AccountId>::Nominator(vec![21]))
|
.add_staker(71, 71, 10, StakerStatus::<AccountId>::Nominator(vec![21]))
|
||||||
.add_staker(81, 81, 50, StakerStatus::<AccountId>::Nominator(vec![21]))
|
.add_staker(81, 81, 50, StakerStatus::<AccountId>::Nominator(vec![21]))
|
||||||
.build_and_execute(|| {
|
.build_and_execute(|| {
|
||||||
assert_ok!(<Staking as ElectionDataProvider>::electing_voters(None));
|
// default bounds are unbounded.
|
||||||
|
assert_ok!(<Staking as ElectionDataProvider>::electing_voters(
|
||||||
|
DataProviderBounds::default()
|
||||||
|
));
|
||||||
assert_eq!(MinimumActiveStake::<Test>::get(), 10);
|
assert_eq!(MinimumActiveStake::<Test>::get(), 10);
|
||||||
|
|
||||||
// remove staker with lower bond by limiting the number of voters and check
|
// remove staker with lower bond by limiting the number of voters and check
|
||||||
// `MinimumActiveStake` again after electing voters.
|
// `MinimumActiveStake` again after electing voters.
|
||||||
assert_ok!(<Staking as ElectionDataProvider>::electing_voters(Some(5)));
|
let bounds = ElectionBoundsBuilder::default().voters_count(5.into()).build();
|
||||||
|
assert_ok!(<Staking as ElectionDataProvider>::electing_voters(bounds.voters));
|
||||||
assert_eq!(MinimumActiveStake::<Test>::get(), 50);
|
assert_eq!(MinimumActiveStake::<Test>::get(), 50);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -4522,8 +4529,11 @@ mod election_data_provider {
|
|||||||
fn set_minimum_active_stake_lower_bond_works() {
|
fn set_minimum_active_stake_lower_bond_works() {
|
||||||
// if there are no voters, minimum active stake is zero (should not happen).
|
// if there are no voters, minimum active stake is zero (should not happen).
|
||||||
ExtBuilder::default().has_stakers(false).build_and_execute(|| {
|
ExtBuilder::default().has_stakers(false).build_and_execute(|| {
|
||||||
|
// default bounds are unbounded.
|
||||||
|
assert_ok!(<Staking as ElectionDataProvider>::electing_voters(
|
||||||
|
DataProviderBounds::default()
|
||||||
|
));
|
||||||
assert_eq!(<Test as Config>::VoterList::count(), 0);
|
assert_eq!(<Test as Config>::VoterList::count(), 0);
|
||||||
assert_ok!(<Staking as ElectionDataProvider>::electing_voters(None));
|
|
||||||
assert_eq!(MinimumActiveStake::<Test>::get(), 0);
|
assert_eq!(MinimumActiveStake::<Test>::get(), 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -4537,7 +4547,9 @@ mod election_data_provider {
|
|||||||
assert_ok!(Staking::nominate(RuntimeOrigin::signed(4), vec![1]));
|
assert_ok!(Staking::nominate(RuntimeOrigin::signed(4), vec![1]));
|
||||||
assert_eq!(<Test as Config>::VoterList::count(), 5);
|
assert_eq!(<Test as Config>::VoterList::count(), 5);
|
||||||
|
|
||||||
let voters_before = <Staking as ElectionDataProvider>::electing_voters(None).unwrap();
|
let voters_before =
|
||||||
|
<Staking as ElectionDataProvider>::electing_voters(DataProviderBounds::default())
|
||||||
|
.unwrap();
|
||||||
assert_eq!(MinimumActiveStake::<Test>::get(), 5);
|
assert_eq!(MinimumActiveStake::<Test>::get(), 5);
|
||||||
|
|
||||||
// update minimum nominator bond.
|
// update minimum nominator bond.
|
||||||
@@ -4547,7 +4559,9 @@ mod election_data_provider {
|
|||||||
// lower than `MinNominatorBond`.
|
// lower than `MinNominatorBond`.
|
||||||
assert_eq!(<Test as Config>::VoterList::count(), 5);
|
assert_eq!(<Test as Config>::VoterList::count(), 5);
|
||||||
|
|
||||||
let voters = <Staking as ElectionDataProvider>::electing_voters(None).unwrap();
|
let voters =
|
||||||
|
<Staking as ElectionDataProvider>::electing_voters(DataProviderBounds::default())
|
||||||
|
.unwrap();
|
||||||
assert_eq!(voters_before, voters);
|
assert_eq!(voters_before, voters);
|
||||||
|
|
||||||
// minimum active stake is lower than `MinNominatorBond`.
|
// minimum active stake is lower than `MinNominatorBond`.
|
||||||
@@ -4563,7 +4577,10 @@ mod election_data_provider {
|
|||||||
.add_staker(61, 61, 2_000, StakerStatus::<AccountId>::Nominator(vec![21]))
|
.add_staker(61, 61, 2_000, StakerStatus::<AccountId>::Nominator(vec![21]))
|
||||||
.build_and_execute(|| {
|
.build_and_execute(|| {
|
||||||
assert_eq!(Staking::weight_of(&101), 500);
|
assert_eq!(Staking::weight_of(&101), 500);
|
||||||
let voters = <Staking as ElectionDataProvider>::electing_voters(None).unwrap();
|
let voters = <Staking as ElectionDataProvider>::electing_voters(
|
||||||
|
DataProviderBounds::default(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
assert_eq!(voters.len(), 5);
|
assert_eq!(voters.len(), 5);
|
||||||
assert_eq!(MinimumActiveStake::<Test>::get(), 500);
|
assert_eq!(MinimumActiveStake::<Test>::get(), 500);
|
||||||
|
|
||||||
@@ -4575,7 +4592,10 @@ mod election_data_provider {
|
|||||||
// corrupt ledger state by lowering max unlocking chunks bounds.
|
// corrupt ledger state by lowering max unlocking chunks bounds.
|
||||||
MaxUnlockingChunks::set(1);
|
MaxUnlockingChunks::set(1);
|
||||||
|
|
||||||
let voters = <Staking as ElectionDataProvider>::electing_voters(None).unwrap();
|
let voters = <Staking as ElectionDataProvider>::electing_voters(
|
||||||
|
DataProviderBounds::default(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
// number of returned voters decreases since ledger entry of stash 101 is now
|
// number of returned voters decreases since ledger entry of stash 101 is now
|
||||||
// corrupt.
|
// corrupt.
|
||||||
assert_eq!(voters.len(), 4);
|
assert_eq!(voters.len(), 4);
|
||||||
@@ -4593,8 +4613,9 @@ mod election_data_provider {
|
|||||||
#[test]
|
#[test]
|
||||||
fn voters_include_self_vote() {
|
fn voters_include_self_vote() {
|
||||||
ExtBuilder::default().nominate(false).build_and_execute(|| {
|
ExtBuilder::default().nominate(false).build_and_execute(|| {
|
||||||
|
// default bounds are unbounded.
|
||||||
assert!(<Validators<Test>>::iter().map(|(x, _)| x).all(|v| Staking::electing_voters(
|
assert!(<Validators<Test>>::iter().map(|(x, _)| x).all(|v| Staking::electing_voters(
|
||||||
None
|
DataProviderBounds::default()
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -4602,39 +4623,11 @@ mod election_data_provider {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn respects_snapshot_len_limits() {
|
|
||||||
ExtBuilder::default()
|
|
||||||
.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>::VoterList::count(), 5);
|
|
||||||
|
|
||||||
// if limits is less..
|
|
||||||
assert_eq!(Staking::electing_voters(Some(1)).unwrap().len(), 1);
|
|
||||||
|
|
||||||
// if limit is equal..
|
|
||||||
assert_eq!(Staking::electing_voters(Some(5)).unwrap().len(), 5);
|
|
||||||
|
|
||||||
// if limit is more.
|
|
||||||
assert_eq!(Staking::electing_voters(Some(55)).unwrap().len(), 5);
|
|
||||||
|
|
||||||
// if target limit is more..
|
|
||||||
assert_eq!(Staking::electable_targets(Some(6)).unwrap().len(), 4);
|
|
||||||
assert_eq!(Staking::electable_targets(Some(4)).unwrap().len(), 4);
|
|
||||||
|
|
||||||
// if target limit is less, then we return an error.
|
|
||||||
assert_eq!(
|
|
||||||
Staking::electable_targets(Some(1)).unwrap_err(),
|
|
||||||
"Target snapshot too big"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests the criteria that in `ElectionDataProvider::voters` function, we try to get at most
|
// 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` voters, and if some of them end up being skipped, we iterate at most `2 *
|
||||||
// maybe_max_len`.
|
// maybe_max_len`.
|
||||||
#[test]
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
fn only_iterates_max_2_times_max_allowed_len() {
|
fn only_iterates_max_2_times_max_allowed_len() {
|
||||||
ExtBuilder::default()
|
ExtBuilder::default()
|
||||||
.nominate(false)
|
.nominate(false)
|
||||||
@@ -4659,13 +4652,14 @@ mod election_data_provider {
|
|||||||
StakerStatus::<AccountId>::Nominator(vec![21, 22, 23, 24, 25]),
|
StakerStatus::<AccountId>::Nominator(vec![21, 22, 23, 24, 25]),
|
||||||
)
|
)
|
||||||
.build_and_execute(|| {
|
.build_and_execute(|| {
|
||||||
|
let bounds_builder = ElectionBoundsBuilder::default();
|
||||||
// all voters ordered by stake,
|
// all voters ordered by stake,
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
<Test as Config>::VoterList::iter().collect::<Vec<_>>(),
|
<Test as Config>::VoterList::iter().collect::<Vec<_>>(),
|
||||||
vec![61, 71, 81, 11, 21, 31]
|
vec![61, 71, 81, 11, 21, 31]
|
||||||
);
|
);
|
||||||
|
|
||||||
MaxNominations::set(2);
|
AbsoluteMaxNominations::set(2);
|
||||||
|
|
||||||
// we want 2 voters now, and in maximum we allow 4 iterations. This is what happens:
|
// we want 2 voters now, and in maximum we allow 4 iterations. This is what happens:
|
||||||
// 61 is pruned;
|
// 61 is pruned;
|
||||||
@@ -4674,7 +4668,7 @@ mod election_data_provider {
|
|||||||
// 11 is taken;
|
// 11 is taken;
|
||||||
// we finish since the 2x limit is reached.
|
// we finish since the 2x limit is reached.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Staking::electing_voters(Some(2))
|
Staking::electing_voters(bounds_builder.voters_count(2.into()).build().voters)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(stash, _, _)| stash)
|
.map(|(stash, _, _)| stash)
|
||||||
@@ -4685,6 +4679,189 @@ mod election_data_provider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn respects_snapshot_count_limits() {
|
||||||
|
ExtBuilder::default()
|
||||||
|
.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>::VoterList::count(), 5);
|
||||||
|
|
||||||
|
let bounds_builder = ElectionBoundsBuilder::default();
|
||||||
|
|
||||||
|
// if voter count limit is less..
|
||||||
|
assert_eq!(
|
||||||
|
Staking::electing_voters(bounds_builder.voters_count(1.into()).build().voters)
|
||||||
|
.unwrap()
|
||||||
|
.len(),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
// if voter count limit is equal..
|
||||||
|
assert_eq!(
|
||||||
|
Staking::electing_voters(bounds_builder.voters_count(5.into()).build().voters)
|
||||||
|
.unwrap()
|
||||||
|
.len(),
|
||||||
|
5
|
||||||
|
);
|
||||||
|
|
||||||
|
// if voter count limit is more.
|
||||||
|
assert_eq!(
|
||||||
|
Staking::electing_voters(bounds_builder.voters_count(55.into()).build().voters)
|
||||||
|
.unwrap()
|
||||||
|
.len(),
|
||||||
|
5
|
||||||
|
);
|
||||||
|
|
||||||
|
// if target count limit is more..
|
||||||
|
assert_eq!(
|
||||||
|
Staking::electable_targets(
|
||||||
|
bounds_builder.targets_count(6.into()).build().targets
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.len(),
|
||||||
|
4
|
||||||
|
);
|
||||||
|
|
||||||
|
// if target count limit is equal..
|
||||||
|
assert_eq!(
|
||||||
|
Staking::electable_targets(
|
||||||
|
bounds_builder.targets_count(4.into()).build().targets
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.len(),
|
||||||
|
4
|
||||||
|
);
|
||||||
|
|
||||||
|
// if target limit count is less, then we return an error.
|
||||||
|
assert_eq!(
|
||||||
|
Staking::electable_targets(
|
||||||
|
bounds_builder.targets_count(1.into()).build().targets
|
||||||
|
)
|
||||||
|
.unwrap_err(),
|
||||||
|
"Target snapshot too big"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn respects_snapshot_size_limits() {
|
||||||
|
ExtBuilder::default().build_and_execute(|| {
|
||||||
|
// voters: set size bounds that allows only for 1 voter.
|
||||||
|
let bounds = ElectionBoundsBuilder::default().voters_size(26.into()).build();
|
||||||
|
let elected = Staking::electing_voters(bounds.voters).unwrap();
|
||||||
|
assert!(elected.encoded_size() == 26 as usize);
|
||||||
|
let prev_len = elected.len();
|
||||||
|
|
||||||
|
// larger size bounds means more quota for voters.
|
||||||
|
let bounds = ElectionBoundsBuilder::default().voters_size(100.into()).build();
|
||||||
|
let elected = Staking::electing_voters(bounds.voters).unwrap();
|
||||||
|
assert!(elected.encoded_size() <= 100 as usize);
|
||||||
|
assert!(elected.len() > 1 && elected.len() > prev_len);
|
||||||
|
|
||||||
|
// targets: set size bounds that allows for only one target to fit in the snapshot.
|
||||||
|
let bounds = ElectionBoundsBuilder::default().targets_size(10.into()).build();
|
||||||
|
let elected = Staking::electable_targets(bounds.targets).unwrap();
|
||||||
|
assert!(elected.encoded_size() == 9 as usize);
|
||||||
|
let prev_len = elected.len();
|
||||||
|
|
||||||
|
// larger size bounds means more space for targets.
|
||||||
|
let bounds = ElectionBoundsBuilder::default().targets_size(100.into()).build();
|
||||||
|
let elected = Staking::electable_targets(bounds.targets).unwrap();
|
||||||
|
assert!(elected.encoded_size() <= 100 as usize);
|
||||||
|
assert!(elected.len() > 1 && elected.len() > prev_len);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nomination_quota_checks_at_nominate_works() {
|
||||||
|
ExtBuilder::default().nominate(false).build_and_execute(|| {
|
||||||
|
// stash bond of 222 has a nomination quota of 2 targets.
|
||||||
|
bond(61, 222);
|
||||||
|
assert_eq!(Staking::api_nominations_quota(222), 2);
|
||||||
|
|
||||||
|
// nominating with targets below the nomination quota works.
|
||||||
|
assert_ok!(Staking::nominate(RuntimeOrigin::signed(61), vec![11]));
|
||||||
|
assert_ok!(Staking::nominate(RuntimeOrigin::signed(61), vec![11, 12]));
|
||||||
|
|
||||||
|
// nominating with targets above the nomination quota returns error.
|
||||||
|
assert_noop!(
|
||||||
|
Staking::nominate(RuntimeOrigin::signed(61), vec![11, 12, 13]),
|
||||||
|
Error::<Test>::TooManyTargets
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lazy_quota_npos_voters_works_above_quota() {
|
||||||
|
ExtBuilder::default()
|
||||||
|
.nominate(false)
|
||||||
|
.add_staker(
|
||||||
|
61,
|
||||||
|
60,
|
||||||
|
300, // 300 bond has 16 nomination quota.
|
||||||
|
StakerStatus::<AccountId>::Nominator(vec![21, 22, 23, 24, 25]),
|
||||||
|
)
|
||||||
|
.build_and_execute(|| {
|
||||||
|
// unbond 78 from stash 60 so that it's bonded balance is 222, which has a lower
|
||||||
|
// nomination quota than at nomination time (max 2 targets).
|
||||||
|
assert_ok!(Staking::unbond(RuntimeOrigin::signed(61), 78));
|
||||||
|
assert_eq!(Staking::api_nominations_quota(300 - 78), 2);
|
||||||
|
|
||||||
|
// even through 61 has nomination quota of 2 at the time of the election, all the
|
||||||
|
// nominations (5) will be used.
|
||||||
|
assert_eq!(
|
||||||
|
Staking::electing_voters(DataProviderBounds::default())
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|(stash, _, targets)| (*stash, targets.len()))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
vec![(11, 1), (21, 1), (31, 1), (61, 5)],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nominations_quota_limits_size_work() {
|
||||||
|
ExtBuilder::default()
|
||||||
|
.nominate(false)
|
||||||
|
.add_staker(
|
||||||
|
71,
|
||||||
|
70,
|
||||||
|
333,
|
||||||
|
StakerStatus::<AccountId>::Nominator(vec![16, 15, 14, 13, 12, 11, 10]),
|
||||||
|
)
|
||||||
|
.build_and_execute(|| {
|
||||||
|
// nominations of controller 70 won't be added due to voter size limit exceeded.
|
||||||
|
let bounds = ElectionBoundsBuilder::default().voters_size(100.into()).build();
|
||||||
|
assert_eq!(
|
||||||
|
Staking::electing_voters(bounds.voters)
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|(stash, _, targets)| (*stash, targets.len()))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
vec![(11, 1), (21, 1), (31, 1)],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
*staking_events().last().unwrap(),
|
||||||
|
Event::SnapshotVotersSizeExceeded { size: 75 }
|
||||||
|
);
|
||||||
|
|
||||||
|
// however, if the election voter size bounds were largers, the snapshot would
|
||||||
|
// include the electing voters of 70.
|
||||||
|
let bounds = ElectionBoundsBuilder::default().voters_size(1_000.into()).build();
|
||||||
|
assert_eq!(
|
||||||
|
Staking::electing_voters(bounds.voters)
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|(stash, _, targets)| (*stash, targets.len()))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
vec![(11, 1), (21, 1), (31, 1), (71, 7)],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn estimate_next_election_works() {
|
fn estimate_next_election_works() {
|
||||||
ExtBuilder::default().session_per_era(5).period(5).build_and_execute(|| {
|
ExtBuilder::default().session_per_era(5).period(5).build_and_execute(|| {
|
||||||
@@ -5120,7 +5297,8 @@ fn min_commission_works() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn change_of_max_nominations() {
|
#[should_panic]
|
||||||
|
fn change_of_absolute_max_nominations() {
|
||||||
use frame_election_provider_support::ElectionDataProvider;
|
use frame_election_provider_support::ElectionDataProvider;
|
||||||
ExtBuilder::default()
|
ExtBuilder::default()
|
||||||
.add_staker(61, 61, 10, StakerStatus::Nominator(vec![1]))
|
.add_staker(61, 61, 10, StakerStatus::Nominator(vec![1]))
|
||||||
@@ -5128,7 +5306,7 @@ fn change_of_max_nominations() {
|
|||||||
.balance_factor(10)
|
.balance_factor(10)
|
||||||
.build_and_execute(|| {
|
.build_and_execute(|| {
|
||||||
// pre-condition
|
// pre-condition
|
||||||
assert_eq!(MaxNominations::get(), 16);
|
assert_eq!(AbsoluteMaxNominations::get(), 16);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Nominators::<Test>::iter()
|
Nominators::<Test>::iter()
|
||||||
@@ -5136,11 +5314,15 @@ fn change_of_max_nominations() {
|
|||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
vec![(101, 2), (71, 3), (61, 1)]
|
vec![(101, 2), (71, 3), (61, 1)]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// default bounds are unbounded.
|
||||||
|
let bounds = DataProviderBounds::default();
|
||||||
|
|
||||||
// 3 validators and 3 nominators
|
// 3 validators and 3 nominators
|
||||||
assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 3);
|
assert_eq!(Staking::electing_voters(bounds).unwrap().len(), 3 + 3);
|
||||||
|
|
||||||
// abrupt change from 16 to 4, everyone should be fine.
|
// abrupt change from 16 to 4, everyone should be fine.
|
||||||
MaxNominations::set(4);
|
AbsoluteMaxNominations::set(4);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Nominators::<Test>::iter()
|
Nominators::<Test>::iter()
|
||||||
@@ -5148,10 +5330,10 @@ fn change_of_max_nominations() {
|
|||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
vec![(101, 2), (71, 3), (61, 1)]
|
vec![(101, 2), (71, 3), (61, 1)]
|
||||||
);
|
);
|
||||||
assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 3);
|
assert_eq!(Staking::electing_voters(bounds).unwrap().len(), 3 + 3);
|
||||||
|
|
||||||
// abrupt change from 4 to 3, everyone should be fine.
|
// abrupt change from 4 to 3, everyone should be fine.
|
||||||
MaxNominations::set(3);
|
AbsoluteMaxNominations::set(3);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Nominators::<Test>::iter()
|
Nominators::<Test>::iter()
|
||||||
@@ -5159,11 +5341,11 @@ fn change_of_max_nominations() {
|
|||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
vec![(101, 2), (71, 3), (61, 1)]
|
vec![(101, 2), (71, 3), (61, 1)]
|
||||||
);
|
);
|
||||||
assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 3);
|
assert_eq!(Staking::electing_voters(bounds).unwrap().len(), 3 + 3);
|
||||||
|
|
||||||
// abrupt change from 3 to 2, this should cause some nominators to be non-decodable, and
|
// abrupt change from 3 to 2, this should cause some nominators to be non-decodable, and
|
||||||
// thus non-existent unless if they update.
|
// thus non-existent unless if they update.
|
||||||
MaxNominations::set(2);
|
AbsoluteMaxNominations::set(2);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Nominators::<Test>::iter()
|
Nominators::<Test>::iter()
|
||||||
@@ -5176,12 +5358,12 @@ fn change_of_max_nominations() {
|
|||||||
// but its value cannot be decoded and default is returned.
|
// but its value cannot be decoded and default is returned.
|
||||||
assert!(Nominators::<Test>::get(71).is_none());
|
assert!(Nominators::<Test>::get(71).is_none());
|
||||||
|
|
||||||
assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 2);
|
assert_eq!(Staking::electing_voters(bounds).unwrap().len(), 3 + 2);
|
||||||
assert!(Nominators::<Test>::contains_key(101));
|
assert!(Nominators::<Test>::contains_key(101));
|
||||||
|
|
||||||
// abrupt change from 2 to 1, this should cause some nominators to be non-decodable, and
|
// abrupt change from 2 to 1, this should cause some nominators to be non-decodable, and
|
||||||
// thus non-existent unless if they update.
|
// thus non-existent unless if they update.
|
||||||
MaxNominations::set(1);
|
AbsoluteMaxNominations::set(1);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Nominators::<Test>::iter()
|
Nominators::<Test>::iter()
|
||||||
@@ -5193,7 +5375,7 @@ fn change_of_max_nominations() {
|
|||||||
assert!(Nominators::<Test>::contains_key(61));
|
assert!(Nominators::<Test>::contains_key(61));
|
||||||
assert!(Nominators::<Test>::get(71).is_none());
|
assert!(Nominators::<Test>::get(71).is_none());
|
||||||
assert!(Nominators::<Test>::get(61).is_some());
|
assert!(Nominators::<Test>::get(61).is_some());
|
||||||
assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 1);
|
assert_eq!(Staking::electing_voters(bounds).unwrap().len(), 3 + 1);
|
||||||
|
|
||||||
// now one of them can revive themselves by re-nominating to a proper value.
|
// now one of them can revive themselves by re-nominating to a proper value.
|
||||||
assert_ok!(Staking::nominate(RuntimeOrigin::signed(71), vec![1]));
|
assert_ok!(Staking::nominate(RuntimeOrigin::signed(71), vec![1]));
|
||||||
@@ -5213,6 +5395,42 @@ fn change_of_max_nominations() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nomination_quota_max_changes_decoding() {
|
||||||
|
use frame_election_provider_support::ElectionDataProvider;
|
||||||
|
ExtBuilder::default()
|
||||||
|
.add_staker(60, 61, 10, StakerStatus::Nominator(vec![1]))
|
||||||
|
.add_staker(70, 71, 10, StakerStatus::Nominator(vec![1, 2, 3]))
|
||||||
|
.add_staker(30, 330, 10, StakerStatus::Nominator(vec![1, 2, 3, 4]))
|
||||||
|
.add_staker(50, 550, 10, StakerStatus::Nominator(vec![1, 2, 3, 4]))
|
||||||
|
.balance_factor(10)
|
||||||
|
.build_and_execute(|| {
|
||||||
|
// pre-condition.
|
||||||
|
assert_eq!(MaxNominationsOf::<Test>::get(), 16);
|
||||||
|
|
||||||
|
let unbonded_election = DataProviderBounds::default();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Nominators::<Test>::iter()
|
||||||
|
.map(|(k, n)| (k, n.targets.len()))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
vec![(70, 3), (101, 2), (50, 4), (30, 4), (60, 1)]
|
||||||
|
);
|
||||||
|
// 4 validators and 4 nominators
|
||||||
|
assert_eq!(Staking::electing_voters(unbonded_election).unwrap().len(), 4 + 4);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn api_nominations_quota_works() {
|
||||||
|
ExtBuilder::default().build_and_execute(|| {
|
||||||
|
assert_eq!(Staking::api_nominations_quota(10), MaxNominationsOf::<Test>::get());
|
||||||
|
assert_eq!(Staking::api_nominations_quota(333), MaxNominationsOf::<Test>::get());
|
||||||
|
assert_eq!(Staking::api_nominations_quota(222), 2);
|
||||||
|
assert_eq!(Staking::api_nominations_quota(111), 1);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
mod sorted_list_provider {
|
mod sorted_list_provider {
|
||||||
use super::*;
|
use super::*;
|
||||||
use frame_election_provider_support::SortedListProvider;
|
use frame_election_provider_support::SortedListProvider;
|
||||||
|
|||||||
@@ -404,7 +404,7 @@ impl<AccountId: IdentifierT> Voter<AccountId> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This voter's budget
|
/// This voter's budget.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn budget(&self) -> ExtendedBalance {
|
pub fn budget(&self) -> ExtendedBalance {
|
||||||
self.budget
|
self.budget
|
||||||
|
|||||||
Reference in New Issue
Block a user