Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
basic implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
shawntabrizi committed Aug 20, 2020
1 parent 368903f commit 62e4ad8
Showing 1 changed file with 47 additions and 0 deletions.
47 changes: 47 additions & 0 deletions frame/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,9 @@ decl_storage! {
/// Minimum number of staking participants before emergency conditions are imposed.
pub MinimumValidatorCount get(fn minimum_validator_count) config(): u32;

/// Maximum number of validators allowed to be selected.
pub MaximumValidatorCount get(fn maximum_validator_count) config(): u32;

/// Any validators that may never be slashed or forcibly kicked. It's a Vec since they're
/// easy to initialize and the performance hit is minimal (we expect no more than four
/// invulnerables) and restricted to testnets.
Expand Down Expand Up @@ -2894,8 +2897,10 @@ impl<T: Trait> Module<T> {

// Populate Stakers and write slot stake.
let mut total_stake: BalanceOf<T> = Zero::zero();
let mut exposure_totals = Vec::<BalanceOf<T>>::new();

This comment has been minimized.

Copy link
@robamler

robamler Aug 24, 2020

Nit pick: I'm not very familiar with the code base, but I'd suggest instead:

let mut exposure_totals = Vec::<BalanceOf<T>>::with_capacity(exposures.len());
//                                             ^^^^^^^^^^^^^

Or simpler:

let exposure_totals = exposures.into_iter().map(|(stash, exposure)| {
    // ... update ErasStakersClipped as below ...
    exposure.total
}).collect::<Vec<_>>();
exposures.into_iter().for_each(|(stash, exposure)| {
total_stake = total_stake.saturating_add(exposure.total);
exposure_totals.push(exposure.total);
<ErasStakers<T>>::insert(current_era, &stash, &exposure);

let mut exposure_clipped = exposure;
Expand All @@ -2907,6 +2912,12 @@ impl<T: Trait> Module<T> {
<ErasStakersClipped<T>>::insert(&current_era, &stash, exposure_clipped);
});

let new_validator_count = Self::update_target_validators(exposure_totals);

This comment has been minimized.

Copy link
@kianenigma

kianenigma Aug 24, 2020

Contributor

Learning from the past:

The major problem with this is that now we force all substrate chains to be dynamic, which is IMO not desirable. Staking should ideally accept a config that tells it how to update its validator count.

Not sure if it is trivial to do though, but I think it is an important grumble.

This comment has been minimized.

Copy link
@kianenigma

kianenigma Aug 24, 2020

Contributor

perhaps you do with a type Type: Convert<u32, u32> that converts the old one to the new one.

sp_std::if_std!{
println!("New validator count: {}", new_validator_count);
}
// ValidatorCount::put(new_validator_count);

// Insert current era staking information
<ErasTotalStake<T>>::insert(&current_era, total_stake);

Expand Down Expand Up @@ -3127,6 +3138,42 @@ impl<T: Trait> Module<T> {
})
}

/// Use a damping function to update the target number of validators for the next selection.
fn update_target_validators(mut exposures: Vec<BalanceOf<T>>) -> u32 {
let average = |all: &[BalanceOf<T>]| -> BalanceOf<T> {
all.iter().fold(Zero::zero(), |total: BalanceOf<T>, x: &BalanceOf<T>| {
// `total` should never saturate since total issuance fits inside `Balance`
total.saturating_add(*x)
}) / BalanceOf::<T>::saturated_from(all.len() as u128)

This comment has been minimized.

Copy link
@niklasad1

niklasad1 Aug 24, 2020

Member

Potential div by 0 if all.len() == 0.

Seems very unlikely but is there any precondition that prevents it from happening?

};
// Current validator count should never be zero.
let current_validator_count = Self::validator_count().max(1);
let one_percent_count = (current_validator_count / 100).max(1);
let global_average = average(&exposures);
// Sort exposures by balance
exposures.sort();

// Rule 1: If the bottom 1% of validators (more precisely, ceil(0.01 k) validators) have an
// average staked value strictly above 40% of the global average, in the next era increase
// the number of active validators by 1%, i.e. k <-- min{max_limit, k + ceil(0.01 k)}.
let one_percent_average = average(&exposures[0..(one_percent_count as usize)]);
if one_percent_average > Percent::from_percent(40) * global_average {
return (current_validator_count + one_percent_count).min(MaximumValidatorCount::get())
} else {
let two_percent_count = (current_validator_count / 50).max(1);
let two_percent_average = average(&exposures[0..(two_percent_count as usize)]);
// Rule 2: If the bottom 2% of validators (more precisely, 2 ceil(0.01 k) validators) have
// an average stake value strictly below 20% of the global average, in the next era decrease
// the number of active validators by 1%, i.e. k <-- max{min_limit, k - ceil(0.01 k)}.
if two_percent_average < Percent::from_percent(20) * global_average {
return (current_validator_count - one_percent_count).max(MinimumValidatorCount::get())
}
}

// If neither condition is met, keep the validator count the same
return current_validator_count
}

/// Add reward points to validators using their stash account ID.
///
/// Validators are keyed by stash account ID and must be in the current elected set.
Expand Down

0 comments on commit 62e4ad8

Please sign in to comment.