bounding staking: BoundedElectionProvider trait (#12362)

* add a bounded election provider trait

* extract common trait election provider base

* fmt

* only bound the outer support vector

* fix tests

* docs

* fix rust docs

* fmt

* fix rustdocs

* docs

* improve docs

* small doc change
This commit is contained in:
Ankan
2022-09-28 22:52:16 +02:00
committed by GitHub
parent ea43466879
commit 6137c8707b
7 changed files with 99 additions and 55 deletions
@@ -231,7 +231,8 @@
use codec::{Decode, Encode}; use codec::{Decode, Encode};
use frame_election_provider_support::{ use frame_election_provider_support::{
ElectionDataProvider, ElectionProvider, InstantElectionProvider, NposSolution, ElectionDataProvider, ElectionProvider, ElectionProviderBase, InstantElectionProvider,
NposSolution,
}; };
use frame_support::{ use frame_support::{
dispatch::DispatchClass, dispatch::DispatchClass,
@@ -289,7 +290,7 @@ pub type SolutionTargetIndexOf<T> = <SolutionOf<T> as NposSolution>::TargetIndex
pub type SolutionAccuracyOf<T> = pub type SolutionAccuracyOf<T> =
<SolutionOf<<T as crate::Config>::MinerConfig> as NposSolution>::Accuracy; <SolutionOf<<T as crate::Config>::MinerConfig> as NposSolution>::Accuracy;
/// The fallback election type. /// The fallback election type.
pub type FallbackErrorOf<T> = <<T as crate::Config>::Fallback as ElectionProvider>::Error; pub type FallbackErrorOf<T> = <<T as crate::Config>::Fallback as ElectionProviderBase>::Error;
/// Configuration for the benchmarks of the pallet. /// Configuration for the benchmarks of the pallet.
pub trait BenchmarkingConfig { pub trait BenchmarkingConfig {
@@ -312,7 +313,7 @@ pub trait BenchmarkingConfig {
/// A fallback implementation that transitions the pallet to the emergency phase. /// A fallback implementation that transitions the pallet to the emergency phase.
pub struct NoFallback<T>(sp_std::marker::PhantomData<T>); pub struct NoFallback<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> ElectionProvider for NoFallback<T> { impl<T: Config> ElectionProviderBase for NoFallback<T> {
type AccountId = T::AccountId; type AccountId = T::AccountId;
type BlockNumber = T::BlockNumber; type BlockNumber = T::BlockNumber;
type DataProvider = T::DataProvider; type DataProvider = T::DataProvider;
@@ -321,7 +322,9 @@ impl<T: Config> ElectionProvider for NoFallback<T> {
fn ongoing() -> bool { fn ongoing() -> bool {
false false
} }
}
impl<T: Config> ElectionProvider for NoFallback<T> {
fn elect() -> Result<Supports<T::AccountId>, Self::Error> { fn elect() -> Result<Supports<T::AccountId>, Self::Error> {
// Do nothing, this will enable the emergency phase. // Do nothing, this will enable the emergency phase.
Err("NoFallback.") Err("NoFallback.")
@@ -1563,7 +1566,7 @@ impl<T: Config> Pallet<T> {
<QueuedSolution<T>>::take() <QueuedSolution<T>>::take()
.ok_or(ElectionError::<T>::NothingQueued) .ok_or(ElectionError::<T>::NothingQueued)
.or_else(|_| { .or_else(|_| {
T::Fallback::elect() <T::Fallback as ElectionProvider>::elect()
.map(|supports| ReadySolution { .map(|supports| ReadySolution {
supports, supports,
score: Default::default(), score: Default::default(),
@@ -1598,7 +1601,7 @@ impl<T: Config> Pallet<T> {
} }
} }
impl<T: Config> ElectionProvider for Pallet<T> { impl<T: Config> ElectionProviderBase for Pallet<T> {
type AccountId = T::AccountId; type AccountId = T::AccountId;
type BlockNumber = T::BlockNumber; type BlockNumber = T::BlockNumber;
type Error = ElectionError<T>; type Error = ElectionError<T>;
@@ -1610,7 +1613,9 @@ impl<T: Config> ElectionProvider for Pallet<T> {
_ => true, _ => true,
} }
} }
}
impl<T: Config> ElectionProvider for Pallet<T> {
fn elect() -> Result<Supports<T::AccountId>, Self::Error> { fn elect() -> Result<Supports<T::AccountId>, Self::Error> {
match Self::do_elect() { match Self::do_elect() {
Ok(supports) => { Ok(supports) => {
@@ -1627,7 +1632,6 @@ impl<T: Config> ElectionProvider for Pallet<T> {
} }
} }
} }
/// convert a DispatchError to a custom InvalidTransaction with the inner code being the error /// convert a DispatchError to a custom InvalidTransaction with the inner code being the error
/// number. /// number.
pub fn dispatch_error_to_invalid(error: DispatchError) -> InvalidTransaction { pub fn dispatch_error_to_invalid(error: DispatchError) -> InvalidTransaction {
@@ -297,7 +297,7 @@ impl onchain::Config for OnChainSeqPhragmen {
} }
pub struct MockFallback; pub struct MockFallback;
impl ElectionProvider for MockFallback { impl ElectionProviderBase for MockFallback {
type AccountId = AccountId; type AccountId = AccountId;
type BlockNumber = u64; type BlockNumber = u64;
type Error = &'static str; type Error = &'static str;
@@ -306,7 +306,8 @@ impl ElectionProvider for MockFallback {
fn ongoing() -> bool { fn ongoing() -> bool {
false false
} }
}
impl ElectionProvider for MockFallback {
fn elect() -> Result<Supports<AccountId>, Self::Error> { fn elect() -> Result<Supports<AccountId>, Self::Error> {
Self::elect_with_bounds(Bounded::max_value(), Bounded::max_value()) Self::elect_with_bounds(Bounded::max_value(), Bounded::max_value())
} }
@@ -20,10 +20,11 @@
//! This crate provides two traits that could interact to enable extensible election functionality //! This crate provides two traits that could interact to enable extensible election functionality
//! within FRAME pallets. //! within FRAME pallets.
//! //!
//! Something that will provide the functionality of election will implement [`ElectionProvider`], //! Something that will provide the functionality of election will implement
//! whilst needing an associated [`ElectionProvider::DataProvider`], which needs to be fulfilled by //! [`ElectionProvider`] and its parent-trait [`ElectionProviderBase`], whilst needing an
//! an entity implementing [`ElectionDataProvider`]. Most often, *the data provider is* the receiver //! associated [`ElectionProviderBase::DataProvider`], which needs to be
//! of the election, resulting in a diagram as below: //! fulfilled by an entity implementing [`ElectionDataProvider`]. Most often, *the data provider is*
//! the receiver of the election, resulting in a diagram as below:
//! //!
//! ```ignore //! ```ignore
//! ElectionDataProvider //! ElectionDataProvider
@@ -131,12 +132,16 @@
//! type DataProvider: ElectionDataProvider<AccountId=AccountId, BlockNumber = BlockNumber>; //! type DataProvider: ElectionDataProvider<AccountId=AccountId, BlockNumber = BlockNumber>;
//! } //! }
//! //!
//! impl<T: Config> ElectionProvider for GenericElectionProvider<T> { //! impl<T: Config> ElectionProviderBase for GenericElectionProvider<T> {
//! type AccountId = AccountId; //! type AccountId = AccountId;
//! type BlockNumber = BlockNumber; //! type BlockNumber = BlockNumber;
//! type Error = &'static str; //! type Error = &'static str;
//! type DataProvider = T::DataProvider; //! type DataProvider = T::DataProvider;
//! fn ongoing() -> bool { false } //! fn ongoing() -> bool { false }
//!
//! }
//!
//! impl<T: Config> ElectionProvider for GenericElectionProvider<T> {
//! fn elect() -> Result<Supports<AccountId>, Self::Error> { //! fn elect() -> Result<Supports<AccountId>, Self::Error> {
//! Self::DataProvider::electable_targets(None) //! Self::DataProvider::electable_targets(None)
//! .map_err(|_| "failed to elect") //! .map_err(|_| "failed to elect")
@@ -177,8 +182,8 @@ pub use frame_support::{traits::Get, weights::Weight, BoundedVec, RuntimeDebug};
/// Re-export some type as they are used in the interface. /// Re-export some type as they are used in the interface.
pub use sp_arithmetic::PerThing; pub use sp_arithmetic::PerThing;
pub use sp_npos_elections::{ pub use sp_npos_elections::{
Assignment, BalancingConfig, ElectionResult, Error, ExtendedBalance, IdentifierT, PerThing128, Assignment, BalancingConfig, BoundedSupports, ElectionResult, Error, ExtendedBalance,
Support, Supports, VoteWeight, IdentifierT, PerThing128, Support, Supports, VoteWeight,
}; };
pub use traits::NposSolution; pub use traits::NposSolution;
@@ -349,12 +354,12 @@ pub trait ElectionDataProvider {
fn clear() {} fn clear() {}
} }
/// Something that can compute the result of an election and pass it back to the caller. /// Base trait for [`ElectionProvider`] and [`BoundedElectionProvider`]. It is
/// meant to be used only with an extension trait that adds an election
/// functionality.
/// ///
/// This trait only provides an interface to _request_ an election, i.e. /// Data can be bounded or unbounded and is fetched from [`Self::DataProvider`].
/// [`ElectionProvider::elect`]. That data required for the election need to be passed to the pub trait ElectionProviderBase {
/// implemented of this trait through [`ElectionProvider::DataProvider`].
pub trait ElectionProvider {
/// The account identifier type. /// The account identifier type.
type AccountId; type AccountId;
@@ -372,24 +377,39 @@ pub trait ElectionProvider {
/// Indicate if this election provider is currently ongoing an asynchronous election or not. /// Indicate if this election provider is currently ongoing an asynchronous election or not.
fn ongoing() -> bool; fn ongoing() -> bool;
}
/// Elect a new set of winners, without specifying any bounds on the amount of data fetched from /// Elect a new set of winners, bounded by `MaxWinners`.
/// [`Self::DataProvider`]. An implementation could nonetheless impose its own custom limits. ///
/// /// Returns a result in bounded, target major format, namely as
/// The result is returned in a target major format, namely as *vector of supports*. /// *BoundedVec<(AccountId, Vec<Support>), MaxWinners>*.
/// pub trait BoundedElectionProvider: ElectionProviderBase {
/// This should be implemented as a self-weighing function. The implementor should register its /// The upper bound on election winners.
/// appropriate weight at the end of execution with the system pallet directly. type MaxWinners: Get<u32>;
/// Performs the election. 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 elect() -> Result<BoundedSupports<Self::AccountId, Self::MaxWinners>, Self::Error>;
}
/// Same a [`BoundedElectionProvider`], but no bounds are imposed on the number
/// of winners.
///
/// The result is returned in a target major format, namely as
///*Vec<(AccountId, Vec<Support>)>*.
pub trait ElectionProvider: ElectionProviderBase {
/// Performs the election. This should be implemented as a self-weighing
/// function, similar to [`BoundedElectionProvider::elect()`].
fn elect() -> Result<Supports<Self::AccountId>, Self::Error>; fn elect() -> Result<Supports<Self::AccountId>, Self::Error>;
} }
/// A sub-trait of the [`ElectionProvider`] for cases where we need to be sure an election needs to /// A sub-trait of the [`ElectionProvider`] for cases where we need to be sure
/// happen instantly, not asynchronously. /// an election needs to happen instantly, not asynchronously.
/// ///
/// The same `DataProvider` is assumed to be used. /// The same `DataProvider` is assumed to be used.
/// ///
/// Consequently, allows for control over the amount of data that is being fetched from the /// Consequently, allows for control over the amount of data that is being
/// [`ElectionProvider::DataProvider`]. /// fetched from the [`ElectionProviderBase::DataProvider`].
pub trait InstantElectionProvider: ElectionProvider { pub trait InstantElectionProvider: ElectionProvider {
/// Elect a new set of winners, but unlike [`ElectionProvider::elect`] which cannot enforce /// Elect a new set of winners, but unlike [`ElectionProvider::elect`] which cannot enforce
/// bounds, this trait method can enforce bounds on the amount of data provided by the /// bounds, this trait method can enforce bounds on the amount of data provided by the
@@ -410,7 +430,7 @@ pub trait InstantElectionProvider: ElectionProvider {
pub struct NoElection<X>(sp_std::marker::PhantomData<X>); pub struct NoElection<X>(sp_std::marker::PhantomData<X>);
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl<AccountId, BlockNumber, DataProvider> ElectionProvider impl<AccountId, BlockNumber, DataProvider> ElectionProviderBase
for NoElection<(AccountId, BlockNumber, DataProvider)> for NoElection<(AccountId, BlockNumber, DataProvider)>
where where
DataProvider: ElectionDataProvider<AccountId = AccountId, BlockNumber = BlockNumber>, DataProvider: ElectionDataProvider<AccountId = AccountId, BlockNumber = BlockNumber>,
@@ -420,15 +440,22 @@ where
type Error = &'static str; type Error = &'static str;
type DataProvider = DataProvider; type DataProvider = DataProvider;
fn elect() -> Result<Supports<AccountId>, Self::Error> {
Err("<NoElection as ElectionProvider> cannot do anything.")
}
fn ongoing() -> bool { fn ongoing() -> bool {
false false
} }
} }
#[cfg(feature = "std")]
impl<AccountId, BlockNumber, DataProvider> ElectionProvider
for NoElection<(AccountId, BlockNumber, DataProvider)>
where
DataProvider: ElectionDataProvider<AccountId = AccountId, BlockNumber = BlockNumber>,
{
fn elect() -> Result<Supports<AccountId>, Self::Error> {
Err("<NoElection as ElectionProvider> cannot do anything.")
}
}
/// A utility trait for something to implement `ElectionDataProvider` in a sensible way. /// A utility trait for something to implement `ElectionDataProvider` in a sensible way.
/// ///
/// This is generic over `AccountId` and it can represent a validator, a nominator, or any other /// This is generic over `AccountId` and it can represent a validator, a nominator, or any other
@@ -20,7 +20,8 @@
//! careful when using it onchain. //! careful when using it onchain.
use crate::{ use crate::{
Debug, ElectionDataProvider, ElectionProvider, InstantElectionProvider, NposSolver, WeightInfo, Debug, ElectionDataProvider, ElectionProvider, ElectionProviderBase, InstantElectionProvider,
NposSolver, WeightInfo,
}; };
use frame_support::{dispatch::DispatchClass, traits::Get}; use frame_support::{dispatch::DispatchClass, traits::Get};
use sp_npos_elections::*; use sp_npos_elections::*;
@@ -133,15 +134,6 @@ fn elect_with<T: Config>(
} }
impl<T: Config> ElectionProvider for UnboundedExecution<T> { impl<T: Config> ElectionProvider for UnboundedExecution<T> {
type AccountId = <T::System as frame_system::Config>::AccountId;
type BlockNumber = <T::System as frame_system::Config>::BlockNumber;
type Error = Error;
type DataProvider = T::DataProvider;
fn ongoing() -> bool {
false
}
fn elect() -> Result<Supports<Self::AccountId>, Self::Error> { fn elect() -> Result<Supports<Self::AccountId>, Self::Error> {
// This should not be called if not in `std` mode (and therefore neither in genesis nor in // This should not be called if not in `std` mode (and therefore neither in genesis nor in
// testing) // testing)
@@ -156,6 +148,17 @@ impl<T: Config> ElectionProvider for UnboundedExecution<T> {
} }
} }
impl<T: Config> ElectionProviderBase for UnboundedExecution<T> {
type AccountId = <T::System as frame_system::Config>::AccountId;
type BlockNumber = <T::System as frame_system::Config>::BlockNumber;
type Error = Error;
type DataProvider = T::DataProvider;
fn ongoing() -> bool {
false
}
}
impl<T: Config> InstantElectionProvider for UnboundedExecution<T> { impl<T: Config> InstantElectionProvider for UnboundedExecution<T> {
fn elect_with_bounds( fn elect_with_bounds(
max_voters: usize, max_voters: usize,
@@ -165,7 +168,7 @@ impl<T: Config> InstantElectionProvider for UnboundedExecution<T> {
} }
} }
impl<T: BoundedConfig> ElectionProvider for BoundedExecution<T> { impl<T: BoundedConfig> ElectionProviderBase for BoundedExecution<T> {
type AccountId = <T::System as frame_system::Config>::AccountId; type AccountId = <T::System as frame_system::Config>::AccountId;
type BlockNumber = <T::System as frame_system::Config>::BlockNumber; type BlockNumber = <T::System as frame_system::Config>::BlockNumber;
type Error = Error; type Error = Error;
@@ -174,7 +177,9 @@ impl<T: BoundedConfig> ElectionProvider for BoundedExecution<T> {
fn ongoing() -> bool { fn ongoing() -> bool {
false false
} }
}
impl<T: BoundedConfig> ElectionProvider for BoundedExecution<T> {
fn elect() -> Result<Supports<Self::AccountId>, Self::Error> { fn elect() -> Result<Supports<Self::AccountId>, Self::Error> {
elect_with::<T>(Some(T::VotersBound::get() as usize), Some(T::TargetsBound::get() as usize)) elect_with::<T>(Some(T::VotersBound::get() as usize), Some(T::TargetsBound::get() as usize))
} }
+3 -2
View File
@@ -80,7 +80,7 @@ macro_rules! log {
pub mod pallet { pub mod pallet {
use super::*; use super::*;
use crate::types::*; use crate::types::*;
use frame_election_provider_support::ElectionProvider; use frame_election_provider_support::ElectionProviderBase;
use frame_support::{ use frame_support::{
pallet_prelude::*, pallet_prelude::*,
traits::{Defensive, ReservableCurrency}, traits::{Defensive, ReservableCurrency},
@@ -330,7 +330,8 @@ pub mod pallet {
} }
} }
if <T as pallet_staking::Config>::ElectionProvider::ongoing() { if <<T as pallet_staking::Config>::ElectionProvider as ElectionProviderBase>::ongoing()
{
// NOTE: we assume `ongoing` does not consume any weight. // NOTE: we assume `ongoing` does not consume any weight.
// there is an ongoing election -- we better not do anything. Imagine someone is not // there is an ongoing election -- we better not do anything. Imagine someone is not
// exposed anywhere in the last era, and the snapshot for the election is already // exposed anywhere in the last era, and the snapshot for the election is already
+3 -1
View File
@@ -104,7 +104,7 @@ parameter_types! {
} }
pub struct MockElection; pub struct MockElection;
impl frame_election_provider_support::ElectionProvider for MockElection { impl frame_election_provider_support::ElectionProviderBase for MockElection {
type AccountId = AccountId; type AccountId = AccountId;
type BlockNumber = BlockNumber; type BlockNumber = BlockNumber;
type DataProvider = Staking; type DataProvider = Staking;
@@ -113,7 +113,9 @@ impl frame_election_provider_support::ElectionProvider for MockElection {
fn ongoing() -> bool { fn ongoing() -> bool {
Ongoing::get() Ongoing::get()
} }
}
impl frame_election_provider_support::ElectionProvider for MockElection {
fn elect() -> Result<frame_election_provider_support::Supports<AccountId>, Self::Error> { fn elect() -> Result<frame_election_provider_support::Supports<AccountId>, Self::Error> {
Err(()) Err(())
} }
@@ -74,17 +74,16 @@
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo; use scale_info::TypeInfo;
#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};
use sp_arithmetic::{traits::Zero, Normalizable, PerThing, Rational128, ThresholdOrd}; use sp_arithmetic::{traits::Zero, Normalizable, PerThing, Rational128, ThresholdOrd};
use sp_core::RuntimeDebug; use sp_core::{bounded::BoundedVec, RuntimeDebug};
use sp_std::{ use sp_std::{
cell::RefCell, cmp::Ordering, collections::btree_map::BTreeMap, prelude::*, rc::Rc, vec, cell::RefCell, cmp::Ordering, collections::btree_map::BTreeMap, prelude::*, rc::Rc, vec,
}; };
use codec::{Decode, Encode, MaxEncodedLen};
#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};
#[cfg(test)] #[cfg(test)]
mod mock; mod mock;
#[cfg(test)] #[cfg(test)]
@@ -451,6 +450,11 @@ impl<AccountId> Default for Support<AccountId> {
/// The main advantage of this is that it is encodable. /// The main advantage of this is that it is encodable.
pub type Supports<A> = Vec<(A, Support<A>)>; pub type Supports<A> = Vec<(A, Support<A>)>;
/// Same as `Supports` bounded by `MaxWinners`.
///
/// To note, the inner `Support` is still unbounded.
pub type BoundedSupports<A, MaxWinners> = BoundedVec<(A, Support<A>), MaxWinners>;
/// Linkage from a winner to their [`Support`]. /// Linkage from a winner to their [`Support`].
/// ///
/// This is more helpful than a normal [`Supports`] as it allows faster error checking. /// This is more helpful than a normal [`Supports`] as it allows faster error checking.