diff --git a/Cargo.lock b/Cargo.lock index 39ec3e8c26b18..dbe983e79914b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6669,6 +6669,10 @@ dependencies = [ "jsonrpsee-ws-client", "log", "parity-scale-codec", +<<<<<<< HEAD +======= + "serde_json", +>>>>>>> 59b2fac1b... emergency mode: allow governance origin to provide solution to election in case of failure "sp-core", "sp-io", "sp-runtime", diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 9bec5cc4bd310..dfc7df8c2497f 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -311,9 +311,13 @@ pub enum Phase { /// advising validators not to bother running the unsigned offchain worker. /// /// As validator nodes are free to edit their OCW code, they could simply ignore this advisory - /// and always compute their own solution. However, by default, when the unsigned phase is passive, - /// the offchain workers will not bother running. + /// and always compute their own solution. However, by default, when the unsigned phase is + /// passive, the offchain workers will not bother running. Unsigned((bool, Bn)), + /// The emergency phase. This is enabled upon a failing call to `T::ElectionProvider::elect`. + /// After that, the only way to leave this phase is through a successful + /// `T::ElectionProvider::elect`. + Emergency, } impl Default for Phase { @@ -323,6 +327,11 @@ impl Default for Phase { } impl Phase { + /// Whether the phase is emergency or not. + pub fn is_emergency(&self) -> bool { + matches!(self, Phase::Emergency) + } + /// Whether the phase is signed or not. pub fn is_signed(&self) -> bool { matches!(self, Phase::Signed) @@ -581,7 +590,7 @@ pub mod pallet { /// Configuration for the fallback type Fallback: Get; - /// Origin that can set the minimum score. + /// Origin that can control this pallet. type ForceOrigin: EnsureOrigin; /// The configuration of benchmarking. @@ -793,6 +802,29 @@ pub mod pallet { >::set(maybe_next_score); Ok(()) } + + /// Set a solution in the queue, to be handed out to the client of this pallet in the next + /// call to `ElectionProvider::elect`. + /// + /// This can only be set by `T::ForceOrigin`, and only when the phase is `Emergency`. + /// + /// The solution is not checked for any feasibility and is assumed to be trustworthy, as any + /// feasibility check itself can in principle cause the election process to fail (due to + /// memory/weight constrains). + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] + fn set_emergency_election_result( + origin: OriginFor, + solution: ReadySolution, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + ensure!(Self::current_phase().is_emergency(), >::CallNotAllowed); + + // Note: we don't `rotate_round` at this point; the next call to + // `ElectionProvider::elect` will not succeed and take care of that. + + >::put(solution); + Ok(()) + } } #[pallet::event] @@ -828,6 +860,8 @@ pub mod pallet { PreDispatchWeakSubmission, /// OCW submitted solution for wrong round OcwCallWrongEra, + /// The call is now allowed at this point. + CallNotAllowed, } #[pallet::origin] @@ -1162,14 +1196,14 @@ impl Pallet { /// 1. Increment round. /// 2. Change phase to [`Phase::Off`] /// 3. Clear all snapshot data. - fn post_elect() { - // inc round + fn rotate_round() { + // inc round. >::mutate(|r| *r = *r + 1); - // change phase + // phase is off now. >::put(Phase::Off); - // kill snapshots + // kill snapshots. Self::kill_snapshot(); } @@ -1219,10 +1253,18 @@ impl ElectionProvider for Pallet { type DataProvider = T::DataProvider; fn elect() -> Result<(Supports, Weight), Self::Error> { - let outcome_and_weight = Self::do_elect(); - // IMPORTANT: regardless of if election was `Ok` or `Err`, we shall do some cleanup. - Self::post_elect(); - outcome_and_weight + match Self::do_elect() { + Ok((supports, weight)) => { + // all went okay, put sign to be Off, clean snapshot, etc. + Self::rotate_round(); + Ok((supports, weight)) + }, + Err(why) => { + log!(error, "Entering emergency mode."); + >::put(Phase::Emergency); + Err(why) + } + } } }