Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

[NPoS] Implements dynamic number of nominators #12970

Merged
merged 96 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
8cb3404
Implements dynamic nominations per nominator
gpestana Dec 16, 2022
c9b8a17
Adds SnapshotBounds and ElectionSizeTracker
gpestana Dec 19, 2022
fcc7657
Changes the ElectionDataProvider interface to receive ElectionBounds …
gpestana Dec 19, 2022
3ae3e29
Implements get_npos_voters with ElectionBounds
gpestana Dec 19, 2022
98f6e82
Implements get_npos_targets with ElectionBounds
gpestana Dec 19, 2022
5434b47
Adds comments
gpestana Dec 19, 2022
0a7b714
tests
gpestana Dec 21, 2022
1c347f9
Truncates nomninations that exceed nominations quota; Old tests passing
gpestana Dec 26, 2022
9bf66c3
Uses DataProviderBounds and ElectionBounds (to continue)
gpestana Dec 27, 2022
cdaeb7c
Finishes conversions - tests passing
gpestana Dec 27, 2022
4e38839
Refactor staking in babe mocks
gpestana Dec 27, 2022
11bd646
Replaces MaxElectableTargets and MaxElectingVoters with ElectionBound…
gpestana Dec 27, 2022
3fe1076
Merge branch 'master' into gpestana/staking-dynamic-nominators
gpestana Dec 27, 2022
a1f77ad
Fixes nits; node compiling
gpestana Dec 28, 2022
905b8e0
bechmarks
gpestana Dec 29, 2022
775fe25
removes nomination_quota extrinsic to request the nomination quota
gpestana Jan 3, 2023
eb2456b
Lazy quota check, ie. at nominate time only
gpestana Jan 3, 2023
b0b21a4
remove non-working test (for now)
gpestana Jan 3, 2023
82ac2db
tests lazy nominations quota when quota is lower than current number …
gpestana Jan 4, 2023
8218e17
Adds runtime API and custom RPC call for clients to query the nominat…
gpestana Jan 4, 2023
af0c52b
removes old rpc
gpestana Feb 28, 2023
5b5231a
Merge branch 'master' into gpestana/staking-dynamic-nominators
gpestana Feb 28, 2023
8e7ecbb
Cosmetic touches
gpestana Feb 28, 2023
bfbb91b
All mocks working
gpestana Feb 28, 2023
2c44857
Fixes benchmarking mocks
gpestana Feb 28, 2023
1bf29f7
nits
gpestana Feb 28, 2023
125ff54
more tests
gpestana Mar 1, 2023
7d9fdfc
renames trait methods
gpestana Mar 1, 2023
8fddf41
nit
gpestana Mar 1, 2023
805e3f7
".git/.scripts/commands/fmt/fmt.sh"
Mar 1, 2023
3317bd7
Fix V2 PoV benchmarking (#13485)
ggwpez Feb 28, 2023
1c20979
Move BEEFY code to consensus (#13484)
davxy Feb 28, 2023
8ba9e1e
chore: move genesis block builder to chain-spec crate. (#13427)
yjhmelody Feb 28, 2023
761d7ae
Speed up storage iteration from within the runtime (#13479)
koute Mar 1, 2023
3136966
Make unbounded channels size warning exact (part 1) (#13490)
dmitry-markin Mar 1, 2023
8d78bee
Removal of Prometheus alerting rules deployment in cloud-infra (#13499)
lazam Mar 1, 2023
397ce89
sp-consensus: remove unused error variants (#13495)
andresilva Mar 1, 2023
1349e6a
Expose `ChargedAmount` (#13488)
pmikolajczyk41 Mar 1, 2023
92b1231
sc-consensus-beefy: fix metrics: use correct names (#13494)
acatangiu Mar 1, 2023
42eeb97
clippy fix
gpestana Mar 1, 2023
5a29c9c
Merge remote-tracking branch 'origin/master' into gpestana/staking-dy…
Mar 1, 2023
f51083b
Merge remote-tracking branch 'origin/master' into gpestana/staking-dy…
Mar 6, 2023
d067a1c
removes NominationsQuotaExceeded event
gpestana Mar 6, 2023
422393c
Update frame/staking/src/lib.rs
gpestana Mar 16, 2023
fb418a9
adds back the npos_max_iter
gpestana Mar 16, 2023
b786d33
Merge branch 'master' into gpestana/staking-dynamic-nominators
gpestana Mar 16, 2023
af3b8ba
remove duplicate imports added after merge
gpestana Mar 16, 2023
7d8ffda
fmt
gpestana Mar 16, 2023
1246c73
Adds comment in public struct; Refactors CountBound and SizeCount to …
gpestana Mar 21, 2023
325c91f
addresses various pr comments
gpestana Mar 21, 2023
1f8f502
PR comment reviews
gpestana Mar 23, 2023
67c4734
Fixes on-chain election bounds and related code
gpestana Mar 24, 2023
70d8579
EPM checks the size of the voter list returned by the data provider
gpestana Mar 24, 2023
a68a37d
cosmetic changes
gpestana Mar 24, 2023
849a2e0
updates e2e tests mock
gpestana Mar 24, 2023
306668e
Adds more tests for size tracker and refactors code
gpestana Mar 25, 2023
7ec6305
Adds back only_iterates_max_2_times_max_allowed_len test
gpestana Mar 25, 2023
e8576b7
Refactor
gpestana Mar 25, 2023
e12f287
removes unecessary dependency
gpestana Mar 25, 2023
c7acde4
Merge branch 'master' into gpestana/staking-dynamic-nominators
gpestana Mar 25, 2023
61a8ca5
empty commit -- restart all stuck CI jobs
gpestana Mar 27, 2023
5c8a034
restarts ci jobs
gpestana Mar 31, 2023
187ce07
Renames ElectionBounds -> Bounds in benchmarking mocks et al
gpestana Apr 2, 2023
9023997
updates mocks
gpestana Apr 2, 2023
ee5c863
Merge branch 'master' into gpestana/staking-dynamic-nominators
gpestana Apr 2, 2023
c3b4375
Update frame/election-provider-support/src/lib.rs
gpestana Apr 11, 2023
9f0319d
Update frame/staking/src/pallet/impls.rs
gpestana Apr 11, 2023
bece9d5
Merge branch 'master' into gpestana/staking-dynamic-nominators
gpestana Apr 11, 2023
8bd5ce9
Update frame/election-provider-support/src/lib.rs
gpestana Apr 11, 2023
7970ade
Update frame/staking/src/tests.rs
gpestana Apr 11, 2023
208ee20
more checks in api_nominations_quota in tests
gpestana Apr 11, 2023
6c5b3b1
Improves docs
gpestana Apr 11, 2023
6619b5d
fixes e2e tests
gpestana Apr 12, 2023
02d8e78
Uses size_hint rather than mem::size_of in size tracker; Refactor siz…
gpestana Apr 12, 2023
4805516
Merge branch 'master' into gpestana/staking-dynamic-nominators
gpestana Apr 12, 2023
d18b175
nits from reviews
gpestana Apr 13, 2023
31ed378
Merge branch 'master' into gpestana/staking-dynamic-nominators
gpestana Apr 19, 2023
4769857
Refactors bounds to own module; improves docs
gpestana Apr 19, 2023
a5cd416
More tests and docs
gpestana Apr 19, 2023
742f07d
fixes docs
gpestana Apr 19, 2023
fe61184
Fixes benchmarks
gpestana Apr 20, 2023
87945f6
Fixes rust docs
gpestana Apr 20, 2023
75be862
fixes bags-list remote-ext-tests
gpestana Apr 20, 2023
b3aab85
Simplify bound checks in create_snapshot_external
gpestana Apr 20, 2023
bc0e8bd
Merge branch 'master' into gpestana/staking-dynamic-nominators
gpestana May 17, 2023
aab74d6
Merge branch 'master' into gpestana/staking-dynamic-nominators
gpestana Jun 3, 2023
fe2dfe3
Adds target size check in get_npos_targets
gpestana Jun 4, 2023
491ddfc
Merge branch 'master' into gpestana/staking-dynamic-nominators
gpestana Aug 5, 2023
8bfd731
".git/.scripts/commands/fmt/fmt.sh"
Aug 5, 2023
747aa52
restart ci
gpestana Aug 5, 2023
0bda7a4
rust doc fixes and cosmetic nits
gpestana Aug 6, 2023
157e3a7
Merge remote-tracking branch 'origin/master' into gpestana/staking-dy…
Aug 8, 2023
3c3fc5a
Merge remote-tracking branch 'origin/master' into gpestana/staking-dy…
Aug 9, 2023
86ab41c
rollback upgrade on parity-scale-codec version (unecessary)
gpestana Aug 9, 2023
e4f7f9f
reset cargo lock, no need to update it
gpestana Aug 9, 2023
07c4a7d
Merge branch 'master' into gpestana/staking-dynamic-nominators
gpestana Aug 9, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
631 changes: 371 additions & 260 deletions Cargo.lock

Large diffs are not rendered by default.

22 changes: 11 additions & 11 deletions bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@

use codec::{Decode, Encode, MaxEncodedLen};
use frame_election_provider_support::{
onchain, BalancingConfig, ElectionDataProvider, SequentialPhragmen, VoteWeight,
onchain, BalancingConfig, ElectionBounds, ElectionBoundsBuilder, ElectionDataProvider,
SequentialPhragmen, VoteWeight,
};
use frame_support::{
construct_runtime,
Expand Down Expand Up @@ -548,14 +549,16 @@ parameter_types! {
pub HistoryDepth: u32 = 84;
}

/// Upper limit on the number of NPOS nominations.
const MAX_QUOTA_NOMINATIONS: u32 = 16;
gpestana marked this conversation as resolved.
Show resolved Hide resolved

pub struct StakingBenchmarkingConfig;
impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig {
type MaxNominators = ConstU32<1000>;
type MaxValidators = ConstU32<1000>;
}

impl pallet_staking::Config for Runtime {
type MaxNominations = MaxNominations;
type Currency = Balances;
type CurrencyBalance = Balance;
type UnixTime = Timestamp;
Expand All @@ -580,6 +583,7 @@ impl pallet_staking::Config for Runtime {
type ElectionProvider = ElectionProviderMultiPhase;
type GenesisElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
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
type TargetList = pallet_staking::UseValidatorsMap<Self>;
type MaxUnlockingChunks = ConstU32<32>;
Expand Down Expand Up @@ -633,17 +637,15 @@ frame_election_provider_support::generate_solution_type!(
VoterIndex = u32,
TargetIndex = u16,
Accuracy = sp_runtime::PerU16,
MaxVoters = MaxElectingVoters,
MaxVoters = MaxNominations,
>(16)
gpestana marked this conversation as resolved.
Show resolved Hide resolved
);

parameter_types! {
pub ElectionBoundsMultiPhase: ElectionBounds = ElectionBoundsBuilder::new().voters_count(40_000).targets_count(10_000).build();
gpestana marked this conversation as resolved.
Show resolved Hide resolved
pub MaxNominations: u32 = <NposSolution16 as frame_election_provider_support::NposSolution>::LIMIT as u32;
pub MaxElectingVoters: u32 = 40_000;
pub MaxElectableTargets: u16 = 10_000;
// OnChain values are lower.
pub MaxOnChainElectingVoters: u32 = 5000;
pub MaxOnChainElectableTargets: u16 = 1250;
pub ElectionBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::new().voters_count(5_000).targets_count(1_250).build();
// The maximum winners that can be elected by the Election pallet which is equivalent to the
// maximum active validators the staking pallet can have.
pub MaxActiveValidators: u32 = 1000;
Expand Down Expand Up @@ -698,8 +700,7 @@ impl onchain::Config for OnChainSeqPhragmen {
type DataProvider = <Runtime as pallet_election_provider_multi_phase::Config>::DataProvider;
type WeightInfo = frame_election_provider_support::weights::SubstrateWeight<Runtime>;
type MaxWinners = <Runtime as pallet_election_provider_multi_phase::Config>::MaxWinners;
type VotersBound = MaxOnChainElectingVoters;
type TargetsBound = MaxOnChainElectableTargets;
type ElectionBounds = ElectionBoundsOnChain;
}

impl pallet_election_provider_multi_phase::MinerConfig for Runtime {
Expand Down Expand Up @@ -746,9 +747,8 @@ impl pallet_election_provider_multi_phase::Config for Runtime {
type GovernanceFallback = onchain::OnChainExecution<OnChainSeqPhragmen>;
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Self>, OffchainRandomBalancing>;
type ForceOrigin = EnsureRootOrHalfCouncil;
type MaxElectableTargets = MaxElectableTargets;
type MaxWinners = MaxActiveValidators;
type MaxElectingVoters = MaxElectingVoters;
type ElectionBounds = ElectionBoundsMultiPhase;
type BenchmarkingConfig = ElectionProviderBenchmarkConfig;
type WeightInfo = pallet_election_provider_multi_phase::weights::SubstrateWeight<Self>;
}
Expand Down
11 changes: 7 additions & 4 deletions frame/babe/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@

use crate::{self as pallet_babe, Config, CurrentSlot};
use codec::Encode;
use frame_election_provider_support::{onchain, SequentialPhragmen};
use frame_election_provider_support::{
onchain, ElectionBounds, ElectionBoundsBuilder, SequentialPhragmen,
};
use frame_support::{
parameter_types,
traits::{ConstU128, ConstU32, ConstU64, GenesisBuild, KeyOwnerProofSystem, OnInitialize},
};
use pallet_session::historical as pallet_session_historical;
use pallet_staking::FixedNominationsQuota;
use sp_consensus_babe::{AuthorityId, AuthorityPair, Slot};
use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof};
use sp_core::{
Expand Down Expand Up @@ -163,6 +166,7 @@ parameter_types! {
pub const SlashDeferDuration: EraIndex = 0;
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(16);
pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::new().build();
gpestana marked this conversation as resolved.
Show resolved Hide resolved
}

pub struct OnChainSeqPhragmen;
Expand All @@ -172,12 +176,10 @@ impl onchain::Config for OnChainSeqPhragmen {
type DataProvider = Staking;
type WeightInfo = ();
type MaxWinners = ConstU32<100>;
type VotersBound = ConstU32<{ u32::MAX }>;
type TargetsBound = ConstU32<{ u32::MAX }>;
type ElectionBounds = ElectionsBounds;
}

impl pallet_staking::Config for Test {
type MaxNominations = ConstU32<16>;
type RewardRemainder = ();
type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote;
type RuntimeEvent = RuntimeEvent;
Expand All @@ -199,6 +201,7 @@ impl pallet_staking::Config for Test {
type GenesisElectionProvider = Self::ElectionProvider;
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
type TargetList = pallet_staking::UseValidatorsMap<Self>;
type NominationsQuota = FixedNominationsQuota<16>;
type MaxUnlockingChunks = ConstU32<32>;
type HistoryDepth = ConstU32<84>;
type OnStakerSlash = ();
Expand Down
20 changes: 12 additions & 8 deletions frame/bags-list/remote-tests/src/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@

//! Test to execute the snapshot using the voter bag.

use frame_election_provider_support::SortedListProvider;
use frame_election_provider_support::{ElectionBounds, SortedListProvider};
use frame_support::traits::PalletInfoAccess;
use remote_externalities::{Builder, Mode, OnlineConfig};
use sp_runtime::{traits::Block as BlockT, DeserializeOwned};

/// Execute create a snapshot from pallet-staking.
pub async fn execute<Runtime, Block>(voter_limit: Option<usize>, currency_unit: u64, ws_url: String)
where
pub async fn execute<Runtime, Block>(
election_bounds: ElectionBounds,
currency_unit: u64,
ws_url: String,
) where
Runtime: crate::RuntimeT<pallet_bags_list::Instance1>,
Block: BlockT + DeserializeOwned,
Block::Header: DeserializeOwned,
Expand Down Expand Up @@ -62,9 +65,10 @@ where
<Runtime as pallet_staking::Config>::VoterList::count(),
);

let voters =
<pallet_staking::Pallet<Runtime> as ElectionDataProvider>::electing_voters(voter_limit)
.unwrap();
let voters = <pallet_staking::Pallet<Runtime> as ElectionDataProvider>::electing_voters(
election_bounds.voters,
)
.unwrap();

let mut voters_nominator_only = voters
.iter()
Expand All @@ -82,8 +86,8 @@ where
.map(|(x, y, _)| (x.clone(), *y as f64 / currency_unit));
log::info!(
target: crate::LOG_TARGET,
"a snapshot with limit {:?} has been created, {} voters are taken. min nominator: {:?}, max: {:?}",
voter_limit,
"a snapshot with limits {:?} has been created, {} voters are taken. min nominator: {:?}, max: {:?}",
election_bounds,
voters.len(),
min_voter,
max_voter
Expand Down
10 changes: 6 additions & 4 deletions frame/beefy/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@

use std::vec;

use frame_election_provider_support::{onchain, SequentialPhragmen};
use frame_election_provider_support::{
onchain, ElectionBounds, ElectionBoundsBuilder, SequentialPhragmen,
};
use frame_support::{
construct_runtime, parameter_types,
sp_io::TestExternalities,
Expand Down Expand Up @@ -192,6 +194,7 @@ parameter_types! {
pub const BondingDuration: EraIndex = 3;
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17);
pub static ElectionsBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::new().build();
}

pub struct OnChainSeqPhragmen;
Expand All @@ -201,12 +204,10 @@ impl onchain::Config for OnChainSeqPhragmen {
type DataProvider = Staking;
type WeightInfo = ();
type MaxWinners = ConstU32<100>;
type VotersBound = ConstU32<{ u32::MAX }>;
type TargetsBound = ConstU32<{ u32::MAX }>;
type ElectionBounds = ElectionsBoundsOnChain;
}

impl pallet_staking::Config for Test {
type MaxNominations = ConstU32<16>;
type RewardRemainder = ();
type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote;
type RuntimeEvent = RuntimeEvent;
Expand All @@ -228,6 +229,7 @@ impl pallet_staking::Config for Test {
type GenesisElectionProvider = Self::ElectionProvider;
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
type TargetList = pallet_staking::UseValidatorsMap<Self>;
type NominationsQuota = pallet_staking::FixedNominationsQuota<16>;
type MaxUnlockingChunks = ConstU32<32>;
type HistoryDepth = ConstU32<84>;
type OnStakerSlash = ();
Expand Down
5 changes: 3 additions & 2 deletions frame/election-provider-multi-phase/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use super::*;
use crate::{unsigned::IndexAssignmentOf, Pallet as MultiPhase};
use frame_benchmarking::account;
use frame_election_provider_support::DataProviderBounds;
use frame_support::{
assert_ok,
traits::{Hooks, TryCollect},
Expand Down Expand Up @@ -268,8 +269,8 @@ frame_benchmarking::benchmarks! {

// 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);
let targets = T::DataProvider::electable_targets(None)?;
let voters = T::DataProvider::electing_voters(None)?;
let targets = T::DataProvider::electable_targets(DataProviderBounds::new_unbounded())?;
let voters = T::DataProvider::electing_voters(DataProviderBounds::new_unbounded())?;
let desired_targets = T::DataProvider::desired_targets()?;
assert!(<MultiPhase<T>>::snapshot().is_none());
}: {
Expand Down
90 changes: 52 additions & 38 deletions frame/election-provider-multi-phase/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,9 @@

use codec::{Decode, Encode};
use frame_election_provider_support::{
BoundedSupportsOf, ElectionDataProvider, ElectionProvider, ElectionProviderBase,
InstantElectionProvider, NposSolution,
BoundedSupportsOf, DataProviderBounds, ElectionBounds, ElectionBoundsBuilder,
ElectionDataProvider, ElectionProvider, ElectionProviderBase, InstantElectionProvider,
NposSolution,
};
use frame_support::{
dispatch::DispatchClass,
Expand Down Expand Up @@ -654,23 +655,19 @@ pub mod pallet {
#[pallet::constant]
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`
/// implementation.
///
/// Note: This must always be greater or equal to `T::DataProvider::desired_targets()`.
#[pallet::constant]
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.
type SlashHandler: OnUnbalanced<NegativeImbalanceOf<Self>>;

Expand Down Expand Up @@ -1081,14 +1078,18 @@ pub mod pallet {
) -> DispatchResult {
T::ForceOrigin::ensure_origin(origin)?;
ensure!(Self::current_phase().is_emergency(), <Error<T>>::CallNotAllowed);

let supports =
T::GovernanceFallback::instant_elect(maybe_max_voters, maybe_max_targets).map_err(
|e| {
log!(error, "GovernanceFallback failed: {:?}", e);
Error::<T>::FallbackFailed
},
)?;
let election_bounds = ElectionBoundsBuilder::new()
.voters_count(maybe_max_voters.unwrap_or(0))
.targets_count(maybe_max_targets.unwrap_or(0))
.build();
gpestana marked this conversation as resolved.
Show resolved Hide resolved
let supports = T::GovernanceFallback::instant_elect(
election_bounds.voters,
election_bounds.targets,
)
.map_err(|e| {
log!(error, "GovernanceFallback failed: {:?}", e);
Error::<T>::FallbackFailed
})?;

// transform BoundedVec<_, T::GovernanceFallback::MaxWinners> into
// `BoundedVec<_, T::MaxWinners>`
Expand Down Expand Up @@ -1404,13 +1405,16 @@ impl<T: Config> Pallet<T> {
/// Extracted for easier weight calculation.
fn create_snapshot_external(
) -> Result<(Vec<T::AccountId>, Vec<VoterOf<T>>, u32), ElectionError<T>> {
let target_limit = T::MaxElectableTargets::get().saturated_into::<usize>();
let voter_limit = T::MaxElectingVoters::get().saturated_into::<usize>();
let election_bounds = T::ElectionBounds::get();
let target_limit =
election_bounds.targets.count.unwrap_or(u32::MAX).saturated_into::<usize>();
rossbulat marked this conversation as resolved.
Show resolved Hide resolved
let voter_limit =
election_bounds.voters.count.unwrap_or(u32::MAX).saturated_into::<usize>();

let targets = T::DataProvider::electable_targets(Some(target_limit))
let targets = T::DataProvider::electable_targets(election_bounds.targets)
.map_err(ElectionError::DataProvider)?;

let voters = T::DataProvider::electing_voters(Some(voter_limit))
let voters = T::DataProvider::electing_voters(election_bounds.voters)
.map_err(ElectionError::DataProvider)?;

if targets.len() > target_limit || voters.len() > voter_limit {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can just get rid of this. It is double checking some bounds that are already "guaranteed" to be met. If we are to check, we should also check the byte size (if possible to do cheaply).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goal here was to double check that the data coming from the election provider meet the expected bounds, since we have no guarantee that the data provider is properly implemented. fwiw, there are a couple of old test checking for this.

Regarding checking the size in bytes of the voter list, this pallet doesn't have the concept of size in bytes of a voter list, since that is a concept only on the data provider side (the size tracker and bounds are implemented on the data provider side). Given the point made above that EPM should not blindly trust the data provider, perhaps we could also add the size in bytes of the voter list checks in EPM (needs a new bound config for this pallet), or we could just assume the data provider always behaves as expected and remove the checks altogether. wdyt?

Copy link
Contributor Author

@gpestana gpestana Mar 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added voter size checks here too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your point about this pallet not dealing with byte size is correct, but in extension it would also not have to deal with length either.

In principle, this pallet only knows that there is an election_bounds, which has voters and targets, and once passed to the T::DataProvider, they need to be respected.

If you were to do this pedantically, you would make the fields of ElectionProviderBounds private to hide this actually.

I would formulate this then as:

let election_bounds = T::ElectionBounds::get();

let targets = T::DataProvider::electable_targets(election_bounds.targets)
	.and_then(|t| election_bounds.targets.ensure_respected(&v))
	.map_err(ElectionError::DataProvider)?;

let voters = T::DataProvider::electing_voters(election_bounds.voters)
	.and_then(|v| election_bounds.voters.ensure_respected(&v))
	.map_err(ElectionError::DataProvider)?;

and then inside ensure_respected implementation you can check length, and check byte-size again as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great input. I have refactored this code and it ended up much simpler, thanks!

Expand Down Expand Up @@ -1592,15 +1596,18 @@ impl<T: Config> Pallet<T> {
<QueuedSolution<T>>::take()
.ok_or(ElectionError::<T>::NothingQueued)
.or_else(|_| {
T::Fallback::instant_elect(None, None)
.map_err(|fe| ElectionError::Fallback(fe))
.and_then(|supports| {
Ok(ReadySolution {
supports,
score: Default::default(),
compute: ElectionCompute::Fallback,
})
T::Fallback::instant_elect(
DataProviderBounds::new_unbounded(),
gpestana marked this conversation as resolved.
Show resolved Hide resolved
DataProviderBounds::new_unbounded(),
)
.map_err(|fe| ElectionError::Fallback(fe))
.and_then(|supports| {
Ok(ReadySolution {
supports,
score: Default::default(),
compute: ElectionCompute::Fallback,
})
})
})
.map(|ReadySolution { compute, score, supports }| {
Self::deposit_event(Event::ElectionFinalized { compute, score });
Expand Down Expand Up @@ -1876,7 +1883,7 @@ mod tests {
mock::{
multi_phase_events, raw_solution, roll_to, roll_to_signed, roll_to_unsigned, AccountId,
ExtBuilder, MockWeightInfo, MockedWeightInfo, MultiPhase, Runtime, RuntimeOrigin,
SignedMaxSubmissions, System, TargetIndex, Targets,
SignedMaxSubmissions, System,
},
Phase,
};
Expand Down Expand Up @@ -2479,7 +2486,11 @@ mod tests {
fn snapshot_too_big_failure_onchain_fallback() {
// the `MockStaking` is designed such that if it has too many targets, it simply fails.
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::new().targets_count(1_000).build();
crate::mock::ElectionsBounds::set(new_bounds);

crate::mock::Targets::set((0..(1_000 as AccountId) + 1).collect::<Vec<_>>());

// Signed phase failed to open.
roll_to(15);
Expand Down Expand Up @@ -2514,9 +2525,11 @@ mod tests {
fn snapshot_too_big_failure_no_fallback() {
// and if the backup mode is nothing, we go into the emergency mode..
ExtBuilder::default().onchain_fallback(false).build_and_execute(|| {
crate::mock::Targets::set(
(0..(TargetIndex::max_value() as AccountId) + 1).collect::<Vec<_>>(),
);
// sets bounds on number of targets.
let new_bounds = ElectionBoundsBuilder::new().targets_count(1_000).build();
crate::mock::ElectionsBounds::set(new_bounds);

crate::mock::Targets::set((0..(1_000 as AccountId) + 1).collect::<Vec<_>>());

// Signed phase failed to open.
roll_to(15);
Expand Down Expand Up @@ -2548,7 +2561,8 @@ mod tests {
// we have 8 voters in total.
assert_eq!(crate::mock::Voters::get().len(), 8);
// but we want to take 2.
crate::mock::MaxElectingVoters::set(2);
let new_bounds = ElectionBoundsBuilder::new().voters_count(2).build();
crate::mock::ElectionsBounds::set(new_bounds);

// Signed phase opens just fine.
roll_to_signed();
Expand Down
Loading