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

Commit 2d0fe68

Browse files
rphmeiertomusdrw
authored andcommitted
Allow slashing only on current era (#3411)
* only slash in current era * prune journal for last era * comment own_slash * emit an event when old slashing events are discarded
1 parent b7f44fc commit 2d0fe68

File tree

2 files changed

+75
-5
lines changed

2 files changed

+75
-5
lines changed

srml/staking/src/lib.rs

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ use sr_primitives::traits::{
280280
};
281281
use sr_staking_primitives::{
282282
SessionIndex, CurrentElectedSet,
283-
offence::{OnOffenceHandler, OffenceDetails},
283+
offence::{OnOffenceHandler, OffenceDetails, Offence, ReportOffence},
284284
};
285285
#[cfg(feature = "std")]
286286
use sr_primitives::{Serialize, Deserialize};
@@ -441,6 +441,15 @@ pub struct Exposure<AccountId, Balance: HasCompact> {
441441
pub others: Vec<IndividualExposure<AccountId, Balance>>,
442442
}
443443

444+
/// A slashing event occurred, slashing a validator for a given amount of balance.
445+
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default)]
446+
#[cfg_attr(feature = "std", derive(Debug))]
447+
pub struct SlashJournalEntry<AccountId, Balance: HasCompact> {
448+
who: AccountId,
449+
amount: Balance,
450+
own_slash: Balance, // the amount of `who`'s own exposure that was slashed
451+
}
452+
444453
pub type BalanceOf<T> =
445454
<<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;
446455
type PositiveImbalanceOf<T> =
@@ -614,6 +623,10 @@ decl_storage! {
614623

615624
/// A mapping from still-bonded eras to the first session index of that era.
616625
BondedEras: Vec<(EraIndex, SessionIndex)>;
626+
627+
/// All slashes that have occurred in a given era.
628+
EraSlashJournal get(era_slash_journal):
629+
map EraIndex => Vec<SlashJournalEntry<T::AccountId, BalanceOf<T>>>;
617630
}
618631
add_extra_genesis {
619632
config(stakers):
@@ -659,6 +672,9 @@ decl_event!(
659672
Reward(Balance),
660673
/// One validator (and its nominators) has been slashed by the given amount.
661674
Slash(AccountId, Balance),
675+
/// An old slashing report from a prior era was discarded because it could
676+
/// not be processed.
677+
OldSlashingReportDiscarded(SessionIndex),
662678
}
663679
);
664680

@@ -1030,17 +1046,33 @@ impl<T: Trait> Module<T> {
10301046
/// Removes the slash from the validator's balance by preference,
10311047
/// and reduces the nominators' balance if needed.
10321048
///
1033-
/// Returns the resulting `NegativeImbalance` to allow distributing the slashed amount.
1049+
/// Returns the resulting `NegativeImbalance` to allow distributing the slashed amount and
1050+
/// pushes an entry onto the slash journal.
10341051
fn slash_validator(
10351052
stash: &T::AccountId,
10361053
slash: BalanceOf<T>,
10371054
exposure: &Exposure<T::AccountId, BalanceOf<T>>,
1055+
journal: &mut Vec<SlashJournalEntry<T::AccountId, BalanceOf<T>>>,
10381056
) -> NegativeImbalanceOf<T> {
10391057
// The amount we are actually going to slash (can't be bigger than the validator's total
10401058
// exposure)
10411059
let slash = slash.min(exposure.total);
1060+
1061+
// limit what we'll slash of the stash's own to only what's in
1062+
// the exposure.
1063+
//
1064+
// note: this is fine only because we limit reports of the current era.
1065+
// otherwise, these funds may have already been slashed due to something
1066+
// reported from a prior era.
1067+
let already_slashed_own = journal.iter()
1068+
.filter(|entry| &entry.who == stash)
1069+
.map(|entry| entry.own_slash)
1070+
.fold(<BalanceOf<T>>::zero(), |a, c| a.saturating_add(c));
1071+
1072+
let own_remaining = exposure.own.saturating_sub(already_slashed_own);
1073+
10421074
// The amount we'll slash from the validator's stash directly.
1043-
let own_slash = exposure.own.min(slash);
1075+
let own_slash = own_remaining.min(slash);
10441076
let (mut imbalance, missing) = T::Currency::slash(stash, own_slash);
10451077
let own_slash = own_slash - missing;
10461078
// The amount remaining that we can't slash from the validator,
@@ -1058,6 +1090,12 @@ impl<T: Trait> Module<T> {
10581090
}
10591091
}
10601092

1093+
journal.push(SlashJournalEntry {
1094+
who: stash.clone(),
1095+
own_slash: own_slash.clone(),
1096+
amount: slash,
1097+
});
1098+
10611099
// trigger the event
10621100
Self::deposit_event(
10631101
RawEvent::Slash(stash.clone(), slash)
@@ -1178,6 +1216,10 @@ impl<T: Trait> Module<T> {
11781216

11791217
// Increment current era.
11801218
let current_era = CurrentEra::mutate(|s| { *s += 1; *s });
1219+
1220+
// prune journal for last era.
1221+
<EraSlashJournal<T>>::remove(current_era - 1);
1222+
11811223
CurrentEraStartSessionIndex::mutate(|v| {
11821224
*v = start_session_index;
11831225
});
@@ -1471,6 +1513,7 @@ impl<T: Trait> SelectInitialValidators<T::AccountId> for Module<T> {
14711513
}
14721514
}
14731515

1516+
/// This is intended to be used with `FilterHistoricalOffences`.
14741517
impl <T: Trait> OnOffenceHandler<T::AccountId, session::historical::IdentificationTuple<T>> for Module<T> where
14751518
T: session::Trait<ValidatorId = <T as system::Trait>::AccountId>,
14761519
T: session::historical::Trait<
@@ -1489,6 +1532,8 @@ impl <T: Trait> OnOffenceHandler<T::AccountId, session::historical::Identificati
14891532
let mut remaining_imbalance = <NegativeImbalanceOf<T>>::zero();
14901533
let slash_reward_fraction = SlashRewardFraction::get();
14911534

1535+
let era_now = Self::current_era();
1536+
let mut journal = Self::era_slash_journal(era_now);
14921537
for (details, slash_fraction) in offenders.iter().zip(slash_fraction) {
14931538
let stash = &details.offender.0;
14941539
let exposure = &details.offender.1;
@@ -1512,7 +1557,7 @@ impl <T: Trait> OnOffenceHandler<T::AccountId, session::historical::Identificati
15121557
// force a new era, to select a new validator set
15131558
ForceEra::put(Forcing::ForceNew);
15141559
// actually slash the validator
1515-
let slashed_amount = Self::slash_validator(stash, amount, exposure);
1560+
let slashed_amount = Self::slash_validator(stash, amount, exposure, &mut journal);
15161561

15171562
// distribute the rewards according to the slash
15181563
let slash_reward = slash_reward_fraction * slashed_amount.peek();
@@ -1533,12 +1578,37 @@ impl <T: Trait> OnOffenceHandler<T::AccountId, session::historical::Identificati
15331578
remaining_imbalance.subsume(slashed_amount);
15341579
}
15351580
}
1581+
<EraSlashJournal<T>>::insert(era_now, journal);
15361582

15371583
// Handle the rest of imbalances
15381584
T::Slash::on_unbalanced(remaining_imbalance);
15391585
}
15401586
}
15411587

1588+
/// Filter historical offences out and only allow those from the current era.
1589+
pub struct FilterHistoricalOffences<T, R> {
1590+
_inner: rstd::marker::PhantomData<(T, R)>,
1591+
}
1592+
1593+
impl<T, Reporter, Offender, R, O> ReportOffence<Reporter, Offender, O>
1594+
for FilterHistoricalOffences<Module<T>, R> where
1595+
T: Trait,
1596+
R: ReportOffence<Reporter, Offender, O>,
1597+
O: Offence<Offender>,
1598+
{
1599+
fn report_offence(reporters: Vec<Reporter>, offence: O) {
1600+
// disallow any slashing from before the current era.
1601+
let offence_session = offence.session_index();
1602+
if offence_session >= <Module<T>>::current_era_start_session_index() {
1603+
R::report_offence(reporters, offence)
1604+
} else {
1605+
<Module<T>>::deposit_event(
1606+
RawEvent::OldSlashingReportDiscarded(offence_session).into()
1607+
)
1608+
}
1609+
}
1610+
}
1611+
15421612
/// Returns the currently elected validator set represented by their stash accounts.
15431613
pub struct CurrentElectedStashAccounts<T>(rstd::marker::PhantomData<T>);
15441614

srml/staking/src/tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1910,7 +1910,7 @@ fn reward_validator_slashing_validator_doesnt_overflow() {
19101910
]});
19111911

19121912
// Check slashing
1913-
let _ = Staking::slash_validator(&11, reward_slash, &Staking::stakers(&11));
1913+
let _ = Staking::slash_validator(&11, reward_slash, &Staking::stakers(&11), &mut Vec::new());
19141914
assert_eq!(Balances::total_balance(&11), stake - 1);
19151915
assert_eq!(Balances::total_balance(&2), 1);
19161916
})

0 commit comments

Comments
 (0)