safe multi-era slashing for NPoS (#3846)

* define slashing spans

* tests and pruning for slashing-spans record

* validators get slashed before nominators

* apply slash to nominators as well

* chill and end slashing spans

* actually perform slashes

* integration (tests failing)

* prune metadata

* fix compilation

* some tests for slashing and metadata garbage collection

* correctly pass session index to slash handler

* test span-max property for nominators and validators

* test that slashes are summed correctly

* reward value computation

* implement rewarding

* add comment about rewards

* do not adjust slash fraction in offences module

* fix offences tests

* remove unused new_offenders field

* update runtime version

* fix up some docs

* fix some CI failures

* remove no-std incompatible vec! invocation

* try to fix span-max rounding error

* Update srml/staking/src/slashing.rs

Fix type: winow -> window

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* slashes from prior spans don't kick validator again

* more information for nominators, suppression

* ensure ledger is consistent with itself post-slash

* implement slash out of unlocking funds also

* slashing: create records to be applied after-the-fact

* queue slashes for a few eras later

* method for canceling deferred slashes

* attempt to fix test in CI

* storage migration for `Nominators`

* update node-runtime to use SlashDeferDuration

* adjust migration entry-points somewhat

* fix migration compilation

* add manual Vec import to migration

* enable migrations feature in node-runtime

* bump runtime version

* update to latest master crate renames

* update to use ensure-origin

* Apply suggestions from code review

use `ensure!`

Co-Authored-By: Gavin Wood <gavin@parity.io>

* fix multi-slash removal

* initialize storage version to current in genesis

* add test for version initialization
This commit is contained in:
Robert Habermeier
2019-11-27 20:06:32 +01:00
committed by Gavin Wood
parent de5686509c
commit 4598e13015
13 changed files with 1921 additions and 249 deletions
+57 -2
View File
@@ -21,10 +21,10 @@ use sr_primitives::{Perbill, KeyTypeId};
use sr_primitives::curve::PiecewiseLinear;
use sr_primitives::traits::{IdentityLookup, Convert, OpaqueKeys, OnInitialize, SaturatedConversion};
use sr_primitives::testing::{Header, UintAuthorityId};
use sr_staking_primitives::SessionIndex;
use sr_staking_primitives::{SessionIndex, offence::{OffenceDetails, OnOffenceHandler}};
use primitives::{H256, crypto::key_types};
use runtime_io;
use support::{assert_ok, impl_outer_origin, parameter_types, StorageLinkedMap};
use support::{assert_ok, impl_outer_origin, parameter_types, StorageLinkedMap, StorageValue};
use support::traits::{Currency, Get, FindAuthor};
use crate::{
EraIndex, GenesisConfig, Module, Trait, StakerStatus, ValidatorPrefs, RewardDestination,
@@ -48,6 +48,7 @@ impl Convert<u128, u64> for CurrencyToVoteHandler {
thread_local! {
static SESSION: RefCell<(Vec<AccountId>, HashSet<AccountId>)> = RefCell::new(Default::default());
static EXISTENTIAL_DEPOSIT: RefCell<u64> = RefCell::new(0);
static SLASH_DEFER_DURATION: RefCell<EraIndex> = RefCell::new(0);
}
pub struct TestSessionHandler;
@@ -87,6 +88,13 @@ impl Get<u64> for ExistentialDeposit {
}
}
pub struct SlashDeferDuration;
impl Get<EraIndex> for SlashDeferDuration {
fn get() -> EraIndex {
SLASH_DEFER_DURATION.with(|v| *v.borrow())
}
}
impl_outer_origin!{
pub enum Origin for Test {}
}
@@ -202,6 +210,8 @@ impl Trait for Test {
type Slash = ();
type Reward = ();
type SessionsPerEra = SessionsPerEra;
type SlashDeferDuration = SlashDeferDuration;
type SlashCancelOrigin = system::EnsureRoot<Self::AccountId>;
type BondingDuration = BondingDuration;
type SessionInterface = Self;
type RewardCurve = RewardCurve;
@@ -213,6 +223,7 @@ pub struct ExtBuilder {
nominate: bool,
validator_count: u32,
minimum_validator_count: u32,
slash_defer_duration: EraIndex,
fair: bool,
num_validators: Option<u32>,
invulnerables: Vec<u64>,
@@ -226,6 +237,7 @@ impl Default for ExtBuilder {
nominate: true,
validator_count: 2,
minimum_validator_count: 0,
slash_defer_duration: 0,
fair: true,
num_validators: None,
invulnerables: vec![],
@@ -254,6 +266,10 @@ impl ExtBuilder {
self.minimum_validator_count = count;
self
}
pub fn slash_defer_duration(mut self, eras: EraIndex) -> Self {
self.slash_defer_duration = eras;
self
}
pub fn fair(mut self, is_fair: bool) -> Self {
self.fair = is_fair;
self
@@ -268,6 +284,7 @@ impl ExtBuilder {
}
pub fn set_associated_consts(&self) {
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
SLASH_DEFER_DURATION.with(|v| *v.borrow_mut() = self.slash_defer_duration);
}
pub fn build(self) -> runtime_io::TestExternalities {
self.set_associated_consts();
@@ -393,6 +410,14 @@ pub fn assert_is_stash(acc: u64) {
assert!(Staking::bonded(&acc).is_some(), "Not a stash.");
}
pub fn assert_ledger_consistent(stash: u64) {
assert_is_stash(stash);
let ledger = Staking::ledger(stash - 1).unwrap();
let real_total: Balance = ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value);
assert_eq!(real_total, ledger.total);
}
pub fn bond_validator(acc: u64, val: u64) {
// a = controller
// a + 1 = stash
@@ -451,3 +476,33 @@ pub fn reward_all_elected() {
pub fn validator_controllers() -> Vec<AccountId> {
Session::validators().into_iter().map(|s| Staking::bonded(&s).expect("no controller for validator")).collect()
}
pub fn on_offence_in_era(
offenders: &[OffenceDetails<AccountId, session::historical::IdentificationTuple<Test>>],
slash_fraction: &[Perbill],
era: EraIndex,
) {
let bonded_eras = crate::BondedEras::get();
for &(bonded_era, start_session) in bonded_eras.iter() {
if bonded_era == era {
Staking::on_offence(offenders, slash_fraction, start_session);
return
} else if bonded_era > era {
break
}
}
if Staking::current_era() == era {
Staking::on_offence(offenders, slash_fraction, Staking::current_era_start_session_index());
} else {
panic!("cannot slash in era {}", era);
}
}
pub fn on_offence_now(
offenders: &[OffenceDetails<AccountId, session::historical::IdentificationTuple<Test>>],
slash_fraction: &[Perbill],
) {
let now = Staking::current_era();
on_offence_in_era(offenders, slash_fraction, now)
}