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

safe multi-era slashing for NPoS #3846

Merged
merged 56 commits into from
Nov 27, 2019
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
c6bdd50
define slashing spans
rphmeier Oct 4, 2019
fef2d4c
tests and pruning for slashing-spans record
rphmeier Oct 4, 2019
59ea56a
validators get slashed before nominators
rphmeier Oct 17, 2019
c6c9da8
apply slash to nominators as well
rphmeier Oct 17, 2019
8427efb
chill and end slashing spans
rphmeier Oct 21, 2019
4288736
actually perform slashes
rphmeier Oct 22, 2019
372a4ce
integration (tests failing)
rphmeier Oct 22, 2019
06af8d0
prune metadata
rphmeier Oct 24, 2019
2468334
fix compilation
rphmeier Oct 24, 2019
d0854ae
some tests for slashing and metadata garbage collection
rphmeier Oct 25, 2019
47f3c54
correctly pass session index to slash handler
rphmeier Oct 25, 2019
3145ac3
test span-max property for nominators and validators
rphmeier Oct 25, 2019
13f3083
test that slashes are summed correctly
rphmeier Oct 25, 2019
187b3f6
reward value computation
rphmeier Oct 27, 2019
52ff078
implement rewarding
rphmeier Oct 27, 2019
e4d9864
add comment about rewards
rphmeier Oct 27, 2019
459ebe7
do not adjust slash fraction in offences module
rphmeier Oct 27, 2019
2d10e2c
fix offences tests
rphmeier Oct 27, 2019
ababe08
remove unused new_offenders field
rphmeier Oct 28, 2019
a682448
update runtime version
rphmeier Oct 28, 2019
d593569
fix up some docs
rphmeier Oct 28, 2019
036fdbd
fix some CI failures
rphmeier Oct 28, 2019
0d9e4da
Merge branch 'master' into rh-npos-slashing
rphmeier Oct 29, 2019
0d3f17f
remove no-std incompatible vec! invocation
rphmeier Oct 29, 2019
177e7e6
try to fix span-max rounding error
rphmeier Oct 29, 2019
ee189aa
Update srml/staking/src/slashing.rs
rphmeier Oct 29, 2019
e673db0
Merge branch 'master' into rh-npos-slashing
rphmeier Oct 29, 2019
f8e7fab
slashes from prior spans don't kick validator again
rphmeier Oct 30, 2019
8a24768
more information for nominators, suppression
rphmeier Nov 6, 2019
8aca6f4
ensure ledger is consistent with itself post-slash
rphmeier Nov 8, 2019
b165b27
implement slash out of unlocking funds also
rphmeier Nov 8, 2019
76608ab
slashing: create records to be applied after-the-fact
rphmeier Nov 9, 2019
9c30d7d
queue slashes for a few eras later
rphmeier Nov 11, 2019
d3ac5a2
method for canceling deferred slashes
rphmeier Nov 11, 2019
8c245b3
Merge branch 'rh-npos-slashing' of github.com:paritytech/substrate in…
rphmeier Nov 11, 2019
a62d08b
Merge branch 'master' into rh-npos-slashing
rphmeier Nov 11, 2019
a5a8e99
attempt to fix test in CI
rphmeier Nov 11, 2019
5bbdafe
Merge branch 'master' into rh-npos-slashing
rphmeier Nov 12, 2019
5ba349e
storage migration for `Nominators`
rphmeier Nov 13, 2019
36f3b34
update node-runtime to use SlashDeferDuration
rphmeier Nov 13, 2019
fd8c70f
Merge branch 'master' into rh-npos-slashing
rphmeier Nov 15, 2019
11b71f3
adjust migration entry-points somewhat
rphmeier Nov 15, 2019
74087ba
fix migration compilation
rphmeier Nov 17, 2019
b823286
add manual Vec import to migration
rphmeier Nov 18, 2019
1866a50
enable migrations feature in node-runtime
rphmeier Nov 18, 2019
7106425
bump runtime version
rphmeier Nov 18, 2019
c44ee91
Merge branch 'master' into rh-npos-slashing
rphmeier Nov 22, 2019
7988052
Merge branch 'master' into rh-npos-slashing
rphmeier Nov 22, 2019
deda336
update to latest master crate renames
rphmeier Nov 22, 2019
aee412c
update to use ensure-origin
rphmeier Nov 22, 2019
d578e2e
Apply suggestions from code review
rphmeier Nov 22, 2019
4e4c254
Merge branch 'master' into rh-npos-slashing
rphmeier Nov 23, 2019
a94d403
fix multi-slash removal
rphmeier Nov 23, 2019
4fc80f7
initialize storage version to current in genesis
rphmeier Nov 27, 2019
b7599d7
add test for version initialization
rphmeier Nov 27, 2019
8f9a743
Merge branch 'master' into rh-npos-slashing
rphmeier Nov 27, 2019
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
2 changes: 1 addition & 1 deletion bin/node/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ nicks = { package = "pallet-nicks", path = "../../../palette/nicks", default-fea
offences = { package = "pallet-offences", path = "../../../palette/offences", default-features = false }
randomness-collective-flip = { package = "pallet-randomness-collective-flip", path = "../../../palette/randomness-collective-flip", default-features = false }
session = { package = "pallet-session", path = "../../../palette/session", default-features = false, features = ["historical"] }
staking = { package = "pallet-staking", path = "../../../palette/staking", default-features = false }
staking = { package = "pallet-staking", path = "../../../palette/staking", default-features = false, features = ["migrate"] }
pallet-staking-reward-curve = { path = "../../../palette/staking/reward-curve"}
sudo = { package = "pallet-sudo", path = "../../../palette/sudo", default-features = false }
support = { package = "palette-support", path = "../../../palette/support", default-features = false }
Expand Down
4 changes: 4 additions & 0 deletions bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ pallet_staking_reward_curve::build! {
parameter_types! {
pub const SessionsPerEra: sr_staking_primitives::SessionIndex = 6;
pub const BondingDuration: staking::EraIndex = 24 * 28;
pub const SlashDeferDuration: staking::EraIndex = 24 * 7; // 1/4 the bonding duration.
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
}

Expand All @@ -263,6 +264,9 @@ impl staking::Trait for Runtime {
type Reward = (); // rewards are minted from the void
type SessionsPerEra = SessionsPerEra;
type BondingDuration = BondingDuration;
type SlashDeferDuration = SlashDeferDuration;
/// A super-majority of the council can cancel the slash.
type SlashCancelOrigin = collective::EnsureProportionAtLeast<_3, _4, AccountId, CouncilCollective>;
Copy link
Member

Choose a reason for hiding this comment

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

👍

type SessionInterface = Self;
type RewardCurve = RewardCurve;
}
Expand Down
1 change: 1 addition & 0 deletions palette/balances/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,7 @@ where
) -> (Self::NegativeImbalance, Self::Balance) {
let free_balance = Self::free_balance(who);
let free_slash = cmp::min(free_balance, value);

Self::set_free_balance(who, free_balance - free_slash);
let remaining_slash = value - free_slash;
// NOTE: `slash()` prefers free balance, but assumes that reserve balance can be drawn
Expand Down
68 changes: 17 additions & 51 deletions palette/offences/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand Down Expand Up @@ -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,
Expand All @@ -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(),
);
}
}

Expand All @@ -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 {
Expand All @@ -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()
Expand All @@ -202,7 +171,6 @@ impl<T: Trait> Module<T> {
storage.save();

Some(TriageOutcome {
new_offenders,
concurrent_offenders,
})
} else {
Expand All @@ -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>>,
}
Expand Down
5 changes: 2 additions & 3 deletions palette/offences/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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(
Expand Down
33 changes: 1 addition & 32 deletions palette/offences/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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(|| {
Expand Down
1 change: 1 addition & 0 deletions palette/staking/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pallet-staking-reward-curve = { path = "../staking/reward-curve"}

[features]
equalize = []
migrate = []
default = ["std", "equalize"]
std = [
"serde",
Expand Down
Loading