Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduces multi-block election types and traits and refactors relevant pallets #5438

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
init
  • Loading branch information
gpestana committed Aug 21, 2024
commit 7b57145e6cebf936e0fcf75d831090fe5d7f27c2
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

90 changes: 34 additions & 56 deletions substrate/frame/election-provider-multi-phase/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ use codec::{Decode, Encode};
use frame_election_provider_support::{
bounds::{CountBound, ElectionBounds, ElectionBoundsBuilder, SizeBound},
BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider,
ElectionProviderBase, InstantElectionProvider, NposSolution,
InstantElectionProvider, NposSolution, BoundedSupports, PageIndex,
};
use frame_support::{
dispatch::DispatchClass,
Expand All @@ -251,7 +251,7 @@ use sp_arithmetic::{
traits::{CheckedAdd, Zero},
UpperOf,
};
use sp_npos_elections::{BoundedSupports, ElectionScore, IdentifierT, Supports, VoteWeight};
use sp_npos_elections::{ElectionScore, IdentifierT, Supports, VoteWeight};
use sp_runtime::{
transaction_validity::{
InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
Expand Down Expand Up @@ -296,7 +296,7 @@ pub type SolutionTargetIndexOf<T> = <SolutionOf<T> as NposSolution>::TargetIndex
pub type SolutionAccuracyOf<T> =
<SolutionOf<<T as crate::Config>::MinerConfig> as NposSolution>::Accuracy;
/// The fallback election type.
pub type FallbackErrorOf<T> = <<T as crate::Config>::Fallback as ElectionProviderBase>::Error;
pub type FallbackErrorOf<T> = <<T as crate::Config>::Fallback as ElectionProvider>::Error;

/// Configuration for the benchmarks of the pallet.
pub trait BenchmarkingConfig {
Expand Down Expand Up @@ -433,17 +433,18 @@ impl<C: Default> Default for RawSolution<C> {
DefaultNoBound,
scale_info::TypeInfo,
)]
#[scale_info(skip_type_params(AccountId, MaxWinners))]
pub struct ReadySolution<AccountId, MaxWinners>
#[scale_info(skip_type_params(AccountId, MaxWinnersPerPage, MaxBackersPerWinner))]
pub struct ReadySolution<AccountId, MaxWinnersPerPage, MaxBackersPerWinner>
where
AccountId: IdentifierT,
MaxWinners: Get<u32>,
MaxWinnersPerPage: Get<u32>,
MaxBackersPerWinner: Get<u32>,
{
/// The final supports of the solution.
///
/// This is target-major vector, storing each winners, total backing, and each individual
/// backer.
pub supports: BoundedSupports<AccountId, MaxWinners>,
pub supports: BoundedSupports<AccountId, MaxWinnersPerPage, MaxBackersPerWinner>,
/// The score of the solution.
///
/// This is needed to potentially challenge the solution.
Expand Down Expand Up @@ -615,7 +616,8 @@ pub mod pallet {
type MinerConfig: crate::unsigned::MinerConfig<
AccountId = Self::AccountId,
MaxVotesPerVoter = <Self::DataProvider as ElectionDataProvider>::MaxVotesPerVoter,
MaxWinners = Self::MaxWinners,
MaxWinnersPerPage = Self::MaxWinnersPerPage,
MaxBackersPerWinner = Self::MaxBackersPerWinner,
>;

/// Maximum number of signed submissions that can be queued.
Expand Down Expand Up @@ -652,12 +654,21 @@ pub mod pallet {
#[pallet::constant]
type SignedDepositWeight: Get<BalanceOf<Self>>;

/// The maximum number of winners that can be elected by this `ElectionProvider`
/// implementation.
/// Maximum number of winners that a page supports.
///
/// Note: This must always be greater or equal to `T::DataProvider::desired_targets()`.
#[pallet::constant]
type MaxWinners: Get<u32>;
type MaxWinnersPerPage: Get<u32>;

/// Maximum number of voters that can support a single target, across ALL the solution
/// pages. Thus, this can only be verified when processing the last solution page.
///
/// This limit must be set so that the memory limits of the rest of the system are
/// respected.
type MaxBackersPerWinner: Get<u32>;

/// Number of pages.
type Pages: Get<PageIndex>;

/// Something that calculates the signed deposit base based on the signed submissions queue
/// size.
Expand Down Expand Up @@ -685,7 +696,8 @@ pub mod pallet {
AccountId = Self::AccountId,
BlockNumber = BlockNumberFor<Self>,
DataProvider = Self::DataProvider,
MaxWinners = Self::MaxWinners,
MaxWinnersPerPage = Self::MaxWinnersPerPage,
MaxBackersPerWinner = Self::MaxBackersPerWinner,
>;

/// Configuration of the governance-only fallback.
Expand All @@ -696,7 +708,8 @@ pub mod pallet {
AccountId = Self::AccountId,
BlockNumber = BlockNumberFor<Self>,
DataProvider = Self::DataProvider,
MaxWinners = Self::MaxWinners,
MaxWinnersPerPage = Self::MaxWinnersPerPage,
MaxBackersPerWinner = Self::MaxBackersPerWinner,
>;

/// OCW election solution miner algorithm implementation.
Expand Down Expand Up @@ -733,7 +746,7 @@ pub mod pallet {

#[pallet::constant_name(MinerMaxWinners)]
fn max_winners() -> u32 {
<T::MinerConfig as MinerConfig>::MaxWinners::get()
<T::MinerConfig as MinerConfig>::MaxWinnersPerPage::get()
}
}

Expand Down Expand Up @@ -1104,9 +1117,7 @@ pub mod pallet {
Error::<T>::FallbackFailed
})?;

// transform BoundedVec<_, T::GovernanceFallback::MaxWinners> into
// `BoundedVec<_, T::MaxWinners>`
let supports: BoundedVec<_, T::MaxWinners> = supports
let supports: BoundedVec<_, _> = supports
.into_inner()
.try_into()
.defensive_map_err(|_| Error::<T>::BoundNotMet)?;
Expand Down Expand Up @@ -1267,7 +1278,7 @@ pub mod pallet {
/// Always sorted by score.
#[pallet::storage]
pub type QueuedSolution<T: Config> =
StorageValue<_, ReadySolution<T::AccountId, T::MaxWinners>>;
StorageValue<_, ReadySolution<T::AccountId, T::MaxWinnersPerPage, T::MaxBackersPerWinner>>;

/// Snapshot data of the round.
///
Expand Down Expand Up @@ -1399,7 +1410,7 @@ impl<T: Config> Pallet<T> {
/// Current best solution, signed or unsigned, queued to be returned upon `elect`.
///
/// Always sorted by score.
pub fn queued_solution() -> Option<ReadySolution<T::AccountId, T::MaxWinners>> {
pub fn queued_solution() -> Option<ReadySolution<T::AccountId, T::MaxWinnersPerPage, T::MaxBackersPerWinner>> {
QueuedSolution::<T>::get()
}

Expand Down Expand Up @@ -1509,7 +1520,7 @@ impl<T: Config> Pallet<T> {
) -> Result<(Vec<T::AccountId>, Vec<VoterOf<T>>, u32), ElectionError<T>> {
let election_bounds = T::ElectionBounds::get();

let targets = T::DataProvider::electable_targets(election_bounds.targets)
let targets = T::DataProvider::electable_targets(election_bounds.targets, Zero::zero())
.and_then(|t| {
election_bounds.ensure_targets_limits(
CountBound(t.len() as u32),
Expand All @@ -1519,7 +1530,7 @@ impl<T: Config> Pallet<T> {
})
.map_err(ElectionError::DataProvider)?;

let voters = T::DataProvider::electing_voters(election_bounds.voters)
let voters = T::DataProvider::electing_voters(election_bounds.voters, Zero::zero())
.and_then(|v| {
election_bounds.ensure_voters_limits(
CountBound(v.len() as u32),
Expand All @@ -1529,7 +1540,7 @@ impl<T: Config> Pallet<T> {
})
.map_err(ElectionError::DataProvider)?;

let mut desired_targets = <Pallet<T> as ElectionProviderBase>::desired_targets_checked()
let mut desired_targets = <Pallet<T> as ElectionProvider>::desired_targets_checked()
.map_err(|e| ElectionError::DataProvider(e))?;

// If `desired_targets` > `targets.len()`, cap `desired_targets` to that level and emit a
Expand Down Expand Up @@ -1584,7 +1595,7 @@ impl<T: Config> Pallet<T> {
pub fn feasibility_check(
raw_solution: RawSolution<SolutionOf<T::MinerConfig>>,
compute: ElectionCompute,
) -> Result<ReadySolution<T::AccountId, T::MaxWinners>, FeasibilityError> {
) -> Result<ReadySolution<T::AccountId, T::MaxWinnersPerPage, T::MaxBackersPerWinner>, FeasibilityError> {
let desired_targets =
DesiredTargets::<T>::get().ok_or(FeasibilityError::SnapshotUnavailable)?;

Expand Down Expand Up @@ -1756,39 +1767,6 @@ impl<T: Config> Pallet<T> {
}
}

impl<T: Config> ElectionProviderBase for Pallet<T> {
type AccountId = T::AccountId;
type BlockNumber = BlockNumberFor<T>;
type Error = ElectionError<T>;
type MaxWinners = T::MaxWinners;
type DataProvider = T::DataProvider;
}

impl<T: Config> ElectionProvider for Pallet<T> {
fn ongoing() -> bool {
match CurrentPhase::<T>::get() {
Phase::Off => false,
_ => true,
}
}

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.
Self::weigh_supports(&supports);
Self::rotate_round();
Ok(supports)
},
Err(why) => {
log!(error, "Entering emergency mode: {:?}", why);
Self::phase_transition(Phase::Emergency);
Err(why)
},
}
}
}

/// convert a DispatchError to a custom InvalidTransaction with the inner code being the error
/// number.
pub fn dispatch_error_to_invalid(error: DispatchError) -> InvalidTransaction {
Expand Down
22 changes: 13 additions & 9 deletions substrate/frame/election-provider-multi-phase/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
use super::*;
use crate::{self as multi_phase, signed::GeometricDepositBase, unsigned::MinerConfig};
use frame_election_provider_support::{
bounds::{DataProviderBounds, ElectionBounds},
bounds::{DataProviderBounds, ElectionBounds}, PageIndex,
data_provider, onchain, ElectionDataProvider, NposSolution, SequentialPhragmen,
};
pub use frame_support::derive_impl;
Expand Down Expand Up @@ -295,7 +295,8 @@ parameter_types! {
pub static MaxElectableTargets: TargetIndex = TargetIndex::max_value();

#[derive(Debug)]
pub static MaxWinners: u32 = 200;
pub static MaxWinnersPerPage: u32 = 200;
pub static MaxBackersPerWinner: u32 = 300;
// `ElectionBounds` and `OnChainElectionsBounds` are defined separately to set them independently in the tests.
pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build();
pub static OnChainElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build();
Expand All @@ -309,17 +310,18 @@ impl onchain::Config for OnChainSeqPhragmen {
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, Balancing>;
type DataProvider = StakingMock;
type WeightInfo = ();
type MaxWinners = MaxWinners;
type MaxWinnersPerPage = MaxWinnersPerPage;
type Bounds = OnChainElectionsBounds;
}

pub struct MockFallback;
impl ElectionProviderBase for MockFallback {
impl ElectionProvider for MockFallback {
type BlockNumber = BlockNumber;
type AccountId = AccountId;
type Error = &'static str;
type DataProvider = StakingMock;
type MaxWinners = MaxWinners;
type MaxWinnersPerPage = MaxWinnersPerPage;
type MaxBackersPerPage = MaxBackersPerPage;
}

impl InstantElectionProvider for MockFallback {
Expand Down Expand Up @@ -361,7 +363,8 @@ impl MinerConfig for Runtime {
type MaxLength = MinerMaxLength;
type MaxWeight = MinerMaxWeight;
type MaxVotesPerVoter = <StakingMock as ElectionDataProvider>::MaxVotesPerVoter;
type MaxWinners = MaxWinners;
type MaxWinnersPerPage = MaxWinnersPerPage;
type MaxBackersPerWinner = MaxBackersPerWinner;
type Solution = TestNposSolution;

fn solution_weight(v: u32, t: u32, a: u32, d: u32) -> Weight {
Expand Down Expand Up @@ -403,7 +406,8 @@ impl crate::Config for Runtime {
type GovernanceFallback =
frame_election_provider_support::onchain::OnChainExecution<OnChainSeqPhragmen>;
type ForceOrigin = frame_system::EnsureRoot<AccountId>;
type MaxWinners = MaxWinners;
type MaxWinnersPerPage = MaxWinnersPerPage;
type MaxBackersPerWinner = MaxBackersPerWinner;
type MinerConfig = Self;
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, Balancing>;
type ElectionBounds = ElectionsBounds;
Expand Down Expand Up @@ -446,7 +450,7 @@ impl ElectionDataProvider for StakingMock {
type AccountId = AccountId;
type MaxVotesPerVoter = MaxNominations;

fn electable_targets(bounds: DataProviderBounds) -> data_provider::Result<Vec<AccountId>> {
fn electable_targets(bounds: DataProviderBounds, _remaining_page: PageIndex) -> data_provider::Result<Vec<AccountId>> {
let targets = Targets::get();

if !DataProviderAllowBadData::get() &&
Expand All @@ -458,7 +462,7 @@ impl ElectionDataProvider for StakingMock {
Ok(targets)
}

fn electing_voters(bounds: DataProviderBounds) -> data_provider::Result<Vec<VoterOf<Runtime>>> {
fn electing_voters(bounds: DataProviderBounds, _remaining_page: PageIndex) -> data_provider::Result<Vec<VoterOf<Runtime>>> {
let mut voters = Voters::get();

if !DataProviderAllowBadData::get() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ impl<T: Config> Pallet<T> {
///
/// Infallible
pub fn finalize_signed_phase_accept_solution(
ready_solution: ReadySolution<T::AccountId, T::MaxWinners>,
ready_solution: ReadySolution<T::AccountId, T::MaxWinnersPerPage, T::MaxBackersPerWinner>,
who: &T::AccountId,
deposit: BalanceOf<T>,
call_fee: BalanceOf<T>,
Expand Down
14 changes: 8 additions & 6 deletions substrate/frame/election-provider-multi-phase/src/unsigned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ impl<T: Config> Pallet<T> {
// ensure score is being improved. Panic henceforth.
ensure!(
QueuedSolution::<T>::get()
.map_or(true, |q: ReadySolution<_, _>| raw_solution.score > q.score),
.map_or(true, |q: ReadySolution<_, _, _>| raw_solution.score > q.score),
Error::<T>::PreDispatchWeakSubmission,
);

Expand Down Expand Up @@ -423,8 +423,10 @@ pub trait MinerConfig {
///
/// The weight is computed using `solution_weight`.
type MaxWeight: Get<Weight>;
/// The maximum number of winners that can be elected.
type MaxWinners: Get<u32>;
/// The maximum number of winners that can be elected per page (and overall).
type MaxWinnersPerPage: Get<u32>;
/// The maximum number of backers (edges) per winner in the last solution.
type MaxBackersPerWinner: Get<u32>;
/// Something that can compute the weight of a solution.
///
/// This weight estimate is then used to trim the solution, based on [`MinerConfig::MaxWeight`].
Expand Down Expand Up @@ -743,7 +745,7 @@ impl<T: MinerConfig> Miner<T> {
snapshot: RoundSnapshot<T::AccountId, MinerVoterOf<T>>,
current_round: u32,
minimum_untrusted_score: Option<ElectionScore>,
) -> Result<ReadySolution<T::AccountId, T::MaxWinners>, FeasibilityError> {
) -> Result<ReadySolution<T::AccountId, T::MaxWinnersPerPage, T::MaxBackersPerWinner>, FeasibilityError> {
let RawSolution { solution, score, round } = raw_solution;
let RoundSnapshot { voters: snapshot_voters, targets: snapshot_targets } = snapshot;

Expand All @@ -755,7 +757,7 @@ impl<T: MinerConfig> Miner<T> {

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::MaxWinners::get(), FeasibilityError::TooManyDesiredTargets);
ensure!(desired_targets <= T::MaxWinnersPerPage::get(), FeasibilityError::TooManyDesiredTargets);

// Ensure that the solution's score can pass absolute min-score.
let submitted_score = raw_solution.score;
Expand Down Expand Up @@ -812,7 +814,7 @@ impl<T: MinerConfig> Miner<T> {
let known_score = supports.evaluate();
ensure!(known_score == score, FeasibilityError::InvalidScore);

// Size of winners in miner solution is equal to `desired_targets` <= `MaxWinners`.
// Size of winners in miner solution is equal to `desired_targets` <= `MaxWinnersPerPage`.
let supports = supports
.try_into()
.defensive_map_err(|_| FeasibilityError::BoundedConversionFailed)?;
Expand Down
2 changes: 2 additions & 0 deletions substrate/frame/election-provider-support/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ sp-arithmetic = { workspace = true }
sp-npos-elections = { workspace = true }
sp-runtime = { workspace = true }
sp-core = { workspace = true }
sp-std = { workspace = true }

[dev-dependencies]
rand = { features = ["small_rng"], workspace = true, default-features = true }
Expand All @@ -40,6 +41,7 @@ std = [
"scale-info/std",
"sp-arithmetic/std",
"sp-core/std",
"sp-std/std",
"sp-io/std",
"sp-npos-elections/std",
"sp-runtime/std",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub(crate) fn codec_and_info_impl(
let scale_info = scale_info_impl(&ident, &voter_type, &target_type, &weight_type, count);

quote! {
impl _fepsp::codec::EncodeLike for #ident {}
#encode
#decode
#scale_info
Expand Down
Loading
Loading