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

Commit d080781

Browse files
committed
1 parent f45a2b9 commit d080781

5 files changed

Lines changed: 95 additions & 49 deletions

File tree

frame/staking/src/impls.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,6 +1359,7 @@ where
13591359
>],
13601360
slash_fraction: &[Perbill],
13611361
slash_session: SessionIndex,
1362+
disable_strategy: DisableStrategy,
13621363
) -> Weight {
13631364
let reward_proportion = <SlashRewardFraction<T>>::get();
13641365
let mut consumed_weight: Weight = 0;
@@ -1428,6 +1429,7 @@ where
14281429
window_start,
14291430
now: active_era,
14301431
reward_proportion,
1432+
disable_strategy,
14311433
});
14321434

14331435
if let Some(mut unapplied) = unapplied {

frame/staking/src/lib.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2168,17 +2168,15 @@ pub mod pallet {
21682168
let total_kton = T::KtonCurrency::total_balance(&stash);
21692169
let ed_ring = T::RingCurrency::minimum_balance();
21702170
let ed_kton = T::KtonCurrency::minimum_balance();
2171-
let mut reapable = false;
2172-
2173-
if let Some(ledger) =
2171+
let reapable = if let Some(ledger) =
21742172
Self::ledger(Self::bonded(stash.clone()).ok_or(Error::<T>::NotStash)?)
21752173
{
2176-
reapable = ((total_ring.is_zero() || total_ring < ed_ring)
2174+
((total_ring.is_zero() || total_ring < ed_ring)
21772175
&& (total_kton.is_zero() || total_kton < ed_kton))
2178-
|| (ledger.active < ed_ring && ledger.active_kton < ed_kton);
2176+
|| (ledger.active < ed_ring && ledger.active_kton < ed_kton)
21792177
} else {
2180-
reapable = true;
2181-
}
2178+
true
2179+
};
21822180

21832181
ensure!(reapable, <Error<T>>::FundedTarget);
21842182

frame/staking/src/mock.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -862,11 +862,12 @@ pub fn on_offence_in_era(
862862
>],
863863
slash_fraction: &[Perbill],
864864
era: EraIndex,
865+
disable_strategy: DisableStrategy,
865866
) {
866867
let bonded_eras = <BondedEras<Test>>::get();
867868
for &(bonded_era, start_session) in bonded_eras.iter() {
868869
if bonded_era == era {
869-
let _ = Staking::on_offence(offenders, slash_fraction, start_session);
870+
let _ = Staking::on_offence(offenders, slash_fraction, start_session, disable_strategy);
870871
return;
871872
} else if bonded_era > era {
872873
break;
@@ -878,6 +879,7 @@ pub fn on_offence_in_era(
878879
offenders,
879880
slash_fraction,
880881
Staking::eras_start_session_index(era).unwrap(),
882+
disable_strategy,
881883
);
882884
} else {
883885
panic!("cannot slash in era {}", era);
@@ -892,7 +894,7 @@ pub fn on_offence_now(
892894
slash_fraction: &[Perbill],
893895
) {
894896
let now = active_era();
895-
on_offence_in_era(offenders, slash_fraction, now)
897+
on_offence_in_era(offenders, slash_fraction, now, DisableStrategy::WhenSlashed)
896898
}
897899

898900
pub fn add_slash(who: &AccountId) {

frame/staking/src/slashing.rs

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ use sp_runtime::{
6262
traits::{Saturating, Zero},
6363
DispatchResult, Perbill, RuntimeDebug,
6464
};
65+
use sp_staking::offence::DisableStrategy;
6566
use sp_std::{
6667
ops::{Add, AddAssign, Sub},
6768
prelude::*,
@@ -298,6 +299,8 @@ pub struct SlashParams<'a, T: 'a + Config> {
298299
/// The maximum percentage of a slash that ever gets paid out.
299300
/// This is f_inf in the paper.
300301
pub reward_proportion: Perbill,
302+
/// When to disable offenders.
303+
pub disable_strategy: DisableStrategy,
301304
}
302305

303306
/// Computes a slash of a validator and nominators. It returns an unapplied
@@ -309,30 +312,33 @@ pub struct SlashParams<'a, T: 'a + Config> {
309312
pub fn compute_slash<T: Config>(
310313
params: SlashParams<T>,
311314
) -> Option<UnappliedSlash<T::AccountId, RingBalance<T>, KtonBalance<T>>> {
312-
let SlashParams { stash, slash, exposure, slash_era, window_start, now, reward_proportion } =
313-
params.clone();
314-
315315
let mut reward_payout = Zero::zero();
316316
let mut val_slashed = Zero::zero();
317317

318318
// is the slash amount here a maximum for the era?
319-
let own_slash =
320-
RK { r: slash * exposure.own_ring_balance, k: slash * exposure.own_kton_balance };
321-
if (slash * exposure.total_power).is_zero() {
319+
let own_slash = RK {
320+
r: params.slash * params.exposure.own_ring_balance,
321+
k: params.slash * params.exposure.own_kton_balance,
322+
};
323+
if (params.slash * params.exposure.total_power).is_zero() {
322324
// kick out the validator even if they won't be slashed,
323325
// as long as the misbehavior is from their most recent slashing span.
324326
kick_out_if_recent::<T>(params);
325327
return None;
326328
}
327329

328330
let (prior_slash_p, _era_slash) =
329-
<Pallet<T> as Store>::ValidatorSlashInEra::get(&slash_era, stash)
331+
<Pallet<T> as Store>::ValidatorSlashInEra::get(&params.slash_era, params.stash)
330332
.unwrap_or((Perbill::zero(), Zero::zero()));
331333

332334
// compare slash proportions rather than slash values to avoid issues due to rounding
333335
// error.
334-
if slash.deconstruct() > prior_slash_p.deconstruct() {
335-
<Pallet<T> as Store>::ValidatorSlashInEra::insert(&slash_era, stash, &(slash, own_slash));
336+
if params.slash.deconstruct() > prior_slash_p.deconstruct() {
337+
<Pallet<T> as Store>::ValidatorSlashInEra::insert(
338+
&params.slash_era,
339+
params.stash,
340+
&(params.slash, own_slash),
341+
);
336342
} else {
337343
// we slash based on the max in era - this new event is not the max,
338344
// so neither the validator or any nominators will need an update.
@@ -347,35 +353,34 @@ pub fn compute_slash<T: Config>(
347353
// apply slash to validator.
348354
{
349355
let mut spans = fetch_spans::<T>(
350-
stash,
351-
window_start,
356+
params.stash,
357+
params.window_start,
352358
&mut reward_payout,
353359
&mut val_slashed,
354-
reward_proportion,
360+
params.reward_proportion,
355361
);
356362

357-
let target_span = spans.compare_and_update_span_slash(slash_era, own_slash);
363+
let target_span = spans.compare_and_update_span_slash(params.slash_era, own_slash);
358364

359365
if target_span == Some(spans.span_index()) {
360366
// misbehavior occurred within the current slashing span - take appropriate
361367
// actions.
362368

363369
// chill the validator - it misbehaved in the current span and should
364370
// not continue in the next election. also end the slashing span.
365-
spans.end_span(now);
366-
<Pallet<T>>::chill_stash(stash);
371+
spans.end_span(params.now);
372+
<Pallet<T>>::chill_stash(params.stash);
367373
}
368374
}
369375

370-
// add the validator to the offenders list and make sure it is disabled for
371-
// the duration of the era
372-
add_offending_validator::<T>(params.stash, true);
376+
let disable_when_slashed = params.disable_strategy != DisableStrategy::Never;
377+
add_offending_validator::<T>(params.stash, disable_when_slashed);
373378

374379
let mut nominators_slashed = vec![];
375-
reward_payout += slash_nominators::<T>(params, prior_slash_p, &mut nominators_slashed);
380+
reward_payout += slash_nominators::<T>(params.clone(), prior_slash_p, &mut nominators_slashed);
376381

377382
Some(UnappliedSlash {
378-
validator: stash.clone(),
383+
validator: params.stash.clone(),
379384
own: val_slashed,
380385
others: nominators_slashed,
381386
reporters: vec![],
@@ -402,9 +407,8 @@ fn kick_out_if_recent<T: Config>(params: SlashParams<T>) {
402407
<Pallet<T>>::chill_stash(params.stash);
403408
}
404409

405-
// add the validator to the offenders list but since there's no slash being
406-
// applied there's no need to disable the validator
407-
add_offending_validator::<T>(params.stash, false);
410+
let disable_without_slash = params.disable_strategy == DisableStrategy::Always;
411+
add_offending_validator::<T>(params.stash, disable_without_slash);
408412
}
409413

410414
/// Add the given validator to the offenders list and optionally disable it.
@@ -457,13 +461,10 @@ fn slash_nominators<T: Config>(
457461
prior_slash_p: Perbill,
458462
nominators_slashed: &mut Vec<(T::AccountId, RKT<T>)>,
459463
) -> RKT<T> {
460-
let SlashParams { stash: _, slash, exposure, slash_era, window_start, now, reward_proportion } =
461-
params;
462-
463464
let mut reward_payout = Zero::zero();
464465

465-
nominators_slashed.reserve(exposure.others.len());
466-
for nominator in &exposure.others {
466+
nominators_slashed.reserve(params.exposure.others.len());
467+
for nominator in &params.exposure.others {
467468
let stash = &nominator.who;
468469
let mut nom_slashed = Zero::zero();
469470

@@ -474,16 +475,19 @@ fn slash_nominators<T: Config>(
474475
r: prior_slash_p * nominator.ring_balance,
475476
k: prior_slash_p * nominator.kton_balance,
476477
};
477-
let own_slash_by_validator =
478-
RK { r: slash * nominator.ring_balance, k: slash * nominator.kton_balance };
478+
let own_slash_by_validator = RK {
479+
r: params.slash * nominator.ring_balance,
480+
k: params.slash * nominator.kton_balance,
481+
};
479482
let own_slash_difference = own_slash_by_validator.saturating_sub(own_slash_prior);
480483

481-
let mut era_slash = <Pallet<T> as Store>::NominatorSlashInEra::get(&slash_era, stash)
482-
.unwrap_or_else(|| Zero::zero());
484+
let mut era_slash =
485+
<Pallet<T> as Store>::NominatorSlashInEra::get(&params.slash_era, stash)
486+
.unwrap_or_else(|| Zero::zero());
483487

484488
era_slash += own_slash_difference;
485489

486-
<Pallet<T> as Store>::NominatorSlashInEra::insert(&slash_era, stash, &era_slash);
490+
<Pallet<T> as Store>::NominatorSlashInEra::insert(&params.slash_era, stash, &era_slash);
487491

488492
era_slash
489493
};
@@ -492,18 +496,18 @@ fn slash_nominators<T: Config>(
492496
{
493497
let mut spans = fetch_spans::<T>(
494498
stash,
495-
window_start,
499+
params.window_start,
496500
&mut reward_payout,
497501
&mut nom_slashed,
498-
reward_proportion,
502+
params.reward_proportion,
499503
);
500504

501-
let target_span = spans.compare_and_update_span_slash(slash_era, era_slash);
505+
let target_span = spans.compare_and_update_span_slash(params.slash_era, era_slash);
502506

503507
if target_span == Some(spans.span_index()) {
504508
// End the span, but don't chill the nominator. its nomination
505509
// on this validator will be ignored in the future.
506-
spans.end_span(now);
510+
spans.end_span(params.now);
507511
}
508512
}
509513

frame/staking/src/substrate_tests.rs

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2633,6 +2633,7 @@ fn slash_in_old_span_does_not_deselect() {
26332633
}],
26342634
&[Perbill::from_percent(0)],
26352635
1,
2636+
DisableStrategy::WhenSlashed,
26362637
);
26372638

26382639
// the validator doesn't get chilled again
@@ -2649,6 +2650,7 @@ fn slash_in_old_span_does_not_deselect() {
26492650
// NOTE: A 100% slash here would clean up the account, causing de-registration.
26502651
&[Perbill::from_percent(95)],
26512652
1,
2653+
DisableStrategy::WhenSlashed,
26522654
);
26532655

26542656
// the validator doesn't get chilled again
@@ -2955,6 +2957,7 @@ fn slashing_nominators_by_span_max() {
29552957
}],
29562958
&[Perbill::from_percent(10)],
29572959
2,
2960+
DisableStrategy::WhenSlashed,
29582961
);
29592962

29602963
assert_eq!(Ring::free_balance(11), 900);
@@ -2981,6 +2984,7 @@ fn slashing_nominators_by_span_max() {
29812984
}],
29822985
&[Perbill::from_percent(30)],
29832986
3,
2987+
DisableStrategy::WhenSlashed,
29842988
);
29852989

29862990
// 11 was not further slashed, but 21 and 101 were.
@@ -3002,6 +3006,7 @@ fn slashing_nominators_by_span_max() {
30023006
}],
30033007
&[Perbill::from_percent(20)],
30043008
2,
3009+
DisableStrategy::WhenSlashed,
30053010
);
30063011

30073012
// 11 was further slashed, but 21 and 101 were not.
@@ -3137,6 +3142,7 @@ fn remove_deferred() {
31373142
&[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }],
31383143
&[Perbill::from_percent(15)],
31393144
1,
3145+
DisableStrategy::WhenSlashed,
31403146
);
31413147

31423148
// fails if empty
@@ -3330,6 +3336,40 @@ fn non_slashable_offence_doesnt_disable_validator() {
33303336
});
33313337
}
33323338

3339+
#[test]
3340+
fn slashing_independent_of_disabling_validator() {
3341+
ExtBuilder::default().build_and_execute(|| {
3342+
mock::start_active_era(1);
3343+
assert_eq_uvec!(Session::validators(), vec![11, 21]);
3344+
3345+
let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, &11);
3346+
let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21);
3347+
3348+
let now = Staking::active_era().unwrap().index;
3349+
3350+
// offence with no slash associated, BUT disabling
3351+
on_offence_in_era(
3352+
&[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }],
3353+
&[Perbill::zero()],
3354+
now,
3355+
DisableStrategy::Always,
3356+
);
3357+
3358+
// offence that slashes 25% of the bond, BUT not disabling
3359+
on_offence_in_era(
3360+
&[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }],
3361+
&[Perbill::from_percent(25)],
3362+
now,
3363+
DisableStrategy::Never,
3364+
);
3365+
3366+
// the offence for validator 10 was explicitly disabled
3367+
assert!(is_disabled(10));
3368+
// whereas validator 20 is explicitly not disabled
3369+
assert!(!is_disabled(20));
3370+
});
3371+
}
3372+
33333373
#[test]
33343374
fn offence_threshold_triggers_new_era() {
33353375
ExtBuilder::default()
@@ -4032,7 +4072,7 @@ fn offences_weight_calculated_correctly() {
40324072
// On offence with zero offenders: 4 Reads, 1 Write
40334073
let zero_offence_weight = <Test as frame_system::Config>::DbWeight::get().reads_writes(4, 1);
40344074
assert_eq!(
4035-
Staking::on_offence(&[], &[Perbill::from_percent(50)], 0),
4075+
Staking::on_offence(&[], &[Perbill::from_percent(50)], 0, DisableStrategy::WhenSlashed),
40364076
zero_offence_weight
40374077
);
40384078

@@ -4056,7 +4096,7 @@ fn offences_weight_calculated_correctly() {
40564096
})
40574097
.collect();
40584098
assert_eq!(
4059-
Staking::on_offence(&offenders, &[Perbill::from_percent(50)], 0),
4099+
Staking::on_offence(&offenders, &[Perbill::from_percent(50)], 0, DisableStrategy::WhenSlashed),
40604100
n_offence_unapplied_weight
40614101
);
40624102

@@ -4081,7 +4121,7 @@ fn offences_weight_calculated_correctly() {
40814121
+ <Test as frame_system::Config>::DbWeight::get().reads_writes(2, 2);
40824122

40834123
assert_eq!(
4084-
Staking::on_offence(&one_offender, &[Perbill::from_percent(50)], 0),
4124+
Staking::on_offence(&one_offender, &[Perbill::from_percent(50)], 0, DisableStrategy::WhenSlashed),
40854125
one_offence_unapplied_weight
40864126
);
40874127
});

0 commit comments

Comments
 (0)