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

Bounds EPM's CurrentPhase, SnapshotMetadata and Snapshot storage structs #14543

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 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
74 changes: 54 additions & 20 deletions frame/election-provider-multi-phase/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@

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

use codec::{Decode, Encode};
use codec::{Decode, Encode, MaxEncodedLen};
use frame_election_provider_support::{
BoundedSupportsOf, ElectionDataProvider, ElectionProvider, ElectionProviderBase,
InstantElectionProvider, NposSolution,
Expand All @@ -239,7 +239,7 @@ use frame_support::{
ensure,
traits::{Currency, DefensiveResult, Get, OnUnbalanced, ReservableCurrency},
weights::Weight,
DefaultNoBound, EqNoBound, PartialEqNoBound,
BoundedVec, DefaultNoBound, EqNoBound, PartialEqNoBound,
};
use frame_system::{ensure_none, offchain::SendTransactionTypes, pallet_prelude::BlockNumberFor};
use scale_info::TypeInfo;
Expand Down Expand Up @@ -294,6 +294,10 @@ 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;
/// The maximum electable targets, as seen by the miner config.
pub type MaxElectableTargetsOf<T> = <T as MinerConfig>::MaxElectableTargets;
/// The maximum electing voters, as seen by the miner config.
pub type MaxElectingVotersOf<T> = <T as MinerConfig>::MaxElectingVoters;

/// Configuration for the benchmarks of the pallet.
pub trait BenchmarkingConfig {
Expand All @@ -314,7 +318,7 @@ pub trait BenchmarkingConfig {
}

/// 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 @@ -453,21 +457,22 @@ where
/// [`ElectionDataProvider`] and are kept around until the round is finished.
///
/// These are stored together because they are often accessed together.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct RoundSnapshot<AccountId, DataProvider> {
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, MaxEncodedLen, TypeInfo)]
#[codec(mel_bound(skip_type_params(V, T)))]
#[scale_info(skip_type_params(V, T))]
pub struct RoundSnapshot<AccountId, DataProvider, V: Get<u32>, T: Get<u32>> {
/// All of the voters.
pub voters: Vec<DataProvider>,
pub voters: BoundedVec<DataProvider, V>,
/// All of the targets.
pub targets: Vec<AccountId>,
pub targets: BoundedVec<AccountId, T>,
}

/// Encodes the length of a solution or a snapshot.
///
/// 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 @@ -619,6 +624,8 @@ pub mod pallet {
AccountId = Self::AccountId,
MaxVotesPerVoter = <Self::DataProvider as ElectionDataProvider>::MaxVotesPerVoter,
MaxWinners = Self::MaxWinners,
MaxElectingVoters = Self::MaxElectingVoters,
MaxElectableTargets = Self::MaxElectableTargets,
>;

/// Maximum number of signed submissions that can be queued.
Expand Down Expand Up @@ -663,11 +670,11 @@ pub mod pallet {
/// are only over a single block, but once multi-block elections are introduced they will
/// take place over multiple blocks.
#[pallet::constant]
type MaxElectingVoters: Get<SolutionVoterIndexOf<Self::MinerConfig>>;
type MaxElectingVoters: Get<SolutionVoterIndexOf<Self::MinerConfig>> + Get<u32>;

/// The maximum number of electable targets to put in the snapshot.
#[pallet::constant]
type MaxElectableTargets: Get<SolutionTargetIndexOf<Self::MinerConfig>>;
type MaxElectableTargets: Get<SolutionTargetIndexOf<Self::MinerConfig>> + Get<u32>;

/// The maximum number of winners that can be elected by this `ElectionProvider`
/// implementation.
Expand All @@ -686,6 +693,8 @@ pub mod pallet {
type DataProvider: ElectionDataProvider<
AccountId = Self::AccountId,
BlockNumber = BlockNumberFor<Self>,
MaxElectingVoters = Self::MaxElectingVoters,
MaxElectableTargets = Self::MaxElectableTargets,
>;

/// Configuration for the fallback.
Expand Down Expand Up @@ -1267,6 +1276,7 @@ pub mod pallet {
///
/// Always sorted by score.
#[pallet::storage]
#[pallet::unbounded]
#[pallet::getter(fn queued_solution)]
pub type QueuedSolution<T: Config> =
StorageValue<_, ReadySolution<T::AccountId, T::MaxWinners>>;
Expand All @@ -1276,7 +1286,15 @@ pub mod pallet {
/// This is created at the beginning of the signed phase and cleared upon calling `elect`.
#[pallet::storage]
#[pallet::getter(fn snapshot)]
pub type Snapshot<T: Config> = StorageValue<_, RoundSnapshot<T::AccountId, VoterOf<T>>>;
pub type Snapshot<T: Config> = StorageValue<
_,
RoundSnapshot<
T::AccountId,
VoterOf<T>,
MaxElectingVotersOf<T::MinerConfig>,
MaxElectableTargetsOf<T::MinerConfig>,
gpestana marked this conversation as resolved.
Show resolved Hide resolved
>,
>;

/// Desired number of targets to elect for this round.
///
Expand Down Expand Up @@ -1326,6 +1344,7 @@ pub mod pallet {
/// Twox note: the key of the map is an auto-incrementing index which users cannot inspect or
/// affect; we shouldn't need a cryptographically secure hasher.
#[pallet::storage]
#[pallet::unbounded]
pub type SignedSubmissionsMap<T: Config> =
StorageMap<_, Twox64Concat, u32, SignedSubmissionOf<T>, OptionQuery>;

Expand All @@ -1345,7 +1364,6 @@ pub mod pallet {
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);

#[pallet::pallet]
#[pallet::without_storage_info]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
}
Expand Down Expand Up @@ -1393,8 +1411,8 @@ impl<T: Config> Pallet<T> {
///
/// Extracted for easier weight calculation.
fn create_snapshot_internal(
targets: Vec<T::AccountId>,
voters: Vec<VoterOf<T>>,
targets: BoundedVec<T::AccountId, T::MaxElectableTargets>,
voters: BoundedVec<VoterOf<T>, T::MaxElectingVoters>,
desired_targets: u32,
) {
let metadata =
Expand All @@ -1407,7 +1425,15 @@ impl<T: Config> Pallet<T> {
// instead of using storage APIs, we do a manual encoding into a fixed-size buffer.
// `encoded_size` encodes it without storing it anywhere, this should not cause any
// allocation.
let snapshot = RoundSnapshot::<T::AccountId, VoterOf<T>> { voters, targets };
let snapshot = RoundSnapshot::<
T::AccountId,
VoterOf<T>,
T::MaxElectingVoters,
T::MaxElectableTargets,
> {
voters,
targets,
};
let size = snapshot.encoded_size();
log!(debug, "snapshot pre-calculated size {:?}", size);
let mut buffer = Vec::with_capacity(size);
Expand All @@ -1424,10 +1450,16 @@ impl<T: Config> Pallet<T> {
/// Parts of [`create_snapshot`] that happen outside of this pallet.
///
/// Extracted for easier weight calculation.
fn create_snapshot_external(
) -> Result<(Vec<T::AccountId>, Vec<VoterOf<T>>, u32), ElectionError<T>> {
let target_limit = T::MaxElectableTargets::get().saturated_into::<usize>();
let voter_limit = T::MaxElectingVoters::get().saturated_into::<usize>();
fn create_snapshot_external() -> Result<
(
BoundedVec<T::AccountId, T::MaxElectableTargets>,
BoundedVec<VoterOf<T>, T::MaxElectingVoters>,
u32,
),
ElectionError<T>,
> {
let target_limit = <T::MaxElectableTargets as Get<u32>>::get().saturated_into::<usize>();
let voter_limit = <T::MaxElectingVoters as Get<u32>>::get().saturated_into::<usize>();

let targets = T::DataProvider::electable_targets(Some(target_limit))
.map_err(ElectionError::DataProvider)?;
Expand Down Expand Up @@ -1678,6 +1710,8 @@ impl<T: Config> ElectionProviderBase for Pallet<T> {
type AccountId = T::AccountId;
type BlockNumber = BlockNumberFor<T>;
type Error = ElectionError<T>;
type MaxElectingVoters = T::MaxElectingVoters;
type MaxElectableTargets = T::MaxElectableTargets;
type MaxWinners = T::MaxWinners;
type DataProvider = T::DataProvider;
}
Expand Down
51 changes: 36 additions & 15 deletions frame/election-provider-multi-phase/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,13 @@ pub fn trim_helpers() -> TrimHelpers {

let desired_targets = MultiPhase::desired_targets().unwrap();

let ElectionResult::<_, SolutionAccuracyOf<Runtime>> { mut assignments, .. } =
seq_phragmen(desired_targets as usize, targets.clone(), voters.clone(), None).unwrap();
let ElectionResult::<_, SolutionAccuracyOf<Runtime>> { mut assignments, .. } = seq_phragmen(
desired_targets as usize,
targets.clone().into_inner(),
voters.clone().into_inner(),
None,
)
.unwrap();

// sort by decreasing order of stake
assignments.sort_by_key(|assignment| {
Expand All @@ -165,7 +170,12 @@ pub fn trim_helpers() -> TrimHelpers {
.collect::<Result<Vec<_>, _>>()
.expect("test assignments don't contain any voters with too many votes");

TrimHelpers { voters, assignments, encoded_size_of, voter_index: Box::new(voter_index) }
TrimHelpers {
voters: voters.to_vec(),
assignments,
encoded_size_of,
voter_index: Box::new(voter_index),
}
}

/// Spit out a verifiable raw solution.
Expand All @@ -176,7 +186,13 @@ pub fn raw_solution() -> RawSolution<SolutionOf<Runtime>> {
let desired_targets = MultiPhase::desired_targets().unwrap();

let ElectionResult::<_, SolutionAccuracyOf<Runtime>> { winners: _, assignments } =
seq_phragmen(desired_targets as usize, targets.clone(), voters.clone(), None).unwrap();
seq_phragmen(
desired_targets as usize,
targets.clone().into_inner(),
voters.clone().into_inner(),
None,
)
.unwrap();

// closures
let cache = helpers::generate_voter_cache::<Runtime>(&voters);
Expand Down Expand Up @@ -311,6 +327,8 @@ impl onchain::Config for OnChainSeqPhragmen {
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, Balancing>;
type DataProvider = StakingMock;
type WeightInfo = ();
type MaxElectingVoters = MaxElectingVoters;
type MaxElectableTargets = MaxElectableTargets;
type MaxWinners = MaxWinners;
type VotersBound = ConstU32<{ u32::MAX }>;
type TargetsBound = ConstU32<{ u32::MAX }>;
Expand All @@ -322,6 +340,8 @@ impl ElectionProviderBase for MockFallback {
type AccountId = AccountId;
type Error = &'static str;
type DataProvider = StakingMock;
type MaxElectingVoters = MaxElectingVoters;
type MaxElectableTargets = MaxElectableTargets;
type MaxWinners = MaxWinners;
}

Expand Down Expand Up @@ -360,6 +380,8 @@ impl MinerConfig for Runtime {
type AccountId = AccountId;
type MaxLength = MinerMaxLength;
type MaxWeight = MinerMaxWeight;
type MaxElectingVoters = MaxElectingVoters;
type MaxElectableTargets = MaxElectableTargets;
type MaxVotesPerVoter = <StakingMock as ElectionDataProvider>::MaxVotesPerVoter;
type MaxWinners = MaxWinners;
type Solution = TestNposSolution;
Expand Down Expand Up @@ -435,8 +457,12 @@ impl ElectionDataProvider for StakingMock {
type BlockNumber = BlockNumber;
type AccountId = AccountId;
type MaxVotesPerVoter = MaxNominations;
type MaxElectingVoters = MaxElectingVoters;
type MaxElectableTargets = MaxElectableTargets;

fn electable_targets(maybe_max_len: Option<usize>) -> data_provider::Result<Vec<AccountId>> {
fn electable_targets(
maybe_max_len: Option<usize>,
) -> data_provider::Result<BoundedVec<AccountId, Self::MaxElectableTargets>> {
let targets = Targets::get();

if !DataProviderAllowBadData::get() &&
Expand All @@ -445,20 +471,15 @@ impl ElectionDataProvider for StakingMock {
return Err("Targets too big")
}

Ok(targets)
Ok(BoundedVec::truncate_from(targets))
}

fn electing_voters(
maybe_max_len: Option<usize>,
) -> data_provider::Result<Vec<VoterOf<Runtime>>> {
let mut voters = Voters::get();
if !DataProviderAllowBadData::get() {
if let Some(max_len) = maybe_max_len {
voters.truncate(max_len)
}
}
_maybe_max_len: Option<usize>,
) -> data_provider::Result<BoundedVec<VoterOf<Runtime>, Self::MaxElectingVoters>> {
let voters = Voters::get();

Ok(voters)
Ok(BoundedVec::truncate_from(voters))
}

fn desired_targets() -> data_provider::Result<u32> {
Expand Down
30 changes: 0 additions & 30 deletions frame/election-provider-multi-phase/src/signed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -561,36 +561,6 @@ 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(|| {
Expand Down
Loading