Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Staking failsafes
- Don't slash/unstake/change session when too few staking participants
- Introduce set_balance PrivCall
  • Loading branch information
gavofyork committed Aug 22, 2018
commit 42c58563d98fd4b98fdaf404068f62302e9e8d31
35 changes: 31 additions & 4 deletions substrate/runtime/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ mod genesis_config;
#[cfg(feature = "std")]
pub use genesis_config::GenesisConfig;

const MINIMUM_REQUIRED_VALIDATORS: usize = 4;

/// Number of account IDs stored per enum set.
const ENUM_SET_SIZE: usize = 64;

Expand Down Expand Up @@ -164,6 +166,7 @@ decl_module! {
fn set_validator_count(new: u32) -> Result = 2;
fn force_new_era(apply_rewards: bool) -> Result = 3;
fn set_offline_slash_grace(new: u32) -> Result = 4;
fn set_balance(who: RawAddress<T::AccountId, T::AccountIndex>, free: T::Balance, reserved: T::Balance) -> Result = 5;
}
}

Expand Down Expand Up @@ -399,6 +402,10 @@ impl<T: Trait> Module<T> {
///
/// Effects will be felt at the beginning of the next era.
fn unstake(aux: &T::PublicAux, intentions_index: u32) -> Result {
// unstake fails in degenerate case of having too few existing staked parties
if Self::intentions().len() <= MINIMUM_REQUIRED_VALIDATORS {
return Err("cannot unstake when there are too few staked participants")
}
Self::apply_unstake(aux.ref_into(), intentions_index as usize)
}

Expand Down Expand Up @@ -541,6 +548,14 @@ impl<T: Trait> Module<T> {
Ok(())
}

/// Set the balances of a given account.
fn set_balance(who: Address<T>, free: T::Balance, reserved: T::Balance) -> Result {
let who = Self::lookup(who)?;
Self::set_free_balance(&who, free);
Self::set_reserved_balance(&who, reserved);
Ok(())
}

// PUBLIC MUTABLES (DANGEROUS)

/// Set the free balance of an account to some new value.
Expand Down Expand Up @@ -725,6 +740,12 @@ impl<T: Trait> Module<T> {
/// Slash a given validator by a specific amount. Removes the slash from their balance by preference,
/// and reduces the nominators' balance if needed.
fn slash_validator(v: &T::AccountId, slash: T::Balance) {
// skip the slash in degenerate case of having only 4 staking participants despite having a larger
// desired number of validators (validator_count).
if Self::intentions().len() <= MINIMUM_REQUIRED_VALIDATORS {
return
}

if let Some(rem) = Self::slash(v, slash) {
let noms = Self::current_nominators_for(v);
let total = noms.iter().map(Self::voting_balance).fold(T::Balance::zero(), |acc, x| acc + x);
Expand Down Expand Up @@ -813,10 +834,16 @@ impl<T: Trait> Module<T> {
// for now, this just orders would-be stakers by their balances and chooses the top-most
// <ValidatorCount<T>>::get() of them.
// TODO: this is not sound. this should be moved to an off-chain solution mechanism.
let mut intentions = <Intentions<T>>::get()
let mut intentions = Self::intentions()
.into_iter()
.map(|v| (Self::slashable_balance(&v), v))
.collect::<Vec<_>>();

// Avoid making new era if it would leave us with fewer than the minimum needed validators
if intentions.len() < MINIMUM_REQUIRED_VALIDATORS {
return
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But isn't it too late for this? We've already changed current era index and applied some pending changes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment out of date - just mean "reevaluate validator set"


intentions.sort_unstable_by(|&(ref b1, _), &(ref b2, _)| b2.cmp(&b1));

<StakeThreshold<T>>::put(
Expand All @@ -826,9 +853,9 @@ impl<T: Trait> Module<T> {
} else { Zero::zero() }
);
let vals = &intentions.into_iter()
.map(|(_, v)| v)
.take(<ValidatorCount<T>>::get() as usize)
.collect::<Vec<_>>();
.map(|(_, v)| v)
.take(<ValidatorCount<T>>::get() as usize)
.collect::<Vec<_>>();
for v in <session::Module<T>>::validators().iter() {
<CurrentNominatorsFor<T>>::remove(v);
let slash_count = <SlashCount<T>>::take(v);
Expand Down