mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 17:31:05 +00:00
Bound Election and Staking by MaxActiveValidators (#12436)
* bounding election provider with kian * multi phase implement bounded election provider * election provider blanket implementation * staking compiles * fix test for election provider support * fmt * fixing epmp tests, does not compile yet * fix epmp tests * fix staking tests * fmt * fix runtime tests * fmt * remove outdated wip tags * add enum error * sort and truncate supports * comment * error when unsupported number of election winners * compiling wip after kian's suggestions * fix TODOs * remove,fix tags * ensure validator count does not exceed maxwinners * clean up * some more clean up and todos * handle too many winners * rename parameter for mock * todo * add sort and truncate rule if there are too many winners * fmt * fail, not swallow emergency result bound not met * remove too many winners resolution as it can be guaranteed to be bounded * fix benchmark * give MaxWinners more contextual name * make ready solution generic over T * kian feedback * fix stuff * Kian's way of solvign this * comment fix * fix compile * remove use of BoundedExecution * fmt * comment out failing integrity test * cap validator count increment to max winners * dont panic * add test for bad data provider * Update frame/staking/src/pallet/impls.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * fix namespace conflict and add test for onchain max winners less than desired targets * defensive unwrap * early convert to bounded vec * fix syntax * fmt * fix doc * fix rustdoc * fmt * fix maxwinner count for benchmarking * add instant election for noelection * fmt * fix compile * pr feedbacks * always error at validator count exceeding max winners * add useful error message * pr comments * import fix * add checked_desired_targets * fmt * fmt * fix rust doc Co-authored-by: parity-processbot <> Co-authored-by: kianenigma <kian@parity.io> Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
This commit is contained in:
@@ -114,8 +114,8 @@
|
||||
//! If we reach the end of both phases (i.e. call to [`ElectionProvider::elect`] happens) and no
|
||||
//! good solution is queued, then the fallback strategy [`pallet::Config::Fallback`] is used to
|
||||
//! determine what needs to be done. The on-chain election is slow, and contains no balancing or
|
||||
//! reduction post-processing. [`NoFallback`] does nothing and enables [`Phase::Emergency`], which
|
||||
//! is a more *fail-safe* approach.
|
||||
//! reduction post-processing. If [`pallet::Config::Fallback`] fails, the next phase
|
||||
//! [`Phase::Emergency`] is enabled, which is a more *fail-safe* approach.
|
||||
//!
|
||||
//! ### Emergency Phase
|
||||
//!
|
||||
@@ -231,23 +231,25 @@
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use frame_election_provider_support::{
|
||||
ElectionDataProvider, ElectionProvider, ElectionProviderBase, InstantElectionProvider,
|
||||
NposSolution,
|
||||
BoundedSupportsOf, ElectionDataProvider, ElectionProvider, ElectionProviderBase,
|
||||
InstantElectionProvider, NposSolution,
|
||||
};
|
||||
use frame_support::{
|
||||
dispatch::DispatchClass,
|
||||
ensure,
|
||||
traits::{Currency, Get, OnUnbalanced, ReservableCurrency},
|
||||
weights::Weight,
|
||||
DefaultNoBound, EqNoBound, PartialEqNoBound,
|
||||
};
|
||||
use frame_system::{ensure_none, offchain::SendTransactionTypes};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_arithmetic::{
|
||||
traits::{Bounded, CheckedAdd, Zero},
|
||||
traits::{CheckedAdd, Zero},
|
||||
UpperOf,
|
||||
};
|
||||
use sp_npos_elections::{
|
||||
assignment_ratio_to_staked_normalized, ElectionScore, EvaluateSupport, Supports, VoteWeight,
|
||||
assignment_ratio_to_staked_normalized, BoundedSupports, ElectionScore, EvaluateSupport,
|
||||
Supports, VoteWeight,
|
||||
};
|
||||
use sp_runtime::{
|
||||
transaction_validity::{
|
||||
@@ -311,33 +313,6 @@ pub trait BenchmarkingConfig {
|
||||
const MAXIMUM_TARGETS: u32;
|
||||
}
|
||||
|
||||
/// A fallback implementation that transitions the pallet to the emergency phase.
|
||||
pub struct NoFallback<T>(sp_std::marker::PhantomData<T>);
|
||||
|
||||
impl<T: Config> ElectionProviderBase for NoFallback<T> {
|
||||
type AccountId = T::AccountId;
|
||||
type BlockNumber = T::BlockNumber;
|
||||
type DataProvider = T::DataProvider;
|
||||
type Error = &'static str;
|
||||
|
||||
fn ongoing() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> ElectionProvider for NoFallback<T> {
|
||||
fn elect() -> Result<Supports<T::AccountId>, 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> {
|
||||
Err("NoFallback.")
|
||||
}
|
||||
}
|
||||
|
||||
/// Current phase of the pallet.
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo)]
|
||||
pub enum Phase<Bn> {
|
||||
@@ -445,13 +420,23 @@ 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,
|
||||
EqNoBound,
|
||||
Clone,
|
||||
Encode,
|
||||
Decode,
|
||||
RuntimeDebug,
|
||||
DefaultNoBound,
|
||||
scale_info::TypeInfo,
|
||||
)]
|
||||
#[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: BoundedSupports<T::AccountId, T::MaxWinners>,
|
||||
/// The score of the solution.
|
||||
///
|
||||
/// This is needed to potentially challenge the solution.
|
||||
@@ -465,7 +450,6 @@ pub struct ReadySolution<A> {
|
||||
///
|
||||
/// These are stored together because they are often accessed together.
|
||||
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo)]
|
||||
#[codec(mel_bound())]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct RoundSnapshot<T: Config> {
|
||||
/// All of the voters.
|
||||
@@ -561,6 +545,8 @@ pub enum FeasibilityError {
|
||||
InvalidRound,
|
||||
/// Comparison against `MinimumUntrustedScore` failed.
|
||||
UntrustedScoreTooLow,
|
||||
/// Data Provider returned too many desired targets
|
||||
TooManyDesiredTargets,
|
||||
}
|
||||
|
||||
impl From<sp_npos_elections::Error> for FeasibilityError {
|
||||
@@ -574,7 +560,10 @@ pub use pallet::*;
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use frame_election_provider_support::{InstantElectionProvider, NposSolver};
|
||||
use frame_support::{pallet_prelude::*, traits::EstimateCallFee};
|
||||
use frame_support::{
|
||||
pallet_prelude::*,
|
||||
traits::{DefensiveResult, EstimateCallFee},
|
||||
};
|
||||
use frame_system::pallet_prelude::*;
|
||||
|
||||
#[pallet::config]
|
||||
@@ -674,6 +663,13 @@ pub mod pallet {
|
||||
#[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>;
|
||||
|
||||
/// Handler for the slashed deposits.
|
||||
type SlashHandler: OnUnbalanced<NegativeImbalanceOf<Self>>;
|
||||
|
||||
@@ -691,6 +687,7 @@ pub mod pallet {
|
||||
AccountId = Self::AccountId,
|
||||
BlockNumber = Self::BlockNumber,
|
||||
DataProvider = Self::DataProvider,
|
||||
MaxWinners = Self::MaxWinners,
|
||||
>;
|
||||
|
||||
/// Configuration of the governance-only fallback.
|
||||
@@ -701,6 +698,7 @@ pub mod pallet {
|
||||
AccountId = Self::AccountId,
|
||||
BlockNumber = Self::BlockNumber,
|
||||
DataProvider = Self::DataProvider,
|
||||
MaxWinners = Self::MaxWinners,
|
||||
>;
|
||||
|
||||
/// OCW election solution miner algorithm implementation.
|
||||
@@ -968,9 +966,11 @@ pub mod pallet {
|
||||
T::ForceOrigin::ensure_origin(origin)?;
|
||||
ensure!(Self::current_phase().is_emergency(), <Error<T>>::CallNotAllowed);
|
||||
|
||||
// bound supports with T::MaxWinners
|
||||
let supports = supports.try_into().map_err(|_| Error::<T>::TooManyWinners)?;
|
||||
|
||||
// Note: we don't `rotate_round` at this point; the next call to
|
||||
// `ElectionProvider::elect` will succeed and take care of that.
|
||||
|
||||
let solution = ReadySolution {
|
||||
supports,
|
||||
score: Default::default(),
|
||||
@@ -1073,17 +1073,20 @@ pub mod pallet {
|
||||
T::ForceOrigin::ensure_origin(origin)?;
|
||||
ensure!(Self::current_phase().is_emergency(), <Error<T>>::CallNotAllowed);
|
||||
|
||||
let maybe_max_voters = maybe_max_voters.map(|x| x as usize);
|
||||
let maybe_max_targets = maybe_max_targets.map(|x| x as usize);
|
||||
let supports =
|
||||
T::GovernanceFallback::instant_elect(maybe_max_voters, maybe_max_targets).map_err(
|
||||
|e| {
|
||||
log!(error, "GovernanceFallback failed: {:?}", e);
|
||||
Error::<T>::FallbackFailed
|
||||
},
|
||||
)?;
|
||||
|
||||
let supports = T::GovernanceFallback::elect_with_bounds(
|
||||
maybe_max_voters.unwrap_or(Bounded::max_value()),
|
||||
maybe_max_targets.unwrap_or(Bounded::max_value()),
|
||||
)
|
||||
.map_err(|e| {
|
||||
log!(error, "GovernanceFallback failed: {:?}", e);
|
||||
Error::<T>::FallbackFailed
|
||||
})?;
|
||||
// transform BoundedVec<_, T::GovernanceFallback::MaxWinners> into
|
||||
// `BoundedVec<_, T::MaxWinners>`
|
||||
let supports: BoundedVec<_, T::MaxWinners> = supports
|
||||
.into_inner()
|
||||
.try_into()
|
||||
.defensive_map_err(|_| Error::<T>::BoundNotMet)?;
|
||||
|
||||
let solution = ReadySolution {
|
||||
supports,
|
||||
@@ -1154,6 +1157,10 @@ pub mod pallet {
|
||||
CallNotAllowed,
|
||||
/// The fallback failed
|
||||
FallbackFailed,
|
||||
/// Some bound not met
|
||||
BoundNotMet,
|
||||
/// Submitted solution has too many winners
|
||||
TooManyWinners,
|
||||
}
|
||||
|
||||
#[pallet::validate_unsigned]
|
||||
@@ -1227,7 +1234,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.
|
||||
///
|
||||
@@ -1393,31 +1400,28 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
let targets = T::DataProvider::electable_targets(Some(target_limit))
|
||||
.map_err(ElectionError::DataProvider)?;
|
||||
|
||||
let voters = T::DataProvider::electing_voters(Some(voter_limit))
|
||||
.map_err(ElectionError::DataProvider)?;
|
||||
let mut desired_targets =
|
||||
T::DataProvider::desired_targets().map_err(ElectionError::DataProvider)?;
|
||||
|
||||
// Defensive-only.
|
||||
if targets.len() > target_limit || voters.len() > voter_limit {
|
||||
debug_assert!(false, "Snapshot limit has not been respected.");
|
||||
return Err(ElectionError::DataProvider("Snapshot too big for submission."))
|
||||
}
|
||||
|
||||
// If `desired_targets` > `targets.len()`, cap `desired_targets` to that level and emit a
|
||||
// warning
|
||||
let max_len = targets
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(|_| ElectionError::DataProvider("Failed to convert usize"))?;
|
||||
if desired_targets > max_len {
|
||||
let mut desired_targets =
|
||||
T::DataProvider::desired_targets().map_err(ElectionError::DataProvider)?;
|
||||
|
||||
// If `desired_targets` > `targets.len()`, cap `desired_targets` to that
|
||||
// level and emit a warning
|
||||
let max_desired_targets: u32 = (targets.len() as u32).min(T::MaxWinners::get());
|
||||
if desired_targets > max_desired_targets {
|
||||
log!(
|
||||
warn,
|
||||
"desired_targets: {} > targets.len(): {}, capping desired_targets",
|
||||
desired_targets,
|
||||
max_len
|
||||
max_desired_targets
|
||||
);
|
||||
desired_targets = max_len;
|
||||
desired_targets = max_desired_targets;
|
||||
}
|
||||
|
||||
Ok((targets, voters, desired_targets))
|
||||
@@ -1466,7 +1470,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.
|
||||
@@ -1479,6 +1483,11 @@ impl<T: Config> Pallet<T> {
|
||||
Self::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?;
|
||||
|
||||
ensure!(winners.len() as u32 == desired_targets, FeasibilityError::WrongWinnerCount);
|
||||
// Fail early if targets requested by data provider exceed maximum winners supported.
|
||||
ensure!(
|
||||
desired_targets <= <T as pallet::Config>::MaxWinners::get(),
|
||||
FeasibilityError::TooManyDesiredTargets
|
||||
);
|
||||
|
||||
// Ensure that the solution's score can pass absolute min-score.
|
||||
let submitted_score = raw_solution.score;
|
||||
@@ -1539,6 +1548,8 @@ impl<T: Config> Pallet<T> {
|
||||
let known_score = supports.evaluate();
|
||||
ensure!(known_score == score, FeasibilityError::InvalidScore);
|
||||
|
||||
// Size of winners in miner solution is equal to `desired_targets` <= `MaxWinners`.
|
||||
let supports = supports.try_into().expect("checked desired_targets <= MaxWinners; qed");
|
||||
Ok(ReadySolution { supports, compute, score })
|
||||
}
|
||||
|
||||
@@ -1558,7 +1569,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:
|
||||
//
|
||||
@@ -1570,13 +1581,15 @@ impl<T: Config> Pallet<T> {
|
||||
<QueuedSolution<T>>::take()
|
||||
.ok_or(ElectionError::<T>::NothingQueued)
|
||||
.or_else(|_| {
|
||||
<T::Fallback as ElectionProvider>::elect()
|
||||
.map(|supports| ReadySolution {
|
||||
supports,
|
||||
score: Default::default(),
|
||||
compute: ElectionCompute::Fallback,
|
||||
})
|
||||
T::Fallback::instant_elect(None, None)
|
||||
.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 });
|
||||
@@ -1609,18 +1622,19 @@ impl<T: Config> ElectionProviderBase for Pallet<T> {
|
||||
type AccountId = T::AccountId;
|
||||
type BlockNumber = T::BlockNumber;
|
||||
type Error = ElectionError<T>;
|
||||
type MaxWinners = T::MaxWinners;
|
||||
type DataProvider = T::DataProvider;
|
||||
}
|
||||
|
||||
impl<T: Config> ElectionProvider for Pallet<T> {
|
||||
fn ongoing() -> bool {
|
||||
match Self::current_phase() {
|
||||
Phase::Off => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> ElectionProvider for Pallet<T> {
|
||||
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.
|
||||
@@ -1636,6 +1650,7 @@ impl<T: Config> ElectionProvider for Pallet<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// convert a DispatchError to a custom InvalidTransaction with the inner code being the error
|
||||
/// number.
|
||||
pub fn dispatch_error_to_invalid(error: DispatchError) -> InvalidTransaction {
|
||||
@@ -1854,7 +1869,6 @@ mod tests {
|
||||
},
|
||||
Phase,
|
||||
};
|
||||
use frame_election_provider_support::ElectionProvider;
|
||||
use frame_support::{assert_noop, assert_ok};
|
||||
use sp_npos_elections::{BalancingConfig, Support};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user