mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 02:21:04 +00:00
Use proper bounded vector type for nominations (#10601)
* Use proper bounded vector type for nominations * add docs and tweak chill_other for cleanup purposes * Fix the build * remove TODO * add a bit more doc * even more docs gushc * Update frame/staking/src/pallet/mod.rs Co-authored-by: Zeke Mostov <z.mostov@gmail.com> * Update frame/staking/src/pallet/mod.rs Co-authored-by: Zeke Mostov <z.mostov@gmail.com> * Fix the nasty bug * also bound the Snapshot type * fix doc test * document bounded_vec * self-review * remove unused * Fix build * frame-support: repetition overload for bounded_vec Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * fix * remove the need to allocate into unbounded voters etc etc * Don't expect * unbreal the build again * handle macro a bit better Co-authored-by: Zeke Mostov <z.mostov@gmail.com> Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
This commit is contained in:
@@ -20,7 +20,11 @@
|
||||
use super::*;
|
||||
use crate::{unsigned::IndexAssignmentOf, Pallet as MultiPhase};
|
||||
use frame_benchmarking::account;
|
||||
use frame_support::{assert_ok, traits::Hooks};
|
||||
use frame_support::{
|
||||
assert_ok,
|
||||
traits::{Hooks, TryCollect},
|
||||
BoundedVec,
|
||||
};
|
||||
use frame_system::RawOrigin;
|
||||
use rand::{prelude::SliceRandom, rngs::SmallRng, SeedableRng};
|
||||
use sp_arithmetic::{per_things::Percent, traits::One};
|
||||
@@ -69,11 +73,12 @@ fn solution_with_size<T: Config>(
|
||||
let active_voters = (0..active_voters_count)
|
||||
.map(|i| {
|
||||
// chose a random subset of winners.
|
||||
let winner_votes = winners
|
||||
let winner_votes: BoundedVec<_, _> = winners
|
||||
.as_slice()
|
||||
.choose_multiple(&mut rng, <SolutionOf<T>>::LIMIT)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
.try_collect()
|
||||
.expect("<SolutionOf<T>>::LIMIT is the correct bound; qed.");
|
||||
let voter = frame_benchmarking::account::<T::AccountId>("Voter", i, SEED);
|
||||
(voter, stake, winner_votes)
|
||||
})
|
||||
@@ -87,10 +92,11 @@ fn solution_with_size<T: Config>(
|
||||
.collect::<Vec<T::AccountId>>();
|
||||
let rest_voters = (active_voters_count..size.voters)
|
||||
.map(|i| {
|
||||
let votes = (&non_winners)
|
||||
let votes: BoundedVec<_, _> = (&non_winners)
|
||||
.choose_multiple(&mut rng, <SolutionOf<T>>::LIMIT)
|
||||
.cloned()
|
||||
.collect::<Vec<T::AccountId>>();
|
||||
.try_collect()
|
||||
.expect("<SolutionOf<T>>::LIMIT is the correct bound; qed.");
|
||||
let voter = frame_benchmarking::account::<T::AccountId>("Voter", i, SEED);
|
||||
(voter, stake, votes)
|
||||
})
|
||||
@@ -152,7 +158,7 @@ fn set_up_data_provider<T: Config>(v: u32, t: u32) {
|
||||
info,
|
||||
"setting up with voters = {} [degree = {}], targets = {}",
|
||||
v,
|
||||
T::DataProvider::MAXIMUM_VOTES_PER_VOTER,
|
||||
<T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get(),
|
||||
t
|
||||
);
|
||||
|
||||
@@ -165,14 +171,16 @@ fn set_up_data_provider<T: Config>(v: u32, t: u32) {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// we should always have enough voters to fill.
|
||||
assert!(targets.len() > T::DataProvider::MAXIMUM_VOTES_PER_VOTER as usize);
|
||||
targets.truncate(T::DataProvider::MAXIMUM_VOTES_PER_VOTER as usize);
|
||||
assert!(
|
||||
targets.len() > <T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get() as usize
|
||||
);
|
||||
targets.truncate(<T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get() as usize);
|
||||
|
||||
// fill voters.
|
||||
(0..v).for_each(|i| {
|
||||
let voter = frame_benchmarking::account::<T::AccountId>("Voter", i, SEED);
|
||||
let weight = T::Currency::minimum_balance().saturated_into::<u64>() * 1000;
|
||||
T::DataProvider::add_voter(voter, weight, targets.clone());
|
||||
T::DataProvider::add_voter(voter, weight, targets.clone().try_into().unwrap());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
//! Some helper functions/macros for this crate.
|
||||
|
||||
use super::{Config, SolutionTargetIndexOf, SolutionVoterIndexOf, VoteWeight};
|
||||
use crate::{unsigned::VoterOf, Config, SolutionTargetIndexOf, SolutionVoterIndexOf, VoteWeight};
|
||||
use sp_std::{collections::btree_map::BTreeMap, prelude::*};
|
||||
|
||||
#[macro_export]
|
||||
@@ -34,7 +34,7 @@ macro_rules! log {
|
||||
///
|
||||
/// This can be used to efficiently build index getter closures.
|
||||
pub fn generate_voter_cache<T: Config>(
|
||||
snapshot: &Vec<(T::AccountId, VoteWeight, Vec<T::AccountId>)>,
|
||||
snapshot: &Vec<VoterOf<T>>,
|
||||
) -> BTreeMap<T::AccountId, usize> {
|
||||
let mut cache: BTreeMap<T::AccountId, usize> = BTreeMap::new();
|
||||
snapshot.iter().enumerate().for_each(|(i, (x, _, _))| {
|
||||
@@ -97,7 +97,7 @@ pub fn voter_index_fn_usize<T: Config>(
|
||||
/// Not meant to be used in production.
|
||||
#[cfg(test)]
|
||||
pub fn voter_index_fn_linear<T: Config>(
|
||||
snapshot: &Vec<(T::AccountId, VoteWeight, Vec<T::AccountId>)>,
|
||||
snapshot: &Vec<VoterOf<T>>,
|
||||
) -> impl Fn(&T::AccountId) -> Option<SolutionVoterIndexOf<T>> + '_ {
|
||||
move |who| {
|
||||
snapshot
|
||||
@@ -148,7 +148,7 @@ pub fn target_index_fn_linear<T: Config>(
|
||||
/// Create a function that can map a voter index ([`SolutionVoterIndexOf`]) to the actual voter
|
||||
/// account using a linearly indexible snapshot.
|
||||
pub fn voter_at_fn<T: Config>(
|
||||
snapshot: &Vec<(T::AccountId, VoteWeight, Vec<T::AccountId>)>,
|
||||
snapshot: &Vec<VoterOf<T>>,
|
||||
) -> impl Fn(SolutionVoterIndexOf<T>) -> Option<T::AccountId> + '_ {
|
||||
move |i| {
|
||||
<SolutionVoterIndexOf<T> as TryInto<usize>>::try_into(i)
|
||||
@@ -174,7 +174,7 @@ pub fn target_at_fn<T: Config>(
|
||||
/// This is not optimized and uses a linear search.
|
||||
#[cfg(test)]
|
||||
pub fn stake_of_fn_linear<T: Config>(
|
||||
snapshot: &Vec<(T::AccountId, VoteWeight, Vec<T::AccountId>)>,
|
||||
snapshot: &Vec<VoterOf<T>>,
|
||||
) -> impl Fn(&T::AccountId) -> VoteWeight + '_ {
|
||||
move |who| {
|
||||
snapshot
|
||||
@@ -192,7 +192,7 @@ pub fn stake_of_fn_linear<T: Config>(
|
||||
/// The cache need must be derived from the same snapshot. Zero is returned if a voter is
|
||||
/// non-existent.
|
||||
pub fn stake_of_fn<'a, T: Config>(
|
||||
snapshot: &'a Vec<(T::AccountId, VoteWeight, Vec<T::AccountId>)>,
|
||||
snapshot: &'a Vec<VoterOf<T>>,
|
||||
cache: &'a BTreeMap<T::AccountId, usize>,
|
||||
) -> impl Fn(&T::AccountId) -> VoteWeight + 'a {
|
||||
move |who| {
|
||||
|
||||
@@ -269,6 +269,7 @@ const LOG_TARGET: &'static str = "runtime::election-provider";
|
||||
pub mod signed;
|
||||
pub mod unsigned;
|
||||
pub mod weights;
|
||||
use unsigned::VoterOf;
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
pub use signed::{
|
||||
@@ -448,11 +449,13 @@ pub struct ReadySolution<A> {
|
||||
///
|
||||
/// These are stored together because they are often accessed together.
|
||||
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo)]
|
||||
pub struct RoundSnapshot<A> {
|
||||
#[codec(mel_bound(T: Config))]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct RoundSnapshot<T: Config> {
|
||||
/// All of the voters.
|
||||
pub voters: Vec<(A, VoteWeight, Vec<A>)>,
|
||||
pub voters: Vec<VoterOf<T>>,
|
||||
/// All of the targets.
|
||||
pub targets: Vec<A>,
|
||||
pub targets: Vec<T::AccountId>,
|
||||
}
|
||||
|
||||
/// Encodes the length of a solution or a snapshot.
|
||||
@@ -820,7 +823,7 @@ pub mod pallet {
|
||||
// NOTE that this pallet does not really need to enforce this in runtime. The
|
||||
// solution cannot represent any voters more than `LIMIT` anyhow.
|
||||
assert_eq!(
|
||||
<T::DataProvider as ElectionDataProvider>::MAXIMUM_VOTES_PER_VOTER,
|
||||
<T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get(),
|
||||
<SolutionOf<T> as NposSolution>::LIMIT as u32,
|
||||
);
|
||||
}
|
||||
@@ -1140,7 +1143,7 @@ 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>>;
|
||||
pub type Snapshot<T: Config> = StorageValue<_, RoundSnapshot<T>>;
|
||||
|
||||
/// Desired number of targets to elect for this round.
|
||||
///
|
||||
@@ -1257,7 +1260,7 @@ impl<T: Config> Pallet<T> {
|
||||
/// Extracted for easier weight calculation.
|
||||
fn create_snapshot_internal(
|
||||
targets: Vec<T::AccountId>,
|
||||
voters: Vec<crate::unsigned::Voter<T>>,
|
||||
voters: Vec<VoterOf<T>>,
|
||||
desired_targets: u32,
|
||||
) {
|
||||
let metadata =
|
||||
@@ -1270,7 +1273,7 @@ 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 { voters, targets };
|
||||
let snapshot = RoundSnapshot::<T> { voters, targets };
|
||||
let size = snapshot.encoded_size();
|
||||
log!(debug, "snapshot pre-calculated size {:?}", size);
|
||||
let mut buffer = Vec::with_capacity(size);
|
||||
@@ -1288,7 +1291,7 @@ impl<T: Config> Pallet<T> {
|
||||
///
|
||||
/// Extracted for easier weight calculation.
|
||||
fn create_snapshot_external(
|
||||
) -> Result<(Vec<T::AccountId>, Vec<crate::unsigned::Voter<T>>, u32), ElectionError<T>> {
|
||||
) -> Result<(Vec<T::AccountId>, Vec<VoterOf<T>>, u32), ElectionError<T>> {
|
||||
let target_limit = <SolutionTargetIndexOf<T>>::max_value().saturated_into::<usize>();
|
||||
// for now we have just a single block snapshot.
|
||||
let voter_limit = T::VoterSnapshotPerBlock::get().saturated_into::<usize>();
|
||||
|
||||
@@ -22,11 +22,12 @@ use frame_election_provider_support::{
|
||||
};
|
||||
pub use frame_support::{assert_noop, assert_ok};
|
||||
use frame_support::{
|
||||
parameter_types,
|
||||
bounded_vec, parameter_types,
|
||||
traits::{ConstU32, Hooks},
|
||||
weights::Weight,
|
||||
BoundedVec,
|
||||
};
|
||||
use multi_phase::unsigned::{IndexAssignmentOf, Voter};
|
||||
use multi_phase::unsigned::{IndexAssignmentOf, VoterOf};
|
||||
use parking_lot::RwLock;
|
||||
use sp_core::{
|
||||
offchain::{
|
||||
@@ -100,7 +101,7 @@ pub fn roll_to_with_ocw(n: BlockNumber) {
|
||||
}
|
||||
|
||||
pub struct TrimHelpers {
|
||||
pub voters: Vec<Voter<Runtime>>,
|
||||
pub voters: Vec<VoterOf<Runtime>>,
|
||||
pub assignments: Vec<IndexAssignmentOf<Runtime>>,
|
||||
pub encoded_size_of:
|
||||
Box<dyn Fn(&[IndexAssignmentOf<Runtime>]) -> Result<usize, sp_npos_elections::Error>>,
|
||||
@@ -131,13 +132,8 @@ pub fn trim_helpers() -> TrimHelpers {
|
||||
|
||||
let desired_targets = MultiPhase::desired_targets().unwrap();
|
||||
|
||||
let ElectionResult { mut assignments, .. } = seq_phragmen::<_, SolutionAccuracyOf<Runtime>>(
|
||||
desired_targets as usize,
|
||||
targets.clone(),
|
||||
voters.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let ElectionResult::<_, SolutionAccuracyOf<Runtime>> { mut assignments, .. } =
|
||||
seq_phragmen(desired_targets as usize, targets.clone(), voters.clone(), None).unwrap();
|
||||
|
||||
// sort by decreasing order of stake
|
||||
assignments.sort_unstable_by_key(|assignment| {
|
||||
@@ -163,14 +159,8 @@ pub fn raw_solution() -> RawSolution<SolutionOf<Runtime>> {
|
||||
let RoundSnapshot { voters, targets } = MultiPhase::snapshot().unwrap();
|
||||
let desired_targets = MultiPhase::desired_targets().unwrap();
|
||||
|
||||
let ElectionResult { winners: _, assignments } =
|
||||
seq_phragmen::<_, SolutionAccuracyOf<Runtime>>(
|
||||
desired_targets as usize,
|
||||
targets.clone(),
|
||||
voters.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let ElectionResult::<_, SolutionAccuracyOf<Runtime>> { winners: _, assignments } =
|
||||
seq_phragmen(desired_targets as usize, targets.clone(), voters.clone(), None).unwrap();
|
||||
|
||||
// closures
|
||||
let cache = helpers::generate_voter_cache::<Runtime>(&voters);
|
||||
@@ -246,16 +236,16 @@ impl pallet_balances::Config for Runtime {
|
||||
|
||||
parameter_types! {
|
||||
pub static Targets: Vec<AccountId> = vec![10, 20, 30, 40];
|
||||
pub static Voters: Vec<(AccountId, VoteWeight, Vec<AccountId>)> = vec![
|
||||
(1, 10, vec![10, 20]),
|
||||
(2, 10, vec![30, 40]),
|
||||
(3, 10, vec![40]),
|
||||
(4, 10, vec![10, 20, 30, 40]),
|
||||
pub static Voters: Vec<VoterOf<Runtime>> = vec![
|
||||
(1, 10, bounded_vec![10, 20]),
|
||||
(2, 10, bounded_vec![30, 40]),
|
||||
(3, 10, bounded_vec![40]),
|
||||
(4, 10, bounded_vec![10, 20, 30, 40]),
|
||||
// self votes.
|
||||
(10, 10, vec![10]),
|
||||
(20, 20, vec![20]),
|
||||
(30, 30, vec![30]),
|
||||
(40, 40, vec![40]),
|
||||
(10, 10, bounded_vec![10]),
|
||||
(20, 20, bounded_vec![20]),
|
||||
(30, 30, bounded_vec![30]),
|
||||
(40, 40, bounded_vec![40]),
|
||||
];
|
||||
|
||||
pub static DesiredTargets: u32 = 2;
|
||||
@@ -436,6 +426,10 @@ where
|
||||
|
||||
pub type Extrinsic = sp_runtime::testing::TestXt<Call, ()>;
|
||||
|
||||
parameter_types! {
|
||||
pub MaxNominations: u32 = <TestNposSolution as NposSolution>::LIMIT as u32;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExtBuilder {}
|
||||
|
||||
@@ -443,7 +437,7 @@ pub struct StakingMock;
|
||||
impl ElectionDataProvider for StakingMock {
|
||||
type AccountId = AccountId;
|
||||
type BlockNumber = u64;
|
||||
const MAXIMUM_VOTES_PER_VOTER: u32 = <TestNposSolution as NposSolution>::LIMIT as u32;
|
||||
type MaxVotesPerVoter = MaxNominations;
|
||||
fn targets(maybe_max_len: Option<usize>) -> data_provider::Result<Vec<AccountId>> {
|
||||
let targets = Targets::get();
|
||||
|
||||
@@ -454,9 +448,7 @@ impl ElectionDataProvider for StakingMock {
|
||||
Ok(targets)
|
||||
}
|
||||
|
||||
fn voters(
|
||||
maybe_max_len: Option<usize>,
|
||||
) -> data_provider::Result<Vec<(AccountId, VoteWeight, Vec<AccountId>)>> {
|
||||
fn voters(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)
|
||||
@@ -475,7 +467,7 @@ impl ElectionDataProvider for StakingMock {
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn put_snapshot(
|
||||
voters: Vec<(AccountId, VoteWeight, Vec<AccountId>)>,
|
||||
voters: Vec<VoterOf<Runtime>>,
|
||||
targets: Vec<AccountId>,
|
||||
_target_stake: Option<VoteWeight>,
|
||||
) {
|
||||
@@ -490,7 +482,11 @@ impl ElectionDataProvider for StakingMock {
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn add_voter(voter: AccountId, weight: VoteWeight, targets: Vec<AccountId>) {
|
||||
fn add_voter(
|
||||
voter: AccountId,
|
||||
weight: VoteWeight,
|
||||
targets: frame_support::BoundedVec<AccountId, Self::MaxVotesPerVoter>,
|
||||
) {
|
||||
let mut current = Voters::get();
|
||||
current.push((voter, weight, targets));
|
||||
Voters::set(current);
|
||||
@@ -505,7 +501,7 @@ impl ElectionDataProvider for StakingMock {
|
||||
// to be on-par with staking, we add a self vote as well. the stake is really not that
|
||||
// important.
|
||||
let mut current = Voters::get();
|
||||
current.push((target, ExistentialDeposit::get() as u64, vec![target]));
|
||||
current.push((target, ExistentialDeposit::get() as u64, bounded_vec![target]));
|
||||
Voters::set(current);
|
||||
}
|
||||
}
|
||||
@@ -540,7 +536,12 @@ impl ExtBuilder {
|
||||
<DesiredTargets>::set(t);
|
||||
self
|
||||
}
|
||||
pub fn add_voter(self, who: AccountId, stake: Balance, targets: Vec<AccountId>) -> Self {
|
||||
pub fn add_voter(
|
||||
self,
|
||||
who: AccountId,
|
||||
stake: Balance,
|
||||
targets: BoundedVec<AccountId, MaxNominations>,
|
||||
) -> Self {
|
||||
VOTERS.with(|v| v.borrow_mut().push((who, stake, targets)));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -47,11 +47,7 @@ pub(crate) const OFFCHAIN_CACHED_CALL: &[u8] = b"parity/multi-phase-unsigned-ele
|
||||
|
||||
/// A voter's fundamental data: their ID, their stake, and the list of candidates for whom they
|
||||
/// voted.
|
||||
pub type Voter<T> = (
|
||||
<T as frame_system::Config>::AccountId,
|
||||
sp_npos_elections::VoteWeight,
|
||||
Vec<<T as frame_system::Config>::AccountId>,
|
||||
);
|
||||
pub type VoterOf<T> = frame_election_provider_support::VoterOf<<T as Config>::DataProvider>;
|
||||
|
||||
/// The relative distribution of a voter's stake among the winning targets.
|
||||
pub type Assignment<T> =
|
||||
@@ -749,7 +745,9 @@ mod tests {
|
||||
};
|
||||
use codec::Decode;
|
||||
use frame_benchmarking::Zero;
|
||||
use frame_support::{assert_noop, assert_ok, dispatch::Dispatchable, traits::OffchainWorker};
|
||||
use frame_support::{
|
||||
assert_noop, assert_ok, bounded_vec, dispatch::Dispatchable, traits::OffchainWorker,
|
||||
};
|
||||
use sp_npos_elections::IndexAssignment;
|
||||
use sp_runtime::{
|
||||
offchain::storage_lock::{BlockAndTime, StorageLock},
|
||||
@@ -1048,8 +1046,8 @@ mod tests {
|
||||
fn unsigned_per_dispatch_checks_can_only_submit_threshold_better() {
|
||||
ExtBuilder::default()
|
||||
.desired_targets(1)
|
||||
.add_voter(7, 2, vec![10])
|
||||
.add_voter(8, 5, vec![10])
|
||||
.add_voter(7, 2, bounded_vec![10])
|
||||
.add_voter(8, 5, bounded_vec![10])
|
||||
.solution_improvement_threshold(Perbill::from_percent(50))
|
||||
.build_and_execute(|| {
|
||||
roll_to(25);
|
||||
|
||||
Reference in New Issue
Block a user