Newer
Older
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
#[test]
fn offchain_window_is_triggered_when_force_always() {
ExtBuilder::default()
.session_per_era(5)
.session_length(10)
.election_lookahead(3)
.build()
.execute_with(|| {
ForceEra::put(Forcing::ForceAlways);
run_to_block(16);
assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
run_to_block(17); // instead of 37
assert_eq!(Staking::era_election_status(), ElectionStatus::Open(17));
run_to_block(20);
assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
run_to_block(26);
assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
run_to_block(27); // next one again
assert_eq!(Staking::era_election_status(), ElectionStatus::Open(27));
})
}
#[test]
fn offchain_window_closes_when_forcenone() {
ExtBuilder::default()
.session_per_era(5)
.session_length(10)
.election_lookahead(3)
.build()
.execute_with(|| {
ForceEra::put(Forcing::ForceNone);
run_to_block(36);
assert_session_era!(3, 0);
assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
// opens
run_to_block(37);
assert_eq!(Staking::era_election_status(), ElectionStatus::Open(37));
assert!(Staking::is_current_session_final());
assert!(Staking::snapshot_validators().is_some());
// closes normally
run_to_block(40);
assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
assert!(!Staking::is_current_session_final());
assert!(Staking::snapshot_validators().is_none());
assert_session_era!(4, 0);
run_to_block(47);
assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
assert_session_era!(4, 0);
run_to_block(57);
assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
assert_session_era!(5, 0);
run_to_block(67);
assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
// Will not open again as scheduled
run_to_block(87);
assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
assert_session_era!(8, 0);
run_to_block(90);
assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
assert_session_era!(9, 0);
fn offchain_window_on_chain_fallback_works() {
ExtBuilder::default().build_and_execute(|| {
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
start_session(1);
start_session(2);
assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
// some election must have happened by now.
assert_eq!(
System::events()
.into_iter()
.map(|r| r.event)
.filter_map(|e| {
if let MetaEvent::staking(inner) = e {
Some(inner)
} else {
None
}
})
.last()
.unwrap(),
RawEvent::StakingElection(ElectionCompute::OnChain),
);
})
}
#[test]
fn offchain_wont_work_if_snapshot_fails() {
ExtBuilder::default()
.offchain_election_ext()
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
.build()
.execute_with(|| {
run_to_block(12);
assert!(Staking::snapshot_validators().is_some());
assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12));
// validate more than the limit
let limit: NominatorIndex = ValidatorIndex::max_value() as NominatorIndex + 1;
let ctrl = 1_000_000;
for i in 0..limit {
bond_validator((1000 + i).into(), (1000 + i + ctrl).into(), 100);
}
// window stays closed since no snapshot was taken.
run_to_block(27);
assert!(Staking::snapshot_validators().is_none());
assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
})
}
#[test]
fn staking_is_locked_when_election_window_open() {
ExtBuilder::default()
.offchain_election_ext()
.election_lookahead(3)
.build()
.execute_with(|| {
run_to_block(12);
assert!(Staking::snapshot_validators().is_some());
assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12));
// chill et. al. are now not allowed.
assert_noop!(
Staking::chill(Origin::signed(10)),
Error::<Test>::CallNotAllowed,
);
})
}
#[test]
fn signed_result_can_be_submitted() {
// should check that we have a new validator set normally, event says that it comes from
// offchain.
.offchain_election_ext()
.build()
.execute_with(|| {
run_to_block(12);
assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12));
assert!(Staking::snapshot_validators().is_some());
let (compact, winners, score) = prepare_submission_with(true, true, 2, |_| {});
assert_ok!(submit_solution(
Origin::signed(10),
winners,
compact,
score,
));
let queued_result = Staking::queued_elected().unwrap();
assert_eq!(queued_result.compute, ElectionCompute::Signed);
assert_eq!(
System::events()
.into_iter()
.map(|r| r.event)
.filter_map(|e| {
if let MetaEvent::staking(inner) = e {
Some(inner)
} else {
None
}
})
.last()
.unwrap(),
RawEvent::SolutionStored(ElectionCompute::Signed),
);
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
run_to_block(15);
assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
assert_eq!(
System::events()
.into_iter()
.map(|r| r.event)
.filter_map(|e| {
if let MetaEvent::staking(inner) = e {
Some(inner)
} else {
None
}
})
.last()
.unwrap(),
RawEvent::StakingElection(ElectionCompute::Signed),
);
})
}
#[test]
fn signed_result_can_be_submitted_later() {
// same as `signed_result_can_be_submitted` but at a later block.
ExtBuilder::default()
.offchain_election_ext()
.build()
.execute_with(|| {
run_to_block(14);
assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12));
let (compact, winners, score) = prepare_submission_with(true, true, 2, |_| {});
assert_ok!(submit_solution(Origin::signed(10), winners, compact, score));
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
let queued_result = Staking::queued_elected().unwrap();
assert_eq!(queued_result.compute, ElectionCompute::Signed);
run_to_block(15);
assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
assert_eq!(
System::events()
.into_iter()
.map(|r| r.event)
.filter_map(|e| {
if let MetaEvent::staking(inner) = e {
Some(inner)
} else {
None
}
})
.last()
.unwrap(),
RawEvent::StakingElection(ElectionCompute::Signed),
);
})
}
#[test]
fn early_solution_submission_is_rejected() {
// should check that we have a new validator set normally, event says that it comes from
// offchain.
.offchain_election_ext()
.build()
.execute_with(|| {
run_to_block(11);
// submission is not yet allowed
assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
// create all the indices just to build the solution.
Staking::create_stakers_snapshot();
let (compact, winners, score) = prepare_submission_with(true, true, 2, |_| {});
Staking::kill_stakers_snapshot();
assert_err_with_weight!(
Staking::submit_election_solution(
Origin::signed(10),
winners.clone(),
compact.clone(),
current_era(),
ElectionSize::default(),
Error::<Test>::OffchainElectionEarlySubmission,
Some(<Test as frame_system::Trait>::DbWeight::get().reads(1)),
);
})
}
#[test]
fn weak_solution_is_rejected() {
// A solution which is weaker than what we currently have on-chain is rejected.
ExtBuilder::default()
.offchain_election_ext()
.has_stakers(false)
.validator_count(4)
.build()
.execute_with(|| {
build_offchain_election_test_ext();
run_to_block(12);
// a good solution
let (compact, winners, score) = prepare_submission_with(true, true, 2, |_| {});
assert_ok!(submit_solution(
Origin::signed(10),
winners,
compact,
score,
));
// a bad solution
let (compact, winners, score) = horrible_npos_solution(false);
assert_err_with_weight!(
submit_solution(
winners.clone(),
compact.clone(),
Error::<Test>::OffchainElectionWeakSubmission,
Some(<Test as frame_system::Trait>::DbWeight::get().reads(3))
);
})
}
#[test]
fn better_solution_is_accepted() {
// A solution which is better than what we currently have on-chain is accepted.
ExtBuilder::default()
.offchain_election_ext()
.validator_count(4)
.has_stakers(false)
.build()
.execute_with(|| {
build_offchain_election_test_ext();
run_to_block(12);
// a meeeeh solution
let (compact, winners, score) = horrible_npos_solution(false);
assert_ok!(submit_solution(
Origin::signed(10),
winners,
compact,
score,
));
// a better solution
let (compact, winners, score) = prepare_submission_with(true, true, 2, |_| {});
assert_ok!(submit_solution(
Origin::signed(10),
winners,
compact,
score,
));
})
}
#[test]
fn offchain_worker_runs_when_window_open() {
// at the end of the first finalized block with ElectionStatus::open(_), it should execute.
let mut ext = ExtBuilder::default()
.offchain_election_ext()
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
ext.execute_with(|| {
run_to_block(12);
// local key 11 is in the elected set.
assert_eq_uvec!(Session::validators(), vec![11, 21]);
assert_eq!(state.read().transactions.len(), 0);
Staking::offchain_worker(12);
assert_eq!(state.read().transactions.len(), 1);
let encoded = state.read().transactions[0].clone();
let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap();
let call = extrinsic.call;
let inner = match call {
mock::Call::Staking(inner) => inner,
};
assert_eq!(
<Staking as sp_runtime::traits::ValidateUnsigned>::validate_unsigned(
TransactionSource::Local,
&inner,
),
TransactionValidity::Ok(ValidTransaction {
priority: UnsignedPriority::get() + 1125, // the proposed slot stake.
provides: vec![("StakingOffchain", current_era()).encode()],
longevity: 3,
propagate: false,
})
)
})
}
fn offchain_worker_runs_with_balancing() {
// Offchain worker balances based on the number provided by randomness. See the difference
// in the priority, which comes from the computed score.
let mut ext = ExtBuilder::default()
.offchain_election_ext()
.validator_count(2)
.max_offchain_iterations(2)
.build();
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
ext.execute_with(|| {
run_to_block(12);
// local key 11 is in the elected set.
assert_eq_uvec!(Session::validators(), vec![11, 21]);
assert_eq!(state.read().transactions.len(), 0);
Staking::offchain_worker(12);
assert_eq!(state.read().transactions.len(), 1);
let encoded = state.read().transactions[0].clone();
let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap();
let call = extrinsic.call;
let inner = match call {
mock::Call::Staking(inner) => inner,
};
assert_eq!(
<Staking as sp_runtime::traits::ValidateUnsigned>::validate_unsigned(
TransactionSource::Local,
&inner,
),
TransactionValidity::Ok(ValidTransaction {
// the proposed slot stake, with balance_solution.
priority: UnsignedPriority::get() + 1250,
requires: vec![],
provides: vec![("StakingOffchain", active_era()).encode()],
longevity: 3,
propagate: false,
})
)
})
}
#[test]
fn mediocre_submission_from_authority_is_early_rejected() {
let mut ext = ExtBuilder::default()
.offchain_election_ext()
ext.execute_with(|| {
run_to_block(12);
// put a good solution on-chain
let (compact, winners, score) = prepare_submission_with(true, true, 2, |_| {});
assert_ok!(submit_solution(
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
Origin::signed(10),
winners,
compact,
score,
),);
// now run the offchain worker in the same chain state.
Staking::offchain_worker(12);
assert_eq!(state.read().transactions.len(), 1);
let encoded = state.read().transactions[0].clone();
let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap();
let call = extrinsic.call;
let inner = match call {
mock::Call::Staking(inner) => inner,
};
// pass this call to ValidateUnsigned
assert_eq!(
<Staking as sp_runtime::traits::ValidateUnsigned>::validate_unsigned(
TransactionSource::Local,
&inner,
),
TransactionValidity::Err(
InvalidTransaction::Custom(<Error<Test>>::OffchainElectionWeakSubmission.as_u8()).into(),
),
)
})
}
#[test]
fn invalid_election_correct_number_of_winners() {
.offchain_election_ext()
.validator_count(4)
.has_stakers(false)
.build()
.execute_with(|| {
build_offchain_election_test_ext();
run_to_block(12);
ValidatorCount::put(3);
let (compact, winners, score) = prepare_submission_with(true, true, 2, |_| {});
ValidatorCount::put(4);
assert_eq!(winners.len(), 3);
assert_noop!(
submit_solution(
Origin::signed(10),
winners,
compact,
score,
),
Error::<Test>::OffchainElectionBogusWinnerCount,
);
})
}
#[test]
fn invalid_election_solution_size() {
.offchain_election_ext()
.build()
.execute_with(|| {
run_to_block(12);
let (compact, winners, score) = prepare_submission_with(true, true, 2, |_| {});
assert_noop!(
Staking::submit_election_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
ElectionSize::default(),
Error::<Test>::OffchainElectionBogusElectionSize,
fn invalid_election_correct_number_of_winners_1() {
// if we have too little validators, then the number of candidates is the bound.
ExtBuilder::default()
.offchain_election_ext()
.validator_count(8) // we simply cannot elect 8
.has_stakers(false)
.build()
.execute_with(|| {
build_offchain_election_test_ext();
run_to_block(12);
ValidatorCount::put(3);
let (compact, winners, score) = prepare_submission_with(true, true, 2, |_| {});
ValidatorCount::put(4);
assert_eq!(winners.len(), 3);
assert_noop!(
Origin::signed(10),
winners,
compact,
score,
),
Error::<Test>::OffchainElectionBogusWinnerCount,
fn invalid_election_correct_number_of_winners_2() {
// if we have too little validators, then the number of candidates is the bound.
ExtBuilder::default()
.offchain_election_ext()
.validator_count(8) // we simply cannot elect 8
.has_stakers(false)
.build()
.execute_with(|| {
build_offchain_election_test_ext();
let (compact, winners, score) = prepare_submission_with(true, true, 2, |_| {});
assert_eq!(winners.len(), 4);
// all good. We chose 4 and it works.
assert_ok!(submit_solution(
Origin::signed(10),
winners,
compact,
score,
),);
})
}
#[test]
fn invalid_election_out_of_bound_nominator_index() {
// A nominator index which is simply invalid
ExtBuilder::default()
.offchain_election_ext()
.validator_count(4)
.has_stakers(false)
.build()
.execute_with(|| {
build_offchain_election_test_ext();
run_to_block(12);
assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4);
assert_eq!(Staking::snapshot_validators().unwrap().len(), 4);
let (mut compact, winners, score) = prepare_submission_with(true, true, 2, |_| {});
// index 9 doesn't exist.
compact.votes1.push((9, 2));
// The error type sadly cannot be more specific now.
assert_noop!(
Origin::signed(10),
winners,
compact,
score,
),
Error::<Test>::OffchainElectionBogusCompact,
fn invalid_election_out_of_bound_validator_index() {
// A validator index which is out of bound
ExtBuilder::default()
.offchain_election_ext()
.has_stakers(false)
.build()
.execute_with(|| {
build_offchain_election_test_ext();
run_to_block(12);
assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4);
assert_eq!(Staking::snapshot_validators().unwrap().len(), 4);
let (mut compact, winners, score) = prepare_submission_with(true, true, 2, |_| {});
compact.votes1.iter_mut().for_each(|(_, vidx)| if *vidx == 1 { *vidx = 4 });
// The error type sadly cannot be more specific now.
assert_noop!(
Origin::signed(10),
winners,
compact,
score,
),
Error::<Test>::OffchainElectionBogusCompact,
fn invalid_election_out_of_bound_winner_index() {
// A winner index which is simply invalid
ExtBuilder::default()
.offchain_election_ext()
.validator_count(4)
.has_stakers(false)
.build()
.execute_with(|| {
build_offchain_election_test_ext();
run_to_block(12);
assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4);
assert_eq!(Staking::snapshot_validators().unwrap().len(), 4);
let (compact, _, score) = prepare_submission_with(true, true, 2, |_| {});
// index 4 doesn't exist.
let winners = vec![0, 1, 2, 4];
assert_noop!(
Origin::signed(10),
winners,
compact,
score,
),
Error::<Test>::OffchainElectionBogusWinner,
fn invalid_election_non_winner_validator_index() {
// An edge that points to a correct validator index who is NOT a winner. This is very
// similar to the test that raises `OffchainElectionBogusNomination`.
.offchain_election_ext()
.validator_count(2) // we select only 2.
.has_stakers(false)
.build()
.execute_with(|| {
build_offchain_election_test_ext();
run_to_block(12);
assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4);
assert_eq!(Staking::snapshot_validators().unwrap().len(), 4);
let (compact, winners, score) = prepare_submission_with(false, true, 2, |a| {
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
// swap all 11 and 41s in the distribution with non-winners. Note that it is
// important that the count of winners and the count of unique targets remain
// valid.
a.iter_mut().for_each(| StakedAssignment { who, distribution } |
distribution.iter_mut().for_each(|(t, _)| {
if *t == 41 { *t = 31 } else { *t = 21 }
// if it is self vote, correct that.
if *who == 41 { *who = 31 }
if *who == 11 { *who = 21 }
})
);
});
assert_noop!(
submit_solution(
Origin::signed(10),
winners,
compact,
score,
),
Error::<Test>::OffchainElectionBogusNomination,
);
})
}
#[test]
fn offchain_election_unique_target_count_is_checked() {
// Number of unique targets and and winners.len must match.
ExtBuilder::default()
.offchain_election_ext()
.validator_count(2) // we select only 2.
.has_stakers(false)
.build()
.execute_with(|| {
build_offchain_election_test_ext();
run_to_block(12);
assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4);
assert_eq!(Staking::snapshot_validators().unwrap().len(), 4);
let (compact, winners, score) = prepare_submission_with(false, true, 2, |a| {
a.iter_mut()
.find(|x| x.who == 5)
// just add any new target.
.map(|x| {
// old value.
assert_eq!(x.distribution, vec![(41, 100)]);
// new value.
x.distribution = vec![(21, 50), (41, 50)]
});
Origin::signed(10),
winners,
compact,
score,
),
Error::<Test>::OffchainElectionBogusWinnerCount,
fn invalid_election_wrong_self_vote() {
// A self vote for someone else.
ExtBuilder::default()
.offchain_election_ext()
.validator_count(4)
.has_stakers(false)
.build()
.execute_with(|| {
build_offchain_election_test_ext();
let (compact, winners, score) = prepare_submission_with(true, true, 2, |a| {
// mutate a self vote to target someone else. That someone else is still among the
// winners
a.iter_mut().find(|x| x.who == 11).map(|x| {
x.distribution
.iter_mut()
.find(|y| y.0 == 11)
.map(|y| y.0 = 21)
});
});
assert_noop!(
Origin::signed(10),
winners,
compact,
score,
),
Error::<Test>::OffchainElectionBogusSelfVote,
fn invalid_election_wrong_self_vote_2() {
// A self validator voting for someone else next to self vote.
ExtBuilder::default()
.offchain_election_ext()
.validator_count(4)
.has_stakers(false)
.build()
.execute_with(|| {
build_offchain_election_test_ext();
let (compact, winners, score) = prepare_submission_with(true, true, 2, |a| {
// Remove the self vote.
a.retain(|x| x.who != 11);
// add is as a new double vote
a.push(StakedAssignment {
who: 11,
distribution: vec![(11, 50), (21, 50)],
});
});
// This raises score issue.
assert_noop!(
Origin::signed(10),
winners,
compact,
score,
),
Error::<Test>::OffchainElectionBogusSelfVote,
fn invalid_election_over_stake() {
// Someone's edge ratios sums to more than 100%.
ExtBuilder::default()
.offchain_election_ext()
.validator_count(4)
.has_stakers(false)
.build()
.execute_with(|| {
build_offchain_election_test_ext();
run_to_block(12);
// Note: we don't reduce here to be able to tweak votes3. votes3 will vanish if you
// reduce.
let (mut compact, winners, score) = prepare_submission_with(true, false, 0, |_| {});
if let Some(c) = compact.votes3.iter_mut().find(|x| x.0 == 0) {
// by default it should have been (0, [(2, 33%), (1, 33%)], 0)
// now the sum is above 100%
c.1 = [(2, percent(66)), (1, percent(66))];
}
assert_noop!(
Origin::signed(10),
winners,
compact,
score,
),
Error::<Test>::OffchainElectionBogusCompact,
fn invalid_election_under_stake() {
// at the time of this writing, we cannot under stake someone. The compact assignment works
// in a way that some of the stakes are presented by the submitter, and the last one is read
// from chain by subtracting the rest from total. Hence, the sum is always correct.
// This test is only here as a demonstration.
}
#[test]
fn invalid_election_invalid_target_stealing() {
// A valid voter who voted for someone who is a candidate, and is a correct winner, but is
// actually NOT nominated by this nominator.
ExtBuilder::default()
.offchain_election_ext()
.validator_count(4)
.has_stakers(false)
.build()
.execute_with(|| {
build_offchain_election_test_ext();
let (compact, winners, score) = prepare_submission_with(true, false, 0, |a| {
// 3 only voted for 20 and 40. We add a fake vote to 30. The stake sum is still
// correctly 100.
a.iter_mut()
.find(|x| x.who == 3)
.map(|x| x.distribution = vec![(21, 50), (41, 30), (31, 20)]);
});
assert_noop!(
Origin::signed(10),
winners,
compact,
score,
),
Error::<Test>::OffchainElectionBogusNomination,
);
})
}
#[test]
fn nomination_slash_filter_is_checked() {
// If a nominator has voted for someone who has been recently slashed, that particular
// nomination should be disabled for the upcoming election. A solution must respect this
// rule.
ExtBuilder::default()
.offchain_election_ext()
.validator_count(4)
.has_stakers(false)
.build()
.execute_with(|| {
build_offchain_election_test_ext();
// finalize the round with fallback. This is needed since all nominator submission
// are in era zero and we want this one to pass with no problems.
run_to_block(15);
// go to the next session to trigger mock::start_era and bump the active era
run_to_block(20);
// slash 10. This must happen outside of the election window.
let offender_expo = Staking::eras_stakers(Staking::active_era().unwrap().index, 11);
on_offence_now(
&[OffenceDetails {
offender: (11, offender_expo.clone()),
reporters: vec![],
}],
&[Perbill::from_percent(50)],
);
// validate 10 again for the next round. But this guy will not have the votes that
// it should have had from 1 and 2.
assert_ok!(Staking::validate(
Origin::signed(10),
Default::default()
));
// open the election window and create snapshots.
run_to_block(32);
// a solution that has been prepared after the slash.
let (compact, winners, score) = prepare_submission_with(true, false, 0, |a| {
// no one is allowed to vote for 10, except for itself.
a.into_iter()
.filter(|s| s.who != 11)
.for_each(|s|
assert!(s.distribution.iter().find(|(t, _)| *t == 11).is_none())
);
});
// can be submitted.
assert_ok!(submit_solution(
Origin::signed(10),
winners,
compact,
score,
));
// a wrong solution.
let (compact, winners, score) = prepare_submission_with(true, false, 0, |a| {
// add back the vote that has been filtered out.
a.push(StakedAssignment {
who: 1,
distribution: vec![(11, 100)]
});
});
// is rejected.
assert_noop!(
Origin::signed(10),
winners,
compact,
score,
),
Error::<Test>::OffchainElectionSlashedNomination,
fn invalid_election_wrong_score() {
// A valid voter who's total distributed stake is more than what they bond
ExtBuilder::default()
.offchain_election_ext()
.validator_count(4)
.has_stakers(false)
.build()
.execute_with(|| {
build_offchain_election_test_ext();
let (compact, winners, mut score) = prepare_submission_with(true, true, 2, |_| {});
score[0] += 1;
assert_noop!(
Origin::signed(10),
winners,
compact,
score,