diff --git a/Cargo.lock b/Cargo.lock index ac30433c5ea28..f4e21fddda8b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2200,8 +2200,6 @@ dependencies = [ "rand 0.7.3", "scale-info", "sp-arithmetic", - "sp-core", - "sp-io", "sp-npos-elections", "sp-runtime", "sp-std", @@ -5036,6 +5034,7 @@ dependencies = [ "pallet-conviction-voting", "pallet-democracy", "pallet-election-provider-multi-phase", + "pallet-election-provider-support-onchain", "pallet-elections-phragmen", "pallet-gilt", "pallet-grandpa", @@ -5561,12 +5560,12 @@ name = "pallet-babe" version = "4.0.0-dev" dependencies = [ "frame-benchmarking", - "frame-election-provider-support", "frame-support", "frame-system", "log 0.4.14", "pallet-authorship", "pallet-balances", + "pallet-election-provider-support-onchain", "pallet-offences", "pallet-session", "pallet-staking", @@ -5884,6 +5883,7 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-balances", + "pallet-election-provider-support-onchain", "parity-scale-codec", "parking_lot 0.12.0", "rand 0.7.3", @@ -5899,6 +5899,23 @@ dependencies = [ "strum", ] +[[package]] +name = "pallet-election-provider-support-onchain" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-npos-elections", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-elections-phragmen" version = "5.0.0-dev" @@ -5990,12 +6007,12 @@ version = "4.0.0-dev" dependencies = [ "finality-grandpa", "frame-benchmarking", - "frame-election-provider-support", "frame-support", "frame-system", "log 0.4.14", "pallet-authorship", "pallet-balances", + "pallet-election-provider-support-onchain", "pallet-offences", "pallet-session", "pallet-staking", @@ -6222,11 +6239,11 @@ name = "pallet-offences-benchmarking" version = "4.0.0-dev" dependencies = [ "frame-benchmarking", - "frame-election-provider-support", "frame-support", "frame-system", "pallet-babe", "pallet-balances", + "pallet-election-provider-support-onchain", "pallet-grandpa", "pallet-im-online", "pallet-offences", @@ -6384,10 +6401,10 @@ name = "pallet-session-benchmarking" version = "4.0.0-dev" dependencies = [ "frame-benchmarking", - "frame-election-provider-support", "frame-support", "frame-system", "pallet-balances", + "pallet-election-provider-support-onchain", "pallet-session", "pallet-staking", "pallet-staking-reward-curve", @@ -6431,6 +6448,7 @@ dependencies = [ "pallet-authorship", "pallet-bags-list", "pallet-balances", + "pallet-election-provider-support-onchain", "pallet-session", "pallet-staking-reward-curve", "pallet-timestamp", diff --git a/Cargo.toml b/Cargo.toml index 13657dd1234a5..171d28c40f379 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,6 +88,7 @@ members = [ "frame/election-provider-support", "frame/election-provider-support/solution-type", "frame/election-provider-support/solution-type/fuzzer", + "frame/election-provider-support/onchain", "frame/examples/basic", "frame/examples/offchain-worker", "frame/examples/parallel", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 686508b47dba1..44d59500d1ad2 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -65,6 +65,7 @@ pallet-contracts-rpc-runtime-api = { version = "4.0.0-dev", default-features = f pallet-conviction-voting = { version = "4.0.0-dev", default-features = false, path = "../../../frame/conviction-voting" } pallet-democracy = { version = "4.0.0-dev", default-features = false, path = "../../../frame/democracy" } pallet-election-provider-multi-phase = { version = "4.0.0-dev", default-features = false, path = "../../../frame/election-provider-multi-phase" } +pallet-election-provider-support-onchain = { version = "4.0.0-dev", default-features = false, path = "../../../frame/election-provider-support/onchain" } pallet-elections-phragmen = { version = "5.0.0-dev", default-features = false, path = "../../../frame/elections-phragmen" } pallet-gilt = { version = "4.0.0-dev", default-features = false, path = "../../../frame/gilt" } pallet-grandpa = { version = "4.0.0-dev", default-features = false, path = "../../../frame/grandpa" } @@ -161,6 +162,7 @@ std = [ "frame-system-rpc-runtime-api/std", "frame-system/std", "pallet-election-provider-multi-phase/std", + "pallet-election-provider-support-onchain/std", "pallet-timestamp/std", "pallet-tips/std", "pallet-transaction-payment-rpc-runtime-api/std", @@ -195,6 +197,7 @@ runtime-benchmarks = [ "pallet-contracts/runtime-benchmarks", "pallet-conviction-voting/runtime-benchmarks", "pallet-democracy/runtime-benchmarks", + "pallet-election-provider-support-onchain/runtime-benchmarks", "pallet-election-provider-multi-phase/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", "pallet-gilt/runtime-benchmarks", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 565f151ce2a08..a0e12c3387536 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -23,7 +23,7 @@ #![recursion_limit = "256"] use codec::{Decode, Encode, MaxEncodedLen}; -use frame_election_provider_support::{onchain, ExtendedBalance, SequentialPhragmen, VoteWeight}; +use frame_election_provider_support::{ExtendedBalance, SequentialPhragmen, VoteWeight}; use frame_support::{ construct_runtime, pallet_prelude::Get, @@ -47,6 +47,7 @@ pub use node_primitives::{AccountId, Signature}; use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Index, Moment}; use pallet_contracts::weights::WeightInfo; use pallet_election_provider_multi_phase::SolutionAccuracyOf; +use pallet_election_provider_support_onchain::BoundedExecution; use pallet_grandpa::{ fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, }; @@ -557,7 +558,8 @@ impl pallet_staking::Config for Runtime { type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = ElectionProviderMultiPhase; - type GenesisElectionProvider = onchain::UnboundedExecution; + type GenesisElectionProvider = + BoundedExecution>>; type VoterList = BagsList; type MaxUnlockingChunks = ConstU32<32>; type WeightInfo = pallet_staking::weights::SubstrateWeight; @@ -618,6 +620,14 @@ impl pallet_election_provider_multi_phase::BenchmarkingConfig for ElectionProvid const MAXIMUM_TARGETS: u32 = 300; } +impl pallet_election_provider_support_onchain::BenchmarkingConfig + for ElectionProviderBenchmarkConfig +{ + const VOTERS: [u32; 2] = [1000, 2000]; + const TARGETS: [u32; 2] = [500, 1000]; + const VOTES_PER_VOTER: [u32; 2] = [5, 16]; +} + /// Maximum number of iterations for balancing that will be executed in the embedded OCW /// miner of election provider multi phase. pub const MINER_MAX_ITERATIONS: u32 = 10; @@ -642,19 +652,12 @@ impl Get> for OffchainRandomBalancing { } } -pub struct OnChainSeqPhragmen; -impl onchain::ExecutionConfig for OnChainSeqPhragmen { - type System = Runtime; - type Solver = SequentialPhragmen< - AccountId, - pallet_election_provider_multi_phase::SolutionAccuracyOf, - >; +impl pallet_election_provider_support_onchain::Config for Runtime { type DataProvider = ::DataProvider; -} - -impl onchain::BoundedExecutionConfig for OnChainSeqPhragmen { - type VotersBound = ConstU32<20_000>; - type TargetsBound = ConstU32<2_000>; + type MaxVoters = MaxElectingVoters; + type MaxTargets = ConstU32<1_000>; + type BenchmarkingConfig = ElectionProviderBenchmarkConfig; + type WeightInfo = pallet_election_provider_support_onchain::weights::SubstrateWeight; } impl pallet_election_provider_multi_phase::Config for Runtime { @@ -678,8 +681,10 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type RewardHandler = (); // nothing to do upon rewards type DataProvider = Staking; type Solution = NposSolution16; - type Fallback = onchain::BoundedExecution; - type GovernanceFallback = onchain::BoundedExecution; + type Fallback = + BoundedExecution>>; + type GovernanceFallback = + BoundedExecution>>; type Solver = SequentialPhragmen, OffchainRandomBalancing>; type ForceOrigin = EnsureRootOrHalfCouncil; type MaxElectableTargets = ConstU16<{ u16::MAX }>; @@ -1420,6 +1425,7 @@ construct_runtime!( TransactionPayment: pallet_transaction_payment, AssetTxPayment: pallet_asset_tx_payment, ElectionProviderMultiPhase: pallet_election_provider_multi_phase, + ElectionProviderSupportOnchain: pallet_election_provider_support_onchain, Staking: pallet_staking, Session: pallet_session, Democracy: pallet_democracy, @@ -1531,6 +1537,7 @@ mod benches { [pallet_contracts, Contracts] [pallet_democracy, Democracy] [pallet_election_provider_multi_phase, ElectionProviderMultiPhase] + [pallet_election_provider_support_onchain, ElectionProviderSupportOnchain] [pallet_elections_phragmen, Elections] [pallet_gilt, Gilt] [pallet_grandpa, Grandpa] diff --git a/frame/babe/Cargo.toml b/frame/babe/Cargo.toml index a13a2d71f25d3..9d0c697465bdd 100644 --- a/frame/babe/Cargo.toml +++ b/frame/babe/Cargo.toml @@ -37,7 +37,7 @@ pallet-offences = { version = "4.0.0-dev", path = "../offences" } pallet-staking = { version = "4.0.0-dev", path = "../staking" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } sp-core = { version = "6.0.0", path = "../../primitives/core" } -frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } +pallet-election-provider-support-onchain = { version = "4.0.0-dev", path = "../election-provider-support/onchain" } [features] default = ["std"] diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 37d8e9e37a5f4..6a0e5b980468c 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -19,11 +19,11 @@ use crate::{self as pallet_babe, Config, CurrentSlot}; use codec::Encode; -use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ parameter_types, traits::{ConstU128, ConstU32, ConstU64, GenesisBuild, KeyOwnerProofSystem, OnInitialize}, }; +use pallet_election_provider_support_onchain::OnChainPhragmen; use pallet_session::historical as pallet_session_historical; use sp_consensus_babe::{AuthorityId, AuthorityPair, Slot}; use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; @@ -172,11 +172,12 @@ parameter_types! { pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(16); } -pub struct OnChainSeqPhragmen; -impl onchain::ExecutionConfig for OnChainSeqPhragmen { - type System = Test; - type Solver = SequentialPhragmen; +impl pallet_election_provider_support_onchain::Config for Test { type DataProvider = Staking; + type MaxVoters = ConstU32<600>; + type MaxTargets = ConstU32<400>; + type BenchmarkingConfig = (); + type WeightInfo = (); } impl pallet_staking::Config for Test { @@ -197,7 +198,7 @@ impl pallet_staking::Config for Test { type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; - type ElectionProvider = onchain::UnboundedExecution; + type ElectionProvider = OnChainPhragmen; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; diff --git a/frame/election-provider-multi-phase/Cargo.toml b/frame/election-provider-multi-phase/Cargo.toml index 25f98d965d86b..e46b029729311 100644 --- a/frame/election-provider-multi-phase/Cargo.toml +++ b/frame/election-provider-multi-phase/Cargo.toml @@ -38,6 +38,7 @@ rand = { version = "0.7.3", default-features = false, optional = true, features "small_rng", ] } strum = { optional = true, default-features = false, version = "0.23.0", features = ["derive"] } +pallet-election-provider-support-onchain = { version = "4.0.0-dev", optional = true, default-features = false, path = "../election-provider-support/onchain" } [dev-dependencies] parking_lot = "0.12.0" @@ -70,12 +71,14 @@ std = [ "log/std", "frame-benchmarking/std", + "pallet-election-provider-support-onchain/std", "rand/std", "strum/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-election-provider-support/runtime-benchmarks", + "pallet-election-provider-support-onchain/runtime-benchmarks", "rand", "strum", ] diff --git a/frame/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs index 923e9e2d984cc..fea76c0e4b6b6 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -26,11 +26,19 @@ use frame_support::{ BoundedVec, }; use frame_system::RawOrigin; +use pallet_election_provider_support_onchain::SEED; use rand::{prelude::SliceRandom, rngs::SmallRng, SeedableRng}; use sp_arithmetic::{per_things::Percent, traits::One}; use sp_runtime::InnerOf; -const SEED: u32 = 999; +fn set_up_data_provider(voters_len: u32, targets_len: u32) { + pallet_election_provider_support_onchain::set_up_data_provider::( + voters_len, + targets_len, + ::MaxVotesPerVoter::get(), + T::Currency::minimum_balance().saturated_into::() * 1000, + ); +} /// Creates a **valid** solution with exactly the given size. /// @@ -154,38 +162,6 @@ fn solution_with_size( Ok(RawSolution { solution, score, round }) } -fn set_up_data_provider(v: u32, t: u32) { - T::DataProvider::clear(); - log!( - info, - "setting up with voters = {} [degree = {}], targets = {}", - v, - ::MaxVotesPerVoter::get(), - t - ); - - // fill targets. - let mut targets = (0..t) - .map(|i| { - let target = frame_benchmarking::account::("Target", i, SEED); - T::DataProvider::add_target(target.clone()); - target - }) - .collect::>(); - // we should always have enough voters to fill. - assert!( - targets.len() > ::MaxVotesPerVoter::get() as usize - ); - targets.truncate(::MaxVotesPerVoter::get() as usize); - - // fill voters. - (0..v).for_each(|i| { - let voter = frame_benchmarking::account::("Voter", i, SEED); - let weight = T::Currency::minimum_balance().saturated_into::() * 1000; - T::DataProvider::add_voter(voter, weight, targets.clone().try_into().unwrap()); - }); -} - frame_benchmarking::benchmarks! { on_initialize_nothing { assert!(>::current_phase().is_off()); diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 1b3c4d9306246..3ce0318944023 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -18,7 +18,7 @@ use super::*; use crate as multi_phase; use frame_election_provider_support::{ - data_provider, onchain, ElectionDataProvider, NposSolution, SequentialPhragmen, + data_provider, ElectionDataProvider, NposSolution, SequentialPhragmen, }; pub use frame_support::{assert_noop, assert_ok}; use frame_support::{ @@ -28,6 +28,7 @@ use frame_support::{ BoundedVec, }; use multi_phase::unsigned::{IndexAssignmentOf, VoterOf}; +use pallet_election_provider_support_onchain::BoundedExecution; use parking_lot::RwLock; use sp_core::{ offchain::{ @@ -275,11 +276,12 @@ parameter_types! { pub static OnChainFallback: bool = true; } -pub struct OnChainSeqPhragmen; -impl onchain::ExecutionConfig for OnChainSeqPhragmen { - type System = Runtime; - type Solver = SequentialPhragmen, Balancing>; +impl pallet_election_provider_support_onchain::Config for Runtime { type DataProvider = StakingMock; + type MaxVoters = ConstU32<600>; + type MaxTargets = ConstU32<100_000>; + type WeightInfo = (); + type BenchmarkingConfig = TestBenchmarkingConfig; } pub struct MockFallback; @@ -300,11 +302,11 @@ impl InstantElectionProvider for MockFallback { max_targets: usize, ) -> Result, Self::Error> { if OnChainFallback::get() { - onchain::UnboundedExecution::::elect_with_bounds( - max_voters, - max_targets, - ) - .map_err(|_| "UnboundedExecution failed") + BoundedExecution::< + Runtime, + SequentialPhragmen, Balancing>, + >::elect_with_bounds(max_voters, max_targets) + .map_err(|_| "BoundedExecution failed") } else { super::NoFallback::::elect_with_bounds(max_voters, max_targets) } @@ -407,6 +409,12 @@ impl BenchmarkingConfig for TestBenchmarkingConfig { const MAXIMUM_TARGETS: u32 = 200; } +impl pallet_election_provider_support_onchain::BenchmarkingConfig for TestBenchmarkingConfig { + const VOTES_PER_VOTER: [u32; 2] = [1, 2]; + const TARGETS: [u32; 2] = [200, 400]; + const VOTERS: [u32; 2] = [400, 600]; +} + impl crate::Config for Runtime { type Event = Event; type Currency = Balances; diff --git a/frame/election-provider-support/Cargo.toml b/frame/election-provider-support/Cargo.toml index be0c05e46df32..44ddb22c977ad 100644 --- a/frame/election-provider-support/Cargo.toml +++ b/frame/election-provider-support/Cargo.toml @@ -25,9 +25,6 @@ frame-election-provider-solution-type = { version = "4.0.0-dev", path = "solutio [dev-dependencies] rand = "0.7.3" -sp-npos-elections = { version = "4.0.0-dev", path = "../../primitives/npos-elections" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } [features] default = ["std"] diff --git a/frame/election-provider-support/onchain/Cargo.toml b/frame/election-provider-support/onchain/Cargo.toml new file mode 100644 index 0000000000000..8634685e30e84 --- /dev/null +++ b/frame/election-provider-support/onchain/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "pallet-election-provider-support-onchain" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "election provider support onchain config trait" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/npos-elections" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } +frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = ".." } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking", optional = true } + +[dev-dependencies] +sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-io = { version = "6.0.0", path = "../../../primitives/io" } + + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "sp-npos-elections/std", + "sp-runtime/std", + "sp-std/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-election-provider-support/runtime-benchmarks", +] diff --git a/frame/election-provider-support/onchain/src/benchmarking.rs b/frame/election-provider-support/onchain/src/benchmarking.rs new file mode 100644 index 0000000000000..2e00535f03ed4 --- /dev/null +++ b/frame/election-provider-support/onchain/src/benchmarking.rs @@ -0,0 +1,104 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! election provider support onchain pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; + +use frame_benchmarking::{benchmarks, Vec}; +use frame_support::log; + +#[cfg(test)] +use crate::Pallet as ElectionProviderSupportOnchain; + +// This is also used in `pallet_election_provider_multi_phase` benchmarking. +pub const SEED: u32 = 999; +pub fn set_up_data_provider< + T: frame_system::Config, + DataProvider: ElectionDataProvider, +>( + voters_len: u32, + targets_len: u32, + degree: u32, + weight: u64, +) { + DataProvider::clear(); + log::info!( + "setting up with voters = {} [degree = {}], targets = {}", + voters_len, + degree, + targets_len + ); + + // fill targets. + let mut targets = (0..targets_len) + .map(|i| { + let target = frame_benchmarking::account::("Target", i, SEED); + DataProvider::add_target(target.clone()); + target + }) + .collect::>(); + // we should always have enough voters to fill. + assert!(targets.len() > degree as usize); + targets.truncate(degree as usize); + + // fill voters. + (0..voters_len).for_each(|i| { + let voter = frame_benchmarking::account::("Voter", i, SEED); + DataProvider::add_voter(voter, weight, targets.clone().try_into().unwrap()); + }); +} + +benchmarks! { + phragmen { + // number of votes in snapshot. + let v in (T::BenchmarkingConfig::VOTERS[0]) .. T::BenchmarkingConfig::VOTERS[1]; + // number of targets in snapshot. + let t in (T::BenchmarkingConfig::TARGETS[0]) .. T::BenchmarkingConfig::TARGETS[1]; + // number of votes per voter (ie the degree). + let d in (T::BenchmarkingConfig::VOTES_PER_VOTER[0]) .. T::BenchmarkingConfig::VOTES_PER_VOTER[1]; + + // we don't directly need the data-provider to be populated, but it is just easy to use it. + set_up_data_provider::(v, t, d, 1_000u64); + }: { + assert!(OnChainPhragmen::::elect().is_ok()); + } verify { + } + + phragmms { + // number of votes in snapshot. + let v in (T::BenchmarkingConfig::VOTERS[0]) .. T::BenchmarkingConfig::VOTERS[1]; + // number of targets in snapshot. + let t in (T::BenchmarkingConfig::TARGETS[0]) .. T::BenchmarkingConfig::TARGETS[1]; + // number of votes per voter (ie the degree). + let d in (T::BenchmarkingConfig::VOTES_PER_VOTER[0]) .. T::BenchmarkingConfig::VOTES_PER_VOTER[1]; + + // we don't directly need the data-provider to be populated, but it is just easy to use it. + set_up_data_provider::(v, t, d, 1_000u64); + }: { + assert!(OnChainPhragMMS::::elect().is_ok()); + } verify { + } + + impl_benchmark_test_suite!( + ElectionProviderSupportOnchain, + sp_io::TestExternalities::new_empty(), + crate::tests::Runtime, + ); +} diff --git a/frame/election-provider-support/onchain/src/lib.rs b/frame/election-provider-support/onchain/src/lib.rs new file mode 100644 index 0000000000000..f344cdabf45c6 --- /dev/null +++ b/frame/election-provider-support/onchain/src/lib.rs @@ -0,0 +1,359 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! An implementation of [`ElectionProvider`] that uses an `NposSolver` to do the election. +//! +//! A simple on-chain implementation of the election provider trait. +//! +//! This will accept voting data on the fly and produce the results immediately. +//! +//! Finally, the [`ElectionProvider`] implementation of this type does not impose any dynamic limits +//! on the number of voters and targets that are fetched. However, one can impose bounds on it by +//! using the `MaxVotes` and `MaxTargets` bounds in the `Config` trait. +//! +//! On the other hand, the [`InstantElectionProvider`] implementation does limit these inputs +//! dynamically. If you use `elect_with_bounds` along with `InstantElectionProvider`, the bound that +//! would be used is the minimum of the 2 bounds. +//! +//! It is advisable to use the former ([`ElectionProvider::elect`]) only at genesis, or for testing, +//! the latter [`InstantElectionProvider::elect_with_bounds`] for onchain operations, with +//! thoughtful bounds. +//! +//! ### Warning +//! +//! This can be very expensive to run frequently on-chain. Use with care. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::alloc::collections::BTreeMap; +use frame_election_provider_support::{ + ElectionDataProvider, ElectionProvider, InstantElectionProvider, NposSolver, PhragMMS, + SequentialPhragmen, +}; +use frame_support::{ + pallet_prelude::PhantomData, + traits::Get, + weights::{DispatchClass, Weight}, +}; +use sp_npos_elections::{ + assignment_ratio_to_staked_normalized, to_supports, ElectionResult, Supports, VoteWeight, +}; + +mod benchmarking; +#[cfg(feature = "runtime-benchmarks")] +pub use benchmarking::{set_up_data_provider, SEED}; + +pub mod weights; +pub use weights::WeightInfo; + +/// `OnChainPhragmen` is a simple implementation of the `ElectionProvider` and +/// `InstantElectionProvider` traits, that uses the `SequentialPhragmen` algorithm to solve for the +/// solution. +pub type OnChainPhragmen = BoundedExecution< + T, + SequentialPhragmen<::AccountId, Accuracy, Balancing>, +>; + +/// Similar to `OnChainPhragmen`, but uses the `PhragMMS` algorithm to solve for the solution. +pub type OnChainPhragMMS = + BoundedExecution::AccountId, Accuracy, Balancing>>; + +/// Errors of the on-chain election. +#[derive(Eq, PartialEq, Debug)] +pub enum Error { + /// An internal error in the NPoS elections crate. + NposElections(sp_npos_elections::Error), + /// Errors from the data provider. + DataProvider(&'static str), +} + +impl From for Error { + fn from(e: sp_npos_elections::Error) -> Self { + Error::NposElections(e) + } +} + +/// Configuration for the benchmarks of the pallet. +pub trait BenchmarkingConfig { + /// Range of voters. + const VOTERS: [u32; 2]; + /// Range of targets. + const TARGETS: [u32; 2]; + /// Range of number of votes per voter. + const VOTES_PER_VOTER: [u32; 2]; +} + +// Default values for ease. +impl BenchmarkingConfig for () { + const VOTERS: [u32; 2] = [400, 600]; + const TARGETS: [u32; 2] = [200, 400]; + const VOTES_PER_VOTER: [u32; 2] = [1, 2]; +} + +/// Configuration for the weight measuring function of the `NposSolver`. +pub trait WeightConfig { + fn weight(v: u32, t: u32, d: u32) -> Weight; +} + +impl WeightConfig + for SequentialPhragmen +{ + fn weight(v: u32, t: u32, d: u32) -> Weight { + T::WeightInfo::phragmen(v, t, d) + } +} + +impl WeightConfig for PhragMMS { + fn weight(v: u32, t: u32, d: u32) -> Weight { + T::WeightInfo::phragmms(v, t, d) + } +} + +pub use pallet::*; +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Something that provides the data for election. + type DataProvider: ElectionDataProvider< + AccountId = Self::AccountId, + BlockNumber = Self::BlockNumber, + >; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// Bounds the number of voters. + type MaxVoters: Get; + + /// Bounds the number of targets. + type MaxTargets: Get; + + /// The configuration of benchmarking. + type BenchmarkingConfig: BenchmarkingConfig; + } + + #[pallet::pallet] + pub struct Pallet(PhantomData); +} + +/// `NposSolver` that should be used, an example would be `PhragMMS`. +/// It's advised to use the `OnChainPhragmen` or `OnChainPhragMMS` instead, as they implement the +/// `WeightConfig` trait. +pub struct BoundedExecution< + T: Config, + Solver: NposSolver, +>(PhantomData<(T, Solver)>); + +impl< + T: Config, + Solver: NposSolver + WeightConfig, + > BoundedExecution +{ + fn elect_with(max_voters: usize, max_targets: usize) -> Result, Error> { + let voters = + T::DataProvider::electing_voters(Some(max_voters)).map_err(Error::DataProvider)?; + let targets = + T::DataProvider::electable_targets(Some(max_targets)).map_err(Error::DataProvider)?; + let desired_targets = T::DataProvider::desired_targets().map_err(Error::DataProvider)?; + + let voters_len = voters.len() as u32; + let targets_len = targets.len() as u32; + + let stake_map: BTreeMap<_, _> = voters + .iter() + .map(|(validator, vote_weight, _)| (validator.clone(), *vote_weight)) + .collect(); + + let stake_of = + |w: &T::AccountId| -> VoteWeight { stake_map.get(w).cloned().unwrap_or_default() }; + + let ElectionResult { winners: _, assignments } = + Solver::solve(desired_targets as usize, targets, voters).map_err(Error::from)?; + + let staked = assignment_ratio_to_staked_normalized(assignments, &stake_of)?; + + let weight = Solver::weight::( + voters_len, + targets_len, + ::MaxVotesPerVoter::get(), + ); + frame_system::Pallet::::register_extra_weight_unchecked( + weight, + DispatchClass::Mandatory, + ); + + Ok(to_supports(&staked)) + } +} + +impl< + T: Config, + Solver: NposSolver + WeightConfig, + > ElectionProvider for BoundedExecution +{ + type AccountId = T::AccountId; + type BlockNumber = T::BlockNumber; + type Error = Error; + type DataProvider = T::DataProvider; + + fn elect() -> Result, Self::Error> { + Self::elect_with(T::MaxVoters::get() as usize, T::MaxTargets::get() as usize) + } +} + +impl< + T: Config, + Solver: NposSolver + WeightConfig, + > InstantElectionProvider for BoundedExecution +{ + fn elect_with_bounds( + max_voters: usize, + max_targets: usize, + ) -> Result, Self::Error> { + Self::elect_with( + max_voters.min(T::MaxVoters::get() as usize), + max_targets.min(T::MaxTargets::get() as usize), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::traits::ConstU32; + use sp_npos_elections::Support; + type AccountId = u64; + type BlockNumber = u64; + + pub type Header = sp_runtime::generic::Header; + pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; + pub type Block = sp_runtime::generic::Block; + + frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Event}, + } + ); + + impl frame_system::Config for Runtime { + type SS58Prefix = (); + type BaseCallFilter = frame_support::traits::Everything; + type Origin = Origin; + type Index = AccountId; + type BlockNumber = BlockNumber; + type Call = Call; + type Hash = sp_core::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = sp_runtime::testing::Header; + type Event = (); + type BlockHashCount = (); + type DbWeight = (); + type BlockLength = (); + type BlockWeights = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; + } + + pub struct ElectionProviderBenchmarkConfig; + impl BenchmarkingConfig for ElectionProviderBenchmarkConfig { + const VOTERS: [u32; 2] = [400, 600]; + const TARGETS: [u32; 2] = [200, 400]; + const VOTES_PER_VOTER: [u32; 2] = [1, 2]; + } + + impl Config for Runtime { + type DataProvider = mock_data_provider::DataProvider; + type MaxVoters = ConstU32<600>; + type MaxTargets = ConstU32<400>; + type BenchmarkingConfig = ElectionProviderBenchmarkConfig; + type WeightInfo = (); + } + + mod mock_data_provider { + use frame_support::{bounded_vec, traits::ConstU32}; + + use super::*; + use frame_election_provider_support::{data_provider, VoterOf}; + + pub struct DataProvider; + impl ElectionDataProvider for DataProvider { + type AccountId = AccountId; + type BlockNumber = BlockNumber; + type MaxVotesPerVoter = ConstU32<2>; + fn electing_voters(_: Option) -> data_provider::Result>> { + Ok(vec![ + (1, 10, bounded_vec![10, 20]), + (2, 20, bounded_vec![30, 20]), + (3, 30, bounded_vec![10, 30]), + ]) + } + + fn electable_targets(_: Option) -> data_provider::Result> { + Ok(vec![10, 20, 30]) + } + + fn desired_targets() -> data_provider::Result { + Ok(2) + } + + fn next_election_prediction(_: BlockNumber) -> BlockNumber { + 0 + } + } + } + + #[test] + fn onchain_seq_phragmen_works() { + sp_io::TestExternalities::new_empty().execute_with(|| { + assert_eq!( + OnChainPhragmen::::elect().unwrap(), + vec![ + (10, Support { total: 25, voters: vec![(1, 10), (3, 15)] }), + (30, Support { total: 35, voters: vec![(2, 20), (3, 15)] }) + ] + ); + }) + } + + #[test] + fn onchain_phragmms_works() { + sp_io::TestExternalities::new_empty().execute_with(|| { + assert_eq!( + OnChainPhragMMS::::elect().unwrap(), + vec![ + (10, Support { total: 25, voters: vec![(1, 10), (3, 15)] }), + (30, Support { total: 35, voters: vec![(2, 20), (3, 15)] }) + ] + ); + }) + } +} diff --git a/frame/election-provider-support/onchain/src/weights.rs b/frame/election-provider-support/onchain/src/weights.rs new file mode 100644 index 0000000000000..60e10a6af1638 --- /dev/null +++ b/frame/election-provider-support/onchain/src/weights.rs @@ -0,0 +1,146 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_election_provider_support_onchain +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-03-27, STEPS: `1`, REPEAT: 1, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/substrate +// benchmark +// --chain=dev +// --steps=1 +// --repeat=1 +// --pallet=pallet_election_provider_support_onchain +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=frame/election-provider-support/onchain/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_election_provider_support_onchain. +pub trait WeightInfo { + fn phragmen(v: u32, t: u32, d: u32, ) -> Weight; + fn phragmms(v: u32, t: u32, d: u32, ) -> Weight; +} + +/// Weights for pallet_election_provider_support_onchain using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: BagsList CounterForListNodes (r:1 w:0) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: BagsList ListBags (r:200 w:0) + // Storage: BagsList ListNodes (r:2000 w:0) + // Storage: Staking Nominators (r:2000 w:0) + // Storage: Staking Validators (r:1001 w:0) + // Storage: Staking Bonded (r:2000 w:0) + // Storage: Staking Ledger (r:2000 w:0) + // Storage: Staking CounterForValidators (r:1 w:0) + // Storage: Staking ValidatorCount (r:1 w:0) + fn phragmen(v: u32, t: u32, d: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 2_448_000 + .saturating_add((27_728_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 4_896_000 + .saturating_add((31_153_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 222_545_000 + .saturating_add((405_848_000 as Weight).saturating_mul(d as Weight)) + .saturating_add(T::DbWeight::get().reads(205 as Weight)) + .saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(v as Weight))) + .saturating_add(T::DbWeight::get().reads((5 as Weight).saturating_mul(t as Weight))) + } + // Storage: BagsList CounterForListNodes (r:1 w:0) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: BagsList ListBags (r:200 w:0) + // Storage: BagsList ListNodes (r:2000 w:0) + // Storage: Staking Nominators (r:2000 w:0) + // Storage: Staking Validators (r:1001 w:0) + // Storage: Staking Bonded (r:2000 w:0) + // Storage: Staking Ledger (r:2000 w:0) + // Storage: Staking CounterForValidators (r:1 w:0) + // Storage: Staking ValidatorCount (r:1 w:0) + fn phragmms(v: u32, t: u32, d: u32, ) -> Weight { + (2_275_152_000 as Weight) + // Standard Error: 1_211_000 + .saturating_add((23_576_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 2_423_000 + .saturating_add((26_701_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 110_171_000 + .saturating_add((43_303_000 as Weight).saturating_mul(d as Weight)) + .saturating_add(T::DbWeight::get().reads(205 as Weight)) + .saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(v as Weight))) + .saturating_add(T::DbWeight::get().reads((5 as Weight).saturating_mul(t as Weight))) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: BagsList CounterForListNodes (r:1 w:0) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: BagsList ListBags (r:200 w:0) + // Storage: BagsList ListNodes (r:2000 w:0) + // Storage: Staking Nominators (r:2000 w:0) + // Storage: Staking Validators (r:1001 w:0) + // Storage: Staking Bonded (r:2000 w:0) + // Storage: Staking Ledger (r:2000 w:0) + // Storage: Staking CounterForValidators (r:1 w:0) + // Storage: Staking ValidatorCount (r:1 w:0) + fn phragmen(v: u32, t: u32, d: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 2_448_000 + .saturating_add((27_728_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 4_896_000 + .saturating_add((31_153_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 222_545_000 + .saturating_add((405_848_000 as Weight).saturating_mul(d as Weight)) + .saturating_add(RocksDbWeight::get().reads(205 as Weight)) + .saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(v as Weight))) + .saturating_add(RocksDbWeight::get().reads((5 as Weight).saturating_mul(t as Weight))) + } + // Storage: BagsList CounterForListNodes (r:1 w:0) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: BagsList ListBags (r:200 w:0) + // Storage: BagsList ListNodes (r:2000 w:0) + // Storage: Staking Nominators (r:2000 w:0) + // Storage: Staking Validators (r:1001 w:0) + // Storage: Staking Bonded (r:2000 w:0) + // Storage: Staking Ledger (r:2000 w:0) + // Storage: Staking CounterForValidators (r:1 w:0) + // Storage: Staking ValidatorCount (r:1 w:0) + fn phragmms(v: u32, t: u32, d: u32, ) -> Weight { + (2_275_152_000 as Weight) + // Standard Error: 1_211_000 + .saturating_add((23_576_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 2_423_000 + .saturating_add((26_701_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 110_171_000 + .saturating_add((43_303_000 as Weight).saturating_mul(d as Weight)) + .saturating_add(RocksDbWeight::get().reads(205 as Weight)) + .saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(v as Weight))) + .saturating_add(RocksDbWeight::get().reads((5 as Weight).saturating_mul(t as Weight))) + } +} diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index d79b5289dffe3..c8395da167171 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -166,7 +166,6 @@ #![cfg_attr(not(feature = "std"), no_std)] -pub mod onchain; pub mod traits; #[cfg(feature = "std")] use codec::{Decode, Encode}; diff --git a/frame/election-provider-support/src/onchain.rs b/frame/election-provider-support/src/onchain.rs deleted file mode 100644 index 57fd931a467d1..0000000000000 --- a/frame/election-provider-support/src/onchain.rs +++ /dev/null @@ -1,284 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! An implementation of [`ElectionProvider`] that uses an `NposSolver` to do the election. - -use crate::{ElectionDataProvider, ElectionProvider, InstantElectionProvider, NposSolver}; -use frame_support::{traits::Get, weights::DispatchClass}; -use sp_npos_elections::*; -use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData, prelude::*}; - -/// Errors of the on-chain election. -#[derive(Eq, PartialEq, Debug)] -pub enum Error { - /// An internal error in the NPoS elections crate. - NposElections(sp_npos_elections::Error), - /// Errors from the data provider. - DataProvider(&'static str), -} - -impl From for Error { - fn from(e: sp_npos_elections::Error) -> Self { - Error::NposElections(e) - } -} - -/// A simple on-chain implementation of the election provider trait. -/// -/// This will accept voting data on the fly and produce the results immediately. -/// -/// 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. One could, however, impose bounds on it by using for example -/// `BoundedExecution` which will the bounds provided in the configuration. -/// -/// On the other hand, the [`InstantElectionProvider`] implementation does limit these inputs, -/// either via using `BoundedExecution` and imposing the bounds there, or dynamically via calling -/// `elect_with_bounds` providing these bounds. If you use `elect_with_bounds` along with -/// `InstantElectionProvider`, the bound that would be used is the minimum of the 2 bounds. -/// -/// It is advisable to use the former ([`ElectionProvider::elect`]) only at genesis, or for testing, -/// the latter [`InstantElectionProvider::elect_with_bounds`] for onchain operations, with -/// thoughtful bounds. -/// -/// Please use `BoundedExecution` at all times except at genesis or for testing, with thoughtful -/// bounds in order to bound the potential execution time. Limit the use `UnboundedExecution` at -/// genesis or for testing, as it does not bound the inputs. However, this can be used with -/// `[InstantElectionProvider::elect_with_bounds`] that dynamically imposes limits. -pub struct BoundedExecution(PhantomData); - -/// An unbounded variant of [`BoundedExecution`]. -/// -/// ### Warning -/// -/// This can be very expensive to run frequently on-chain. Use with care. Moreover, this -/// implementation ignores the additional data of the election data provider and gives no insight on -/// how much weight was consumed. -pub struct UnboundedExecution(PhantomData); - -/// Configuration trait of [`UnboundedExecution`]. -pub trait ExecutionConfig { - /// Something that implements the system pallet configs. This is to enable to register extra - /// weight. - type System: frame_system::Config; - /// `NposSolver` that should be used, an example would be `PhragMMS`. - type Solver: NposSolver< - AccountId = ::AccountId, - Error = sp_npos_elections::Error, - >; - /// Something that provides the data for election. - type DataProvider: ElectionDataProvider< - AccountId = ::AccountId, - BlockNumber = ::BlockNumber, - >; -} - -/// Configuration trait of [`BoundedExecution`]. -pub trait BoundedExecutionConfig: ExecutionConfig { - /// Bounds the number of voters. - type VotersBound: Get; - /// Bounds the number of targets. - type TargetsBound: Get; -} - -fn elect_with( - maybe_max_voters: Option, - maybe_max_targets: Option, -) -> Result::AccountId>, Error> { - let voters = T::DataProvider::electing_voters(maybe_max_voters).map_err(Error::DataProvider)?; - let targets = - T::DataProvider::electable_targets(maybe_max_targets).map_err(Error::DataProvider)?; - let desired_targets = T::DataProvider::desired_targets().map_err(Error::DataProvider)?; - - let stake_map: BTreeMap<_, _> = voters - .iter() - .map(|(validator, vote_weight, _)| (validator.clone(), *vote_weight)) - .collect(); - - let stake_of = |w: &::AccountId| -> VoteWeight { - stake_map.get(w).cloned().unwrap_or_default() - }; - - let ElectionResult { winners: _, assignments } = - T::Solver::solve(desired_targets as usize, targets, voters).map_err(Error::from)?; - - let staked = assignment_ratio_to_staked_normalized(assignments, &stake_of)?; - - let weight = ::BlockWeights::get().max_block; - frame_system::Pallet::::register_extra_weight_unchecked( - weight, - DispatchClass::Mandatory, - ); - - Ok(to_supports(&staked)) -} - -impl ElectionProvider for UnboundedExecution { - type AccountId = ::AccountId; - type BlockNumber = ::BlockNumber; - type Error = Error; - type DataProvider = T::DataProvider; - - fn elect() -> Result::AccountId>, Self::Error> { - // This should not be called if not in `std` mode (and therefore neither in genesis nor in - // testing) - if cfg!(not(feature = "std")) { - frame_support::log::error!( - "Please use `InstantElectionProvider` instead to provide bounds on election if not in \ - genesis or testing mode" - ); - } - - elect_with::(None, None) - } -} - -impl InstantElectionProvider for UnboundedExecution { - fn elect_with_bounds( - max_voters: usize, - max_targets: usize, - ) -> Result, Self::Error> { - elect_with::(Some(max_voters), Some(max_targets)) - } -} - -impl ElectionProvider for BoundedExecution { - type AccountId = ::AccountId; - type BlockNumber = ::BlockNumber; - type Error = Error; - type DataProvider = T::DataProvider; - - fn elect() -> Result::AccountId>, Self::Error> { - elect_with::(Some(T::VotersBound::get() as usize), Some(T::TargetsBound::get() as usize)) - } -} - -impl InstantElectionProvider for BoundedExecution { - fn elect_with_bounds( - max_voters: usize, - max_targets: usize, - ) -> Result, Self::Error> { - elect_with::( - Some(max_voters.min(T::VotersBound::get() as usize)), - Some(max_targets.min(T::TargetsBound::get() as usize)), - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use sp_npos_elections::Support; - use sp_runtime::Perbill; - type AccountId = u64; - type BlockNumber = u64; - - pub type Header = sp_runtime::generic::Header; - pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; - pub type Block = sp_runtime::generic::Block; - - frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic - { - System: frame_system::{Pallet, Call, Event}, - } - ); - - impl frame_system::Config for Runtime { - type SS58Prefix = (); - type BaseCallFilter = frame_support::traits::Everything; - type Origin = Origin; - type Index = AccountId; - type BlockNumber = BlockNumber; - type Call = Call; - type Hash = sp_core::H256; - type Hashing = sp_runtime::traits::BlakeTwo256; - type AccountId = AccountId; - type Lookup = sp_runtime::traits::IdentityLookup; - type Header = sp_runtime::testing::Header; - type Event = (); - type BlockHashCount = (); - type DbWeight = (); - type BlockLength = (); - type BlockWeights = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; - } - - impl ExecutionConfig for Runtime { - type System = Self; - type Solver = crate::SequentialPhragmen; - type DataProvider = mock_data_provider::DataProvider; - } - - type OnChainPhragmen = UnboundedExecution; - - mod mock_data_provider { - use frame_support::{bounded_vec, traits::ConstU32}; - - use super::*; - use crate::{data_provider, VoterOf}; - - pub struct DataProvider; - impl ElectionDataProvider for DataProvider { - type AccountId = AccountId; - type BlockNumber = BlockNumber; - type MaxVotesPerVoter = ConstU32<2>; - fn electing_voters(_: Option) -> data_provider::Result>> { - Ok(vec![ - (1, 10, bounded_vec![10, 20]), - (2, 20, bounded_vec![30, 20]), - (3, 30, bounded_vec![10, 30]), - ]) - } - - fn electable_targets(_: Option) -> data_provider::Result> { - Ok(vec![10, 20, 30]) - } - - fn desired_targets() -> data_provider::Result { - Ok(2) - } - - fn next_election_prediction(_: BlockNumber) -> BlockNumber { - 0 - } - } - } - - #[test] - fn onchain_seq_phragmen_works() { - sp_io::TestExternalities::new_empty().execute_with(|| { - assert_eq!( - OnChainPhragmen::elect().unwrap(), - vec![ - (10, Support { total: 25, voters: vec![(1, 10), (3, 15)] }), - (30, Support { total: 35, voters: vec![(2, 20), (3, 15)] }) - ] - ); - }) - } -} diff --git a/frame/grandpa/Cargo.toml b/frame/grandpa/Cargo.toml index cad238a4e365f..09fbfa20a04c6 100644 --- a/frame/grandpa/Cargo.toml +++ b/frame/grandpa/Cargo.toml @@ -39,7 +39,7 @@ pallet-offences = { version = "4.0.0-dev", path = "../offences" } pallet-staking = { version = "4.0.0-dev", path = "../staking" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } pallet-timestamp = { version = "4.0.0-dev", path = "../timestamp" } -frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } +pallet-election-provider-support-onchain = { version = "4.0.0-dev", path = "../election-provider-support/onchain" } [features] default = ["std"] diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 0296cd2e28d88..2d656fef3426d 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -22,13 +22,13 @@ use crate::{self as pallet_grandpa, AuthorityId, AuthorityList, Config, ConsensusLog}; use ::grandpa as finality_grandpa; use codec::Encode; -use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ parameter_types, traits::{ ConstU128, ConstU32, ConstU64, GenesisBuild, KeyOwnerProofSystem, OnFinalize, OnInitialize, }, }; +use pallet_election_provider_support_onchain::OnChainPhragmen; use pallet_session::historical as pallet_session_historical; use sp_core::{crypto::KeyTypeId, H256}; use sp_finality_grandpa::{RoundNumber, SetId, GRANDPA_ENGINE_ID}; @@ -180,11 +180,12 @@ parameter_types! { pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); } -pub struct OnChainSeqPhragmen; -impl onchain::ExecutionConfig for OnChainSeqPhragmen { - type System = Test; - type Solver = SequentialPhragmen<::AccountId, Perbill>; +impl pallet_election_provider_support_onchain::Config for Test { type DataProvider = Staking; + type MaxVoters = ConstU32<600>; + type MaxTargets = ConstU32<400>; + type BenchmarkingConfig = (); + type WeightInfo = (); } impl pallet_staking::Config for Test { @@ -205,7 +206,7 @@ impl pallet_staking::Config for Test { type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; - type ElectionProvider = onchain::UnboundedExecution; + type ElectionProvider = OnChainPhragmen; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; diff --git a/frame/offences/benchmarking/Cargo.toml b/frame/offences/benchmarking/Cargo.toml index 605e0c60a03c2..5041b80878458 100644 --- a/frame/offences/benchmarking/Cargo.toml +++ b/frame/offences/benchmarking/Cargo.toml @@ -32,11 +32,11 @@ pallet-staking = { version = "4.0.0-dev", default-features = false, features = [ sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/staking" } sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } -frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../../election-provider-support" } [dev-dependencies] pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" } pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } +pallet-election-provider-support-onchain = { version = "4.0.0-dev", path = "../../election-provider-support/onchain" } sp-core = { version = "6.0.0", path = "../../../primitives/core" } sp-io = { version = "6.0.0", path = "../../../primitives/io" } @@ -55,7 +55,6 @@ std = [ "pallet-staking/std", "sp-runtime/std", "sp-staking/std", - "frame-election-provider-support/std", "sp-std/std", "codec/std", "scale-info/std", diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index 1a4414de0b0b0..8da85247ceb09 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -20,13 +20,13 @@ #![cfg(test)] use super::*; -use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ parameter_types, traits::{ConstU32, ConstU64}, weights::constants::WEIGHT_PER_SECOND, }; use frame_system as system; +use pallet_election_provider_support_onchain::OnChainPhragmen; use pallet_session::historical as pallet_session_historical; use sp_runtime::{ testing::{Header, UintAuthorityId}, @@ -150,11 +150,12 @@ parameter_types! { pub type Extrinsic = sp_runtime::testing::TestXt; -pub struct OnChainSeqPhragmen; -impl onchain::ExecutionConfig for OnChainSeqPhragmen { - type System = Test; - type Solver = SequentialPhragmen; +impl pallet_election_provider_support_onchain::Config for Test { type DataProvider = Staking; + type MaxVoters = ConstU32<600>; + type MaxTargets = ConstU32<400>; + type BenchmarkingConfig = (); + type WeightInfo = (); } impl pallet_staking::Config for Test { @@ -175,7 +176,7 @@ impl pallet_staking::Config for Test { type NextNewSession = Session; type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = (); - type ElectionProvider = onchain::UnboundedExecution; + type ElectionProvider = OnChainPhragmen; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; diff --git a/frame/session/benchmarking/Cargo.toml b/frame/session/benchmarking/Cargo.toml index b00d1335d22a2..b91639f5b8251 100644 --- a/frame/session/benchmarking/Cargo.toml +++ b/frame/session/benchmarking/Cargo.toml @@ -33,7 +33,7 @@ sp-io = { version = "6.0.0", path = "../../../primitives/io" } pallet-balances = { version = "4.0.0-dev", path = "../../balances" } pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" } -frame-election-provider-support = { version = "4.0.0-dev", path = "../../election-provider-support" } +pallet-election-provider-support-onchain = { version = "4.0.0-dev", path = "../../election-provider-support/onchain" } [features] default = ["std"] diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index 24b42b3e9f4b5..1da93f9b74d6c 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -19,11 +19,11 @@ #![cfg(test)] -use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ parameter_types, traits::{ConstU32, ConstU64}, }; +use pallet_election_provider_support_onchain::OnChainPhragmen; use sp_runtime::traits::IdentityLookup; type AccountId = u64; @@ -156,11 +156,12 @@ where type Extrinsic = Extrinsic; } -pub struct OnChainSeqPhragmen; -impl onchain::ExecutionConfig for OnChainSeqPhragmen { - type System = Test; - type Solver = SequentialPhragmen; +impl pallet_election_provider_support_onchain::Config for Test { type DataProvider = Staking; + type MaxVoters = ConstU32<600>; + type MaxTargets = ConstU32<400>; + type BenchmarkingConfig = (); + type WeightInfo = (); } impl pallet_staking::Config for Test { @@ -181,7 +182,7 @@ impl pallet_staking::Config for Test { type NextNewSession = Session; type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = (); - type ElectionProvider = onchain::UnboundedExecution; + type ElectionProvider = OnChainPhragmen; type GenesisElectionProvider = Self::ElectionProvider; type MaxUnlockingChunks = ConstU32<32>; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index 073d959e2d1dd..b1247f5d760c1 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -44,6 +44,7 @@ pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-timestamp = { version = "4.0.0-dev", path = "../timestamp" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } pallet-bags-list = { version = "4.0.0-dev", features = ["runtime-benchmarks"], path = "../bags-list" } +pallet-election-provider-support-onchain = { version = "4.0.0-dev", path = "../election-provider-support/onchain" } substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking" } frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index bb90aded852ee..35037c04c1dbd 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -18,9 +18,7 @@ //! Test utilities use crate::{self as pallet_staking, *}; -use frame_election_provider_support::{ - onchain, SequentialPhragmen, SortedListProvider, VoteWeight, -}; +use frame_election_provider_support::{SortedListProvider, VoteWeight}; use frame_support::{ assert_ok, parameter_types, traits::{ @@ -29,6 +27,7 @@ use frame_support::{ }, weights::constants::RocksDbWeight, }; +use pallet_election_provider_support_onchain::OnChainPhragmen; use sp_core::H256; use sp_io; use sp_runtime::{ @@ -247,11 +246,12 @@ impl pallet_bags_list::Config for Test { type Score = VoteWeight; } -pub struct OnChainSeqPhragmen; -impl onchain::ExecutionConfig for OnChainSeqPhragmen { - type System = Test; - type Solver = SequentialPhragmen; +impl pallet_election_provider_support_onchain::Config for Test { type DataProvider = Staking; + type MaxVoters = ConstU32<600>; + type MaxTargets = ConstU32<400>; + type BenchmarkingConfig = (); + type WeightInfo = (); } impl crate::pallet::pallet::Config for Test { @@ -272,7 +272,7 @@ impl crate::pallet::pallet::Config for Test { type NextNewSession = Session; type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; - type ElectionProvider = onchain::UnboundedExecution; + type ElectionProvider = OnChainPhragmen; type GenesisElectionProvider = Self::ElectionProvider; // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. type VoterList = BagsList;