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
+17 -51
View File
@@ -24,17 +24,11 @@
mod mock;
mod tests;
use rstd::{
vec::Vec,
collections::btree_set::BTreeSet,
};
use rstd::vec::Vec;
use support::{
decl_module, decl_event, decl_storage, Parameter,
};
use sr_primitives::{
Perbill,
traits::{Hash, Saturating},
};
use sr_primitives::traits::Hash;
use sr_staking_primitives::{
offence::{Offence, ReportOffence, Kind, OnOffenceHandler, OffenceDetails},
};
@@ -100,10 +94,11 @@ where
// Go through all offenders in the offence report and find all offenders that was spotted
// in unique reports.
let TriageOutcome {
new_offenders,
concurrent_offenders,
} = match Self::triage_offence_report::<O>(reporters, &time_slot, offenders) {
let TriageOutcome { concurrent_offenders } = match Self::triage_offence_report::<O>(
reporters,
&time_slot,
offenders,
) {
Some(triage) => triage,
// The report contained only duplicates, so there is no need to slash again.
None => return,
@@ -113,44 +108,18 @@ where
Self::deposit_event(Event::Offence(O::ID, time_slot.encode()));
let offenders_count = concurrent_offenders.len() as u32;
let previous_offenders_count = offenders_count - new_offenders.len() as u32;
// The amount new offenders are slashed
let new_fraction = O::slash_fraction(offenders_count, validator_set_count);
// The amount previous offenders are slashed additionally.
//
// Since they were slashed in the past, we slash by:
// x = (new - prev) / (1 - prev)
// because:
// Y = X * (1 - prev)
// Z = Y * (1 - x)
// Z = X * (1 - new)
let old_fraction = if previous_offenders_count > 0 {
let previous_fraction = O::slash_fraction(
offenders_count.saturating_sub(previous_offenders_count),
validator_set_count,
);
let numerator = new_fraction.saturating_sub(previous_fraction);
let denominator = Perbill::one().saturating_sub(previous_fraction);
denominator.saturating_mul(numerator)
} else {
new_fraction.clone()
};
let slash_perbill: Vec<_> = (0..concurrent_offenders.len())
.map(|_| new_fraction.clone()).collect();
// calculate how much to slash
let slash_perbill = concurrent_offenders
.iter()
.map(|details| {
if previous_offenders_count > 0 && new_offenders.contains(&details.offender) {
new_fraction.clone()
} else {
old_fraction.clone()
}
})
.collect::<Vec<_>>();
T::OnOffenceHandler::on_offence(&concurrent_offenders, &slash_perbill);
T::OnOffenceHandler::on_offence(
&concurrent_offenders,
&slash_perbill,
offence.session_index(),
);
}
}
@@ -173,13 +142,13 @@ impl<T: Trait> Module<T> {
offenders: Vec<T::IdentificationTuple>,
) -> Option<TriageOutcome<T>> {
let mut storage = ReportIndexStorage::<T, O>::load(time_slot);
let mut new_offenders = BTreeSet::new();
let mut any_new = false;
for offender in offenders {
let report_id = Self::report_id::<O>(time_slot, &offender);
if !<Reports<T>>::exists(&report_id) {
new_offenders.insert(offender.clone());
any_new = true;
<Reports<T>>::insert(
&report_id,
OffenceDetails {
@@ -192,7 +161,7 @@ impl<T: Trait> Module<T> {
}
}
if !new_offenders.is_empty() {
if any_new {
// Load report details for the all reports happened at the same time.
let concurrent_offenders = storage.concurrent_reports
.iter()
@@ -202,7 +171,6 @@ impl<T: Trait> Module<T> {
storage.save();
Some(TriageOutcome {
new_offenders,
concurrent_offenders,
})
} else {
@@ -212,8 +180,6 @@ impl<T: Trait> Module<T> {
}
struct TriageOutcome<T: Trait> {
/// Offenders that was spotted in the unique reports.
new_offenders: BTreeSet<T::IdentificationTuple>,
/// Other reports for the same report kinds.
concurrent_offenders: Vec<OffenceDetails<T::AccountId, T::IdentificationTuple>>,
}
+2 -3
View File
@@ -46,6 +46,7 @@ impl<Reporter, Offender> offence::OnOffenceHandler<Reporter, Offender> for OnOff
fn on_offence(
_offenders: &[OffenceDetails<Reporter, Offender>],
slash_fraction: &[Perbill],
_offence_session: SessionIndex,
) {
ON_OFFENCE_PERBILL.with(|f| {
*f.borrow_mut() = slash_fraction.to_vec();
@@ -148,9 +149,7 @@ impl<T: Clone> offence::Offence<T> for Offence<T> {
}
fn session_index(&self) -> SessionIndex {
// session index is not used by the pallet-offences directly, but rather it exists only for
// filtering historical reports.
unimplemented!()
1
}
fn slash_fraction(
+1 -32
View File
@@ -23,6 +23,7 @@ use crate::mock::{
Offences, System, Offence, TestEvent, KIND, new_test_ext, with_on_offence_fractions,
offence_reports,
};
use sr_primitives::Perbill;
use system::{EventRecord, Phase};
#[test]
@@ -48,38 +49,6 @@ fn should_report_an_authority_and_trigger_on_offence() {
});
}
#[test]
fn should_calculate_the_fraction_correctly() {
new_test_ext().execute_with(|| {
// given
let time_slot = 42;
assert_eq!(offence_reports(KIND, time_slot), vec![]);
let offence1 = Offence {
validator_set_count: 5,
time_slot,
offenders: vec![5],
};
let offence2 = Offence {
validator_set_count: 5,
time_slot,
offenders: vec![4],
};
// when
Offences::report_offence(vec![], offence1);
with_on_offence_fractions(|f| {
assert_eq!(f.clone(), vec![Perbill::from_percent(25)]);
});
Offences::report_offence(vec![], offence2);
// then
with_on_offence_fractions(|f| {
assert_eq!(f.clone(), vec![Perbill::from_percent(15), Perbill::from_percent(45)]);
});
});
}
#[test]
fn should_not_report_the_same_authority_twice_in_the_same_slot() {
new_test_ext().execute_with(|| {