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

Commit f5f7839

Browse files
kianenigmapmikolajczyk41
authored andcommitted
Properly defer slashes (paritytech#11823)
* initial draft of fixing slashing * fix test * Update frame/staking/src/tests.rs Co-authored-by: Piotr Mikołajczyk <[email protected]> * last touches * add more detail about unbonding * add migration * fmt Co-authored-by: Piotr Mikołajczyk <[email protected]> Co-authored-by: parity-processbot <>
1 parent c799398 commit f5f7839

File tree

6 files changed

+177
-41
lines changed

6 files changed

+177
-41
lines changed

frame/staking/src/lib.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -872,11 +872,12 @@ enum Releases {
872872
V2_0_0,
873873
V3_0_0,
874874
V4_0_0,
875-
V5_0_0, // blockable validators.
876-
V6_0_0, // removal of all storage associated with offchain phragmen.
877-
V7_0_0, // keep track of number of nominators / validators in map
878-
V8_0_0, // populate `VoterList`.
879-
V9_0_0, // inject validators into `VoterList` as well.
875+
V5_0_0, // blockable validators.
876+
V6_0_0, // removal of all storage associated with offchain phragmen.
877+
V7_0_0, // keep track of number of nominators / validators in map
878+
V8_0_0, // populate `VoterList`.
879+
V9_0_0, // inject validators into `VoterList` as well.
880+
V10_0_0, // remove `EarliestUnappliedSlash`.
880881
}
881882

882883
impl Default for Releases {

frame/staking/src/migrations.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,29 @@ use super::*;
2020
use frame_election_provider_support::SortedListProvider;
2121
use frame_support::traits::OnRuntimeUpgrade;
2222

23+
pub mod v10 {
24+
use super::*;
25+
use frame_support::storage_alias;
26+
27+
#[storage_alias]
28+
type EarliestUnappliedSlash<T: Config> = StorageValue<Pallet<T>, EraIndex>;
29+
30+
pub struct MigrateToV10<T>(sp_std::marker::PhantomData<T>);
31+
impl<T: Config> OnRuntimeUpgrade for MigrateToV10<T> {
32+
fn on_runtime_upgrade() -> frame_support::weights::Weight {
33+
if StorageVersion::<T>::get() == Releases::V9_0_0 {
34+
EarliestUnappliedSlash::<T>::kill();
35+
StorageVersion::<T>::put(Releases::V10_0_0);
36+
37+
T::DbWeight::get().reads_writes(1, 1)
38+
} else {
39+
log!(warn, "MigrateToV10 should be removed.");
40+
T::DbWeight::get().reads(1)
41+
}
42+
}
43+
}
44+
}
45+
2346
pub mod v9 {
2447
use super::*;
2548

frame/staking/src/mock.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,7 @@ impl ExtBuilder {
549549
ext
550550
}
551551
pub fn build_and_execute(self, test: impl FnOnce() -> ()) {
552+
sp_tracing::try_init_simple();
552553
let mut ext = self.build();
553554
ext.execute_with(test);
554555
ext.execute_with(post_conditions);
@@ -884,6 +885,20 @@ pub(crate) fn staking_events() -> Vec<crate::Event<Test>> {
884885
.collect()
885886
}
886887

888+
parameter_types! {
889+
static StakingEventsIndex: usize = 0;
890+
}
891+
892+
pub(crate) fn staking_events_since_last_call() -> Vec<crate::Event<Test>> {
893+
let all: Vec<_> = System::events()
894+
.into_iter()
895+
.filter_map(|r| if let Event::Staking(inner) = r.event { Some(inner) } else { None })
896+
.collect();
897+
let seen = StakingEventsIndex::get();
898+
StakingEventsIndex::set(all.len());
899+
all.into_iter().skip(seen).collect()
900+
}
901+
887902
pub(crate) fn balances(who: &AccountId) -> (Balance, Balance) {
888903
(Balances::free_balance(who), Balances::reserved_balance(who))
889904
}

frame/staking/src/pallet/impls.rs

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use frame_support::{
3232
use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin};
3333
use pallet_session::historical;
3434
use sp_runtime::{
35-
traits::{Bounded, Convert, SaturatedConversion, Saturating, StaticLookup, Zero},
35+
traits::{Bounded, Convert, One, SaturatedConversion, Saturating, StaticLookup, Zero},
3636
Perbill,
3737
};
3838
use sp_staking::{
@@ -599,20 +599,17 @@ impl<T: Config> Pallet<T> {
599599

600600
/// Apply previously-unapplied slashes on the beginning of a new era, after a delay.
601601
fn apply_unapplied_slashes(active_era: EraIndex) {
602-
let slash_defer_duration = T::SlashDeferDuration::get();
603-
<Self as Store>::EarliestUnappliedSlash::mutate(|earliest| {
604-
if let Some(ref mut earliest) = earliest {
605-
let keep_from = active_era.saturating_sub(slash_defer_duration);
606-
for era in (*earliest)..keep_from {
607-
let era_slashes = <Self as Store>::UnappliedSlashes::take(&era);
608-
for slash in era_slashes {
609-
slashing::apply_slash::<T>(slash, era);
610-
}
611-
}
612-
613-
*earliest = (*earliest).max(keep_from)
614-
}
615-
})
602+
let era_slashes = <Self as Store>::UnappliedSlashes::take(&active_era);
603+
log!(
604+
debug,
605+
"found {} slashes scheduled to be executed in era {:?}",
606+
era_slashes.len(),
607+
active_era,
608+
);
609+
for slash in era_slashes {
610+
let slash_era = active_era.saturating_sub(T::SlashDeferDuration::get());
611+
slashing::apply_slash::<T>(slash, slash_era);
612+
}
616613
}
617614

618615
/// Add reward points to validators using their stash account ID.
@@ -1209,11 +1206,6 @@ where
12091206
}
12101207
};
12111208

1212-
<Self as Store>::EarliestUnappliedSlash::mutate(|earliest| {
1213-
if earliest.is_none() {
1214-
*earliest = Some(active_era)
1215-
}
1216-
});
12171209
add_db_reads_writes(1, 1);
12181210

12191211
let slash_defer_duration = T::SlashDeferDuration::get();
@@ -1263,9 +1255,18 @@ where
12631255
}
12641256
} else {
12651257
// Defer to end of some `slash_defer_duration` from now.
1266-
<Self as Store>::UnappliedSlashes::mutate(active_era, move |for_later| {
1267-
for_later.push(unapplied)
1268-
});
1258+
log!(
1259+
debug,
1260+
"deferring slash of {:?}% happened in {:?} (reported in {:?}) to {:?}",
1261+
slash_fraction,
1262+
slash_era,
1263+
active_era,
1264+
slash_era + slash_defer_duration + 1,
1265+
);
1266+
<Self as Store>::UnappliedSlashes::mutate(
1267+
slash_era.saturating_add(slash_defer_duration).saturating_add(One::one()),
1268+
move |for_later| for_later.push(unapplied),
1269+
);
12691270
add_db_reads_writes(1, 1);
12701271
}
12711272
} else {

frame/staking/src/pallet/mod.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -477,10 +477,6 @@ pub mod pallet {
477477
ValueQuery,
478478
>;
479479

480-
/// The earliest era for which we have a pending, unapplied slash.
481-
#[pallet::storage]
482-
pub(crate) type EarliestUnappliedSlash<T> = StorageValue<_, EraIndex>;
483-
484480
/// The last planned session scheduled by the session pallet.
485481
///
486482
/// This is basically in sync with the call to [`pallet_session::SessionManager::new_session`].

frame/staking/src/tests.rs

Lines changed: 109 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2777,12 +2777,103 @@ fn deferred_slashes_are_deferred() {
27772777
assert_eq!(Balances::free_balance(11), 1000);
27782778
assert_eq!(Balances::free_balance(101), 2000);
27792779

2780+
System::reset_events();
2781+
27802782
// at the start of era 4, slashes from era 1 are processed,
27812783
// after being deferred for at least 2 full eras.
27822784
mock::start_active_era(4);
27832785

27842786
assert_eq!(Balances::free_balance(11), 900);
27852787
assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10));
2788+
2789+
assert_eq!(
2790+
staking_events_since_last_call(),
2791+
vec![
2792+
Event::StakersElected,
2793+
Event::EraPaid(3, 11075, 33225),
2794+
Event::Slashed(11, 100),
2795+
Event::Slashed(101, 12)
2796+
]
2797+
);
2798+
})
2799+
}
2800+
2801+
#[test]
2802+
fn retroactive_deferred_slashes_two_eras_before() {
2803+
ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| {
2804+
assert_eq!(BondingDuration::get(), 3);
2805+
2806+
mock::start_active_era(1);
2807+
let exposure_11_at_era1 = Staking::eras_stakers(active_era(), 11);
2808+
2809+
mock::start_active_era(3);
2810+
on_offence_in_era(
2811+
&[OffenceDetails { offender: (11, exposure_11_at_era1), reporters: vec![] }],
2812+
&[Perbill::from_percent(10)],
2813+
1, // should be deferred for two full eras, and applied at the beginning of era 4.
2814+
DisableStrategy::Never,
2815+
);
2816+
System::reset_events();
2817+
2818+
mock::start_active_era(4);
2819+
2820+
assert_eq!(
2821+
staking_events_since_last_call(),
2822+
vec![
2823+
Event::StakersElected,
2824+
Event::EraPaid(3, 7100, 21300),
2825+
Event::Slashed(11, 100),
2826+
Event::Slashed(101, 12)
2827+
]
2828+
);
2829+
})
2830+
}
2831+
2832+
#[test]
2833+
fn retroactive_deferred_slashes_one_before() {
2834+
ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| {
2835+
assert_eq!(BondingDuration::get(), 3);
2836+
2837+
mock::start_active_era(1);
2838+
let exposure_11_at_era1 = Staking::eras_stakers(active_era(), 11);
2839+
2840+
// unbond at slash era.
2841+
mock::start_active_era(2);
2842+
assert_ok!(Staking::chill(Origin::signed(10)));
2843+
assert_ok!(Staking::unbond(Origin::signed(10), 100));
2844+
2845+
mock::start_active_era(3);
2846+
on_offence_in_era(
2847+
&[OffenceDetails { offender: (11, exposure_11_at_era1), reporters: vec![] }],
2848+
&[Perbill::from_percent(10)],
2849+
2, // should be deferred for two full eras, and applied at the beginning of era 5.
2850+
DisableStrategy::Never,
2851+
);
2852+
System::reset_events();
2853+
2854+
mock::start_active_era(4);
2855+
assert_eq!(
2856+
staking_events_since_last_call(),
2857+
vec![Event::StakersElected, Event::EraPaid(3, 11075, 33225)]
2858+
);
2859+
2860+
assert_eq!(Staking::ledger(10).unwrap().total, 1000);
2861+
// slash happens after the next line.
2862+
mock::start_active_era(5);
2863+
assert_eq!(
2864+
staking_events_since_last_call(),
2865+
vec![
2866+
Event::StakersElected,
2867+
Event::EraPaid(4, 11075, 33225),
2868+
Event::Slashed(11, 100),
2869+
Event::Slashed(101, 12)
2870+
]
2871+
);
2872+
2873+
// their ledger has already been slashed.
2874+
assert_eq!(Staking::ledger(10).unwrap().total, 900);
2875+
assert_ok!(Staking::unbond(Origin::signed(10), 1000));
2876+
assert_eq!(Staking::ledger(10).unwrap().total, 900);
27862877
})
27872878
}
27882879

@@ -2871,6 +2962,7 @@ fn remove_deferred() {
28712962
assert_eq!(Balances::free_balance(101), 2000);
28722963
let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value;
28732964

2965+
// deferred to start of era 4.
28742966
on_offence_now(
28752967
&[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }],
28762968
&[Perbill::from_percent(10)],
@@ -2881,6 +2973,7 @@ fn remove_deferred() {
28812973

28822974
mock::start_active_era(2);
28832975

2976+
// reported later, but deferred to start of era 4 as well.
28842977
on_offence_in_era(
28852978
&[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }],
28862979
&[Perbill::from_percent(15)],
@@ -2894,7 +2987,8 @@ fn remove_deferred() {
28942987
Error::<Test>::EmptyTargets
28952988
);
28962989

2897-
assert_ok!(Staking::cancel_deferred_slash(Origin::root(), 1, vec![0]));
2990+
// cancel one of them.
2991+
assert_ok!(Staking::cancel_deferred_slash(Origin::root(), 4, vec![0]));
28982992

28992993
assert_eq!(Balances::free_balance(11), 1000);
29002994
assert_eq!(Balances::free_balance(101), 2000);
@@ -2906,13 +3000,19 @@ fn remove_deferred() {
29063000

29073001
// at the start of era 4, slashes from era 1 are processed,
29083002
// after being deferred for at least 2 full eras.
3003+
System::reset_events();
29093004
mock::start_active_era(4);
29103005

2911-
// the first slash for 10% was cancelled, so no effect.
2912-
assert_eq!(Balances::free_balance(11), 1000);
2913-
assert_eq!(Balances::free_balance(101), 2000);
2914-
2915-
mock::start_active_era(5);
3006+
// the first slash for 10% was cancelled, but the 15% one
3007+
assert_eq!(
3008+
staking_events_since_last_call(),
3009+
vec![
3010+
Event::StakersElected,
3011+
Event::EraPaid(3, 11075, 33225),
3012+
Event::Slashed(11, 50),
3013+
Event::Slashed(101, 7)
3014+
]
3015+
);
29163016

29173017
let slash_10 = Perbill::from_percent(10);
29183018
let slash_15 = Perbill::from_percent(15);
@@ -2965,7 +3065,7 @@ fn remove_multi_deferred() {
29653065
&[Perbill::from_percent(25)],
29663066
);
29673067

2968-
assert_eq!(<Staking as Store>::UnappliedSlashes::get(&1).len(), 5);
3068+
assert_eq!(<Staking as Store>::UnappliedSlashes::get(&4).len(), 5);
29693069

29703070
// fails if list is not sorted
29713071
assert_noop!(
@@ -2983,9 +3083,9 @@ fn remove_multi_deferred() {
29833083
Error::<Test>::InvalidSlashIndex
29843084
);
29853085

2986-
assert_ok!(Staking::cancel_deferred_slash(Origin::root(), 1, vec![0, 2, 4]));
3086+
assert_ok!(Staking::cancel_deferred_slash(Origin::root(), 4, vec![0, 2, 4]));
29873087

2988-
let slashes = <Staking as Store>::UnappliedSlashes::get(&1);
3088+
let slashes = <Staking as Store>::UnappliedSlashes::get(&4);
29893089
assert_eq!(slashes.len(), 2);
29903090
assert_eq!(slashes[0].validator, 21);
29913091
assert_eq!(slashes[1].validator, 42);

0 commit comments

Comments
 (0)