diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 03ab5574a2180..180efa49a4cfa 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -229,7 +229,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_election_provider_support::{ BoundedSupportsOf, ElectionDataProvider, ElectionProvider, ElectionProviderBase, InstantElectionProvider, NposSolution, @@ -239,7 +239,7 @@ use frame_support::{ ensure, traits::{Currency, DefensiveResult, Get, OnUnbalanced, ReservableCurrency}, weights::Weight, - DefaultNoBound, EqNoBound, PartialEqNoBound, + BoundedVec, DefaultNoBound, EqNoBound, PartialEqNoBound, }; use frame_system::{ensure_none, offchain::SendTransactionTypes, pallet_prelude::BlockNumberFor}; use scale_info::TypeInfo; @@ -294,6 +294,10 @@ pub type SolutionAccuracyOf = ::MinerConfig> as NposSolution>::Accuracy; /// The fallback election type. pub type FallbackErrorOf = <::Fallback as ElectionProviderBase>::Error; +/// The maximum electable targets, as seen by the miner config. +pub type MaxElectableTargetsOf = ::MaxElectableTargets; +/// The maximum electing voters, as seen by the miner config. +pub type MaxElectingVotersOf = ::MaxElectingVoters; /// Configuration for the benchmarks of the pallet. pub trait BenchmarkingConfig { @@ -314,7 +318,7 @@ pub trait BenchmarkingConfig { } /// Current phase of the pallet. -#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo)] +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] pub enum Phase { /// Nothing, the election is not happening. Off, @@ -453,13 +457,14 @@ where /// [`ElectionDataProvider`] and are kept around until the round is finished. /// /// These are stored together because they are often accessed together. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo)] -#[scale_info(skip_type_params(T))] -pub struct RoundSnapshot { +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, MaxEncodedLen, TypeInfo)] +#[codec(mel_bound(skip_type_params(V, T)))] +#[scale_info(skip_type_params(V, T))] +pub struct RoundSnapshot, T: Get> { /// All of the voters. - pub voters: Vec, + pub voters: BoundedVec, /// All of the targets. - pub targets: Vec, + pub targets: BoundedVec, } /// Encodes the length of a solution or a snapshot. @@ -467,7 +472,7 @@ pub struct RoundSnapshot { /// This is stored automatically on-chain, and it contains the **size of the entire snapshot**. /// This is also used in dispatchables as weight witness data and should **only contain the size of /// the presented solution**, not the entire snapshot. -#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, Debug, Default, TypeInfo)] +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, Debug, Default, TypeInfo, MaxEncodedLen)] pub struct SolutionOrSnapshotSize { /// The length of voters. #[codec(compact)] @@ -619,6 +624,8 @@ pub mod pallet { AccountId = Self::AccountId, MaxVotesPerVoter = ::MaxVotesPerVoter, MaxWinners = Self::MaxWinners, + MaxElectingVoters = Self::MaxElectingVoters, + MaxElectableTargets = Self::MaxElectableTargets, >; /// Maximum number of signed submissions that can be queued. @@ -663,11 +670,11 @@ pub mod pallet { /// 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>; + type MaxElectingVoters: Get> + Get; /// The maximum number of electable targets to put in the snapshot. #[pallet::constant] - type MaxElectableTargets: Get>; + type MaxElectableTargets: Get> + Get; /// The maximum number of winners that can be elected by this `ElectionProvider` /// implementation. @@ -686,6 +693,8 @@ pub mod pallet { type DataProvider: ElectionDataProvider< AccountId = Self::AccountId, BlockNumber = BlockNumberFor, + MaxElectingVoters = Self::MaxElectingVoters, + MaxElectableTargets = Self::MaxElectableTargets, >; /// Configuration for the fallback. @@ -1267,6 +1276,7 @@ pub mod pallet { /// /// Always sorted by score. #[pallet::storage] + #[pallet::unbounded] #[pallet::getter(fn queued_solution)] pub type QueuedSolution = StorageValue<_, ReadySolution>; @@ -1276,7 +1286,10 @@ pub mod pallet { /// This is created at the beginning of the signed phase and cleared upon calling `elect`. #[pallet::storage] #[pallet::getter(fn snapshot)] - pub type Snapshot = StorageValue<_, RoundSnapshot>>; + pub type Snapshot = StorageValue< + _, + RoundSnapshot, T::MaxElectingVoters, T::MaxElectableTargets>, + >; /// Desired number of targets to elect for this round. /// @@ -1326,6 +1339,7 @@ pub mod pallet { /// Twox note: the key of the map is an auto-incrementing index which users cannot inspect or /// affect; we shouldn't need a cryptographically secure hasher. #[pallet::storage] + #[pallet::unbounded] pub type SignedSubmissionsMap = StorageMap<_, Twox64Concat, u32, SignedSubmissionOf, OptionQuery>; @@ -1345,7 +1359,6 @@ pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); #[pallet::pallet] - #[pallet::without_storage_info] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); } @@ -1393,8 +1406,8 @@ impl Pallet { /// /// Extracted for easier weight calculation. fn create_snapshot_internal( - targets: Vec, - voters: Vec>, + targets: BoundedVec, + voters: BoundedVec, T::MaxElectingVoters>, desired_targets: u32, ) { let metadata = @@ -1407,7 +1420,15 @@ impl Pallet { // instead of using storage APIs, we do a manual encoding into a fixed-size buffer. // `encoded_size` encodes it without storing it anywhere, this should not cause any // allocation. - let snapshot = RoundSnapshot::> { voters, targets }; + let snapshot = RoundSnapshot::< + T::AccountId, + VoterOf, + T::MaxElectingVoters, + T::MaxElectableTargets, + > { + voters, + targets, + }; let size = snapshot.encoded_size(); log!(debug, "snapshot pre-calculated size {:?}", size); let mut buffer = Vec::with_capacity(size); @@ -1424,10 +1445,16 @@ impl Pallet { /// Parts of [`create_snapshot`] that happen outside of this pallet. /// /// Extracted for easier weight calculation. - fn create_snapshot_external( - ) -> Result<(Vec, Vec>, u32), ElectionError> { - let target_limit = T::MaxElectableTargets::get().saturated_into::(); - let voter_limit = T::MaxElectingVoters::get().saturated_into::(); + fn create_snapshot_external() -> Result< + ( + BoundedVec, + BoundedVec, T::MaxElectingVoters>, + u32, + ), + ElectionError, + > { + let target_limit = >::get().saturated_into::(); + let voter_limit = >::get().saturated_into::(); let targets = T::DataProvider::electable_targets(Some(target_limit)) .map_err(ElectionError::DataProvider)?; @@ -1678,6 +1705,8 @@ impl ElectionProviderBase for Pallet { type AccountId = T::AccountId; type BlockNumber = BlockNumberFor; type Error = ElectionError; + type MaxElectingVoters = T::MaxElectingVoters; + type MaxElectableTargets = T::MaxElectableTargets; type MaxWinners = T::MaxWinners; type DataProvider = T::DataProvider; } diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index c5133d89e4c82..21086355bbae4 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -148,8 +148,13 @@ pub fn trim_helpers() -> TrimHelpers { let desired_targets = MultiPhase::desired_targets().unwrap(); - let ElectionResult::<_, SolutionAccuracyOf> { mut assignments, .. } = - seq_phragmen(desired_targets as usize, targets.clone(), voters.clone(), None).unwrap(); + let ElectionResult::<_, SolutionAccuracyOf> { mut assignments, .. } = seq_phragmen( + desired_targets as usize, + targets.clone().into_inner(), + voters.clone().into_inner(), + None, + ) + .unwrap(); // sort by decreasing order of stake assignments.sort_by_key(|assignment| { @@ -165,7 +170,12 @@ pub fn trim_helpers() -> TrimHelpers { .collect::, _>>() .expect("test assignments don't contain any voters with too many votes"); - TrimHelpers { voters, assignments, encoded_size_of, voter_index: Box::new(voter_index) } + TrimHelpers { + voters: voters.to_vec(), + assignments, + encoded_size_of, + voter_index: Box::new(voter_index), + } } /// Spit out a verifiable raw solution. @@ -176,7 +186,13 @@ pub fn raw_solution() -> RawSolution> { let desired_targets = MultiPhase::desired_targets().unwrap(); let ElectionResult::<_, SolutionAccuracyOf> { winners: _, assignments } = - seq_phragmen(desired_targets as usize, targets.clone(), voters.clone(), None).unwrap(); + seq_phragmen( + desired_targets as usize, + targets.clone().into_inner(), + voters.clone().into_inner(), + None, + ) + .unwrap(); // closures let cache = helpers::generate_voter_cache::(&voters); @@ -311,6 +327,8 @@ impl onchain::Config for OnChainSeqPhragmen { type Solver = SequentialPhragmen, Balancing>; type DataProvider = StakingMock; type WeightInfo = (); + type MaxElectingVoters = MaxElectingVoters; + type MaxElectableTargets = MaxElectableTargets; type MaxWinners = MaxWinners; type VotersBound = ConstU32<{ u32::MAX }>; type TargetsBound = ConstU32<{ u32::MAX }>; @@ -322,6 +340,8 @@ impl ElectionProviderBase for MockFallback { type AccountId = AccountId; type Error = &'static str; type DataProvider = StakingMock; + type MaxElectingVoters = MaxElectingVoters; + type MaxElectableTargets = MaxElectableTargets; type MaxWinners = MaxWinners; } @@ -360,6 +380,8 @@ impl MinerConfig for Runtime { type AccountId = AccountId; type MaxLength = MinerMaxLength; type MaxWeight = MinerMaxWeight; + type MaxElectingVoters = MaxElectingVoters; + type MaxElectableTargets = MaxElectableTargets; type MaxVotesPerVoter = ::MaxVotesPerVoter; type MaxWinners = MaxWinners; type Solution = TestNposSolution; @@ -435,8 +457,12 @@ impl ElectionDataProvider for StakingMock { type BlockNumber = BlockNumber; type AccountId = AccountId; type MaxVotesPerVoter = MaxNominations; + type MaxElectingVoters = MaxElectingVoters; + type MaxElectableTargets = MaxElectableTargets; - fn electable_targets(maybe_max_len: Option) -> data_provider::Result> { + fn electable_targets( + maybe_max_len: Option, + ) -> data_provider::Result> { let targets = Targets::get(); if !DataProviderAllowBadData::get() && @@ -445,20 +471,15 @@ impl ElectionDataProvider for StakingMock { return Err("Targets too big") } - Ok(targets) + Ok(BoundedVec::truncate_from(targets)) } fn electing_voters( - maybe_max_len: Option, - ) -> data_provider::Result>> { - let mut voters = Voters::get(); - if !DataProviderAllowBadData::get() { - if let Some(max_len) = maybe_max_len { - voters.truncate(max_len) - } - } + _maybe_max_len: Option, + ) -> data_provider::Result, Self::MaxElectingVoters>> { + let voters = Voters::get(); - Ok(voters) + Ok(BoundedVec::truncate_from(voters)) } fn desired_targets() -> data_provider::Result { diff --git a/frame/election-provider-multi-phase/src/signed.rs b/frame/election-provider-multi-phase/src/signed.rs index 226404e4afc1a..93037835c1024 100644 --- a/frame/election-provider-multi-phase/src/signed.rs +++ b/frame/election-provider-multi-phase/src/signed.rs @@ -561,36 +561,6 @@ mod tests { }) } - #[test] - fn data_provider_should_respect_target_limits() { - ExtBuilder::default().build_and_execute(|| { - // given a reduced expectation of maximum electable targets - MaxElectableTargets::set(2); - // and a data provider that does not respect limits - DataProviderAllowBadData::set(true); - - assert_noop!( - MultiPhase::create_snapshot(), - ElectionError::DataProvider("Snapshot too big for submission."), - ); - }) - } - - #[test] - fn data_provider_should_respect_voter_limits() { - ExtBuilder::default().build_and_execute(|| { - // given a reduced expectation of maximum electing voters - MaxElectingVoters::set(2); - // and a data provider that does not respect limits - DataProviderAllowBadData::set(true); - - assert_noop!( - MultiPhase::create_snapshot(), - ElectionError::DataProvider("Snapshot too big for submission."), - ); - }) - } - #[test] fn desired_targets_greater_than_max_winners() { ExtBuilder::default().build_and_execute(|| { diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index e21e6c5e6d229..d9de408a5575d 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -167,6 +167,7 @@ impl Pallet { let RoundSnapshot { voters, targets } = Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?; let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?; + let (solution, score, size) = Miner::::mine_solution_with_snapshot::< T::Solver, >(voters, targets, desired_targets)?; @@ -380,6 +381,11 @@ pub trait MinerConfig { + Ord + NposSolution + TypeInfo; + + /// Maximum number of electing voters in the snaphots. + type MaxElectingVoters: Get; + /// Maximum number of elecatble targets in the snapshots. + type MaxElectableTargets: Get; /// Maximum number of votes per voter in the snapshots. type MaxVotesPerVoter; /// Maximum length of the solution that the miner is allowed to generate. @@ -405,26 +411,33 @@ pub struct Miner(sp_std::marker::PhantomData); impl Miner { /// Same as [`Pallet::mine_solution`], but the input snapshot data must be given. pub fn mine_solution_with_snapshot( - voters: Vec<(T::AccountId, VoteWeight, BoundedVec)>, - targets: Vec, + voters: BoundedVec< + (T::AccountId, VoteWeight, BoundedVec), + T::MaxElectingVoters, + >, + targets: BoundedVec, desired_targets: u32, ) -> Result<(SolutionOf, ElectionScore, SolutionOrSnapshotSize), MinerError> where S: NposSolver, { - S::solve(desired_targets as usize, targets.clone(), voters.clone()) - .map_err(|e| { - log_no_system!(error, "solver error: {:?}", e); - MinerError::Solver - }) - .and_then(|e| { - Self::prepare_election_result_with_snapshot::( - e, - voters, - targets, - desired_targets, - ) - }) + S::solve( + desired_targets as usize, + targets.clone().into_inner(), + voters.clone().into_inner(), + ) + .map_err(|e| { + log_no_system!(error, "solver error: {:?}", e); + MinerError::Solver + }) + .and_then(|e| { + Self::prepare_election_result_with_snapshot::( + e, + voters, + targets, + desired_targets, + ) + }) } /// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which @@ -433,8 +446,11 @@ impl Miner { /// Will always reduce the solution as well. pub fn prepare_election_result_with_snapshot( election_result: ElectionResult, - voters: Vec<(T::AccountId, VoteWeight, BoundedVec)>, - targets: Vec, + voters: BoundedVec< + (T::AccountId, VoteWeight, BoundedVec), + T::MaxElectingVoters, + >, + targets: BoundedVec, desired_targets: u32, ) -> Result<(SolutionOf, ElectionScore, SolutionOrSnapshotSize), MinerError> { // now make some helper closures. @@ -702,7 +718,12 @@ impl Miner { raw_solution: RawSolution>, compute: ElectionCompute, desired_targets: u32, - snapshot: RoundSnapshot>, + snapshot: RoundSnapshot< + T::AccountId, + MinerVoterOf, + T::MaxElectingVoters, + T::MaxElectableTargets, + >, current_round: u32, minimum_untrusted_score: Option, ) -> Result, FeasibilityError> { diff --git a/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs b/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs index 0d5f8872193ad..2bd30c3fb039d 100644 --- a/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs +++ b/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs @@ -206,8 +206,14 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type SlashHandler = (); type RewardHandler = (); type DataProvider = Staking; - type Fallback = - frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, MaxWinners)>; + type Fallback = frame_election_provider_support::NoElection<( + AccountId, + BlockNumber, + Staking, + MaxElectingVoters, + MaxElectableTargets, + MaxWinners, + )>; type GovernanceFallback = onchain::OnChainExecution; type Solver = SequentialPhragmen, ()>; type ForceOrigin = EnsureRoot; @@ -221,6 +227,8 @@ impl pallet_election_provider_multi_phase::Config for Runtime { impl MinerConfig for Runtime { type AccountId = AccountId; type Solution = MockNposSolution; + type MaxElectingVoters = MaxElectingVoters; + type MaxElectableTargets = MaxElectableTargets; type MaxVotesPerVoter = <::DataProvider as ElectionDataProvider>::MaxVotesPerVoter; type MaxLength = MinerMaxLength; @@ -305,6 +313,8 @@ impl onchain::Config for OnChainSeqPhragmen { >; type DataProvider = Staking; type WeightInfo = (); + type MaxElectingVoters = MaxElectingVoters; + type MaxElectableTargets = MaxElectableTargets; type MaxWinners = MaxWinners; type VotersBound = VotersBound; type TargetsBound = TargetsBound; diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 978a5df427244..23276a4548c53 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -81,7 +81,7 @@ //! ```rust //! # use frame_election_provider_support::{*, data_provider}; //! # use sp_npos_elections::{Support, Assignment}; -//! # use frame_support::traits::ConstU32; +//! # use frame_support::{BoundedVec, traits::ConstU32}; //! # use frame_support::bounded_vec; //! //! type AccountId = u64; @@ -105,17 +105,19 @@ //! type AccountId = AccountId; //! type BlockNumber = BlockNumber; //! type MaxVotesPerVoter = ConstU32<1>; +//! type MaxElectableTargets = ConstU32<400>; +//! type MaxElectingVoters = ConstU32<400>; //! //! fn desired_targets() -> data_provider::Result { //! Ok(1) //! } //! fn electing_voters(maybe_max_len: Option) -//! -> data_provider::Result>> +//! -> data_provider::Result, Self::MaxElectingVoters>> //! { //! Ok(Default::default()) //! } -//! fn electable_targets(maybe_max_len: Option) -> data_provider::Result> { -//! Ok(vec![10, 20, 30]) +//! fn electable_targets(maybe_max_len: Option) -> data_provider::Result> { +//! Ok(bounded_vec![10, 20, 30]) //! } //! fn next_election_prediction(now: BlockNumber) -> BlockNumber { //! 0 @@ -138,6 +140,8 @@ //! type BlockNumber = BlockNumber; //! type Error = &'static str; //! type DataProvider = T::DataProvider; +//! type MaxElectingVoters = ConstU32<{ u32::MAX }>; +//! type MaxElectableTargets = ConstU32<{ u32::MAX }>; //! type MaxWinners = ConstU32<{ u32::MAX }>; //! //! } @@ -280,6 +284,12 @@ pub trait ElectionDataProvider { /// The block number type. type BlockNumber; + /// Maximum number of targets that this data provider is providing. + type MaxElectableTargets: Get; + + /// Maximum number of voters that this data provider is providing. + type MaxElectingVoters: Get; + /// Maximum number of votes per voter that this data provider is providing. type MaxVotesPerVoter: Get; @@ -293,7 +303,7 @@ pub trait ElectionDataProvider { /// appropriate weight at the end of execution with the system pallet directly. fn electable_targets( maybe_max_len: Option, - ) -> data_provider::Result>; + ) -> data_provider::Result>; /// All the voters that participate in the election, thus "electing". /// @@ -304,7 +314,9 @@ pub trait ElectionDataProvider { /// /// 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. - fn electing_voters(maybe_max_len: Option) -> data_provider::Result>>; + fn electing_voters( + maybe_max_len: Option, + ) -> data_provider::Result, Self::MaxElectingVoters>>; /// The number of targets to elect. /// @@ -371,6 +383,26 @@ pub trait ElectionProviderBase { /// The error type that is returned by the provider. type Error: Debug; + /// The upper bound on electing voters that can be returned. + /// + /// # WARNING + /// + /// when communicating with the data provider, one must ensure that + /// `DataProvider::electing_voters` returns a value less than this bound. An + /// implementation can chose to either return an error and/or sort and + /// truncate the output to meet this bound. + type MaxElectingVoters: Get; + + /// The upper bound on the electable targets that can be returned. + /// + /// # WARNING + /// + /// when communicating with the data provider, one must ensure that + /// `DataProvider::electable_targets` returns a value less than this bound. An + /// implementation can chose to either return an error and/or sort and + /// truncate the output to meet this bound. + type MaxElectableTargets: Get; + /// The upper bound on election winners that can be returned. /// /// # WARNING @@ -433,23 +465,45 @@ pub trait InstantElectionProvider: ElectionProviderBase { /// An election provider that does nothing whatsoever. pub struct NoElection(sp_std::marker::PhantomData); -impl ElectionProviderBase - for NoElection<(AccountId, BlockNumber, DataProvider, MaxWinners)> +impl + ElectionProviderBase + for NoElection<( + AccountId, + BlockNumber, + DataProvider, + MaxElectingVoters, + MaxElectableTargets, + MaxWinners, + )> where DataProvider: ElectionDataProvider, + MaxElectingVoters: Get, + MaxElectableTargets: Get, MaxWinners: Get, { type AccountId = AccountId; type BlockNumber = BlockNumber; type Error = &'static str; + type MaxElectingVoters = MaxElectingVoters; + type MaxElectableTargets = MaxElectableTargets; type MaxWinners = MaxWinners; type DataProvider = DataProvider; } -impl ElectionProvider - for NoElection<(AccountId, BlockNumber, DataProvider, MaxWinners)> +impl + ElectionProvider + for NoElection<( + AccountId, + BlockNumber, + DataProvider, + MaxElectingVoters, + MaxElectableTargets, + MaxWinners, + )> where DataProvider: ElectionDataProvider, + MaxElectingVoters: Get, + MaxElectableTargets: Get, MaxWinners: Get, { fn ongoing() -> bool { @@ -461,10 +515,20 @@ where } } -impl InstantElectionProvider - for NoElection<(AccountId, BlockNumber, DataProvider, MaxWinners)> +impl + InstantElectionProvider + for NoElection<( + AccountId, + BlockNumber, + DataProvider, + MaxElectingVoters, + MaxElectableTargets, + MaxWinners, + )> where DataProvider: ElectionDataProvider, + MaxElectingVoters: Get, + MaxElectableTargets: Get, MaxWinners: Get, { fn instant_elect( diff --git a/frame/election-provider-support/src/onchain.rs b/frame/election-provider-support/src/onchain.rs index bfdc21a8fe2d6..b90bc3adbc3c9 100644 --- a/frame/election-provider-support/src/onchain.rs +++ b/frame/election-provider-support/src/onchain.rs @@ -79,6 +79,18 @@ pub trait Config { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; + /// Upper bound on maximum electing voters. + /// + /// As noted in the documentation of [`ElectionProviderBase::MaxElectingVoters`], this value + /// should always be more than `DataProvider::electing_voters`. + type MaxElectingVoters: Get; + + /// Upper bound on electable targets. + /// + /// As noted in the documentation of [`ElectionProviderBase::MaxElectableTargets`], this value + /// should always be more than `DataProvider::electable_targets`. + type MaxElectableTargets: Get; + /// Upper bound on maximum winners from electable targets. /// /// As noted in the documentation of [`ElectionProviderBase::MaxWinners`], this value should @@ -127,7 +139,8 @@ fn elect_with_input_bounds( }; let ElectionResult { winners: _, assignments } = - T::Solver::solve(desired_targets as usize, targets, voters).map_err(Error::from)?; + T::Solver::solve(desired_targets as usize, targets.into_inner(), voters.into_inner()) + .map_err(Error::from)?; let staked = assignment_ratio_to_staked_normalized(assignments, &stake_of)?; @@ -153,6 +166,8 @@ impl ElectionProviderBase for OnChainExecution { type AccountId = ::AccountId; type BlockNumber = frame_system::pallet_prelude::BlockNumberFor; type Error = Error; + type MaxElectingVoters = T::MaxElectingVoters; + type MaxElectableTargets = T::MaxElectableTargets; type MaxWinners = T::MaxWinners; type DataProvider = T::DataProvider; } @@ -186,7 +201,7 @@ impl ElectionProvider for OnChainExecution { mod tests { use super::*; use crate::{ElectionProvider, PhragMMS, SequentialPhragmen}; - use frame_support::{assert_noop, parameter_types, traits::ConstU32}; + use frame_support::{assert_noop, parameter_types, traits::ConstU32, BoundedVec}; use sp_npos_elections::Support; use sp_runtime::Perbill; type AccountId = u64; @@ -236,6 +251,8 @@ mod tests { parameter_types! { pub static MaxWinners: u32 = 10; pub static DesiredTargets: u32 = 2; + pub static MaxElectableTargets: u32 = 400; + pub static MaxElectingVoters: u32 = 600; } impl Config for PhragmenParams { @@ -243,6 +260,8 @@ mod tests { type Solver = SequentialPhragmen; type DataProvider = mock_data_provider::DataProvider; type WeightInfo = (); + type MaxElectingVoters = MaxElectingVoters; + type MaxElectableTargets = MaxElectableTargets; type MaxWinners = MaxWinners; type VotersBound = ConstU32<600>; type TargetsBound = ConstU32<400>; @@ -253,6 +272,8 @@ mod tests { type Solver = PhragMMS; type DataProvider = mock_data_provider::DataProvider; type WeightInfo = (); + type MaxElectingVoters = MaxElectingVoters; + type MaxElectableTargets = MaxElectableTargets; type MaxWinners = MaxWinners; type VotersBound = ConstU32<600>; type TargetsBound = ConstU32<400>; @@ -269,16 +290,22 @@ mod tests { type AccountId = AccountId; type BlockNumber = BlockNumber; type MaxVotesPerVoter = ConstU32<2>; - fn electing_voters(_: Option) -> data_provider::Result>> { - Ok(vec![ + type MaxElectableTargets = MaxElectableTargets; + type MaxElectingVoters = MaxElectingVoters; + fn electing_voters( + _: Option, + ) -> data_provider::Result, Self::MaxElectingVoters>> { + Ok(bounded_vec![ (1, 10, bounded_vec![10, 20]), (2, 20, bounded_vec![30, 20]), (3, 30, bounded_vec![10, 30]), ]) } - fn electable_targets(_: Option) -> data_provider::Result> { - Ok(vec![10, 20, 30]) + fn electable_targets( + _: Option, + ) -> data_provider::Result> { + Ok(bounded_vec![10, 20, 30]) } fn desired_targets() -> data_provider::Result { diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 232b37de7c1b8..350d9dcec9389 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -233,6 +233,8 @@ parameter_types! { pub static MaxUnlockingChunks: u32 = 32; pub static RewardOnUnbalanceWasCalled: bool = false; pub static MaxWinners: u32 = 100; + pub static MaxElectingVoters: u32 = u32::max_value(); + pub static MaxElectableTargets: u32 = u32::max_value(); } type VoterBagsListInstance = pallet_bags_list::Instance1; @@ -252,6 +254,8 @@ impl onchain::Config for OnChainSeqPhragmen { type DataProvider = Staking; type WeightInfo = (); type MaxWinners = MaxWinners; + type MaxElectingVoters = MaxElectingVoters; + type MaxElectableTargets = MaxElectableTargets; type VotersBound = ConstU32<{ u32::MAX }>; type TargetsBound = ConstU32<{ u32::MAX }>; } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 052ba801618c2..b5d7b93e64716 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -18,8 +18,8 @@ //! Implementations for the Staking FRAME Pallet. use frame_election_provider_support::{ - data_provider, BoundedSupportsOf, ElectionDataProvider, ElectionProvider, ScoreProvider, - SortedListProvider, VoteWeight, VoterOf, + data_provider, BoundedSupportsOf, ElectionDataProvider, ElectionProvider, ElectionProviderBase, + ScoreProvider, SortedListProvider, VoteWeight, VoterOf, }; use frame_support::{ defensive, @@ -57,6 +57,11 @@ use frame_support::ensure; #[cfg(any(test, feature = "try-runtime"))] use sp_runtime::TryRuntimeError; +/// Bounds for electing voters defined by the election provider. +type MaxElectingVotersOf = ::MaxElectingVoters; +/// Bounds for electable targets defined by the election provider. +type MaxElectabletargetsOf = ::MaxElectableTargets; + /// The maximum number of iterations that we do whilst iterating over `T::VoterList` in /// `get_npos_voters`. /// @@ -761,13 +766,19 @@ impl Pallet { /// nominators. /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. - pub fn get_npos_voters(maybe_max_len: Option) -> Vec> { + pub fn get_npos_voters( + maybe_max_len: Option, + ) -> BoundedVec, MaxElectingVotersOf> { let max_allowed_len = { let all_voter_count = T::VoterList::count() as usize; maybe_max_len.unwrap_or(all_voter_count).min(all_voter_count) }; - let mut all_voters = Vec::<_>::with_capacity(max_allowed_len); + let mut all_voters = + BoundedVec::<_, MaxElectingVotersOf>::with_max_capacity(); + + debug_assert!(all_voters.capacity() > max_allowed_len, + "voter soft bounds are larger than hard bounds from bounded vec, result may be truncated to hard bounds."); // cache a few things. let weight_of = Self::weight_of_fn(); @@ -798,7 +809,10 @@ impl Pallet { if let Some(Nominations { targets, .. }) = >::get(&voter) { if !targets.is_empty() { - all_voters.push((voter.clone(), voter_weight, targets)); + if all_voters.try_push((voter.clone(), voter_weight, targets)).is_err() { + // bounds saturated, break loop. + break + } nominators_taken.saturating_inc(); } else { // Technically should never happen, but not much we can do about it. @@ -814,7 +828,10 @@ impl Pallet { .try_into() .expect("`MaxVotesPerVoter` must be greater than or equal to 1"), ); - all_voters.push(self_vote); + if all_voters.try_push(self_vote).is_err() { + // bounds saturated, break loop. + break + } validators_taken.saturating_inc(); } else { // this can only happen if: 1. there a bug in the bags-list (or whatever is the @@ -830,9 +847,6 @@ impl Pallet { } } - // all_voters should have not re-allocated. - debug_assert!(all_voters.capacity() == max_allowed_len); - Self::register_weight(T::WeightInfo::get_npos_voters(validators_taken, nominators_taken)); let min_active_stake: T::CurrencyBalance = @@ -854,11 +868,16 @@ impl Pallet { /// Get the targets for an upcoming npos election. /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. - pub fn get_npos_targets(maybe_max_len: Option) -> Vec { + pub fn get_npos_targets( + maybe_max_len: Option, + ) -> BoundedVec> { let max_allowed_len = maybe_max_len.unwrap_or_else(|| T::TargetList::count() as usize); - let mut all_targets = Vec::::with_capacity(max_allowed_len); + let mut all_targets = BoundedVec::>::with_max_capacity(); let mut targets_seen = 0; + debug_assert!(all_targets.capacity() > max_allowed_len, + "target soft bounds are larger than hard bounds from bounded vec, result may be truncated to hard bounds."); + let mut targets_iter = T::TargetList::iter(); while all_targets.len() < max_allowed_len && targets_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * max_allowed_len as u32) @@ -872,7 +891,10 @@ impl Pallet { }; if Validators::::contains_key(&target) { - all_targets.push(target); + if all_targets.try_push(target).is_err() { + // bounds saturated, break loop. + break + } } } @@ -1003,13 +1025,17 @@ impl ElectionDataProvider for Pallet { type AccountId = T::AccountId; type BlockNumber = BlockNumberFor; type MaxVotesPerVoter = T::MaxNominations; + type MaxElectingVoters = ::MaxElectingVoters; + type MaxElectableTargets = ::MaxElectableTargets; fn desired_targets() -> data_provider::Result { Self::register_weight(T::DbWeight::get().reads(1)); Ok(Self::validator_count()) } - fn electing_voters(maybe_max_len: Option) -> data_provider::Result>> { + fn electing_voters( + maybe_max_len: Option, + ) -> data_provider::Result, Self::MaxElectingVoters>> { // This can never fail -- if `maybe_max_len` is `Some(_)` we handle it. let voters = Self::get_npos_voters(maybe_max_len); debug_assert!(maybe_max_len.map_or(true, |max| voters.len() <= max)); @@ -1017,7 +1043,9 @@ impl ElectionDataProvider for Pallet { Ok(voters) } - fn electable_targets(maybe_max_len: Option) -> data_provider::Result> { + fn electable_targets( + maybe_max_len: Option, + ) -> data_provider::Result> { let target_count = T::TargetList::count(); // We can't handle this case yet -- return an error.