mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +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:
@@ -19,7 +19,7 @@
|
||||
|
||||
use frame_election_provider_support::{
|
||||
data_provider, ElectionDataProvider, ElectionProvider, SortedListProvider, Supports,
|
||||
VoteWeight, VoteWeightProvider,
|
||||
VoteWeight, VoteWeightProvider, VoterOf,
|
||||
};
|
||||
use frame_support::{
|
||||
pallet_prelude::*,
|
||||
@@ -661,9 +661,7 @@ impl<T: Config> Pallet<T> {
|
||||
///
|
||||
/// All nominations that have been submitted before the last non-zero slash of the validator are
|
||||
/// auto-chilled, but still count towards the limit imposed by `maybe_max_len`.
|
||||
pub fn get_npos_voters(
|
||||
maybe_max_len: Option<usize>,
|
||||
) -> Vec<(T::AccountId, VoteWeight, Vec<T::AccountId>)> {
|
||||
pub fn get_npos_voters(maybe_max_len: Option<usize>) -> Vec<VoterOf<Self>> {
|
||||
let max_allowed_len = {
|
||||
let nominator_count = Nominators::<T>::count() as usize;
|
||||
let validator_count = Validators::<T>::count() as usize;
|
||||
@@ -677,8 +675,13 @@ impl<T: Config> Pallet<T> {
|
||||
let mut validators_taken = 0u32;
|
||||
for (validator, _) in <Validators<T>>::iter().take(max_allowed_len) {
|
||||
// Append self vote.
|
||||
let self_vote =
|
||||
(validator.clone(), Self::weight_of(&validator), vec![validator.clone()]);
|
||||
let self_vote = (
|
||||
validator.clone(),
|
||||
Self::weight_of(&validator),
|
||||
vec![validator.clone()]
|
||||
.try_into()
|
||||
.expect("`MaxVotesPerVoter` must be greater than or equal to 1"),
|
||||
);
|
||||
all_voters.push(self_vote);
|
||||
validators_taken.saturating_inc();
|
||||
}
|
||||
@@ -724,7 +727,12 @@ impl<T: Config> Pallet<T> {
|
||||
nominators_taken.saturating_inc();
|
||||
}
|
||||
} else {
|
||||
log!(error, "DEFENSIVE: invalid item in `SortedListProvider`: {:?}", nominator)
|
||||
// this can only happen if: 1. there a pretty bad bug in the bags-list (or whatever
|
||||
// is the sorted list) logic and the state of the two pallets is no longer
|
||||
// compatible, or because the nominators is not decodable since they have more
|
||||
// nomination than `T::MaxNominations`. This can rarely happen, and is not really an
|
||||
// emergency or bug if it does.
|
||||
log!(warn, "DEFENSIVE: invalid item in `SortedListProvider`: {:?}, this nominator probably has too many nominations now", nominator)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -772,7 +780,7 @@ impl<T: Config> Pallet<T> {
|
||||
/// NOTE: you must ALWAYS use this function to add nominator or update their targets. Any access
|
||||
/// to `Nominators` or `VoterList` outside of this function is almost certainly
|
||||
/// wrong.
|
||||
pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations<T::AccountId>) {
|
||||
pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations<T>) {
|
||||
if !Nominators::<T>::contains_key(who) {
|
||||
// maybe update sorted list. Error checking is defensive-only - this should never fail.
|
||||
let _ = T::SortedListProvider::on_insert(who.clone(), Self::weight_of(who))
|
||||
@@ -845,16 +853,14 @@ impl<T: Config> Pallet<T> {
|
||||
impl<T: Config> ElectionDataProvider for Pallet<T> {
|
||||
type AccountId = T::AccountId;
|
||||
type BlockNumber = BlockNumberFor<T>;
|
||||
const MAXIMUM_VOTES_PER_VOTER: u32 = T::MAX_NOMINATIONS;
|
||||
type MaxVotesPerVoter = T::MaxNominations;
|
||||
|
||||
fn desired_targets() -> data_provider::Result<u32> {
|
||||
Self::register_weight(T::DbWeight::get().reads(1));
|
||||
Ok(Self::validator_count())
|
||||
}
|
||||
|
||||
fn voters(
|
||||
maybe_max_len: Option<usize>,
|
||||
) -> data_provider::Result<Vec<(T::AccountId, VoteWeight, Vec<T::AccountId>)>> {
|
||||
fn voters(maybe_max_len: Option<usize>) -> data_provider::Result<Vec<VoterOf<Self>>> {
|
||||
// This can never fail -- if `maybe_max_len` is `Some(_)` we handle it.
|
||||
let voters = Self::get_npos_voters(maybe_max_len);
|
||||
debug_assert!(maybe_max_len.map_or(true, |max| voters.len() <= max));
|
||||
@@ -907,7 +913,11 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn add_voter(voter: T::AccountId, weight: VoteWeight, targets: Vec<T::AccountId>) {
|
||||
fn add_voter(
|
||||
voter: T::AccountId,
|
||||
weight: VoteWeight,
|
||||
targets: BoundedVec<T::AccountId, Self::MaxVotesPerVoter>,
|
||||
) {
|
||||
let stake = <BalanceOf<T>>::try_from(weight).unwrap_or_else(|_| {
|
||||
panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.")
|
||||
});
|
||||
@@ -922,6 +932,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
|
||||
claimed_rewards: vec![],
|
||||
},
|
||||
);
|
||||
|
||||
Self::do_add_nominator(&voter, Nominations { targets, submitted_in: 0, suppressed: false });
|
||||
}
|
||||
|
||||
@@ -957,7 +968,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn put_snapshot(
|
||||
voters: Vec<(T::AccountId, VoteWeight, Vec<T::AccountId>)>,
|
||||
voters: Vec<VoterOf<Self>>,
|
||||
targets: Vec<T::AccountId>,
|
||||
target_stake: Option<VoteWeight>,
|
||||
) {
|
||||
@@ -999,7 +1010,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
|
||||
);
|
||||
Self::do_add_nominator(
|
||||
&v,
|
||||
Nominations { targets: t, submitted_in: 0, suppressed: false },
|
||||
Nominations { targets: t.try_into().unwrap(), submitted_in: 0, suppressed: false },
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ use sp_runtime::{
|
||||
DispatchError, Perbill, Percent,
|
||||
};
|
||||
use sp_staking::{EraIndex, SessionIndex};
|
||||
use sp_std::{convert::From, prelude::*, result};
|
||||
use sp_std::{convert::From, prelude::*};
|
||||
|
||||
mod impls;
|
||||
|
||||
@@ -50,6 +50,8 @@ const STAKING_ID: LockIdentifier = *b"staking ";
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use frame_election_provider_support::ElectionDataProvider;
|
||||
|
||||
use crate::BenchmarkingConfig;
|
||||
|
||||
use super::*;
|
||||
@@ -94,7 +96,7 @@ pub mod pallet {
|
||||
>;
|
||||
|
||||
/// Maximum number of nominations per nominator.
|
||||
const MAX_NOMINATIONS: u32;
|
||||
type MaxNominations: Get<u32>;
|
||||
|
||||
/// Tokens have been minted and are unused for validator-reward.
|
||||
/// See [Era payout](./index.html#era-payout).
|
||||
@@ -161,15 +163,6 @@ pub mod pallet {
|
||||
type WeightInfo: WeightInfo;
|
||||
}
|
||||
|
||||
#[pallet::extra_constants]
|
||||
impl<T: Config> Pallet<T> {
|
||||
// TODO: rename to snake case after https://github.com/paritytech/substrate/issues/8826 fixed.
|
||||
#[allow(non_snake_case)]
|
||||
fn MaxNominations() -> u32 {
|
||||
T::MAX_NOMINATIONS
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::type_value]
|
||||
pub(crate) fn HistoryDepthOnEmpty() -> u32 {
|
||||
84u32
|
||||
@@ -246,11 +239,26 @@ pub mod pallet {
|
||||
#[pallet::storage]
|
||||
pub type MaxValidatorsCount<T> = StorageValue<_, u32, OptionQuery>;
|
||||
|
||||
/// The map from nominator stash key to the set of stash keys of all validators to nominate.
|
||||
/// The map from nominator stash key to their nomination preferences, namely the validators that
|
||||
/// they wish to support.
|
||||
///
|
||||
/// Note that the keys of this storage map might become non-decodable in case the
|
||||
/// [`Config::MaxNominations`] configuration is decreased. In this rare case, these nominators
|
||||
/// are still existent in storage, their key is correct and retrievable (i.e. `contains_key`
|
||||
/// indicates that they exist), but their value cannot be decoded. Therefore, the non-decodable
|
||||
/// nominators will effectively not-exist, until they re-submit their preferences such that it
|
||||
/// is within the bounds of the newly set `Config::MaxNominations`.
|
||||
///
|
||||
/// This implies that `::iter_keys().count()` and `::iter().count()` might return different
|
||||
/// values for this map. Moreover, the main `::count()` is aligned with the former, namely the
|
||||
/// number of keys that exist.
|
||||
///
|
||||
/// Lastly, if any of the nominators become non-decodable, they can be chilled immediately via
|
||||
/// [`Call::chill_other`] dispatchable by anyone.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn nominators)]
|
||||
pub type Nominators<T: Config> =
|
||||
CountedStorageMap<_, Twox64Concat, T::AccountId, Nominations<T::AccountId>>;
|
||||
CountedStorageMap<_, Twox64Concat, T::AccountId, Nominations<T>>;
|
||||
|
||||
/// The maximum nominator count before we stop allowing new validators to join.
|
||||
///
|
||||
@@ -681,6 +689,14 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
fn integrity_test() {
|
||||
// ensure that we funnel the correct value to the `DataProvider::MaxVotesPerVoter`;
|
||||
assert_eq!(
|
||||
T::MaxNominations::get(),
|
||||
<Self as ElectionDataProvider>::MaxVotesPerVoter::get()
|
||||
);
|
||||
// and that MaxNominations is always greater than 1, since we count on this.
|
||||
assert!(!T::MaxNominations::get().is_zero());
|
||||
|
||||
sp_std::if_std! {
|
||||
sp_io::TestExternalities::new_empty().execute_with(||
|
||||
assert!(
|
||||
@@ -978,7 +994,7 @@ pub mod pallet {
|
||||
///
|
||||
/// # <weight>
|
||||
/// - The transaction's complexity is proportional to the size of `targets` (N)
|
||||
/// which is capped at CompactAssignments::LIMIT (MAX_NOMINATIONS).
|
||||
/// which is capped at CompactAssignments::LIMIT (T::MaxNominations).
|
||||
/// - Both the reads and writes follow a similar pattern.
|
||||
/// # </weight>
|
||||
#[pallet::weight(T::WeightInfo::nominate(targets.len() as u32))]
|
||||
@@ -1006,11 +1022,11 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
ensure!(!targets.is_empty(), Error::<T>::EmptyTargets);
|
||||
ensure!(targets.len() <= T::MAX_NOMINATIONS as usize, Error::<T>::TooManyTargets);
|
||||
ensure!(targets.len() <= T::MaxNominations::get() as usize, Error::<T>::TooManyTargets);
|
||||
|
||||
let old = Nominators::<T>::get(stash).map_or_else(Vec::new, |x| x.targets);
|
||||
let old = Nominators::<T>::get(stash).map_or_else(Vec::new, |x| x.targets.into_inner());
|
||||
|
||||
let targets = targets
|
||||
let targets: BoundedVec<_, _> = targets
|
||||
.into_iter()
|
||||
.map(|t| T::Lookup::lookup(t).map_err(DispatchError::from))
|
||||
.map(|n| {
|
||||
@@ -1022,11 +1038,13 @@ pub mod pallet {
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<result::Result<Vec<T::AccountId>, _>>()?;
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.try_into()
|
||||
.map_err(|_| Error::<T>::TooManyNominators)?;
|
||||
|
||||
let nominations = Nominations {
|
||||
targets,
|
||||
// Initial nominations are considered submitted at era 0. See `Nominations` doc
|
||||
// Initial nominations are considered submitted at era 0. See `Nominations` doc.
|
||||
submitted_in: Self::current_era().unwrap_or(0),
|
||||
suppressed: false,
|
||||
};
|
||||
@@ -1216,11 +1234,6 @@ pub mod pallet {
|
||||
/// Set the validators who cannot be slashed (if any).
|
||||
///
|
||||
/// The dispatch origin must be Root.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - O(V)
|
||||
/// - Write: Invulnerables
|
||||
/// # </weight>
|
||||
#[pallet::weight(T::WeightInfo::set_invulnerables(invulnerables.len() as u32))]
|
||||
pub fn set_invulnerables(
|
||||
origin: OriginFor<T>,
|
||||
@@ -1234,13 +1247,6 @@ pub mod pallet {
|
||||
/// Force a current staker to become completely unstaked, immediately.
|
||||
///
|
||||
/// The dispatch origin must be Root.
|
||||
///
|
||||
/// # <weight>
|
||||
/// O(S) where S is the number of slashing spans to be removed
|
||||
/// Reads: Bonded, Slashing Spans, Account, Locks
|
||||
/// Writes: Bonded, Slashing Spans (if S > 0), Ledger, Payee, Validators, Nominators,
|
||||
/// Account, Locks Writes Each: SpanSlash * S
|
||||
/// # </weight>
|
||||
#[pallet::weight(T::WeightInfo::force_unstake(*num_slashing_spans))]
|
||||
pub fn force_unstake(
|
||||
origin: OriginFor<T>,
|
||||
@@ -1266,11 +1272,6 @@ pub mod pallet {
|
||||
/// The election process starts multiple blocks before the end of the era.
|
||||
/// If this is called just before a new era is triggered, the election process may not
|
||||
/// have enough blocks to get a result.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - Weight: O(1)
|
||||
/// - Write: ForceEra
|
||||
/// # </weight>
|
||||
#[pallet::weight(T::WeightInfo::force_new_era_always())]
|
||||
pub fn force_new_era_always(origin: OriginFor<T>) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
@@ -1283,14 +1284,6 @@ pub mod pallet {
|
||||
/// Can be called by the `T::SlashCancelOrigin`.
|
||||
///
|
||||
/// Parameters: era and indices of the slashes for that era to kill.
|
||||
///
|
||||
/// # <weight>
|
||||
/// Complexity: O(U + S)
|
||||
/// with U unapplied slashes weighted with U=1000
|
||||
/// and S is the number of slash indices to be canceled.
|
||||
/// - Read: Unapplied Slashes
|
||||
/// - Write: Unapplied Slashes
|
||||
/// # </weight>
|
||||
#[pallet::weight(T::WeightInfo::cancel_deferred_slash(slash_indices.len() as u32))]
|
||||
pub fn cancel_deferred_slash(
|
||||
origin: OriginFor<T>,
|
||||
@@ -1550,6 +1543,11 @@ pub mod pallet {
|
||||
///
|
||||
/// If the caller is different than the controller being targeted, the following conditions
|
||||
/// must be met:
|
||||
///
|
||||
/// * `controller` must belong to a nominator who has become non-decodable,
|
||||
///
|
||||
/// Or:
|
||||
///
|
||||
/// * A `ChillThreshold` must be set and checked which defines how close to the max
|
||||
/// nominators or validators we must reach before users can start chilling one-another.
|
||||
/// * A `MaxNominatorCount` and `MaxValidatorCount` must be set which is used to determine
|
||||
@@ -1568,6 +1566,11 @@ pub mod pallet {
|
||||
let stash = ledger.stash;
|
||||
|
||||
// In order for one user to chill another user, the following conditions must be met:
|
||||
//
|
||||
// * `controller` belongs to a nominator who has become non-decodable,
|
||||
//
|
||||
// Or
|
||||
//
|
||||
// * A `ChillThreshold` is set which defines how close to the max nominators or
|
||||
// validators we must reach before users can start chilling one-another.
|
||||
// * A `MaxNominatorCount` and `MaxValidatorCount` which is used to determine how close
|
||||
@@ -1577,6 +1580,12 @@ pub mod pallet {
|
||||
// threshold bond required.
|
||||
//
|
||||
// Otherwise, if caller is the same as the controller, this is just like `chill`.
|
||||
|
||||
if Nominators::<T>::contains_key(&stash) && Nominators::<T>::get(&stash).is_none() {
|
||||
Self::chill_stash(&stash);
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
if caller != controller {
|
||||
let threshold = ChillThreshold::<T>::get().ok_or(Error::<T>::CannotChillOther)?;
|
||||
let min_active_bond = if Nominators::<T>::contains_key(&stash) {
|
||||
|
||||
Reference in New Issue
Block a user