mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-29 06:47:57 +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:
@@ -220,11 +220,7 @@ frame_benchmarking::benchmarks! {
|
||||
let receiver = account("receiver", 0, SEED);
|
||||
let initial_balance = T::Currency::minimum_balance() * 10u32.into();
|
||||
T::Currency::make_free_balance_be(&receiver, initial_balance);
|
||||
let ready = ReadySolution {
|
||||
supports: vec![],
|
||||
score: Default::default(),
|
||||
compute: Default::default()
|
||||
};
|
||||
let ready = Default::default();
|
||||
let deposit: BalanceOf<T> = 10u32.into();
|
||||
|
||||
let reward: BalanceOf<T> = T::SignedRewardBase::get();
|
||||
@@ -403,7 +399,7 @@ frame_benchmarking::benchmarks! {
|
||||
assert_eq!(raw_solution.solution.voter_count() as u32, a);
|
||||
assert_eq!(raw_solution.solution.unique_targets().len() as u32, d);
|
||||
}: {
|
||||
assert_ok!(<MultiPhase<T>>::feasibility_check(raw_solution, ElectionCompute::Unsigned));
|
||||
assert!(<MultiPhase<T>>::feasibility_check(raw_solution, ElectionCompute::Unsigned).is_ok());
|
||||
}
|
||||
|
||||
// NOTE: this weight is not used anywhere, but the fact that it should succeed when execution 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};
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ use super::*;
|
||||
use crate::{self as multi_phase, unsigned::MinerConfig};
|
||||
use frame_election_provider_support::{
|
||||
data_provider,
|
||||
onchain::{self, UnboundedExecution},
|
||||
onchain::{self},
|
||||
ElectionDataProvider, NposSolution, SequentialPhragmen,
|
||||
};
|
||||
pub use frame_support::{assert_noop, assert_ok, pallet_prelude::GetDefault};
|
||||
@@ -297,6 +297,7 @@ parameter_types! {
|
||||
pub static MockWeightInfo: MockedWeightInfo = MockedWeightInfo::Real;
|
||||
pub static MaxElectingVoters: VoterIndex = u32::max_value();
|
||||
pub static MaxElectableTargets: TargetIndex = TargetIndex::max_value();
|
||||
pub static MaxWinners: u32 = 200;
|
||||
|
||||
pub static EpochLength: u64 = 30;
|
||||
pub static OnChainFallback: bool = true;
|
||||
@@ -308,6 +309,9 @@ impl onchain::Config for OnChainSeqPhragmen {
|
||||
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, Balancing>;
|
||||
type DataProvider = StakingMock;
|
||||
type WeightInfo = ();
|
||||
type MaxWinners = MaxWinners;
|
||||
type VotersBound = ConstU32<{ u32::MAX }>;
|
||||
type TargetsBound = ConstU32<{ u32::MAX }>;
|
||||
}
|
||||
|
||||
pub struct MockFallback;
|
||||
@@ -316,30 +320,19 @@ impl ElectionProviderBase for MockFallback {
|
||||
type BlockNumber = u64;
|
||||
type Error = &'static str;
|
||||
type DataProvider = StakingMock;
|
||||
|
||||
fn ongoing() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
impl ElectionProvider for MockFallback {
|
||||
fn elect() -> Result<Supports<AccountId>, Self::Error> {
|
||||
Self::elect_with_bounds(Bounded::max_value(), Bounded::max_value())
|
||||
}
|
||||
type MaxWinners = MaxWinners;
|
||||
}
|
||||
|
||||
impl InstantElectionProvider for MockFallback {
|
||||
fn elect_with_bounds(
|
||||
max_voters: usize,
|
||||
max_targets: usize,
|
||||
) -> Result<Supports<Self::AccountId>, Self::Error> {
|
||||
fn instant_elect(
|
||||
max_voters: Option<u32>,
|
||||
max_targets: Option<u32>,
|
||||
) -> Result<BoundedSupportsOf<Self>, Self::Error> {
|
||||
if OnChainFallback::get() {
|
||||
onchain::UnboundedExecution::<OnChainSeqPhragmen>::elect_with_bounds(
|
||||
max_voters,
|
||||
max_targets,
|
||||
)
|
||||
.map_err(|_| "onchain::UnboundedExecution failed.")
|
||||
onchain::OnChainExecution::<OnChainSeqPhragmen>::instant_elect(max_voters, max_targets)
|
||||
.map_err(|_| "onchain::OnChainExecution failed.")
|
||||
} else {
|
||||
super::NoFallback::<Runtime>::elect_with_bounds(max_voters, max_targets)
|
||||
Err("NoFallback.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -404,10 +397,12 @@ impl crate::Config for Runtime {
|
||||
type WeightInfo = ();
|
||||
type BenchmarkingConfig = TestBenchmarkingConfig;
|
||||
type Fallback = MockFallback;
|
||||
type GovernanceFallback = UnboundedExecution<OnChainSeqPhragmen>;
|
||||
type GovernanceFallback =
|
||||
frame_election_provider_support::onchain::OnChainExecution<OnChainSeqPhragmen>;
|
||||
type ForceOrigin = frame_system::EnsureRoot<AccountId>;
|
||||
type MaxElectingVoters = MaxElectingVoters;
|
||||
type MaxElectableTargets = MaxElectableTargets;
|
||||
type MaxWinners = MaxWinners;
|
||||
type MinerConfig = Self;
|
||||
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, Balancing>;
|
||||
}
|
||||
@@ -424,6 +419,8 @@ pub type Extrinsic = sp_runtime::testing::TestXt<RuntimeCall, ()>;
|
||||
|
||||
parameter_types! {
|
||||
pub MaxNominations: u32 = <TestNposSolution as NposSolution>::LIMIT as u32;
|
||||
// only used in testing to manipulate mock behaviour
|
||||
pub static DataProviderAllowBadData: bool = false;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -438,7 +435,9 @@ impl ElectionDataProvider for StakingMock {
|
||||
fn electable_targets(maybe_max_len: Option<usize>) -> data_provider::Result<Vec<AccountId>> {
|
||||
let targets = Targets::get();
|
||||
|
||||
if maybe_max_len.map_or(false, |max_len| targets.len() > max_len) {
|
||||
if !DataProviderAllowBadData::get() &&
|
||||
maybe_max_len.map_or(false, |max_len| targets.len() > max_len)
|
||||
{
|
||||
return Err("Targets too big")
|
||||
}
|
||||
|
||||
@@ -449,8 +448,10 @@ impl ElectionDataProvider for StakingMock {
|
||||
maybe_max_len: Option<usize>,
|
||||
) -> data_provider::Result<Vec<VoterOf<Runtime>>> {
|
||||
let mut voters = Voters::get();
|
||||
if let Some(max_len) = maybe_max_len {
|
||||
voters.truncate(max_len)
|
||||
if !DataProviderAllowBadData::get() {
|
||||
if let Some(max_len) = maybe_max_len {
|
||||
voters.truncate(max_len)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(voters)
|
||||
|
||||
@@ -462,7 +462,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>,
|
||||
@@ -537,7 +537,7 @@ impl<T: Config> Pallet<T> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{mock::*, ElectionCompute, Error, Event, Perbill, Phase};
|
||||
use crate::{mock::*, ElectionCompute, ElectionError, Error, Event, Perbill, Phase};
|
||||
use frame_support::{assert_noop, assert_ok, assert_storage_noop};
|
||||
|
||||
#[test]
|
||||
@@ -557,6 +557,50 @@ 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(|| {
|
||||
// given desired_targets bigger than MaxWinners
|
||||
DesiredTargets::set(4);
|
||||
MaxWinners::set(3);
|
||||
|
||||
let (_, _, actual_desired_targets) = MultiPhase::create_snapshot_external().unwrap();
|
||||
|
||||
// snapshot is created with min of desired_targets and MaxWinners
|
||||
assert_eq!(actual_desired_targets, 3);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_pay_deposit() {
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
|
||||
Reference in New Issue
Block a user