diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 58c98e529c319..da81463a8f764 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -499,6 +499,7 @@ impl pallet_staking::Config for Runtime { // The unsigned solution weight targeted by the OCW. We set it to the maximum possible value of // a single extrinsic. type OffchainSolutionWeightLimit = OffchainSolutionWeightLimit; + type CompactSolution = pallet_staking::default_solution::CompactSolution24; type WeightInfo = pallet_staking::weights::SubstrateWeight; } diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index e3d2eb19ef264..c3f075c3fb158 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -205,6 +205,7 @@ impl pallet_staking::Config for Test { type MaxIterations = (); type MinSolutionScoreBump = (); type OffchainSolutionWeightLimit = (); + type CompactSolution = pallet_staking::default_solution::CompactSolution24; type WeightInfo = (); } diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 057e9f181c7af..fb7bd18f2de33 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -1079,7 +1079,7 @@ mod tests { type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); - type SS58Prefix = (); + type SS58Prefix = (); } parameter_types! { diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index e8703dba50ae5..791ea200619d2 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -212,6 +212,7 @@ impl pallet_staking::Config for Test { type MaxIterations = (); type MinSolutionScoreBump = (); type OffchainSolutionWeightLimit = (); + type CompactSolution = pallet_staking::default_solution::CompactSolution24; type WeightInfo = (); } diff --git a/frame/offences/benchmarking/src/lib.rs b/frame/offences/benchmarking/src/lib.rs index 57672f13ed711..2c703fc69f9ce 100644 --- a/frame/offences/benchmarking/src/lib.rs +++ b/frame/offences/benchmarking/src/lib.rs @@ -40,7 +40,8 @@ use pallet_session::historical::{Config as HistoricalConfig, IdentificationTuple use pallet_session::{Config as SessionConfig, SessionManager}; use pallet_staking::{ Module as Staking, Config as StakingConfig, RewardDestination, ValidatorPrefs, - Exposure, IndividualExposure, ElectionStatus, MAX_NOMINATIONS, Event as StakingEvent + Exposure, IndividualExposure, ElectionStatus, Event as StakingEvent, + default_solution::CompactSolution, }; const SEED: u32 = 0; @@ -236,7 +237,7 @@ benchmarks! { let r in 1 .. MAX_REPORTERS; // we skip 1 offender, because in such case there is no slashing let o in 2 .. MAX_OFFENDERS; - let n in 0 .. MAX_NOMINATORS.min(MAX_NOMINATIONS as u32); + let n in 0 .. MAX_NOMINATORS.min(::CompactSolution::LIMIT as u32); // Make r reporters let mut reporters = vec![]; @@ -310,7 +311,7 @@ benchmarks! { } report_offence_grandpa { - let n in 0 .. MAX_NOMINATORS.min(MAX_NOMINATIONS as u32); + let n in 0 .. MAX_NOMINATORS.min(::CompactSolution::LIMIT as u32); // for grandpa equivocation reports the number of reporters // and offenders is always 1 @@ -346,7 +347,7 @@ benchmarks! { } report_offence_babe { - let n in 0 .. MAX_NOMINATORS.min(MAX_NOMINATIONS as u32); + let n in 0 .. MAX_NOMINATORS.min(::CompactSolution::LIMIT as u32); // for babe equivocation reports the number of reporters // and offenders is always 1 diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index e4ec32d0bc3bf..d11d611724ebb 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -170,6 +170,7 @@ impl pallet_staking::Config for Test { type MaxIterations = (); type MinSolutionScoreBump = (); type OffchainSolutionWeightLimit = (); + type CompactSolution = pallet_staking::default_solution::CompactSolution24; type WeightInfo = (); } diff --git a/frame/session/benchmarking/src/lib.rs b/frame/session/benchmarking/src/lib.rs index 06dfa3da34943..bf6e85cdd245d 100644 --- a/frame/session/benchmarking/src/lib.rs +++ b/frame/session/benchmarking/src/lib.rs @@ -35,7 +35,8 @@ use frame_system::RawOrigin; use pallet_session::{historical::Module as Historical, Module as Session, *}; use pallet_staking::{ benchmarking::create_validator_with_nominators, testing_utils::create_validators, - MAX_NOMINATIONS, RewardDestination, + default_solution::CompactSolution, + RewardDestination, }; use sp_runtime::traits::{One, StaticLookup}; @@ -52,10 +53,10 @@ impl OnInitialize for Module { benchmarks! { set_keys { - let n = MAX_NOMINATIONS as u32; + let n = ::CompactSolution::LIMIT as u32; let (v_stash, _) = create_validator_with_nominators::( n, - MAX_NOMINATIONS as u32, + ::CompactSolution::LIMIT as u32, false, RewardDestination::Staked, )?; @@ -68,10 +69,10 @@ benchmarks! { }: _(RawOrigin::Signed(v_controller), keys, proof) purge_keys { - let n = MAX_NOMINATIONS as u32; + let n = ::CompactSolution::LIMIT as u32; let (v_stash, _) = create_validator_with_nominators::( n, - MAX_NOMINATIONS as u32, + ::CompactSolution::LIMIT as u32, false, RewardDestination::Staked )?; diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index b25b169c82edb..db4cbac210d8d 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -174,6 +174,7 @@ impl pallet_staking::Config for Test { type MaxIterations = (); type MinSolutionScoreBump = (); type OffchainSolutionWeightLimit = (); + type CompactSolution = pallet_staking::default_solution::CompactSolution24; type WeightInfo = (); } diff --git a/frame/staking/fuzzer/src/mock.rs b/frame/staking/fuzzer/src/mock.rs index 88b001c7e69e1..f03a2cffaa3af 100644 --- a/frame/staking/fuzzer/src/mock.rs +++ b/frame/staking/fuzzer/src/mock.rs @@ -178,5 +178,6 @@ impl pallet_staking::Config for Test { type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; type UnsignedPriority = (); type OffchainSolutionWeightLimit = (); + type CompactSolution = pallet_staking::default_solution::CompactSolution24; type WeightInfo = (); } diff --git a/frame/staking/fuzzer/src/submit_solution.rs b/frame/staking/fuzzer/src/submit_solution.rs index d94ee49b96db4..11e13a64379d8 100644 --- a/frame/staking/fuzzer/src/submit_solution.rs +++ b/frame/staking/fuzzer/src/submit_solution.rs @@ -100,6 +100,7 @@ fn main() { num_nominators, edge_per_voter as usize, true, + 1000, None, )); diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index beddc326b5109..b82b814208cb7 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -200,18 +200,22 @@ benchmarks! { } kick { - // scenario: we want to kick `k` nominators from nominating us (we are a validator). - // we'll assume that `k` is under 128 for the purposes of determining the slope. - // each nominator should have `MAX_NOMINATIONS` validators nominated, and our validator - // should be somewhere in there. + // scenario: we want to kick `k` nominators from nominating us (we are a validator). we'll + // assume that `k` is under 128 for the purposes of determining the slope. each nominator + // should have `T::CompactSolution::LIMIT` validators nominated, and our validator should be + // somewhere in there. let k in 1 .. 128; - // these are the other validators; there are `MAX_NOMINATIONS - 1` of them, so there are a - // total of `MAX_NOMINATIONS` validators in the system. - let rest_of_validators = create_validators::(MAX_NOMINATIONS as u32 - 1, 100)?; + // these are the other validators; there are `T::CompactSolution::LIMIT - 1` of them, so + // there are a total of `T::CompactSolution::LIMIT` validators in the system. + let rest_of_validators = create_validators::(T::CompactSolution::LIMIT as u32 - 1, 100)?; // this is the validator that will be kicking. - let (stash, controller) = create_stash_controller::(MAX_NOMINATIONS as u32 - 1, 100, Default::default())?; + let (stash, controller) = create_stash_controller::( + T::CompactSolution::LIMIT as u32 - 1, + 100, + Default::default(), + )?; let stash_lookup: ::Source = T::Lookup::unlookup(stash.clone()); // they start validating. @@ -222,7 +226,8 @@ benchmarks! { let mut nominator_stashes = Vec::with_capacity(k as usize); for i in 0 .. k { // create a nominator stash. - let (n_stash, n_controller) = create_stash_controller::(MAX_NOMINATIONS as u32 + i, 100, Default::default())?; + let (n_stash, n_controller) = + create_stash_controller::(T::CompactSolution::LIMIT as u32 + i, 100, Default::default())?; // bake the nominations; we first clone them from the rest of the validators. let mut nominations = rest_of_validators.clone(); @@ -254,9 +259,9 @@ benchmarks! { } } - // Worst case scenario, MAX_NOMINATIONS + // Worst case scenario, T::CompactSolution::LIMIT nominate { - let n in 1 .. MAX_NOMINATIONS as u32; + let n in 1 .. T::CompactSolution::LIMIT as u32; let (stash, controller) = create_stash_controller::(n + 1, 100, Default::default())?; let validators = create_validators::(n, 100)?; whitelist_account!(controller); @@ -438,7 +443,11 @@ benchmarks! { CurrentEra::put(e); for i in 0 .. e { >::insert(i, T::AccountId::default(), Exposure::>::default()); - >::insert(i, T::AccountId::default(), Exposure::>::default()); + >::insert( + i, + T::AccountId::default(), + Exposure::>::default(), + ); >::insert(i, T::AccountId::default(), ValidatorPrefs::default()); >::insert(i, BalanceOf::::one()); >::insert(i, EraRewardPoints::::default()); @@ -465,7 +474,7 @@ benchmarks! { let v in 1 .. 10; let n in 1 .. 100; - create_validators_with_nominators_for_era::(v, n, MAX_NOMINATIONS, false, None)?; + create_validators_with_nominators_for_era::(v, n, T::CompactSolution::LIMIT, false, 1000, None)?; let session_index = SessionIndex::one(); }: { let validators = Staking::::new_era(session_index).ok_or("`new_era` failed")?; @@ -476,7 +485,7 @@ benchmarks! { payout_all { let v in 1 .. 10; let n in 1 .. 100; - create_validators_with_nominators_for_era::(v, n, MAX_NOMINATIONS, false, None)?; + create_validators_with_nominators_for_era::(v, n, T::CompactSolution::LIMIT, false, 1000, None)?; // Start a new Era let new_validators = Staking::::new_era(SessionIndex::one()).unwrap(); assert!(new_validators.len() == v as usize); @@ -550,22 +559,23 @@ benchmarks! { // `compact.len()`. let a in 200 .. 400; // number of winners, also ValidatorCount. This will be equal to `winner.len()`. - let w in 16 .. 100; + let w in 24 .. 100; - ensure!(w as usize >= MAX_NOMINATIONS, "doesn't support lower value"); + assert!(w as usize >= T::CompactSolution::LIMIT, "doesn't support lower value"); let winners = create_validators_with_nominators_for_era::( v, n, - MAX_NOMINATIONS, + T::CompactSolution::LIMIT, false, + 1000, Some(w), )?; - // needed for the solution to be generates. + // needed for the solution to be generated. assert!(>::create_stakers_snapshot().0); - // set number of winners + // set number of winners. ValidatorCount::put(w); // create a assignments in total for the w winners. @@ -612,7 +622,7 @@ benchmarks! { assert_eq!(>::queued_score().unwrap(), score); } - // same as submit_solution_initial but we place a very weak solution on chian first. + // same as submit_solution_initial but we place a very weak solution on chain first. submit_solution_better { // number of validator intention. let v in 200 .. 400; @@ -621,15 +631,16 @@ benchmarks! { // number of assignments. Basically, number of active nominators. let a in 200 .. 400; // number of winners, also ValidatorCount. - let w in 16 .. 100; + let w in 24 .. 100; - ensure!(w as usize >= MAX_NOMINATIONS, "doesn't support lower value"); + assert!(w as usize >= T::CompactSolution::LIMIT, "doesn't support lower value"); let winners = create_validators_with_nominators_for_era::( v, n, - MAX_NOMINATIONS, + T::CompactSolution::LIMIT, false, + 1000, Some(w), )?; @@ -713,7 +724,7 @@ benchmarks! { // number of nominator intention. let n in 500 .. 1000; - create_validators_with_nominators_for_era::(v, n, MAX_NOMINATIONS, false, None)?; + create_validators_with_nominators_for_era::(v, n, T::CompactSolution::LIMIT, false, 1000, None)?; // needed for the solution to be generates. assert!(>::create_stakers_snapshot().0); @@ -767,7 +778,7 @@ benchmarks! { #[cfg(test)] mod tests { use super::*; - use crate::mock::{ExtBuilder, Test, Balances, Staking, Origin}; + use crate::mock::{ExtBuilder, Test, Balances, Staking, Origin, TestSolution,}; use frame_support::assert_ok; #[test] @@ -776,7 +787,7 @@ mod tests { let v = 10; let n = 100; - create_validators_with_nominators_for_era::(v, n, MAX_NOMINATIONS, false, None) + create_validators_with_nominators_for_era::(v, n, TestSolution::LIMIT, false, 1000, None) .unwrap(); let count_validators = Validators::::iter().count(); @@ -891,7 +902,7 @@ mod tests { assert_ok!(test_benchmark_do_slash::()); assert_ok!(test_benchmark_payout_all::()); // only run one of them to same time on the CI. ignore the other two. - assert_ok!(test_benchmark_submit_solution_initial::()); + assert_ok!(test_benchmark_submit_solution_better::()); }); } @@ -899,7 +910,7 @@ mod tests { #[ignore] fn test_benchmarks_offchain() { ExtBuilder::default().has_stakers(false).build().execute_with(|| { - assert_ok!(test_benchmark_submit_solution_better::()); + assert_ok!(test_benchmark_submit_solution_initial::()); assert_ok!(test_benchmark_submit_solution_weaker::()); }); } diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 3ea66e937e83c..9c58b704d9273 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -305,11 +305,11 @@ use frame_support::{ }; use pallet_session::historical; use sp_runtime::{ - Percent, Perbill, PerU16, RuntimeDebug, DispatchError, + Percent, Perbill, RuntimeDebug, DispatchError, PerThing, curve::PiecewiseLinear, traits::{ Convert, Zero, StaticLookup, CheckedSub, Saturating, SaturatedConversion, - AtLeast32BitUnsigned, Dispatchable, + AtLeast32BitUnsigned, Dispatchable, Bounded, }, transaction_validity::{ TransactionValidityError, TransactionValidity, ValidTransaction, InvalidTransaction, @@ -328,17 +328,35 @@ use frame_system::{ }; use sp_npos_elections::{ ExtendedBalance, Assignment, ElectionScore, ElectionResult as PrimitiveElectionResult, - to_support_map, EvaluateSupport, seq_phragmen, generate_solution_type, is_score_better, - SupportMap, VoteWeight, CompactSolution, PerThing128, + to_support_map, EvaluateSupport, seq_phragmen, is_score_better, CompactSolution, SupportMap, + VoteWeight, PerThing128, }; pub use weights::WeightInfo; +/// The default solution type used in substrate-node with 24 maximum votes per voter. A runtime can +/// customize this to their needs. +pub mod default_solution { + use super::*; + pub use sp_npos_elections::CompactSolution; + + /// The nominator index type. + pub type NominatorIndex = u32; + /// The validator index type. + pub type ValidatorIndex = u16; + /// The accuracy type of the solution. + pub type Accuracy = sp_runtime::PerU16; + + sp_npos_elections::generate_solution_type!{ + pub struct CompactSolution24::(24) + } +} + const STAKING_ID: LockIdentifier = *b"staking "; + +/// Maximum number of unlocking chunks. pub const MAX_UNLOCKING_CHUNKS: usize = 32; -pub const MAX_NOMINATIONS: usize = ::LIMIT; pub(crate) const LOG_TARGET: &'static str = "staking"; - // syntactic sugar for logging. #[macro_export] macro_rules! log { @@ -350,21 +368,15 @@ macro_rules! log { }; } -/// Data type used to index nominators in the compact type -pub type NominatorIndex = u32; - -/// Data type used to index validators in the compact type. -pub type ValidatorIndex = u16; - -// Ensure the size of both ValidatorIndex and NominatorIndex. They both need to be well below usize. -static_assertions::const_assert!(size_of::() <= size_of::()); -static_assertions::const_assert!(size_of::() <= size_of::()); -static_assertions::const_assert!(size_of::() <= size_of::()); -static_assertions::const_assert!(size_of::() <= size_of::()); +/// The nominator index in the compact solution. +pub type NominatorIndexOf = <::CompactSolution as CompactSolution>::Voter; +/// The validators index in the compact solution. +pub type ValidatorIndexOf = <::CompactSolution as CompactSolution>::Target; +/// The offchain accuracy in the compact solution. +pub type OffchainAccuracyOf = <::CompactSolution as CompactSolution>::Accuracy; -/// Maximum number of stakers that can be stored in a snapshot. -pub(crate) const MAX_VALIDATORS: usize = ValidatorIndex::max_value() as usize; -pub(crate) const MAX_NOMINATORS: usize = NominatorIndex::max_value() as usize; +/// Accuracy used for on-chain election. +pub type ChainAccuracy = Perbill; /// Counter for the number of eras that have passed. pub type EraIndex = u32; @@ -372,18 +384,6 @@ pub type EraIndex = u32; /// Counter for the number of "reward" points earned by a given validator. pub type RewardPoint = u32; -// Note: Maximum nomination limit is set here -- 16. -generate_solution_type!( - #[compact] - pub struct CompactAssignments::(16) -); - -/// Accuracy used for on-chain election. -pub type ChainAccuracy = Perbill; - -/// Accuracy used for off-chain election. This better be small. -pub type OffchainAccuracy = PerU16; - /// The balance type of this module. pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -706,13 +706,12 @@ pub enum ElectionStatus { pub struct ElectionSize { /// Number of validators in the snapshot of the current election round. #[codec(compact)] - pub validators: ValidatorIndex, + pub validators: u32, /// Number of nominators in the snapshot of the current election round. #[codec(compact)] - pub nominators: NominatorIndex, + pub nominators: u32, } - impl ElectionStatus { pub fn is_open_at(&self, n: BlockNumber) -> bool { *self == Self::Open(n) @@ -870,6 +869,11 @@ pub trait Config: frame_system::Config + SendTransactionTypes> { /// enough to fit in the block. type OffchainSolutionWeightLimit: Get; + /// The compact solution type used to accept offchain solution. + /// + /// This is implicitly encoding the maximum number of nominations as well. + type CompactSolution: CompactSolution + frame_support::Parameter + Default; + /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; } @@ -1301,6 +1305,9 @@ decl_module! { /// their reward. This used to limit the i/o cost for the nominator payout. const MaxNominatorRewardedPerValidator: u32 = T::MaxNominatorRewardedPerValidator::get(); + /// The maximum of validator candidates each nominator may nominate. + const MaxNominations: u32 = T::CompactSolution::LIMIT as u32; + type Error = Error; fn deposit_event() = default; @@ -1396,6 +1403,14 @@ decl_module! { } fn integrity_test() { + // Ensure the size of both ValidatorIndex and NominatorIndex. They both need to be less or + // equal to usize. + assert!(size_of::>() <= size_of::()); + assert!(size_of::>() <= size_of::()); + assert!(size_of::>() <= size_of::()); + assert!(size_of::>() <= size_of::()); + + sp_io::TestExternalities::new_empty().execute_with(|| assert!( T::SlashDeferDuration::get() < T::BondingDuration::get() || T::BondingDuration::get() == 0, @@ -1405,22 +1420,25 @@ decl_module! { ) ); - use sp_runtime::UpperOf; + use sp_runtime::{UpperOf, traits::CheckedMul}; // see the documentation of `Assignment::try_normalize`. Now we can ensure that this // will always return `Ok`. // 1. Maximum sum of Vec must fit into `UpperOf`. assert!( - >>::try_into(MAX_NOMINATIONS) - .unwrap() + >>::try_into(T::CompactSolution::LIMIT) + .unwrap_or_else(|_| panic!()) .checked_mul(::one().deconstruct().try_into().unwrap()) .is_some() ); - // 2. Maximum sum of Vec must fit into `UpperOf`. + // 2. Maximum sum of Vec> must fit into + // `UpperOf>`. + let max_inner = >::one().deconstruct(); + let max_inner = >>::from(max_inner); assert!( - >>::try_into(MAX_NOMINATIONS) - .unwrap() - .checked_mul(::one().deconstruct().try_into().unwrap()) + >>>::try_into(T::CompactSolution::LIMIT) + .unwrap_or_else(|_| panic!()) + .checked_mul(&max_inner) .is_some() ); } @@ -1703,7 +1721,7 @@ decl_module! { /// /// # /// - The transaction's complexity is proportional to the size of `targets` (N) - /// which is capped at CompactAssignments::LIMIT (MAX_NOMINATIONS). + /// which is capped at (T::CompactSolution::LIMIT). /// - Both the reads and writes follow a similar pattern. /// --------- /// Weight: O(N) @@ -1719,7 +1737,7 @@ decl_module! { let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; let stash = &ledger.stash; ensure!(!targets.is_empty(), Error::::EmptyTargets); - ensure!(targets.len() <= MAX_NOMINATIONS, Error::::TooManyTargets); + ensure!(targets.len() <= T::CompactSolution::LIMIT, Error::::TooManyTargets); let old = Nominators::::get(stash).map_or_else(Vec::new, |x| x.targets); @@ -2126,8 +2144,8 @@ decl_module! { /// - The `score` that they claim their solution has. /// /// Both validators and nominators will be represented by indices in the solution. The - /// indices should respect the corresponding types ([`ValidatorIndex`] and - /// [`NominatorIndex`]). Moreover, they should be valid when used to index into + /// indices should respect the corresponding types ([`ValidatorIndexOf`] and + /// [`NominatorIndexOf`]). Moreover, they should be valid when used to index into /// [`SnapshotValidators`] and [`SnapshotNominators`]. Any invalid index will cause the /// solution to be rejected. These two storage items are set during the election window and /// may be used to determine the indices. @@ -2163,8 +2181,8 @@ decl_module! { )] pub fn submit_election_solution( origin, - winners: Vec, - compact: CompactAssignments, + winners: Vec>, + compact: T::CompactSolution, score: ElectionScore, era: EraIndex, size: ElectionSize, @@ -2197,8 +2215,8 @@ decl_module! { )] pub fn submit_election_solution_unsigned( origin, - winners: Vec, - compact: CompactAssignments, + winners: Vec>, + compact: T::CompactSolution, score: ElectionScore, era: EraIndex, size: ElectionSize, @@ -2296,21 +2314,23 @@ impl Module { let validators = >::iter().map(|(v, _)| v).collect::>(); let mut nominators = >::iter().map(|(n, _)| n).collect::>(); + let max_validators: usize = >::max_value().saturated_into(); + let max_nominators: usize = >::max_value().saturated_into(); + let num_validators = validators.len(); let num_nominators = nominators.len(); add_db_reads_writes((num_validators + num_nominators) as Weight, 0); - if - num_validators > MAX_VALIDATORS || - num_nominators.saturating_add(num_validators) > MAX_NOMINATORS + if num_validators > max_validators + || num_nominators.saturating_add(num_validators) > max_nominators { log!( warn, "💸 Snapshot size too big [{} <> {}][{} <> {}].", num_validators, - MAX_VALIDATORS, + max_validators, num_nominators, - MAX_NOMINATORS, + max_nominators, ); (false, consumed_weight) } else { @@ -2548,8 +2568,8 @@ impl Module { /// Checks a given solution and if correct and improved, writes it on chain as the queued result /// of the next round. This may be called by both a signed and an unsigned transaction. pub fn check_and_replace_solution( - winners: Vec, - compact_assignments: CompactAssignments, + winners: Vec>, + compact_assignments: T::CompactSolution, compute: ElectionCompute, claimed_score: ElectionScore, era: EraIndex, @@ -2605,7 +2625,7 @@ impl Module { // NOTE: at the moment, since staking is explicitly blocking any offence until election // is closed, we don't check here if the account id at `snapshot_validators[widx]` is // actually a validator. If this ever changes, this loop needs to also check this. - snapshot_validators.get(widx as usize).cloned().ok_or(Error::::OffchainElectionBogusWinner) + snapshot_validators.get(widx.saturated_into()).cloned().ok_or(Error::::OffchainElectionBogusWinner) }).collect::, Error>>()?; // decode the rest of the snapshot. @@ -2613,11 +2633,11 @@ impl Module { .ok_or(Error::::SnapshotUnavailable)?; // helpers - let nominator_at = |i: NominatorIndex| -> Option { - snapshot_nominators.get(i as usize).cloned() + let nominator_at = |i: NominatorIndexOf| -> Option { + snapshot_nominators.get(i.saturated_into()).cloned() }; - let validator_at = |i: ValidatorIndex| -> Option { - snapshot_validators.get(i as usize).cloned() + let validator_at = |i: ValidatorIndexOf| -> Option { + snapshot_validators.get(i.saturated_into()).cloned() }; // un-compact. @@ -2677,7 +2697,7 @@ impl Module { // defensive only. A compact assignment of length one does NOT encode the weight and // it is always created to be 100%. ensure!( - distribution[0].1 == OffchainAccuracy::one(), + distribution[0].1 == >::one(), Error::::OffchainElectionBogusSelfVote, ); } diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 0eb77e7c14ac1..b4c80b3e436f5 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -38,6 +38,8 @@ use sp_runtime::{ use sp_staking::offence::{OffenceDetails, OnOffenceHandler}; use std::{cell::RefCell, collections::HashSet}; +pub type TestSolution = crate::default_solution::CompactSolution24; + pub const INIT_TIMESTAMP: u64 = 30_000; pub const BLOCK_TIME: u64 = 1000; @@ -261,6 +263,7 @@ impl Config for Test { type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; type UnsignedPriority = UnsignedPriority; type OffchainSolutionWeightLimit = OffchainSolutionWeightLimit; + type CompactSolution = TestSolution; type WeightInfo = (); } @@ -772,7 +775,7 @@ pub(crate) fn add_slash(who: &AccountId) { // distributed evenly. pub(crate) fn horrible_npos_solution( do_reduce: bool, -) -> (CompactAssignments, Vec, ElectionScore) { +) -> (TestSolution, Vec>, ElectionScore) { let mut backing_stake_of: BTreeMap = BTreeMap::new(); // self stake @@ -859,19 +862,19 @@ pub(crate) fn horrible_npos_solution( let snapshot_validators = Staking::snapshot_validators().unwrap(); let snapshot_nominators = Staking::snapshot_nominators().unwrap(); - let nominator_index = |a: &AccountId| -> Option { - snapshot_nominators.iter().position(|x| x == a).map(|i| i as NominatorIndex) + let nominator_index = |a: &AccountId| -> Option> { + snapshot_nominators.iter().position(|x| x == a).map(|i| i as NominatorIndexOf) }; - let validator_index = |a: &AccountId| -> Option { - snapshot_validators.iter().position(|x| x == a).map(|i| i as ValidatorIndex) + let validator_index = |a: &AccountId| -> Option> { + snapshot_validators.iter().position(|x| x == a).map(|i| i as ValidatorIndexOf) }; // convert back to ratio assignment. This takes less space. let assignments_reduced = - sp_npos_elections::assignment_staked_to_ratio::(staked_assignment); + sp_npos_elections::assignment_staked_to_ratio::>(staked_assignment); let compact = - CompactAssignments::from_assignment(assignments_reduced, nominator_index, validator_index) + TestSolution::from_assignment(assignments_reduced, nominator_index, validator_index) .unwrap(); // winner ids to index @@ -890,12 +893,12 @@ pub(crate) fn prepare_submission_with( do_reduce: bool, iterations: usize, tweak: impl FnOnce(&mut Vec>), -) -> (CompactAssignments, Vec, ElectionScore) { +) -> (TestSolution, Vec>, ElectionScore) { // run election on the default stuff. let sp_npos_elections::ElectionResult { winners, assignments, - } = Staking::do_phragmen::(iterations).unwrap(); + } = Staking::do_phragmen::>(iterations).unwrap(); let winners = sp_npos_elections::to_without_backing(winners); let mut staked = sp_npos_elections::assignment_ratio_to_staked( @@ -913,22 +916,22 @@ pub(crate) fn prepare_submission_with( // convert back to ratio assignment. This takes less space. let snapshot_validators = Staking::snapshot_validators().expect("snapshot not created."); let snapshot_nominators = Staking::snapshot_nominators().expect("snapshot not created."); - let nominator_index = |a: &AccountId| -> Option { + let nominator_index = |a: &AccountId| -> Option> { snapshot_nominators .iter() .position(|x| x == a) .map_or_else( || { println!("unable to find nominator index for {:?}", a); None }, - |i| Some(i as NominatorIndex), + |i| Some(i as NominatorIndexOf), ) }; - let validator_index = |a: &AccountId| -> Option { + let validator_index = |a: &AccountId| -> Option> { snapshot_validators .iter() .position(|x| x == a) .map_or_else( || { println!("unable to find validator index for {:?}", a); None }, - |i| Some(i as ValidatorIndex), + |i| Some(i as ValidatorIndexOf), ) }; @@ -951,7 +954,7 @@ pub(crate) fn prepare_submission_with( }; let compact = - CompactAssignments::from_assignment(assignments_reduced, nominator_index, validator_index) + TestSolution::from_assignment(assignments_reduced, nominator_index, validator_index) .expect("Failed to create compact"); // winner ids to index diff --git a/frame/staking/src/offchain_election.rs b/frame/staking/src/offchain_election.rs index 4f80d75086e7e..326238e801b5d 100644 --- a/frame/staking/src/offchain_election.rs +++ b/frame/staking/src/offchain_election.rs @@ -17,10 +17,7 @@ //! Helpers for offchain worker election. -use crate::{ - Call, CompactAssignments, ElectionSize, Module, NominatorIndex, Nominators, OffchainAccuracy, - Config, ValidatorIndex, WeightInfo, -}; +use crate::*; use codec::Decode; use frame_support::{traits::Get, weights::Weight, IterableStorageMap}; use frame_system::offchain::SubmitTransaction; @@ -110,11 +107,11 @@ pub(crate) fn set_check_offchain_execution_status( /// unsigned transaction, without any signature. pub(crate) fn compute_offchain_election() -> Result<(), OffchainElectionError> { let iters = get_balancing_iters::(); - // compute raw solution. Note that we use `OffchainAccuracy`. + // compute raw solution. Note that we use `OffchainAccuracyOf`. let ElectionResult { winners, assignments, - } = >::do_phragmen::(iters) + } = >::do_phragmen::>(iters) .ok_or(OffchainElectionError::ElectionFailed)?; // process and prepare it for submission. @@ -151,7 +148,7 @@ pub(crate) fn compute_offchain_election() -> Result<(), OffchainElect /// Get a random number of iterations to run the balancing. /// /// Uses the offchain seed to generate a random number. -pub fn get_balancing_iters() -> usize { +pub fn get_balancing_iters() -> usize { match T::MaxIterations::get() { 0 => 0, max @ _ => { @@ -182,12 +179,7 @@ pub fn maximum_compact_len( // helper closures. let weight_with = |voters: u32| -> Weight { - W::submit_solution_better( - size.validators.into(), - size.nominators.into(), - voters, - winners_len, - ) + W::submit_solution_better(size.validators, size.nominators, voters, winners_len) }; let next_voters = |current_weight: Weight, voters: u32, step: u32| -> Result { @@ -246,8 +238,8 @@ pub fn maximum_compact_len( /// /// The weight of the solution is foremost a function of the number of voters (i.e. /// `compact.len()`). Aside from this, the other components of the weight are invariant. The number -/// of winners shall not be changed (otherwise the solution is invalid) and the `ElectionSize` is -/// merely a representation of the total number of stakers. +/// of winners shall not be changed (otherwise the solution is invalid) and the `ElectionSize` +/// is merely a representation of the total number of stakers. /// /// Thus, we reside to stripping away some voters. This means only changing the `compact` struct. /// @@ -259,11 +251,11 @@ pub fn maximum_compact_len( /// then the solution will be discarded. pub fn trim_to_weight( maximum_allowed_voters: u32, - mut compact: CompactAssignments, + mut compact: T::CompactSolution, nominator_index: FN, -) -> Result +) -> Result where - for<'r> FN: Fn(&'r T::AccountId) -> Option, + for<'r> FN: Fn(&'r T::AccountId) -> Option>, { match compact.voter_count().checked_sub(maximum_allowed_voters as usize) { Some(to_remove) if to_remove > 0 => { @@ -284,7 +276,7 @@ where if compact.remove_voter(index) { crate::log!( trace, - "💸 removed a voter at index {} with stake {:?} from compact to reduce the size", + "💸 removed a voter at index {:?} with stake {:?} from compact to reduce the size", index, _stake, ); @@ -319,12 +311,12 @@ where /// /// This does a lot of stuff; read the inline comments. pub fn prepare_submission( - assignments: Vec>, + assignments: Vec>>, winners: Vec<(T::AccountId, ExtendedBalance)>, do_reduce: bool, maximum_weight: Weight, ) -> Result< - (Vec, CompactAssignments, ElectionScore, ElectionSize), + (Vec>, T::CompactSolution, ElectionScore, ElectionSize), OffchainElectionError, > { // make sure that the snapshot is available. @@ -334,30 +326,30 @@ pub fn prepare_submission( >::snapshot_nominators().ok_or(OffchainElectionError::SnapshotUnavailable)?; // all helper closures that we'd ever need. - let nominator_index = |a: &T::AccountId| -> Option { + let nominator_index = |a: &T::AccountId| -> Option> { snapshot_nominators .iter() .position(|x| x == a) - .and_then(|i| >::try_into(i).ok()) + .and_then(|i| >>::try_into(i).ok()) }; - let validator_index = |a: &T::AccountId| -> Option { + let validator_index = |a: &T::AccountId| -> Option> { snapshot_validators .iter() .position(|x| x == a) - .and_then(|i| >::try_into(i).ok()) + .and_then(|i| >>::try_into(i).ok()) }; - let nominator_at = |i: NominatorIndex| -> Option { - snapshot_nominators.get(i as usize).cloned() + let nominator_at = |i: NominatorIndexOf| -> Option { + snapshot_nominators.get(i.saturated_into()).cloned() }; - let validator_at = |i: ValidatorIndex| -> Option { - snapshot_validators.get(i as usize).cloned() + let validator_at = |i: ValidatorIndexOf| -> Option { + snapshot_validators.get(i.saturated_into()).cloned() }; // both conversions are safe; snapshots are not created if they exceed. let size = ElectionSize { - validators: snapshot_validators.len() as ValidatorIndex, - nominators: snapshot_nominators.len() as NominatorIndex, + validators: snapshot_validators.len() as u32, + nominators: snapshot_nominators.len() as u32, }; // Clean winners. @@ -379,7 +371,7 @@ pub fn prepare_submission( .map_err(|e| OffchainElectionError::from(e))?; // compact encode the assignment. - let compact = CompactAssignments::from_assignment( + let compact = T::CompactSolution::from_assignment( low_accuracy_assignment, nominator_index, validator_index, @@ -421,12 +413,11 @@ pub fn prepare_submission( }; // winners to index. Use a simple for loop for a more expressive early exit in case of error. - let mut winners_indexed: Vec = Vec::with_capacity(winners.len()); + let mut winners_indexed: Vec> = Vec::with_capacity(winners.len()); for w in winners { if let Some(idx) = snapshot_validators.iter().position(|v| *v == w) { - let compact_index: ValidatorIndex = idx - .try_into() - .map_err(|_| OffchainElectionError::InvalidWinner)?; + let compact_index: ValidatorIndexOf = + idx.try_into().map_err(|_| OffchainElectionError::InvalidWinner)?; winners_indexed.push(compact_index); } else { return Err(OffchainElectionError::InvalidWinner); @@ -524,10 +515,7 @@ mod test { #[test] fn find_max_voter_binary_search_works() { - let size = ElectionSize { - validators: 0, - nominators: 10, - }; + let size = ElectionSize { validators: 0u32, nominators: 10u32 }; assert_eq!(maximum_compact_len::(0, size, 0), 0); assert_eq!(maximum_compact_len::(0, size, 1), 0); @@ -551,10 +539,7 @@ mod test { assert_eq!(maximum_compact_len::(0, size, 11_000), 10); assert_eq!(maximum_compact_len::(0, size, 22_000), 10); - let size = ElectionSize { - validators: 0, - nominators: 1, - }; + let size = ElectionSize { validators: 0u32, nominators: 1u32 }; assert_eq!(maximum_compact_len::(0, size, 0), 0); assert_eq!(maximum_compact_len::(0, size, 1), 0); @@ -568,10 +553,7 @@ mod test { assert_eq!(maximum_compact_len::(0, size, 2010), 1); assert_eq!(maximum_compact_len::(0, size, 3333), 1); - let size = ElectionSize { - validators: 0, - nominators: 2, - }; + let size = ElectionSize { validators: 0u32, nominators: 2u32 }; assert_eq!(maximum_compact_len::(0, size, 0), 0); assert_eq!(maximum_compact_len::(0, size, 1), 0); diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs index a30c0136550b4..8d229eae87644 100644 --- a/frame/staking/src/testing_utils.rs +++ b/frame/staking/src/testing_utils.rs @@ -108,19 +108,20 @@ pub fn create_validators( /// we are working with a clean state. /// /// Parameters: -/// - `validators`: number of bonded validators +/// - `validators`: number of bonded validators. /// - `nominators`: number of bonded nominators. -/// - `edge_per_nominator`: number of edge (vote) per nominator. +/// - `edges_per_nominator`: number of edges (votes) per nominator. /// - `randomize_stake`: whether to randomize the stakes. /// - `to_nominate`: if `Some(n)`, only the first `n` bonded validator are voted upon. -/// Else, all of them are considered and `edge_per_nominator` random validators are voted for. +/// Else, all of them are considered and `edges_per_nominator` random validators are voted for. /// -/// Return the validators choosen to be nominated. +/// Return the validators chosen to be nominated. pub fn create_validators_with_nominators_for_era( validators: u32, nominators: u32, - edge_per_nominator: usize, + edges_per_nominator: usize, randomize_stake: bool, + balance_factor: u32, to_nominate: Option, ) -> Result::Source>, &'static str> { clear_validators_and_nominators::(); @@ -131,7 +132,7 @@ pub fn create_validators_with_nominators_for_era( // Create validators for i in 0 .. validators { - let balance_factor = if randomize_stake { rng.next_u32() % 255 + 10 } else { 100u32 }; + let balance_factor = if randomize_stake { rng.next_u32() % 255 + balance_factor } else { balance_factor }; let (v_stash, v_controller) = create_stash_controller::(i, balance_factor, RewardDestination::Staked)?; let validator_prefs = ValidatorPrefs { commission: Perbill::from_percent(50), @@ -147,7 +148,7 @@ pub fn create_validators_with_nominators_for_era( // Create nominators for j in 0 .. nominators { - let balance_factor = if randomize_stake { rng.next_u32() % 255 + 10 } else { 100u32 }; + let balance_factor = if randomize_stake { rng.next_u32() % 255 + balance_factor } else { balance_factor }; let (_n_stash, n_controller) = create_stash_controller::( u32::max_value() - j, balance_factor, @@ -157,9 +158,9 @@ pub fn create_validators_with_nominators_for_era( // Have them randomly validate let mut available_validators = validator_choosen.clone(); let mut selected_validators: Vec<::Source> = - Vec::with_capacity(edge_per_nominator); + Vec::with_capacity(edges_per_nominator); - for _ in 0 .. validators.min(edge_per_nominator as u32) { + for _ in 0 .. validators.min(edges_per_nominator as u32) { let selected = rng.next_u32() as usize % available_validators.len(); let validator = available_validators.remove(selected); selected_validators.push(validator); @@ -177,7 +178,7 @@ pub fn create_validators_with_nominators_for_era( /// which has a less score than the seq-phragmen. pub fn get_weak_solution( do_reduce: bool, -) -> (Vec, CompactAssignments, ElectionScore, ElectionSize) { +) -> (Vec>, T::CompactSolution, ElectionScore, ElectionSize) { let mut backing_stake_of: BTreeMap> = BTreeMap::new(); // self stake @@ -222,17 +223,17 @@ pub fn get_weak_solution( let snapshot_validators = >::snapshot_validators().unwrap(); let snapshot_nominators = >::snapshot_nominators().unwrap(); - let nominator_index = |a: &T::AccountId| -> Option { + let nominator_index = |a: &T::AccountId| -> Option> { snapshot_nominators .iter() .position(|x| x == a) - .and_then(|i| >::try_into(i).ok()) + .and_then(|i| >>::try_into(i).ok()) }; - let validator_index = |a: &T::AccountId| -> Option { + let validator_index = |a: &T::AccountId| -> Option> { snapshot_validators .iter() .position(|x| x == a) - .and_then(|i| >::try_into(i).ok()) + .and_then(|i| >>::try_into(i).ok()) }; // convert back to ratio assignment. This takes less space. @@ -241,7 +242,7 @@ pub fn get_weak_solution( // re-calculate score based on what the chain will decode. let score = { - let staked = assignment_ratio_to_staked::<_, OffchainAccuracy, _>( + let staked = assignment_ratio_to_staked::<_, OffchainAccuracyOf, _>( low_accuracy_assignment.clone(), >::slashable_balance_of_fn(), ); @@ -252,7 +253,7 @@ pub fn get_weak_solution( }; // compact encode the assignment. - let compact = CompactAssignments::from_assignment( + let compact = T::CompactSolution::from_assignment( low_accuracy_assignment, nominator_index, validator_index, @@ -268,13 +269,13 @@ pub fn get_weak_solution( .position(|v| *v == w) .unwrap() .try_into() - .unwrap() + .unwrap_or_else(|_| panic!()) }) - .collect::>(); + .collect::>>(); let size = ElectionSize { - validators: snapshot_validators.len() as ValidatorIndex, - nominators: snapshot_nominators.len() as NominatorIndex, + validators: snapshot_validators.len() as u32, + nominators: snapshot_nominators.len() as u32, }; (winners, compact, score, size) @@ -285,8 +286,8 @@ pub fn get_weak_solution( pub fn get_seq_phragmen_solution( do_reduce: bool, ) -> ( - Vec, - CompactAssignments, + Vec>, + T::CompactSolution, ElectionScore, ElectionSize, ) { @@ -295,7 +296,7 @@ pub fn get_seq_phragmen_solution( let sp_npos_elections::ElectionResult { winners, assignments, - } = >::do_phragmen::(iters).unwrap(); + } = >::do_phragmen::>(iters).unwrap(); offchain_election::prepare_submission::( assignments, @@ -311,8 +312,8 @@ pub fn get_single_winner_solution( winner: T::AccountId, ) -> Result< ( - Vec, - CompactAssignments, + Vec>, + T::CompactSolution, ElectionScore, ElectionSize, ), @@ -334,18 +335,19 @@ pub fn get_single_winner_solution( let stake = ::to_vote(stake, T::Currency::total_issuance()) as ExtendedBalance; - let val_index = val_index as ValidatorIndex; - let nom_index = nom_index as NominatorIndex; + let val_index: ValidatorIndexOf = val_index.try_into() + .unwrap_or_else(|_| { panic!("Failed to convert") }); + let nom_index: NominatorIndexOf = nom_index.try_into() + .unwrap_or_else(|_| { panic!("Failed to convert") }); + + let winners: Vec> = vec![val_index]; + let mut compact: T::CompactSolution = Default::default(); + compact.add_edge(nom_index, val_index); - let winners = vec![val_index]; - let compact = CompactAssignments { - votes1: vec![(nom_index, val_index)], - ..Default::default() - }; let score = [stake, stake, stake * stake]; let size = ElectionSize { - validators: snapshot_validators.len() as ValidatorIndex, - nominators: snapshot_nominators.len() as NominatorIndex, + validators: snapshot_validators.len() as u32, + nominators: snapshot_nominators.len() as u32, }; Ok((winners, compact, score, size)) @@ -365,19 +367,22 @@ pub fn init_active_era() { } /// Create random assignments for the given list of winners. Each assignment will have -/// MAX_NOMINATIONS edges. +/// T::CompactSolution::LIMIT edges. pub fn create_assignments_for_offchain( num_assignments: u32, winners: Vec<::Source>, ) -> Result< ( Vec<(T::AccountId, ExtendedBalance)>, - Vec>, + Vec>>, ), &'static str > { - let ratio = OffchainAccuracy::from_rational_approximation(1, MAX_NOMINATIONS); - let assignments: Vec> = >::iter() + let ratio = >::from_rational_approximation( + 1, + T::CompactSolution::LIMIT.try_into().unwrap(), + ); + let assignments: Vec>> = >::iter() .take(num_assignments as usize) .map(|(n, t)| Assignment { who: n, diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 1f5e2a48888a5..1a0f826a2c61a 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -2925,8 +2925,8 @@ mod offchain_election { use std::sync::Arc; use substrate_test_utils::assert_eq_uvec; - fn percent(x: u16) -> OffchainAccuracy { - OffchainAccuracy::from_percent(x) + fn percent(x: u16) -> OffchainAccuracyOf { + >::from_percent(x) } /// setup a new set of validators and nominator storage items independent of the parent mock @@ -2968,15 +2968,15 @@ mod offchain_election { fn election_size() -> ElectionSize { ElectionSize { - validators: Staking::snapshot_validators().unwrap().len() as ValidatorIndex, - nominators: Staking::snapshot_nominators().unwrap().len() as NominatorIndex, + validators: Staking::snapshot_validators().unwrap().len() as u32, + nominators: Staking::snapshot_nominators().unwrap().len() as u32, } } fn submit_solution( origin: Origin, - winners: Vec, - compact: CompactAssignments, + winners: Vec>, + compact: TestSolution, score: ElectionScore, ) -> DispatchResultWithPostInfo { Staking::submit_election_solution( @@ -3194,31 +3194,6 @@ mod offchain_election { }) } - #[test] - #[ignore] - fn offchain_wont_work_if_snapshot_fails() { - ExtBuilder::default() - .offchain_election_ext() - .build() - .execute_with(|| { - run_to_block(12); - assert!(Staking::snapshot_validators().is_some()); - assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12)); - - // validate more than the limit - let limit: NominatorIndex = ValidatorIndex::max_value() as NominatorIndex + 1; - let ctrl = 1_000_000; - for i in 0..limit { - bond_validator((1000 + i).into(), (1000 + i + ctrl).into(), 100); - } - - // window stays closed since no snapshot was taken. - run_to_block(27); - assert!(Staking::snapshot_validators().is_none()); - assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); - }) - } - #[test] fn staking_is_locked_when_election_window_open() { ExtBuilder::default() diff --git a/primitives/npos-elections/compact/src/lib.rs b/primitives/npos-elections/compact/src/lib.rs index 191998a341924..e930439a65d53 100644 --- a/primitives/npos-elections/compact/src/lib.rs +++ b/primitives/npos-elections/compact/src/lib.rs @@ -170,6 +170,8 @@ fn struct_def( let from_impl = assignment::from_impl(count); let into_impl = assignment::into_impl(count, weight_type.clone()); + let single_field_name = field_name_for(1); + Ok(quote! ( /// A struct to encode a election assignment in a compact way. #derives_and_maybe_compact_encoding @@ -248,6 +250,10 @@ fn struct_def( #into_impl Ok(assignments) } + + fn add_edge(&mut self, voter: Self::Voter, target: Self::Target) { + self.#single_field_name.push((voter, target)); + } } )) } diff --git a/primitives/npos-elections/src/lib.rs b/primitives/npos-elections/src/lib.rs index d45698e1747bb..f2a663471b1c0 100644 --- a/primitives/npos-elections/src/lib.rs +++ b/primitives/npos-elections/src/lib.rs @@ -89,7 +89,7 @@ use sp_std::{ }; use sp_core::RuntimeDebug; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, HasCompact}; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; @@ -139,10 +139,32 @@ pub trait CompactSolution: Sized { const LIMIT: usize; /// The voter type. Needs to be an index (convert to usize). - type Voter: UniqueSaturatedInto + TryInto + TryFrom + Debug + Copy + Clone; + type Voter: UniqueSaturatedInto + + TryInto + + TryFrom + + Debug + + Bounded + + Copy + + Clone + + PartialEq + + Eq + + HasCompact + + Encode + + Decode; /// The target type. Needs to be an index (convert to usize). - type Target: UniqueSaturatedInto + TryInto + TryFrom + Debug + Copy + Clone; + type Target: UniqueSaturatedInto + + TryInto + + TryFrom + + Debug + + Bounded + + Copy + + Clone + + PartialEq + + Eq + + HasCompact + + Encode + + Decode; /// The weight/accuracy type of each vote. type Accuracy: PerThing128; @@ -215,6 +237,9 @@ pub trait CompactSolution: Sized { let supports = to_supports(winners, &staked)?; Ok(supports.evaluate()) } + + /// Add a single edge 1-to-1 edge. + fn add_edge(&mut self, _voter: Self::Voter, _target: Self::Target); } // re-export the compact solution type.