Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
9 changes: 6 additions & 3 deletions frame/election-provider-multi-phase/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,10 @@ fn solution_with_size<T: Config>(
let score = solution.clone().score(stake_of, voter_at, target_at).unwrap();
let round = <MultiPhase<T>>::round();

assert!(score[0] > 0, "score is zero, this probably means that the stakes are not set.");
assert!(
score.minimal_stake > 0,
"score is zero, this probably means that the stakes are not set."
);
Ok(RawSolution { solution, score, round })
}

Expand Down Expand Up @@ -312,7 +315,7 @@ frame_benchmarking::benchmarks! {
// the solution will be worse than all of them meaning the score need to be checked against
// ~ log2(c)
let solution = RawSolution {
score: [(10_000_000u128 - 1).into(), 0, 0],
score: ElectionScore { minimal_stake: 10_000_000u128 - 1, ..Default::default() },
..Default::default()
};

Expand All @@ -323,7 +326,7 @@ frame_benchmarking::benchmarks! {
let mut signed_submissions = SignedSubmissions::<T>::get();
for i in 0..c {
let raw_solution = RawSolution {
score: [(10_000_000 + i).into(), 0, 0],
score: ElectionScore { minimal_stake: 10_000_000u128 + (i as u128), ..Default::default() },
..Default::default()
};
let signed_submission = SignedSubmission {
Expand Down
41 changes: 28 additions & 13 deletions frame/election-provider-multi-phase/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -944,8 +944,11 @@ pub mod pallet {
// Note: we don't `rotate_round` at this point; the next call to
// `ElectionProvider::elect` will succeed and take care of that.

let solution =
ReadySolution { supports, score: [0, 0, 0], compute: ElectionCompute::Emergency };
let solution = ReadySolution {
supports,
score: Default::default(),
compute: ElectionCompute::Emergency,
};

<QueuedSolution<T>>::put(solution);
Ok(())
Expand Down Expand Up @@ -1059,8 +1062,11 @@ pub mod pallet {
},
)?;

let solution =
ReadySolution { supports, score: [0, 0, 0], compute: ElectionCompute::Fallback };
let solution = ReadySolution {
supports,
score: Default::default(),
compute: ElectionCompute::Fallback,
};

<QueuedSolution<T>>::put(solution);
Ok(())
Expand Down Expand Up @@ -1138,10 +1144,10 @@ pub mod pallet {
.map_err(dispatch_error_to_invalid)?;

ValidTransaction::with_tag_prefix("OffchainElection")
// The higher the score[0], the better a solution is.
// The higher the score.minimal_stake, the better a solution is.
.priority(
T::MinerTxPriority::get()
.saturating_add(raw_solution.score[0].saturated_into()),
.saturating_add(raw_solution.score.minimal_stake.saturated_into()),
)
// Used to deduplicate unsigned solutions: each validator should produce one
// solution per round at most, and solutions are not propagate.
Expand Down Expand Up @@ -1430,7 +1436,7 @@ impl<T: Config> Pallet<T> {
let submitted_score = raw_solution.score.clone();
ensure!(
Self::minimum_untrusted_score().map_or(true, |min_score| {
sp_npos_elections::is_score_better(submitted_score, min_score, Perbill::zero())
submitted_score.strict_threshold_better(min_score, Perbill::zero())
}),
FeasibilityError::UntrustedScoreTooLow
);
Expand Down Expand Up @@ -1750,7 +1756,7 @@ mod feasibility_check {
assert_eq!(MultiPhase::snapshot().unwrap().voters.len(), 8);

// Simply faff with the score.
solution.score[0] += 1;
solution.score.minimal_stake += 1;

assert_noop!(
MultiPhase::feasibility_check(solution, COMPUTE),
Expand Down Expand Up @@ -1960,7 +1966,10 @@ mod tests {

// fill the queue with signed submissions
for s in 0..SignedMaxSubmissions::get() {
let solution = RawSolution { score: [(5 + s).into(), 0, 0], ..Default::default() };
let solution = RawSolution {
score: ElectionScore { minimal_stake: (5 + s).into(), ..Default::default() },
..Default::default()
};
assert_ok!(MultiPhase::submit(
crate::mock::Origin::signed(99),
Box::new(solution),
Expand Down Expand Up @@ -2087,13 +2096,19 @@ mod tests {
crate::mock::Balancing::set(Some((2, 0)));

let (solution, _) = MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
// Default solution has a score of [50, 100, 5000].
assert_eq!(solution.score, [50, 100, 5000]);
// Default solution's score.
assert!(matches!(solution.score, ElectionScore { minimal_stake: 50, .. }));

<MinimumUntrustedScore<Runtime>>::put([49, 0, 0]);
<MinimumUntrustedScore<Runtime>>::put(ElectionScore {
minimal_stake: 49,
..Default::default()
});
assert_ok!(MultiPhase::feasibility_check(solution.clone(), ElectionCompute::Signed));

<MinimumUntrustedScore<Runtime>>::put([51, 0, 0]);
<MinimumUntrustedScore<Runtime>>::put(ElectionScore {
minimal_stake: 51,
..Default::default()
});
assert_noop!(
MultiPhase::feasibility_check(solution, ElectionCompute::Signed),
FeasibilityError::UntrustedScoreTooLow,
Expand Down
87 changes: 63 additions & 24 deletions frame/election-provider-multi-phase/src/signed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use frame_support::{
traits::{defensive_prelude::*, Currency, Get, OnUnbalanced, ReservableCurrency},
};
use sp_arithmetic::traits::SaturatedConversion;
use sp_npos_elections::{is_score_better, ElectionScore, NposSolution};
use sp_npos_elections::{ElectionScore, NposSolution};
use sp_runtime::{
traits::{Saturating, Zero},
RuntimeDebug,
Expand Down Expand Up @@ -293,7 +293,7 @@ impl<T: Config> SignedSubmissions<T> {
let threshold = T::SolutionImprovementThreshold::get();

// if we haven't improved on the weakest score, don't change anything.
if !is_score_better(insert_score, weakest_score, threshold) {
if !insert_score.strict_threshold_better(weakest_score, threshold) {
return InsertResult::NotInserted
}

Expand Down Expand Up @@ -592,7 +592,7 @@ mod tests {
assert_eq!(balances(&99), (100, 0));

// make the solution invalid.
solution.score[0] += 1;
solution.score.minimal_stake += 1;

assert_ok!(submit_with_witness(Origin::signed(99), solution));
assert_eq!(balances(&99), (95, 5));
Expand All @@ -618,7 +618,7 @@ mod tests {
assert_ok!(submit_with_witness(Origin::signed(99), solution.clone()));

// make the solution invalid and weaker.
solution.score[0] -= 1;
solution.score.minimal_stake -= 1;
assert_ok!(submit_with_witness(Origin::signed(999), solution));
assert_eq!(balances(&99), (95, 5));
assert_eq!(balances(&999), (95, 5));
Expand All @@ -641,12 +641,18 @@ mod tests {

for s in 0..SignedMaxSubmissions::get() {
// score is always getting better
let solution = RawSolution { score: [(5 + s).into(), 0, 0], ..Default::default() };
let solution = RawSolution {
score: ElectionScore { minimal_stake: (5 + s).into(), ..Default::default() },
..Default::default()
};
assert_ok!(submit_with_witness(Origin::signed(99), solution));
}

// weaker.
let solution = RawSolution { score: [4, 0, 0], ..Default::default() };
let solution = RawSolution {
score: ElectionScore { minimal_stake: 4, ..Default::default() },
..Default::default()
};

assert_noop!(
submit_with_witness(Origin::signed(99), solution),
Expand All @@ -663,27 +669,33 @@ mod tests {

for s in 0..SignedMaxSubmissions::get() {
// score is always getting better
let solution = RawSolution { score: [(5 + s).into(), 0, 0], ..Default::default() };
let solution = RawSolution {
score: ElectionScore { minimal_stake: (5 + s).into(), ..Default::default() },
..Default::default()
};
assert_ok!(submit_with_witness(Origin::signed(99), solution));
}

assert_eq!(
MultiPhase::signed_submissions()
.iter()
.map(|s| s.raw_solution.score[0])
.map(|s| s.raw_solution.score.minimal_stake)
.collect::<Vec<_>>(),
vec![5, 6, 7, 8, 9]
);

// better.
let solution = RawSolution { score: [20, 0, 0], ..Default::default() };
let solution = RawSolution {
score: ElectionScore { minimal_stake: 20, ..Default::default() },
..Default::default()
};
assert_ok!(submit_with_witness(Origin::signed(99), solution));

// the one with score 5 was rejected, the new one inserted.
assert_eq!(
MultiPhase::signed_submissions()
.iter()
.map(|s| s.raw_solution.score[0])
.map(|s| s.raw_solution.score.minimal_stake)
.collect::<Vec<_>>(),
vec![6, 7, 8, 9, 20]
);
Expand All @@ -698,30 +710,39 @@ mod tests {

for s in 1..SignedMaxSubmissions::get() {
// score is always getting better
let solution = RawSolution { score: [(5 + s).into(), 0, 0], ..Default::default() };
let solution = RawSolution {
score: ElectionScore { minimal_stake: (5 + s).into(), ..Default::default() },
..Default::default()
};
assert_ok!(submit_with_witness(Origin::signed(99), solution));
}

let solution = RawSolution { score: [4, 0, 0], ..Default::default() };
let solution = RawSolution {
score: ElectionScore { minimal_stake: 4, ..Default::default() },
..Default::default()
};
assert_ok!(submit_with_witness(Origin::signed(99), solution));

assert_eq!(
MultiPhase::signed_submissions()
.iter()
.map(|s| s.raw_solution.score[0])
.map(|s| s.raw_solution.score.minimal_stake)
.collect::<Vec<_>>(),
vec![4, 6, 7, 8, 9],
);

// better.
let solution = RawSolution { score: [5, 0, 0], ..Default::default() };
let solution = RawSolution {
score: ElectionScore { minimal_stake: 5, ..Default::default() },
..Default::default()
};
assert_ok!(submit_with_witness(Origin::signed(99), solution));

// the one with score 5 was rejected, the new one inserted.
assert_eq!(
MultiPhase::signed_submissions()
.iter()
.map(|s| s.raw_solution.score[0])
.map(|s| s.raw_solution.score.minimal_stake)
.collect::<Vec<_>>(),
vec![5, 6, 7, 8, 9],
);
Expand All @@ -736,15 +757,21 @@ mod tests {

for s in 0..SignedMaxSubmissions::get() {
// score is always getting better
let solution = RawSolution { score: [(5 + s).into(), 0, 0], ..Default::default() };
let solution = RawSolution {
score: ElectionScore { minimal_stake: (5 + s).into(), ..Default::default() },
..Default::default()
};
assert_ok!(submit_with_witness(Origin::signed(99), solution));
}

assert_eq!(balances(&99).1, 2 * 5);
assert_eq!(balances(&999).1, 0);

// better.
let solution = RawSolution { score: [20, 0, 0], ..Default::default() };
let solution = RawSolution {
score: ElectionScore { minimal_stake: 20, ..Default::default() },
..Default::default()
};
assert_ok!(submit_with_witness(Origin::signed(999), solution));

// got one bond back.
Expand All @@ -760,19 +787,25 @@ mod tests {
assert!(MultiPhase::current_phase().is_signed());

for i in 0..SignedMaxSubmissions::get() {
let solution = RawSolution { score: [(5 + i).into(), 0, 0], ..Default::default() };
let solution = RawSolution {
score: ElectionScore { minimal_stake: (5 + i).into(), ..Default::default() },
..Default::default()
};
assert_ok!(submit_with_witness(Origin::signed(99), solution));
}
assert_eq!(
MultiPhase::signed_submissions()
.iter()
.map(|s| s.raw_solution.score[0])
.map(|s| s.raw_solution.score.minimal_stake)
.collect::<Vec<_>>(),
vec![5, 6, 7]
);

// 5 is not accepted. This will only cause processing with no benefit.
let solution = RawSolution { score: [5, 0, 0], ..Default::default() };
let solution = RawSolution {
score: ElectionScore { minimal_stake: 5, ..Default::default() },
..Default::default()
};
assert_noop!(
submit_with_witness(Origin::signed(99), solution),
Error::<Runtime>::SignedQueueFull,
Expand Down Expand Up @@ -800,13 +833,13 @@ mod tests {

// make the solution invalidly better and submit. This ought to be slashed.
let mut solution_999 = solution.clone();
solution_999.score[0] += 1;
solution_999.score.minimal_stake += 1;
assert_ok!(submit_with_witness(Origin::signed(999), solution_999));

// make the solution invalidly worse and submit. This ought to be suppressed and
// returned.
let mut solution_9999 = solution.clone();
solution_9999.score[0] -= 1;
solution_9999.score.minimal_stake -= 1;
assert_ok!(submit_with_witness(Origin::signed(9999), solution_9999));

assert_eq!(
Expand Down Expand Up @@ -889,13 +922,19 @@ mod tests {

for s in 0..SignedMaxSubmissions::get() {
// score is always getting better
let solution = RawSolution { score: [(5 + s).into(), 0, 0], ..Default::default() };
let solution = RawSolution {
score: ElectionScore { minimal_stake: (5 + s).into(), ..Default::default() },
..Default::default()
};
assert_ok!(submit_with_witness(Origin::signed(99), solution));
}

// this solution has a higher score than any in the queue
let solution = RawSolution {
score: [(5 + SignedMaxSubmissions::get()).into(), 0, 0],
score: ElectionScore {
minimal_stake: (5 + SignedMaxSubmissions::get()).into(),
..Default::default()
},
..Default::default()
};

Expand Down
Loading