Only maintain at most 1 UnlockChunk per era (#10670)

* Only maintain at most 1 `UnlockChunk` per era

* Bound `unlocking`

* Run cargo +nightly-2021-10-29 fmt

* Make benchmarks stuff compile

* Update frame/staking/src/lib.rs

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Remove DerefMut; Implement neccesary methods directly

* Doc comments for new BoundedVec methods

* Fix benchmarks

* wip bonded_vec macro

* Correct rust doc

* Apply suggestions from code review

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Update staking::Config impls

* Add MaxUnlockingChunks to more places

* Use defensive saturating add

* FMT

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
This commit is contained in:
Zeke Mostov
2022-03-01 15:31:13 -08:00
committed by GitHub
parent fe5a50129a
commit 3f5e0baf4a
12 changed files with 147 additions and 95 deletions
+4 -4
View File
@@ -928,7 +928,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
stash: voter.clone(),
active: stake,
total: stake,
unlocking: vec![],
unlocking: Default::default(),
claimed_rewards: vec![],
},
);
@@ -946,7 +946,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
stash: target.clone(),
active: stake,
total: stake,
unlocking: vec![],
unlocking: Default::default(),
claimed_rewards: vec![],
},
);
@@ -983,7 +983,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
stash: v.clone(),
active: stake,
total: stake,
unlocking: vec![],
unlocking: Default::default(),
claimed_rewards: vec![],
},
);
@@ -1004,7 +1004,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
stash: v.clone(),
active: stake,
total: stake,
unlocking: vec![],
unlocking: Default::default(),
claimed_rewards: vec![],
},
);
+29 -11
View File
@@ -21,8 +21,8 @@ use frame_election_provider_support::SortedListProvider;
use frame_support::{
pallet_prelude::*,
traits::{
Currency, CurrencyToVote, EnsureOrigin, EstimateNextNewSession, Get, LockIdentifier,
LockableCurrency, OnUnbalanced, UnixTime,
Currency, CurrencyToVote, DefensiveSaturating, EnsureOrigin, EstimateNextNewSession, Get,
LockIdentifier, LockableCurrency, OnUnbalanced, UnixTime,
},
weights::Weight,
};
@@ -40,12 +40,11 @@ pub use impls::*;
use crate::{
log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraPayout, EraRewardPoints,
Exposure, Forcing, NegativeImbalanceOf, Nominations, PositiveImbalanceOf, Releases,
RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk,
Exposure, Forcing, MaxUnlockingChunks, NegativeImbalanceOf, Nominations, PositiveImbalanceOf,
Releases, RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk,
ValidatorPrefs,
};
pub const MAX_UNLOCKING_CHUNKS: usize = 32;
const STAKING_ID: LockIdentifier = *b"staking ";
#[frame_support::pallet]
@@ -157,6 +156,10 @@ pub mod pallet {
/// the bags-list is not desired, [`impls::UseNominatorsMap`] is likely the desired option.
type SortedListProvider: SortedListProvider<Self::AccountId>;
/// The maximum number of `unlocking` chunks a [`StakingLedger`] can have. Effectively
/// determines how many unique eras a staker may be unbonding in.
type MaxUnlockingChunks: Get<u32>;
/// Some parameters of the benchmarking.
type BenchmarkingConfig: BenchmarkingConfig;
@@ -772,7 +775,7 @@ pub mod pallet {
stash,
total: value,
active: value,
unlocking: vec![],
unlocking: Default::default(),
claimed_rewards: (last_reward_era..current_era).collect(),
};
Self::update_ledger(&controller, &item);
@@ -837,7 +840,7 @@ pub mod pallet {
/// Once the unlock period is done, you can call `withdraw_unbonded` to actually move
/// the funds out of management ready for transfer.
///
/// No more than a limited number of unlocking chunks (see `MAX_UNLOCKING_CHUNKS`)
/// No more than a limited number of unlocking chunks (see `MaxUnlockingChunks`)
/// can co-exists at the same time. In that case, [`Call::withdraw_unbonded`] need
/// to be called first to remove some of the chunks (if possible).
///
@@ -854,7 +857,10 @@ pub mod pallet {
) -> DispatchResult {
let controller = ensure_signed(origin)?;
let mut ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
ensure!(ledger.unlocking.len() < MAX_UNLOCKING_CHUNKS, Error::<T>::NoMoreChunks,);
ensure!(
ledger.unlocking.len() < MaxUnlockingChunks::get() as usize,
Error::<T>::NoMoreChunks,
);
let mut value = value.min(ledger.active);
@@ -881,7 +887,19 @@ pub mod pallet {
// Note: in case there is no current era it is fine to bond one era more.
let era = Self::current_era().unwrap_or(0) + T::BondingDuration::get();
ledger.unlocking.push(UnlockChunk { value, era });
if let Some(mut chunk) =
ledger.unlocking.last_mut().filter(|chunk| chunk.era == era)
{
// To keep the chunk count down, we only keep one chunk per era. Since
// `unlocking` is a FiFo queue, if a chunk exists for `era` we know that it will
// be the last one.
chunk.value = chunk.value.defensive_saturating_add(value)
} else {
ledger
.unlocking
.try_push(UnlockChunk { value, era })
.map_err(|_| Error::<T>::NoMoreChunks)?;
};
// NOTE: ledger must be updated prior to calling `Self::weight_of`.
Self::update_ledger(&controller, &ledger);
@@ -1348,10 +1366,10 @@ pub mod pallet {
///
/// # <weight>
/// - Time complexity: O(L), where L is unlocking chunks
/// - Bounded by `MAX_UNLOCKING_CHUNKS`.
/// - Bounded by `MaxUnlockingChunks`.
/// - Storage changes: Can't increase storage, only decrease it.
/// # </weight>
#[pallet::weight(T::WeightInfo::rebond(MAX_UNLOCKING_CHUNKS as u32))]
#[pallet::weight(T::WeightInfo::rebond(MaxUnlockingChunks::get() as u32))]
pub fn rebond(
origin: OriginFor<T>,
#[pallet::compact] value: BalanceOf<T>,