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

Bounding ElectionProvider #11499

Closed
wants to merge 14 commits into from
8 changes: 8 additions & 0 deletions bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,8 @@ frame_election_provider_support::generate_solution_type!(
parameter_types! {
pub MaxNominations: u32 = <NposSolution16 as frame_election_provider_support::NposSolution>::LIMIT as u32;
pub MaxElectingVoters: u32 = 10_000;
// Conservative value, unbounded to keep backward compatibility.
pub MaxWinnersPerPage: u32 = u32::max_value();
}

/// The numbers configured here could always be more than the the maximum limits of staking pallet
Expand Down Expand Up @@ -655,6 +657,9 @@ 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>;
// This is being defensive by defending the maximum bound possible.
type MaxBackersPerWinner = MaxElectingVoters;
type MaxWinnersPerPage = MaxWinnersPerPage;
}

impl onchain::BoundedConfig for OnChainSeqPhragmen {
Expand Down Expand Up @@ -708,6 +713,9 @@ impl pallet_election_provider_multi_phase::Config for Runtime {
type ForceOrigin = EnsureRootOrHalfCouncil;
type MaxElectableTargets = ConstU16<{ u16::MAX }>;
type MaxElectingVoters = MaxElectingVoters;
// This is being defensive by defending the maximum bound possible.
type MaxBackersPerWinner = MaxElectingVoters;
type MaxWinnersPerPage = MaxWinnersPerPage;
type BenchmarkingConfig = ElectionProviderBenchmarkConfig;
type WeightInfo = pallet_election_provider_multi_phase::weights::SubstrateWeight<Self>;
}
Expand Down
2 changes: 2 additions & 0 deletions frame/babe/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ impl onchain::Config for OnChainSeqPhragmen {
type Solver = SequentialPhragmen<DummyValidatorId, Perbill>;
type DataProvider = Staking;
type WeightInfo = ();
type MaxBackersPerWinner = ConstU32<{ u32::MAX }>;
type MaxWinnersPerPage = ConstU32<{ u32::MAX }>;
}

impl pallet_staking::Config for Test {
Expand Down
2 changes: 1 addition & 1 deletion frame/election-provider-multi-phase/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ frame_benchmarking::benchmarks! {
let initial_balance = T::Currency::minimum_balance() * 10u32.into();
T::Currency::make_free_balance_be(&receiver, initial_balance);
let ready = ReadySolution {
supports: vec![],
supports: Default::default(),
score: Default::default(),
compute: Default::default()
};
Expand Down
98 changes: 66 additions & 32 deletions frame/election-provider-multi-phase/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,18 +136,18 @@
//! This implies that the user of this pallet (i.e. a staking pallet) should re-try calling
//! `T::ElectionProvider::elect` in case of error, until `OK(_)` is returned.
//!
//! To generate an emergency solution, one must only provide one argument: [`Supports`]. This is
//! essentially a collection of elected winners for the election, and voters who support them. The
//! supports can be generated by any means. In the simplest case, it could be manual. For example,
//! in the case of massive network failure or misbehavior, [`Config::ForceOrigin`] might decide to
//! select only a small number of emergency winners (which would greatly restrict the next validator
//! set, if this pallet is used with `pallet-staking`). If the failure is for other technical
//! reasons, then a simple and safe way to generate supports is using the staking-miner binary
//! provided in the Polkadot repository. This binary has a subcommand named `emergency-solution`
//! which is capable of connecting to a live network, and generating appropriate `supports` using a
//! standard algorithm, and outputting the `supports` in hex format, ready for submission. Note that
//! while this binary lives in the Polkadot repository, this particular subcommand of it can work
//! against any substrate-based chain.
//! To generate an emergency solution, one must only provide one argument: [`BoundedSupportsOf`].
//! This is essentially a collection of elected winners for the election, and voters who support
//! them. The supports can be generated by any means. In the simplest case, it could be manual. For
//! example, in the case of massive network failure or misbehavior, [`Config::ForceOrigin`] might
//! decide to select only a small number of emergency winners (which would greatly restrict the next
//! validator set, if this pallet is used with `pallet-staking`). If the failure is for other
//! technical reasons, then a simple and safe way to generate supports is using the staking-miner
//! binary provided in the Polkadot repository. This binary has a subcommand named
//! `emergency-solution` which is capable of connecting to a live network, and generating
//! appropriate `supports` using a standard algorithm, and outputting the `supports` in hex format,
//! ready for submission. Note that while this binary lives in the Polkadot repository, this
//! particular subcommand of it can work against any substrate-based chain.
//!
//! See the `staking-miner` documentation in the Polkadot repository for more information.
//!
Expand Down Expand Up @@ -229,15 +229,17 @@

#![cfg_attr(not(feature = "std"), no_std)]

use codec::{Decode, Encode};
use codec::{Decode, Encode, MaxEncodedLen};
use frame_election_provider_support::{
ElectionDataProvider, ElectionProvider, InstantElectionProvider, NposSolution,
BoundedSupportsOf, ElectionDataProvider, ElectionProvider, InstantElectionProvider,
NposSolution, TryIntoBoundedSupports,
};
use frame_support::{
dispatch::DispatchResultWithPostInfo,
ensure,
traits::{Currency, Get, OnUnbalanced, ReservableCurrency},
weights::{DispatchClass, Weight},
DefaultNoBound, PartialEqNoBound, RuntimeDebugNoBound,
};
use frame_system::{ensure_none, offchain::SendTransactionTypes};
use scale_info::TypeInfo;
Expand All @@ -246,7 +248,7 @@ use sp_arithmetic::{
UpperOf,
};
use sp_npos_elections::{
assignment_ratio_to_staked_normalized, ElectionScore, EvaluateSupport, Supports, VoteWeight,
assignment_ratio_to_staked_normalized, ElectionScore, EvaluateSupport, VoteWeight,
};
use sp_runtime::{
transaction_validity::{
Expand Down Expand Up @@ -317,21 +319,23 @@ impl<T: Config> ElectionProvider for NoFallback<T> {
type BlockNumber = T::BlockNumber;
type DataProvider = T::DataProvider;
type Error = &'static str;
type MaxBackersPerWinner = T::MaxBackersPerWinner;
type MaxWinnersPerPage = T::MaxWinnersPerPage;

fn elect() -> Result<Supports<T::AccountId>, Self::Error> {
fn elect() -> Result<BoundedSupportsOf<Self>, Self::Error> {
// Do nothing, this will enable the emergency phase.
Err("NoFallback.")
}
}

impl<T: Config> InstantElectionProvider for NoFallback<T> {
fn elect_with_bounds(_: usize, _: usize) -> Result<Supports<T::AccountId>, Self::Error> {
fn elect_with_bounds(_: usize, _: usize) -> Result<BoundedSupportsOf<Self>, Self::Error> {
Err("NoFallback.")
}
}

/// 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<Bn> {
/// Nothing, the election is not happening.
Off,
Expand Down Expand Up @@ -393,7 +397,7 @@ impl<Bn: PartialEq + Eq> Phase<Bn> {
}

/// The type of `Computation` that provided this election data.
#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo)]
#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
pub enum ElectionCompute {
/// Election was computed on-chain.
OnChain,
Expand All @@ -419,7 +423,9 @@ impl Default for ElectionCompute {
///
/// Such a solution should never become effective in anyway before being checked by the
/// `Pallet::feasibility_check`.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, PartialOrd, Ord, TypeInfo)]
#[derive(
PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, PartialOrd, Ord, TypeInfo, MaxEncodedLen,
)]
pub struct RawSolution<S> {
/// the solution itself.
pub solution: S,
Expand All @@ -437,13 +443,24 @@ impl<C: Default> Default for RawSolution<C> {
}

/// A checked solution, ready to be enacted.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo)]
pub struct ReadySolution<A> {
#[derive(
PartialEqNoBound,
Eq,
Clone,
Encode,
Decode,
RuntimeDebugNoBound,
DefaultNoBound,
TypeInfo,
MaxEncodedLen,
)]
#[scale_info(skip_type_params(T))]
pub struct ReadySolution<T: Config> {
/// The final supports of the solution.
///
/// This is target-major vector, storing each winners, total backing, and each individual
/// backer.
pub supports: Supports<A>,
pub supports: BoundedSupportsOf<Pallet<T>>,
/// The score of the solution.
///
/// This is needed to potentially challenge the solution.
Expand Down Expand Up @@ -471,7 +488,7 @@ pub struct RoundSnapshot<T: Config> {
/// 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)]
Expand Down Expand Up @@ -664,6 +681,14 @@ pub mod pallet {
#[pallet::constant]
type MaxElectableTargets: Get<SolutionTargetIndexOf<Self::MinerConfig>>;

/// Check [`ElectionProvider::MaxBackersPerWinner`] for more information.
#[pallet::constant]
type MaxBackersPerWinner: Get<u32>;

/// Maximum number of winner that can be returned, per page (i.e. per call to `elect`).
#[pallet::constant]
type MaxWinnersPerPage: Get<u32>;

/// Handler for the slashed deposits.
type SlashHandler: OnUnbalanced<NegativeImbalanceOf<Self>>;

Expand All @@ -681,6 +706,8 @@ pub mod pallet {
AccountId = Self::AccountId,
BlockNumber = Self::BlockNumber,
DataProvider = Self::DataProvider,
MaxBackersPerWinner = Self::MaxBackersPerWinner,
MaxWinnersPerPage = Self::MaxWinnersPerPage,
>;

/// Configuration of the governance-only fallback.
Expand All @@ -691,6 +718,8 @@ pub mod pallet {
AccountId = Self::AccountId,
BlockNumber = Self::BlockNumber,
DataProvider = Self::DataProvider,
MaxBackersPerWinner = Self::MaxBackersPerWinner,
MaxWinnersPerPage = Self::MaxWinnersPerPage,
>;

/// OCW election solution miner algorithm implementation.
Expand Down Expand Up @@ -934,7 +963,7 @@ pub mod pallet {
#[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
pub fn set_emergency_election_result(
origin: OriginFor<T>,
supports: Supports<T::AccountId>,
supports: BoundedSupportsOf<Pallet<T>>,
) -> DispatchResult {
T::ForceOrigin::ensure_origin(origin)?;
ensure!(Self::current_phase().is_emergency(), <Error<T>>::CallNotAllowed);
Expand Down Expand Up @@ -1195,7 +1224,7 @@ pub mod pallet {
/// Current best solution, signed or unsigned, queued to be returned upon `elect`.
#[pallet::storage]
#[pallet::getter(fn queued_solution)]
pub type QueuedSolution<T: Config> = StorageValue<_, ReadySolution<T::AccountId>>;
pub type QueuedSolution<T: Config> = StorageValue<_, ReadySolution<T>>;

/// Snapshot data of the round.
///
Expand Down Expand Up @@ -1429,7 +1458,7 @@ impl<T: Config> Pallet<T> {
pub fn feasibility_check(
raw_solution: RawSolution<SolutionOf<T::MinerConfig>>,
compute: ElectionCompute,
) -> Result<ReadySolution<T::AccountId>, FeasibilityError> {
) -> Result<ReadySolution<T>, FeasibilityError> {
let RawSolution { solution, score, round } = raw_solution;

// First, check round.
Expand Down Expand Up @@ -1502,6 +1531,9 @@ impl<T: Config> Pallet<T> {
let known_score = supports.evaluate();
ensure!(known_score == score, FeasibilityError::InvalidScore);

let supports = supports
.try_into_bounded_supports()
.map_err(|_| FeasibilityError::WrongWinnerCount)?;
Ok(ReadySolution { supports, compute, score })
}

Expand All @@ -1521,7 +1553,7 @@ impl<T: Config> Pallet<T> {
Self::kill_snapshot();
}

fn do_elect() -> Result<Supports<T::AccountId>, ElectionError<T>> {
fn do_elect() -> Result<BoundedSupportsOf<Self>, ElectionError<T>> {
// We have to unconditionally try finalizing the signed phase here. There are only two
// possibilities:
//
Expand Down Expand Up @@ -1556,7 +1588,7 @@ impl<T: Config> Pallet<T> {
}

/// record the weight of the given `supports`.
fn weigh_supports(supports: &Supports<T::AccountId>) {
fn weigh_supports(supports: &BoundedSupportsOf<Self>) {
let active_voters = supports
.iter()
.map(|(_, x)| x)
Expand All @@ -1571,8 +1603,10 @@ impl<T: Config> ElectionProvider for Pallet<T> {
type BlockNumber = T::BlockNumber;
type Error = ElectionError<T>;
type DataProvider = T::DataProvider;
type MaxBackersPerWinner = T::MaxBackersPerWinner;
type MaxWinnersPerPage = T::MaxWinnersPerPage;

fn elect() -> Result<Supports<T::AccountId>, Self::Error> {
fn elect() -> Result<BoundedSupportsOf<Self>, Self::Error> {
match Self::do_elect() {
Ok(supports) => {
// All went okay, record the weight, put sign to be Off, clean snapshot, etc.
Expand Down Expand Up @@ -1806,7 +1840,7 @@ mod tests {
},
Phase,
};
use frame_election_provider_support::ElectionProvider;
use frame_election_provider_support::{bounded_supports_to_supports, ElectionProvider};
use frame_support::{assert_noop, assert_ok};
use sp_npos_elections::Support;

Expand Down Expand Up @@ -2028,7 +2062,7 @@ mod tests {
let supports = MultiPhase::elect().unwrap();

assert_eq!(
supports,
bounded_supports_to_supports(supports),
vec![
(30, Support { total: 40, voters: vec![(2, 5), (4, 5), (30, 30)] }),
(40, Support { total: 60, voters: vec![(2, 5), (3, 10), (4, 5), (40, 40)] })
Expand Down
10 changes: 8 additions & 2 deletions frame/election-provider-multi-phase/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,8 @@ impl onchain::Config for OnChainSeqPhragmen {
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, Balancing>;
type DataProvider = StakingMock;
type WeightInfo = ();
type MaxBackersPerWinner = MaxElectingVoters;
type MaxWinnersPerPage = MaxElectableTargets;
}

pub struct MockFallback;
Expand All @@ -300,8 +302,10 @@ impl ElectionProvider for MockFallback {
type BlockNumber = u64;
type Error = &'static str;
type DataProvider = StakingMock;
type MaxBackersPerWinner = MaxElectingVoters;
type MaxWinnersPerPage = MaxElectableTargets;

fn elect() -> Result<Supports<AccountId>, Self::Error> {
fn elect() -> Result<BoundedSupportsOf<Self>, Self::Error> {
Self::elect_with_bounds(Bounded::max_value(), Bounded::max_value())
}
}
Expand All @@ -310,7 +314,7 @@ impl InstantElectionProvider for MockFallback {
fn elect_with_bounds(
max_voters: usize,
max_targets: usize,
) -> Result<Supports<Self::AccountId>, Self::Error> {
) -> Result<BoundedSupportsOf<Self>, Self::Error> {
if OnChainFallback::get() {
onchain::UnboundedExecution::<OnChainSeqPhragmen>::elect_with_bounds(
max_voters,
Expand Down Expand Up @@ -375,6 +379,8 @@ impl crate::Config for Runtime {
type SignedMaxWeight = SignedMaxWeight;
type SignedMaxSubmissions = SignedMaxSubmissions;
type SignedMaxRefunds = SignedMaxRefunds;
type MaxBackersPerWinner = MaxElectingVoters;
type MaxWinnersPerPage = MaxElectableTargets;
type SlashHandler = ();
type RewardHandler = ();
type DataProvider = StakingMock;
Expand Down
8 changes: 5 additions & 3 deletions frame/election-provider-multi-phase/src/signed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::{
ReadySolution, SignedSubmissionIndices, SignedSubmissionNextIndex, SignedSubmissionsMap,
SolutionOf, SolutionOrSnapshotSize, Weight, WeightInfo,
};
use codec::{Decode, Encode, HasCompact};
use codec::{Decode, Encode, HasCompact, MaxEncodedLen};
use frame_election_provider_support::NposSolution;
use frame_support::{
storage::bounded_btree_map::BoundedBTreeMap,
Expand All @@ -44,7 +44,9 @@ use sp_std::{
/// A raw, unchecked signed submission.
///
/// This is just a wrapper around [`RawSolution`] and some additional info.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)]
#[derive(
PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen,
)]
pub struct SignedSubmission<AccountId, Balance: HasCompact, Solution> {
/// Who submitted this solution.
pub who: AccountId,
Expand Down Expand Up @@ -451,7 +453,7 @@ impl<T: Config> Pallet<T> {
///
/// Infallible
pub fn finalize_signed_phase_accept_solution(
ready_solution: ReadySolution<T::AccountId>,
ready_solution: ReadySolution<T>,
who: &T::AccountId,
deposit: BalanceOf<T>,
call_fee: BalanceOf<T>,
Expand Down
Loading