Skip to content

Commit

Permalink
Improves the EPM/Staking e2e test setup (paritytech#14292)
Browse files Browse the repository at this point in the history
* Adds onchainify to e2e tests; Adds try-runtime checks at the end of the tests

* Refactors OCW to be more realistic and to submit solutions during unsigned phase

* Uses config palllet Default impl in mock

* Improves OCW progression vode

* simplified OCW tests
  • Loading branch information
gpestana authored Jul 18, 2023
1 parent d6b45e5 commit b975ee7
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 142 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ publish = false
targets = ["x86_64-unknown-linux-gnu"]

[dev-dependencies]
parking_lot = "0.12.1"
codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] }
scale-info = { version = "2.0.1", features = ["derive"] }
log = { version = "0.4.17", default-features = false }
Expand Down
213 changes: 129 additions & 84 deletions frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,14 @@ fn log_current_time() {

#[test]
fn block_progression_works() {
ExtBuilder::default().build_and_execute(|| {
let (mut ext, pool_state, _) = ExtBuilder::default().build_offchainify();

ext.execute_with(|| {
assert_eq!(active_era(), 0);
assert_eq!(Session::current_index(), 0);
assert!(ElectionProviderMultiPhase::current_phase().is_off());

assert!(start_next_active_era().is_ok());
assert!(start_next_active_era(pool_state.clone()).is_ok());
assert_eq!(active_era(), 1);
assert_eq!(Session::current_index(), <SessionsPerEra as Get<u32>>::get());

Expand All @@ -68,12 +70,14 @@ fn block_progression_works() {
assert!(ElectionProviderMultiPhase::current_phase().is_signed());
});

ExtBuilder::default().build_and_execute(|| {
let (mut ext, pool_state, _) = ExtBuilder::default().build_offchainify();

ext.execute_with(|| {
assert_eq!(active_era(), 0);
assert_eq!(Session::current_index(), 0);
assert!(ElectionProviderMultiPhase::current_phase().is_off());

assert!(start_next_active_era_delayed_solution().is_ok());
assert!(start_next_active_era_delayed_solution(pool_state).is_ok());
// if the solution is delayed, EPM will end up in emergency mode..
assert!(ElectionProviderMultiPhase::current_phase().is_emergency());
// .. era won't progress..
Expand All @@ -83,6 +87,45 @@ fn block_progression_works() {
})
}

#[test]
fn offchainify_works() {
use pallet_election_provider_multi_phase::QueuedSolution;

let staking_builder = StakingExtBuilder::default();
let epm_builder = EpmExtBuilder::default();
let (mut ext, pool_state, _) = ExtBuilder::default()
.epm(epm_builder)
.staking(staking_builder)
.build_offchainify();

ext.execute_with(|| {
// test ocw progression and solution queue if submission when unsigned phase submission is
// not delayed.
for _ in 0..100 {
roll_one(pool_state.clone(), false);
let current_phase = ElectionProviderMultiPhase::current_phase();

assert!(
match QueuedSolution::<Runtime>::get() {
Some(_) => current_phase.is_unsigned(),
None => !current_phase.is_unsigned(),
},
"solution must be queued *only* in unsigned phase"
);
}

// test ocw solution queue if submission in unsigned phase is delayed.
for _ in 0..100 {
roll_one(pool_state.clone(), true);
assert_eq!(
QueuedSolution::<Runtime>::get(),
None,
"solution must never be submitted and stored since it is delayed"
);
}
})
}

#[test]
/// Replicates the Kusama incident of 8th Dec 2022 and its resolution through the governance
/// fallback.
Expand All @@ -99,8 +142,9 @@ fn block_progression_works() {
/// restarts. Note that in this test case, the emergency throttling is disabled.
fn enters_emergency_phase_after_forcing_before_elect() {
let epm_builder = EpmExtBuilder::default().disable_emergency_throttling();
let (mut ext, pool_state, _) = ExtBuilder::default().epm(epm_builder).build_offchainify();

ExtBuilder::default().epm(epm_builder).build_and_execute(|| {
ext.execute_with(|| {
log!(
trace,
"current validators (staking): {:?}",
Expand All @@ -117,15 +161,15 @@ fn enters_emergency_phase_after_forcing_before_elect() {

assert_eq!(pallet_staking::ForceEra::<Runtime>::get(), pallet_staking::Forcing::ForceNew);

advance_session_delayed_solution();
advance_session_delayed_solution(pool_state.clone());
assert!(ElectionProviderMultiPhase::current_phase().is_emergency());
log_current_time();

let era_before_delayed_next = Staking::current_era();
// try to advance 2 eras.
assert!(start_next_active_era_delayed_solution().is_ok());
assert!(start_next_active_era_delayed_solution(pool_state.clone()).is_ok());
assert_eq!(Staking::current_era(), era_before_delayed_next);
assert!(start_next_active_era().is_err());
assert!(start_next_active_era(pool_state).is_err());
assert_eq!(Staking::current_era(), era_before_delayed_next);

// EPM is still in emergency phase.
Expand Down Expand Up @@ -169,41 +213,43 @@ fn continous_slashes_below_offending_threshold() {
let staking_builder = StakingExtBuilder::default().validator_count(10);
let epm_builder = EpmExtBuilder::default().disable_emergency_throttling();

ExtBuilder::default()
.staking(staking_builder)
let (mut ext, pool_state, _) = ExtBuilder::default()
.epm(epm_builder)
.build_and_execute(|| {
assert_eq!(Session::validators().len(), 10);
let mut active_validator_set = Session::validators();

roll_to_epm_signed();

// set a minimum election score.
assert!(set_minimum_election_score(500, 1000, 500).is_ok());

// slash 10% of the active validators and progress era until the minimum trusted score
// is reached.
while active_validator_set.len() > 0 {
let slashed = slash_percentage(Perbill::from_percent(10));
assert_eq!(slashed.len(), 1);

// break loop when era does not progress; EPM is in emergency phase as election
// failed due to election minimum score.
if start_next_active_era().is_err() {
assert!(ElectionProviderMultiPhase::current_phase().is_emergency());
break
}
.staking(staking_builder)
.build_offchainify();

active_validator_set = Session::validators();
ext.execute_with(|| {
assert_eq!(Session::validators().len(), 10);
let mut active_validator_set = Session::validators();

log!(
trace,
"slashed 10% of active validators ({:?}). After slash: {:?}",
slashed,
active_validator_set
);
roll_to_epm_signed();

// set a minimum election score.
assert!(set_minimum_election_score(500, 1000, 500).is_ok());

// slash 10% of the active validators and progress era until the minimum trusted score
// is reached.
while active_validator_set.len() > 0 {
let slashed = slash_percentage(Perbill::from_percent(10));
assert_eq!(slashed.len(), 1);

// break loop when era does not progress; EPM is in emergency phase as election
// failed due to election minimum score.
if start_next_active_era(pool_state.clone()).is_err() {
assert!(ElectionProviderMultiPhase::current_phase().is_emergency());
break
}
});

active_validator_set = Session::validators();

log!(
trace,
"slashed 10% of active validators ({:?}). After slash: {:?}",
slashed,
active_validator_set
);
}
});
}

#[test]
Expand All @@ -223,54 +269,53 @@ fn set_validation_intention_after_chilled() {
use frame_election_provider_support::SortedListProvider;
use pallet_staking::{Event, Forcing, Nominators};

let staking_builder = StakingExtBuilder::default();
let epm_builder = EpmExtBuilder::default();
let (mut ext, pool_state, _) = ExtBuilder::default()
.epm(EpmExtBuilder::default())
.staking(StakingExtBuilder::default())
.build_offchainify();

ExtBuilder::default()
.staking(staking_builder)
.epm(epm_builder)
.build_and_execute(|| {
assert_eq!(active_era(), 0);
// validator is part of the validator set.
assert!(Session::validators().contains(&81));
assert!(<Runtime as pallet_staking::Config>::VoterList::contains(&81));

// nominate validator 81.
assert_ok!(Staking::nominate(RuntimeOrigin::signed(21), vec![81]));
assert_eq!(Nominators::<Runtime>::get(21).unwrap().targets, vec![81]);

// validator is slashed. it is removed from the `VoterList` through chilling but in the
// current era, the validator is still part of the active validator set.
add_slash(&81);
assert!(Session::validators().contains(&81));
assert!(!<Runtime as pallet_staking::Config>::VoterList::contains(&81));
assert_eq!(
staking_events(),
[
Event::Chilled { stash: 81 },
Event::ForceEra { mode: Forcing::ForceNew },
Event::SlashReported {
validator: 81,
slash_era: 0,
fraction: Perbill::from_percent(10)
}
],
);
ext.execute_with(|| {
assert_eq!(active_era(), 0);
// validator is part of the validator set.
assert!(Session::validators().contains(&41));
assert!(<Runtime as pallet_staking::Config>::VoterList::contains(&41));

// nominate validator 81.
assert_ok!(Staking::nominate(RuntimeOrigin::signed(21), vec![41]));
assert_eq!(Nominators::<Runtime>::get(21).unwrap().targets, vec![41]);

// validator is slashed. it is removed from the `VoterList` through chilling but in the
// current era, the validator is still part of the active validator set.
add_slash(&41);
assert!(Session::validators().contains(&41));
assert!(!<Runtime as pallet_staking::Config>::VoterList::contains(&41));
assert_eq!(
staking_events(),
[
Event::Chilled { stash: 41 },
Event::ForceEra { mode: Forcing::ForceNew },
Event::SlashReported {
validator: 41,
slash_era: 0,
fraction: Perbill::from_percent(10)
}
],
);

// after the nominator is slashed and chilled, the nominations remain.
assert_eq!(Nominators::<Runtime>::get(21).unwrap().targets, vec![81]);
// after the nominator is slashed and chilled, the nominations remain.
assert_eq!(Nominators::<Runtime>::get(21).unwrap().targets, vec![41]);

// validator sets intention to stake again in the same era it was chilled.
assert_ok!(Staking::validate(RuntimeOrigin::signed(81), Default::default()));
// validator sets intention to stake again in the same era it was chilled.
assert_ok!(Staking::validate(RuntimeOrigin::signed(41), Default::default()));

// progress era and check that the slashed validator is still part of the validator
// set.
assert!(start_next_active_era().is_ok());
assert_eq!(active_era(), 1);
assert!(Session::validators().contains(&81));
assert!(<Runtime as pallet_staking::Config>::VoterList::contains(&81));
// progress era and check that the slashed validator is still part of the validator
// set.
assert!(start_next_active_era(pool_state).is_ok());
assert_eq!(active_era(), 1);
assert!(Session::validators().contains(&41));
assert!(<Runtime as pallet_staking::Config>::VoterList::contains(&41));

// nominations are still active as before the slash.
assert_eq!(Nominators::<Runtime>::get(21).unwrap().targets, vec![81]);
})
// nominations are still active as before the slash.
assert_eq!(Nominators::<Runtime>::get(21).unwrap().targets, vec![41]);
})
}
Loading

0 comments on commit b975ee7

Please sign in to comment.