diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 906de8a6c9d20..84949080aa58c 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -494,6 +494,8 @@ pub enum ElectionError { DataProvider(&'static str), /// An error nested in the fallback. Fallback(FallbackErrorOf), + /// No solution has been queued. + NothingQueued, } // NOTE: we have to do this manually because of the additional where clause needed on @@ -900,7 +902,7 @@ pub mod pallet { let ejected_a_solution = >::exists(); >::put(ready); Self::deposit_event(Event::SolutionStored { - election_compute: ElectionCompute::Unsigned, + compute: ElectionCompute::Unsigned, prev_ejected: ejected_a_solution, }); @@ -948,7 +950,7 @@ pub mod pallet { }; Self::deposit_event(Event::SolutionStored { - election_compute: ElectionCompute::Emergency, + compute: ElectionCompute::Emergency, prev_ejected: QueuedSolution::::exists(), }); @@ -1024,7 +1026,7 @@ pub mod pallet { signed_submissions.put(); Self::deposit_event(Event::SolutionStored { - election_compute: ElectionCompute::Signed, + compute: ElectionCompute::Signed, prev_ejected: ejected_a_solution, }); Ok(()) @@ -1062,7 +1064,7 @@ pub mod pallet { }; Self::deposit_event(Event::SolutionStored { - election_compute: ElectionCompute::Fallback, + compute: ElectionCompute::Fallback, prev_ejected: QueuedSolution::::exists(), }); @@ -1080,10 +1082,13 @@ pub mod pallet { /// solution is unsigned, this means that it has also been processed. /// /// The `bool` is `true` when a previous solution was ejected to make room for this one. - SolutionStored { election_compute: ElectionCompute, prev_ejected: bool }, - /// The election has been finalized, with `Some` of the given computation, or else if the - /// election failed, `None`. - ElectionFinalized { election_compute: Option }, + SolutionStored { compute: ElectionCompute, prev_ejected: bool }, + /// The election has been finalized, with the given computation and score. + ElectionFinalized { compute: ElectionCompute, score: ElectionScore }, + /// An election failed. + /// + /// Not much can be said about which computes failed in the process. + ElectionFailed, /// An account has been rewarded for their signed submission being finalized. Rewarded { account: ::AccountId, value: BalanceOf }, /// An account has been slashed for submitting an invalid signed submission. @@ -1530,23 +1535,25 @@ impl Pallet { // inexpensive (1 read of an empty vector). let _ = Self::finalize_signed_phase(); >::take() - .map_or_else( - || { - T::Fallback::elect() - .map_err(|fe| ElectionError::Fallback(fe)) - .map(|supports| (supports, ElectionCompute::Fallback)) - }, - |ReadySolution { supports, compute, .. }| Ok((supports, compute)), - ) - .map(|(supports, compute)| { - Self::deposit_event(Event::ElectionFinalized { election_compute: Some(compute) }); + .ok_or(ElectionError::::NothingQueued) + .or_else(|_| { + T::Fallback::elect() + .map(|supports| ReadySolution { + supports, + score: Default::default(), + compute: ElectionCompute::Fallback, + }) + .map_err(|fe| ElectionError::Fallback(fe)) + }) + .map(|ReadySolution { compute, score, supports }| { + Self::deposit_event(Event::ElectionFinalized { compute, score }); if Self::round() != 1 { log!(info, "Finalized election round with compute {:?}.", compute); } supports }) .map_err(|err| { - Self::deposit_event(Event::ElectionFinalized { election_compute: None }); + Self::deposit_event(Event::ElectionFailed); if Self::round() != 1 { log!(warn, "Failed to finalize election round. reason {:?}", err); } @@ -1800,8 +1807,9 @@ mod tests { use super::*; use crate::{ mock::{ - multi_phase_events, roll_to, AccountId, ExtBuilder, MockWeightInfo, MockedWeightInfo, - MultiPhase, Origin, Runtime, SignedMaxSubmissions, System, TargetIndex, Targets, + multi_phase_events, raw_solution, roll_to, AccountId, ExtBuilder, MockWeightInfo, + MockedWeightInfo, MultiPhase, Origin, Runtime, SignedMaxSubmissions, System, + TargetIndex, Targets, }, Phase, }; @@ -1967,7 +1975,10 @@ mod tests { multi_phase_events(), vec![ Event::SignedPhaseStarted { round: 1 }, - Event::ElectionFinalized { election_compute: Some(ElectionCompute::Fallback) } + Event::ElectionFinalized { + compute: ElectionCompute::Fallback, + score: Default::default() + } ], ); // All storage items must be cleared. @@ -2016,6 +2027,88 @@ mod tests { }) } + #[test] + fn check_events_with_compute_signed() { + ExtBuilder::default().build_and_execute(|| { + roll_to(14); + assert_eq!(MultiPhase::current_phase(), Phase::Off); + + roll_to(15); + assert!(MultiPhase::current_phase().is_signed()); + + let solution = raw_solution(); + assert_ok!(MultiPhase::submit(crate::mock::Origin::signed(99), Box::new(solution))); + + roll_to(30); + assert_ok!(MultiPhase::elect()); + + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::Rewarded { account: 99, value: 7 }, + Event::UnsignedPhaseStarted { round: 1 }, + Event::ElectionFinalized { + compute: ElectionCompute::Signed, + score: ElectionScore { + minimal_stake: 40, + sum_stake: 100, + sum_stake_squared: 5200 + } + } + ], + ); + }) + } + + #[test] + fn check_events_with_compute_unsigned() { + ExtBuilder::default().build_and_execute(|| { + roll_to(25); + assert!(MultiPhase::current_phase().is_unsigned()); + + // ensure we have snapshots in place. + assert!(MultiPhase::snapshot().is_some()); + assert_eq!(MultiPhase::desired_targets().unwrap(), 2); + + // mine seq_phragmen solution with 2 iters. + let (solution, witness) = MultiPhase::mine_solution().unwrap(); + + // ensure this solution is valid. + assert!(MultiPhase::queued_solution().is_none()); + assert_ok!(MultiPhase::submit_unsigned( + crate::mock::Origin::none(), + Box::new(solution), + witness + )); + assert!(MultiPhase::queued_solution().is_some()); + + roll_to(30); + assert_ok!(MultiPhase::elect()); + + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::UnsignedPhaseStarted { round: 1 }, + Event::SolutionStored { + compute: ElectionCompute::Unsigned, + prev_ejected: false + }, + Event::ElectionFinalized { + compute: ElectionCompute::Unsigned, + score: ElectionScore { + minimal_stake: 40, + sum_stake: 100, + sum_stake_squared: 5200 + } + } + ], + ); + }) + } + #[test] fn fallback_strategy_works() { ExtBuilder::default().onchain_fallback(true).build_and_execute(|| { @@ -2080,12 +2173,15 @@ mod tests { vec![ Event::SignedPhaseStarted { round: 1 }, Event::UnsignedPhaseStarted { round: 1 }, - Event::ElectionFinalized { election_compute: None }, + Event::ElectionFailed, Event::SolutionStored { - election_compute: ElectionCompute::Fallback, + compute: ElectionCompute::Fallback, prev_ejected: false }, - Event::ElectionFinalized { election_compute: Some(ElectionCompute::Fallback) } + Event::ElectionFinalized { + compute: ElectionCompute::Fallback, + score: Default::default() + } ] ); })