better way to resolve Phase::Emergency via governance (#10663)

* better way to resolve Phase::Emergency via governance

* Update frame/election-provider-multi-phase/src/lib.rs

Co-authored-by: Zeke Mostov <z.mostov@gmail.com>

* review grumbles

* Update frame/election-provider-support/src/onchain.rs

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>

* revert usize -> u32

Co-authored-by: Zeke Mostov <z.mostov@gmail.com>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
Kian Paimani
2022-01-27 11:00:48 +01:00
committed by GitHub
parent 2b9e3a73f6
commit 498c3a5ecc
5 changed files with 122 additions and 22 deletions
+8 -6
View File
@@ -23,6 +23,7 @@
#![recursion_limit = "256"]
use codec::{Decode, Encode, MaxEncodedLen};
use frame_election_provider_support::onchain;
use frame_support::{
construct_runtime, parameter_types,
traits::{
@@ -528,12 +529,6 @@ parameter_types! {
pub OffchainRepeat: BlockNumber = 5;
}
use frame_election_provider_support::onchain;
impl onchain::Config for Runtime {
type Accuracy = Perbill;
type DataProvider = Staking;
}
pub struct StakingBenchmarkingConfig;
impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig {
type MaxNominators = ConstU32<1000>;
@@ -649,6 +644,11 @@ impl frame_support::pallet_prelude::Get<Option<(usize, sp_npos_elections::Extend
}
}
impl onchain::Config for Runtime {
type Accuracy = Perbill;
type DataProvider = <Self as pallet_election_provider_multi_phase::Config>::DataProvider;
}
impl pallet_election_provider_multi_phase::Config for Runtime {
type Event = Event;
type Currency = Balances;
@@ -671,6 +671,8 @@ impl pallet_election_provider_multi_phase::Config for Runtime {
type DataProvider = Staking;
type Solution = NposSolution16;
type Fallback = pallet_election_provider_multi_phase::NoFallback<Self>;
type GovernanceFallback =
frame_election_provider_support::onchain::OnChainSequentialPhragmen<Self>;
type Solver = frame_election_provider_support::SequentialPhragmen<
AccountId,
pallet_election_provider_multi_phase::SolutionAccuracyOf<Self>,
@@ -230,7 +230,9 @@
#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Decode, Encode};
use frame_election_provider_support::{ElectionDataProvider, ElectionProvider};
use frame_election_provider_support::{
ElectionDataProvider, ElectionProvider, InstantElectionProvider,
};
use frame_support::{
dispatch::DispatchResultWithPostInfo,
ensure,
@@ -322,6 +324,15 @@ impl<T: Config> ElectionProvider for NoFallback<T> {
}
}
impl<T: Config> InstantElectionProvider for NoFallback<T> {
fn instant_elect(
_: Option<usize>,
_: Option<usize>,
) -> Result<Supports<T::AccountId>, Self::Error> {
Err("NoFallback.")
}
}
/// Current phase of the pallet.
#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo)]
pub enum Phase<Bn> {
@@ -555,7 +566,7 @@ pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_election_provider_support::NposSolver;
use frame_election_provider_support::{InstantElectionProvider, NposSolver};
use frame_support::{pallet_prelude::*, traits::EstimateCallFee};
use frame_system::pallet_prelude::*;
@@ -672,13 +683,23 @@ pub mod pallet {
+ NposSolution
+ TypeInfo;
/// Configuration for the fallback
/// Configuration for the fallback.
type Fallback: ElectionProvider<
AccountId = Self::AccountId,
BlockNumber = Self::BlockNumber,
DataProvider = Self::DataProvider,
>;
/// Configuration of the governance-only fallback.
///
/// As a side-note, it is recommend for test-nets to use `type ElectionProvider =
/// OnChainSeqPhragmen<_>` if the test-net is not expected to have thousands of nominators.
type GovernanceFallback: InstantElectionProvider<
AccountId = Self::AccountId,
BlockNumber = Self::BlockNumber,
DataProvider = Self::DataProvider,
>;
/// OCW election solution miner algorithm implementation.
type Solver: NposSolver<AccountId = Self::AccountId>;
@@ -1013,6 +1034,37 @@ pub mod pallet {
});
Ok(())
}
/// Trigger the governance fallback.
///
/// This can only be called when [`Phase::Emergency`] is enabled, as an alternative to
/// calling [`Call::set_emergency_election_result`].
#[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
pub fn governance_fallback(
origin: OriginFor<T>,
maybe_max_voters: Option<u32>,
maybe_max_targets: Option<u32>,
) -> DispatchResult {
T::ForceOrigin::ensure_origin(origin)?;
ensure!(Self::current_phase().is_emergency(), <Error<T>>::CallNotAllowed);
let maybe_max_voters = maybe_max_voters.map(|x| x as usize);
let maybe_max_targets = maybe_max_targets.map(|x| x as usize);
let supports =
T::GovernanceFallback::instant_elect(maybe_max_voters, maybe_max_targets).map_err(
|e| {
log!(error, "GovernanceFallback failed: {:?}", e);
Error::<T>::FallbackFailed
},
)?;
let solution =
ReadySolution { supports, score: [0, 0, 0], compute: ElectionCompute::Fallback };
<QueuedSolution<T>>::put(solution);
Ok(())
}
}
#[pallet::event]
@@ -1063,6 +1115,8 @@ pub mod pallet {
InvalidSubmissionIndex,
/// The call is not allowed at this point.
CallNotAllowed,
/// The fallback failed
FallbackFailed,
}
#[pallet::validate_unsigned]
@@ -410,6 +410,7 @@ impl crate::Config for Runtime {
type WeightInfo = DualMockWeightInfo;
type BenchmarkingConfig = TestBenchmarkingConfig;
type Fallback = MockFallback;
type GovernanceFallback = NoFallback<Self>;
type ForceOrigin = frame_system::EnsureRoot<AccountId>;
type Solution = TestNposSolution;
type VoterSnapshotPerBlock = VoterSnapshotPerBlock;
@@ -319,6 +319,22 @@ pub trait ElectionProvider {
fn elect() -> Result<Supports<Self::AccountId>, Self::Error>;
}
/// A sub-trait of the [`ElectionProvider`] for cases where we need to be sure an election needs to
/// happen instantly, not asynchronously.
///
/// The same `DataProvider` is assumed to be used.
///
/// Consequently, allows for control over the amount of data that is being fetched from the
/// [`ElectionProvider::DataProvider`].
pub trait InstantElectionProvider: ElectionProvider {
/// Elect a new set of winners, instantly, with the given given limits set on the
/// `DataProvider`.
fn instant_elect(
maybe_max_voters: Option<usize>,
maybe_max_targets: Option<usize>,
) -> Result<Supports<Self::AccountId>, Self::Error>;
}
/// An election provider to be used only for testing.
#[cfg(feature = "std")]
pub struct NoElection<X>(sp_std::marker::PhantomData<X>);
@@ -17,7 +17,7 @@
//! An implementation of [`ElectionProvider`] that does an on-chain sequential phragmen.
use crate::{ElectionDataProvider, ElectionProvider};
use crate::{ElectionDataProvider, ElectionProvider, InstantElectionProvider};
use frame_support::{traits::Get, weights::DispatchClass};
use sp_npos_elections::*;
use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData, prelude::*};
@@ -47,8 +47,14 @@ impl From<sp_npos_elections::Error> for Error {
/// implementation ignores the additional data of the election data provider and gives no insight on
/// how much weight was consumed.
///
/// Finally, this implementation does not impose any limits on the number of voters and targets that
/// are provided.
/// Finally, the [`ElectionProvider`] implementation of this type does not impose any limits on the
/// number of voters and targets that are fetched. This could potentially make this unsuitable for
/// execution onchain. On the other hand, the [`InstantElectionProvider`] implementation does limit
/// these inputs.
///
/// It is advisable to use the former ([`ElectionProvider::elect`]) only at genesis, or for testing,
/// the latter [`InstantElectionProvider::instant_elect`] for onchain operations, with thoughtful
/// bounds.
pub struct OnChainSequentialPhragmen<T: Config>(PhantomData<T>);
/// Configuration trait of [`OnChainSequentialPhragmen`].
@@ -68,16 +74,17 @@ pub trait Config: frame_system::Config {
>;
}
impl<T: Config> ElectionProvider for OnChainSequentialPhragmen<T> {
type AccountId = T::AccountId;
type BlockNumber = T::BlockNumber;
type Error = Error;
type DataProvider = T::DataProvider;
fn elect() -> Result<Supports<T::AccountId>, Self::Error> {
let voters = Self::DataProvider::voters(None).map_err(Error::DataProvider)?;
let targets = Self::DataProvider::targets(None).map_err(Error::DataProvider)?;
let desired_targets = Self::DataProvider::desired_targets().map_err(Error::DataProvider)?;
impl<T: Config> OnChainSequentialPhragmen<T> {
fn elect_with(
maybe_max_voters: Option<usize>,
maybe_max_targets: Option<usize>,
) -> Result<Supports<T::AccountId>, Error> {
let voters = <Self as ElectionProvider>::DataProvider::voters(maybe_max_voters)
.map_err(Error::DataProvider)?;
let targets = <Self as ElectionProvider>::DataProvider::targets(maybe_max_targets)
.map_err(Error::DataProvider)?;
let desired_targets = <Self as ElectionProvider>::DataProvider::desired_targets()
.map_err(Error::DataProvider)?;
let stake_map: BTreeMap<T::AccountId, VoteWeight> = voters
.iter()
@@ -102,6 +109,26 @@ impl<T: Config> ElectionProvider for OnChainSequentialPhragmen<T> {
}
}
impl<T: Config> ElectionProvider for OnChainSequentialPhragmen<T> {
type AccountId = T::AccountId;
type BlockNumber = T::BlockNumber;
type Error = Error;
type DataProvider = T::DataProvider;
fn elect() -> Result<Supports<T::AccountId>, Self::Error> {
Self::elect_with(None, None)
}
}
impl<T: Config> InstantElectionProvider for OnChainSequentialPhragmen<T> {
fn instant_elect(
maybe_max_voters: Option<usize>,
maybe_max_targets: Option<usize>,
) -> Result<Supports<Self::AccountId>, Self::Error> {
Self::elect_with(maybe_max_voters, maybe_max_targets)
}
}
#[cfg(test)]
mod tests {
use super::*;