Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
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
78 changes: 74 additions & 4 deletions srml/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ use sr_primitives::traits::{
};
use sr_staking_primitives::{
SessionIndex, CurrentElectedSet,
offence::{OnOffenceHandler, OffenceDetails},
offence::{OnOffenceHandler, OffenceDetails, Offence, ReportOffence},
};
#[cfg(feature = "std")]
use sr_primitives::{Serialize, Deserialize};
Expand Down Expand Up @@ -441,6 +441,15 @@ pub struct Exposure<AccountId, Balance: HasCompact> {
pub others: Vec<IndividualExposure<AccountId, Balance>>,
}

/// A slashing event occurred, slashing a validator for a given amount of balance.
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct SlashJournalEntry<AccountId, Balance: HasCompact> {
who: AccountId,
amount: Balance,
own_slash: Balance, // the amount of `who`'s own exposure that was slashed
}

pub type BalanceOf<T> =
<<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;
type PositiveImbalanceOf<T> =
Expand Down Expand Up @@ -614,6 +623,10 @@ decl_storage! {

/// A mapping from still-bonded eras to the first session index of that era.
BondedEras: Vec<(EraIndex, SessionIndex)>;

/// All slashes that have occurred in a given era.
EraSlashJournal get(era_slash_journal):
map EraIndex => Vec<SlashJournalEntry<T::AccountId, BalanceOf<T>>>;
}
add_extra_genesis {
config(stakers):
Expand Down Expand Up @@ -659,6 +672,9 @@ decl_event!(
Reward(Balance),
/// One validator (and its nominators) has been slashed by the given amount.
Slash(AccountId, Balance),
/// An old slashing report from a prior era was discarded because it could
/// not be processed.
OldSlashingReportDiscarded(SessionIndex),
}
);

Expand Down Expand Up @@ -1030,17 +1046,33 @@ impl<T: Trait> Module<T> {
/// Removes the slash from the validator's balance by preference,
/// and reduces the nominators' balance if needed.
///
/// Returns the resulting `NegativeImbalance` to allow distributing the slashed amount.
/// Returns the resulting `NegativeImbalance` to allow distributing the slashed amount and
/// pushes an entry onto the slash journal.
fn slash_validator(
stash: &T::AccountId,
slash: BalanceOf<T>,
exposure: &Exposure<T::AccountId, BalanceOf<T>>,
journal: &mut Vec<SlashJournalEntry<T::AccountId, BalanceOf<T>>>,
) -> NegativeImbalanceOf<T> {
// The amount we are actually going to slash (can't be bigger than the validator's total
// exposure)
let slash = slash.min(exposure.total);

// limit what we'll slash of the stash's own to only what's in
// the exposure.
//
// note: this is fine only because we limit reports of the current era.
// otherwise, these funds may have already been slashed due to something
// reported from a prior era.
let already_slashed_own = journal.iter()
.filter(|entry| &entry.who == stash)
Copy link
Contributor

Choose a reason for hiding this comment

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

I suppose stash does not change, right? You can't move to another stash account without unbonding/bonding?

Copy link
Member

Choose a reason for hiding this comment

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

correct

.map(|entry| entry.own_slash)
.fold(<BalanceOf<T>>::zero(), |a, c| a.saturating_add(c));

let own_remaining = exposure.own.saturating_sub(already_slashed_own);

// The amount we'll slash from the validator's stash directly.
let own_slash = exposure.own.min(slash);
let own_slash = own_remaining.min(slash);
let (mut imbalance, missing) = T::Currency::slash(stash, own_slash);
let own_slash = own_slash - missing;
// The amount remaining that we can't slash from the validator,
Expand All @@ -1058,6 +1090,12 @@ impl<T: Trait> Module<T> {
}
}

journal.push(SlashJournalEntry {
who: stash.clone(),
own_slash: own_slash.clone(),
amount: slash,
});

// trigger the event
Self::deposit_event(
RawEvent::Slash(stash.clone(), slash)
Expand Down Expand Up @@ -1178,6 +1216,10 @@ impl<T: Trait> Module<T> {

// Increment current era.
let current_era = CurrentEra::mutate(|s| { *s += 1; *s });

// prune journal for last era.
<EraSlashJournal<T>>::remove(current_era - 1);
Copy link
Contributor

Choose a reason for hiding this comment

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

Couldn't we prune era's with some offset (X) and accept reports for last X eras?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could, but it wouldn't be of any use with this runtime. Historical slashing just isn't doable without more work beyond this milestone.


CurrentEraStartSessionIndex::mutate(|v| {
*v = start_session_index;
});
Expand Down Expand Up @@ -1471,6 +1513,7 @@ impl<T: Trait> SelectInitialValidators<T::AccountId> for Module<T> {
}
}

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

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

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

// Handle the rest of imbalances
T::Slash::on_unbalanced(remaining_imbalance);
}
}

/// Filter historical offences out and only allow those from the current era.
pub struct FilterHistoricalOffences<T, R> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not hook it up in node-runtime right away?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wanted to, but I didn't see that ReportOffence was used anywhere

_inner: rstd::marker::PhantomData<(T, R)>,
}

impl<T, Reporter, Offender, R, O> ReportOffence<Reporter, Offender, O>
for FilterHistoricalOffences<Module<T>, R> where
T: Trait,
R: ReportOffence<Reporter, Offender, O>,
O: Offence<Offender>,
{
fn report_offence(reporters: Vec<Reporter>, offence: O) {
// disallow any slashing from before the current era.
let offence_session = offence.session_index();
if offence_session >= <Module<T>>::current_era_start_session_index() {
R::report_offence(reporters, offence)
} else {
<Module<T>>::deposit_event(
RawEvent::OldSlashingReportDiscarded(offence_session).into()
)
}
}
}

/// Returns the currently elected validator set represented by their stash accounts.
pub struct CurrentElectedStashAccounts<T>(rstd::marker::PhantomData<T>);

Expand Down
2 changes: 1 addition & 1 deletion srml/staking/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1910,7 +1910,7 @@ fn reward_validator_slashing_validator_doesnt_overflow() {
]});

// Check slashing
let _ = Staking::slash_validator(&11, reward_slash, &Staking::stakers(&11));
let _ = Staking::slash_validator(&11, reward_slash, &Staking::stakers(&11), &mut Vec::new());
assert_eq!(Balances::total_balance(&11), stake - 1);
assert_eq!(Balances::total_balance(&2), 1);
})
Expand Down