Improve Staking Limits (#9193)

* only allow `chill_other` near threshold.

* improve test

* skip limit check for existing validators / nominators

* add `ChillThreshold`

* rename to `set` for consistent api

* more tests

* fix some line width
This commit is contained in:
Shawn Tabrizi
2021-06-28 08:54:24 -04:00
committed by GitHub
parent ee192467e2
commit 5cd04820dc
4 changed files with 171 additions and 49 deletions
+58 -22
View File
@@ -1216,6 +1216,12 @@ pub mod pallet {
#[pallet::storage]
pub(crate) type StorageVersion<T: Config> = StorageValue<_, Releases, ValueQuery>;
/// The threshold for when users can start calling `chill_other` for other validators / nominators.
/// The threshold is compared to the actual number of validators / nominators (`CountFor*`) in
/// the system compared to the configured max (`Max*Count`).
#[pallet::storage]
pub(crate) type ChillThreshold<T: Config> = StorageValue<_, Percent, OptionQuery>;
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub history_depth: u32,
@@ -1714,16 +1720,19 @@ pub mod pallet {
pub fn validate(origin: OriginFor<T>, prefs: ValidatorPrefs) -> DispatchResult {
let controller = ensure_signed(origin)?;
// If this error is reached, we need to adjust the `MinValidatorBond` and start calling `chill_other`.
// Until then, we explicitly block new validators to protect the runtime.
if let Some(max_validators) = MaxValidatorsCount::<T>::get() {
ensure!(CounterForValidators::<T>::get() < max_validators, Error::<T>::TooManyValidators);
}
let ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
ensure!(ledger.active >= MinValidatorBond::<T>::get(), Error::<T>::InsufficientBond);
let stash = &ledger.stash;
// Only check limits if they are not already a validator.
if !Validators::<T>::contains_key(stash) {
// If this error is reached, we need to adjust the `MinValidatorBond` and start calling `chill_other`.
// Until then, we explicitly block new validators to protect the runtime.
if let Some(max_validators) = MaxValidatorsCount::<T>::get() {
ensure!(CounterForValidators::<T>::get() < max_validators, Error::<T>::TooManyValidators);
}
}
Self::do_remove_nominator(stash);
Self::do_add_validator(stash, prefs);
Ok(())
@@ -1755,16 +1764,19 @@ pub mod pallet {
) -> DispatchResult {
let controller = ensure_signed(origin)?;
// If this error is reached, we need to adjust the `MinNominatorBond` and start calling `chill_other`.
// Until then, we explicitly block new nominators to protect the runtime.
if let Some(max_nominators) = MaxNominatorsCount::<T>::get() {
ensure!(CounterForNominators::<T>::get() < max_nominators, Error::<T>::TooManyNominators);
}
let ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
ensure!(ledger.active >= MinNominatorBond::<T>::get(), Error::<T>::InsufficientBond);
let stash = &ledger.stash;
// Only check limits if they are not already a nominator.
if !Nominators::<T>::contains_key(stash) {
// If this error is reached, we need to adjust the `MinNominatorBond` and start calling `chill_other`.
// Until then, we explicitly block new nominators to protect the runtime.
if let Some(max_nominators) = MaxNominatorsCount::<T>::get() {
ensure!(CounterForNominators::<T>::get() < max_nominators, Error::<T>::TooManyNominators);
}
}
ensure!(!targets.is_empty(), Error::<T>::EmptyTargets);
ensure!(targets.len() <= T::MAX_NOMINATIONS as usize, Error::<T>::TooManyTargets);
@@ -2266,31 +2278,42 @@ pub mod pallet {
///
/// NOTE: Existing nominators and validators will not be affected by this update.
/// to kick people under the new limits, `chill_other` should be called.
#[pallet::weight(T::WeightInfo::update_staking_limits())]
pub fn update_staking_limits(
#[pallet::weight(T::WeightInfo::set_staking_limits())]
pub fn set_staking_limits(
origin: OriginFor<T>,
min_nominator_bond: BalanceOf<T>,
min_validator_bond: BalanceOf<T>,
max_nominator_count: Option<u32>,
max_validator_count: Option<u32>,
threshold: Option<Percent>,
) -> DispatchResult {
ensure_root(origin)?;
MinNominatorBond::<T>::set(min_nominator_bond);
MinValidatorBond::<T>::set(min_validator_bond);
MaxNominatorsCount::<T>::set(max_nominator_count);
MaxValidatorsCount::<T>::set(max_validator_count);
ChillThreshold::<T>::set(threshold);
Ok(())
}
/// Declare a `controller` as having no desire to either validator or nominate.
/// Declare a `controller` to stop participating as either a validator or nominator.
///
/// Effects will be felt at the beginning of the next era.
///
/// The dispatch origin for this call must be _Signed_, but can be called by anyone.
///
/// If the caller is the same as the controller being targeted, then no further checks
/// are enforced. However, this call can also be made by an third party user who witnesses
/// that this controller does not satisfy the minimum bond requirements to be in their role.
/// If the caller is the same as the controller being targeted, then no further checks are
/// enforced, and this function behaves just like `chill`.
///
/// If the caller is different than the controller being targeted, the following conditions
/// must be met:
/// * 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
/// how close we are to the threshold.
/// * A `MinNominatorBond` and `MinValidatorBond` must be set and checked, which determines
/// if this is a person that should be chilled because they have not met the threshold
/// bond required.
///
/// This can be helpful if bond requirements are updated, and we need to remove old users
/// who do not satisfy these requirements.
@@ -2307,14 +2330,27 @@ pub mod pallet {
let ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
let stash = ledger.stash;
// If the caller is not the controller, we want to check that the minimum bond
// requirements are not satisfied, and thus we have reason to chill this user.
// In order for one user to chill another user, the following conditions must be met:
// * 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
// we are to the threshold.
// * A `MinNominatorBond` and `MinValidatorBond` which is the final condition checked to
// determine this is a person that should be chilled because they have not met the
// threshold bond required.
//
// Otherwise, if caller is the same as the controller, this is just like `chill`.
if caller != controller {
let threshold = ChillThreshold::<T>::get().ok_or(Error::<T>::CannotChillOther)?;
let min_active_bond = if Nominators::<T>::contains_key(&stash) {
let max_nominator_count = MaxNominatorsCount::<T>::get().ok_or(Error::<T>::CannotChillOther)?;
let current_nominator_count = CounterForNominators::<T>::get();
ensure!(threshold * max_nominator_count < current_nominator_count, Error::<T>::CannotChillOther);
MinNominatorBond::<T>::get()
} else if Validators::<T>::contains_key(&stash) {
let max_validator_count = MaxValidatorsCount::<T>::get().ok_or(Error::<T>::CannotChillOther)?;
let current_validator_count = CounterForValidators::<T>::get();
ensure!(threshold * max_validator_count < current_validator_count, Error::<T>::CannotChillOther);
MinValidatorBond::<T>::get()
} else {
Zero::zero()