From 81a286c18bdb61bbd73368121b5330bdb453361a Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 26 May 2021 21:06:22 +0200 Subject: [PATCH 1/2] emergency mode: allow governance origin to provide solution to election in case of failure --- Cargo.lock | 4 + .../election-provider-multi-phase/src/lib.rs | 76 ++++++++++++++++--- 2 files changed, 70 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9019acfcbb239..4e4b38e66e39f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6664,6 +6664,10 @@ dependencies = [ "jsonrpsee-proc-macros", "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 d1de16f7f744f..a4c9e107e3ecd 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) @@ -579,6 +588,9 @@ pub mod pallet { /// Configuration for the fallback type Fallback: Get; + /// Origin that can control this pallet. + type ForceOrigin: EnsureOrigin; + /// The configuration of benchmarking. type BenchmarkingConfig: BenchmarkingConfig; @@ -773,6 +785,40 @@ pub mod pallet { Ok(None.into()) } + + /// Set a new value for `MinimumUntrustedScore`. + /// + /// Dispatch origin must be aligned with `T::ForceOrigin`. + /// + /// This check can be turned off by setting the value to `None`. + #[pallet::weight(T::DbWeight::get().writes(1))] + fn set_minimum_untrusted_score( + origin: OriginFor, + maybe_next_score: Option, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + >::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); + >::put(solution); + Ok(()) + } } #[pallet::event] @@ -808,6 +854,8 @@ pub mod pallet { PreDispatchWeakSubmission, /// OCW submitted solution for wrong round OcwCallWrongEra, + /// The call is now allowed at this point. + CallNotAllowed, } #[pallet::origin] @@ -1125,14 +1173,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(); } @@ -1182,10 +1230,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) + } + } } } From 2abb1d1d74eabe9c7609ab19a961824832c0feea Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 27 May 2021 21:51:32 +0200 Subject: [PATCH 2/2] add a few docs --- frame/election-provider-multi-phase/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index a4c9e107e3ecd..44927bf866257 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -816,6 +816,10 @@ pub mod pallet { ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; ensure!(Self::current_phase().is_emergency(), >::CallNotAllowed); + + // Note: we don't `rotate_phase` at this point; the next call to + // `ElectionProvider::elect` will not succeed and take care of that. + >::put(solution); Ok(()) }