From d998b12e7f8edeccb8cf64c84f4c0474a6b2ea61 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 15 Jan 2021 13:19:35 +0000 Subject: [PATCH 01/29] Base features and traits. --- Cargo.lock | 11 + Cargo.toml | 1 + primitives/election-providers/Cargo.toml | 33 ++ primitives/election-providers/src/lib.rs | 239 +++++++++++++ primitives/election-providers/src/onchain.rs | 168 +++++++++ .../npos-elections/compact/src/assignment.rs | 54 +-- primitives/npos-elections/compact/src/lib.rs | 123 ++++--- .../fuzzer/src/phragmen_balancing.rs | 23 +- .../fuzzer/src/phragmms_balancing.rs | 23 +- .../npos-elections/fuzzer/src/reduce.rs | 9 +- primitives/npos-elections/src/helpers.rs | 23 +- primitives/npos-elections/src/lib.rs | 324 ++++++++++++------ primitives/npos-elections/src/mock.rs | 12 +- primitives/npos-elections/src/phragmen.rs | 46 ++- primitives/npos-elections/src/phragmms.rs | 7 +- primitives/npos-elections/src/tests.rs | 99 ++++-- 16 files changed, 908 insertions(+), 287 deletions(-) create mode 100644 primitives/election-providers/Cargo.toml create mode 100644 primitives/election-providers/src/lib.rs create mode 100644 primitives/election-providers/src/onchain.rs diff --git a/Cargo.lock b/Cargo.lock index c42127aead43c..05f4896e071ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8357,6 +8357,17 @@ dependencies = [ "syn", ] +[[package]] +name = "sp-election-providers" +version = "2.0.0" +dependencies = [ + "parity-scale-codec", + "sp-arithmetic", + "sp-npos-elections", + "sp-runtime", + "sp-std", +] + [[package]] name = "sp-externalities" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 12e79490ef6b0..1754f896c8846 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -139,6 +139,7 @@ members = [ "primitives/database", "primitives/debug-derive", "primitives/externalities", + "primitives/election-providers", "primitives/finality-grandpa", "primitives/inherents", "primitives/io", diff --git a/primitives/election-providers/Cargo.toml b/primitives/election-providers/Cargo.toml new file mode 100644 index 0000000000000..65ca0e400958e --- /dev/null +++ b/primitives/election-providers/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "sp-election-providers" +version = "2.0.0" +authors = ["Parity Technologies "] +edition = "2018" +license = "Apache-2.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "Primitive election providers" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "1.3.4", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-rc6", default-features = false, path = "../std" } +sp-arithmetic = { version = "2.0.0-rc6", default-features = false, path = "../arithmetic" } +sp-npos-elections = { version = "2.0.0-rc6", default-features = false, path = "../npos-elections" } + +[dev-dependencies] +sp-npos-elections = { version = "2.0.0-rc6", path = "../npos-elections" } +sp-runtime = { version = "2.0.0-rc6", path = "../runtime" } + +[features] +default = ["std"] +runtime-benchmarks = [] +std = [ + "codec/std", + "sp-std/std", + "sp-npos-elections/std", + "sp-arithmetic/std", +] diff --git a/primitives/election-providers/src/lib.rs b/primitives/election-providers/src/lib.rs new file mode 100644 index 0000000000000..69261920be9a2 --- /dev/null +++ b/primitives/election-providers/src/lib.rs @@ -0,0 +1,239 @@ +// This file is part of Substrate. + +// Copyright (C) 2020 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. + +//! Primitive traits for providing election functionality. +//! +//! This crate provides two traits that could interact to enable extensible election functionality +//! within FRAME pallets. +//! +//! Something that will provide the functionality of election will implement [`ElectionProvider`], +//! whilst needing an associated [`ElectionProvider::DataProvider`], which needs to be fulfilled by +//! an entity implementing [`ElectionDataProvider`]. Most often, *the data provider is* the receiver +//! of the election, resulting in a diagram as below: +//! +//! ```ignore +//! ElectionDataProvider +//! <------------------------------------------+ +//! | | +//! v | +//! +-----+----+ +------+---+ +//! | | | | +//! pallet-do-election | | | | pallet-needs-election +//! | | | | +//! | | | | +//! +-----+----+ +------+---+ +//! | ^ +//! | | +//! +------------------------------------------+ +//! ElectionProvider +//! ``` +//! +//! > It could also be possible that a third party pallet (C), provides the data of election to an +//! > election provider (B), which then passes the election result to another pallet (A). +//! +//! ## Election Types +//! +//! Typically, two types of elections exist: +//! +//! 1. **Stateless**: Election data is provided, and the election result is immediately ready. +//! 2. **Stateful**: Election data is is queried ahead of time, and the election result might be +//! ready some number of blocks in the future. +//! +//! To accommodate both type of elections in one trait, the traits lean toward **stateful +//! election**, as it is more general than the stateless. This is why [`ElectionProvider::elect`] +//! has no parameters. All value and type parameter must be provided by the [`ElectionDataProvider`] +//! trait, even if the election happens immediately. +//! +//! ## Election Data +//! +//! The data associated with an election, essentially what the [`ElectionDataProvider`] must convey +//! is as follows: +//! +//! 1. A list of voters, with their stake. +//! 2. A list of targets (i.e. _candidates_). +//! 3. A number of desired targets to be elected (i.e. _winners_) +//! +//! In addition to that, the [`ElectionDataProvider`] must also hint [`ElectionProvider`] at when +//! the next election might happen ([`ElectionDataProvider::next_election_prediction`]). A stateless +//! election provider would probably ignore this. A stateful election provider can use this to +//! prepare the election result in advance. +//! +//! Nonetheless, an [`ElectionProvider`] shan't rely on this and should preferably provide some +//! means of fallback election as well, in case the `elect` was called immaturely early. +//! +//! ## Example +//! +//! ```rust +//! # use sp_election_providers::*; +//! # use sp_npos_elections::{Support, Assignment}; +//! +//! type AccountId = u64; +//! type Balance = u64; +//! type BlockNumber = u32; +//! +//! mod data_provider { +//! use super::*; +//! +//! pub trait Config: Sized { +//! type ElectionProvider: ElectionProvider< +//! AccountId, +//! BlockNumber, +//! DataProvider = Module, +//! >; +//! } +//! +//! pub struct Module(std::marker::PhantomData); +//! +//! impl ElectionDataProvider for Module { +//! fn desired_targets() -> u32 { +//! 1 +//! } +//! fn voters() -> Vec<(AccountId, VoteWeight, Vec)> { +//! Default::default() +//! } +//! fn targets() -> Vec { +//! vec![10, 20, 30] +//! } +//! fn next_election_prediction(now: BlockNumber) -> BlockNumber { +//! 0 +//! } +//! } +//! } +//! +//! +//! mod generic_election_provider { +//! use super::*; +//! +//! pub struct GenericElectionProvider(std::marker::PhantomData); +//! +//! pub trait Config { +//! type DataProvider: ElectionDataProvider; +//! } +//! +//! impl ElectionProvider for GenericElectionProvider { +//! type Error = (); +//! type DataProvider = T::DataProvider; +//! +//! fn elect() -> Result, Self::Error> { +//! Self::DataProvider::targets() +//! .first() +//! .map(|winner| vec![(*winner, Support::default())]) +//! .ok_or(()) +//! } +//! } +//! } +//! +//! mod runtime { +//! use super::generic_election_provider; +//! use super::data_provider; +//! use super::AccountId; +//! +//! struct Runtime; +//! impl generic_election_provider::Config for Runtime { +//! type DataProvider = data_provider::Module; +//! } +//! +//! impl data_provider::Config for Runtime { +//! type ElectionProvider = generic_election_provider::GenericElectionProvider; +//! } +//! +//! } +//! +//! # fn main() {} +//! ``` + +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod onchain; +use sp_std::prelude::*; + +/// Re-export some type as they are used in the interface. +pub use sp_arithmetic::PerThing; +pub use sp_npos_elections::{Assignment, ExtendedBalance, PerThing128, Supports, VoteWeight}; + +/// Something that can provide the data to an [`ElectionProvider`]. +pub trait ElectionDataProvider { + /// All possible targets for the election, i.e. the candidates. + fn targets() -> Vec; + + /// All possible voters for the election. + /// + /// Note that if a notion of self-vote exists, it should be represented here. + fn voters() -> Vec<(AccountId, VoteWeight, Vec)>; + + /// The number of targets to elect. + fn desired_targets() -> u32; + + /// Provide a best effort prediction about when the next election is about to happen. + /// + /// In essence, the implementor should predict with this function when it will trigger the + /// [`ElectionProvider::elect`]. + /// + /// This is only useful for stateful election providers. + fn next_election_prediction(now: BlockNumber) -> BlockNumber; + + /// Utility function only to be used in benchmarking scenarios, to be implemented optionally, + /// else a noop. + #[cfg(any(feature = "runtime-benchmarks", test))] + fn put_snapshot( + _voters: Vec<(AccountId, VoteWeight, Vec)>, + _targets: Vec, + ) { + } +} + +impl ElectionDataProvider for () { + fn targets() -> Vec { + Default::default() + } + fn voters() -> Vec<(AccountId, VoteWeight, Vec)> { + Default::default() + } + fn desired_targets() -> u32 { + Default::default() + } + fn next_election_prediction(now: BlockNumber) -> BlockNumber { + now + } +} + +/// Something that can compute the result of an election and pass it back to the caller. +/// +/// This trait only provides an interface to _request_ an election, i.e. +/// [`ElectionProvider::elect`]. That data required for the election need to be passed to the +/// implemented of this trait through [`ElectionProvider::DataProvider`]. +pub trait ElectionProvider { + /// The error type that is returned by the provider. + type Error: sp_std::fmt::Debug; + + /// The data provider of the election. + type DataProvider: ElectionDataProvider; + + /// Elect a new set of winners. + /// + /// The result is returned in a target major format, namely as vector of supports. + fn elect() -> Result, Self::Error>; +} + +impl ElectionProvider for () { + type Error = &'static str; + type DataProvider = (); + + fn elect() -> Result, Self::Error> { + Err("<() as ElectionProvider> cannot do anything.") + } +} diff --git a/primitives/election-providers/src/onchain.rs b/primitives/election-providers/src/onchain.rs new file mode 100644 index 0000000000000..5813d385969fa --- /dev/null +++ b/primitives/election-providers/src/onchain.rs @@ -0,0 +1,168 @@ +// This file is part of Substrate. + +// Copyright (C) 2020 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 does an on-chain sequential phragmen. + +use sp_arithmetic::InnerOf; +use crate::{ElectionDataProvider, ElectionProvider}; +use sp_npos_elections::{ + ElectionResult, ExtendedBalance, IdentifierT, PerThing128, Supports, VoteWeight, +}; +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), +} + +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. +/// +/// ### Warning +/// +/// This can be very expensive to run frequently on-chain. Use with care. +pub struct OnChainSequentialPhragmen(PhantomData); + +/// Configuration trait of [`OnChainSequentialPhragmen`]. +/// +/// Note that this is similar to a pallet traits, but [`OnChainSequentialPhragmen`] is not a pallet. +pub trait Config { + /// The account identifier type. + type AccountId: IdentifierT; + /// The block number type. + type BlockNumber; + /// The accuracy used to compute the election: + type Accuracy: PerThing128; + /// Something that provides the data for election. + type DataProvider: ElectionDataProvider; +} + +impl ElectionProvider for OnChainSequentialPhragmen +where + ExtendedBalance: From>, +{ + type Error = Error; + type DataProvider = T::DataProvider; + + fn elect() -> Result, Self::Error> { + let voters = Self::DataProvider::voters(); + let targets = Self::DataProvider::targets(); + let desired_targets = Self::DataProvider::desired_targets() as usize; + + let mut stake_map: BTreeMap = BTreeMap::new(); + + voters.iter().for_each(|(v, s, _)| { + stake_map.insert(v.clone(), *s); + }); + + let stake_of = Box::new(|w: &T::AccountId| -> VoteWeight { + stake_map.get(w).cloned().unwrap_or_default() + }); + + let ElectionResult { + winners, + assignments, + } = sp_npos_elections::seq_phragmen::<_, T::Accuracy>(desired_targets, targets, voters, None) + .map_err(Error::from)?; + + let staked = + sp_npos_elections::assignment_ratio_to_staked_normalized(assignments, &stake_of)?; + let winners = sp_npos_elections::to_without_backing(winners); + + sp_npos_elections::to_supports(&winners, &staked).map_err(Error::from) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_npos_elections::Support; + use sp_runtime::Perbill; + + type AccountId = u64; + type BlockNumber = u32; + + struct Runtime; + impl Config for Runtime { + type AccountId = AccountId; + type BlockNumber = BlockNumber; + type Accuracy = Perbill; + type DataProvider = mock_data_provider::DataProvider; + } + + type OnChainPhragmen = OnChainSequentialPhragmen; + + mod mock_data_provider { + use super::*; + + pub struct DataProvider; + + impl ElectionDataProvider for DataProvider { + fn voters() -> Vec<(AccountId, VoteWeight, Vec)> { + vec![ + (1, 10, vec![10, 20]), + (2, 20, vec![30, 20]), + (3, 30, vec![10, 30]), + ] + } + + fn targets() -> Vec { + vec![10, 20, 30] + } + + fn desired_targets() -> u32 { + 2 + } + + fn next_election_prediction(_: BlockNumber) -> BlockNumber { + 0 + } + } + } + + #[test] + fn onchain_seq_phragmen_works() { + 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/primitives/npos-elections/compact/src/assignment.rs b/primitives/npos-elections/compact/src/assignment.rs index 4f527aa40a748..12f5ca2b41735 100644 --- a/primitives/npos-elections/compact/src/assignment.rs +++ b/primitives/npos-elections/compact/src/assignment.rs @@ -21,7 +21,7 @@ use crate::field_name_for; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -fn from_impl(count: usize) -> TokenStream2 { +pub(crate) fn from_impl(count: usize) -> TokenStream2 { let from_impl_single = { let name = field_name_for(1); quote!(1 => compact.#name.push( @@ -73,7 +73,7 @@ fn from_impl(count: usize) -> TokenStream2 { ) } -fn into_impl(count: usize, per_thing: syn::Type) -> TokenStream2 { +pub(crate) fn into_impl(count: usize, per_thing: syn::Type) -> TokenStream2 { let into_impl_single = { let name = field_name_for(1); quote!( @@ -153,53 +153,3 @@ fn into_impl(count: usize, per_thing: syn::Type) -> TokenStream2 { #into_impl_rest ) } - -pub(crate) fn assignment( - ident: syn::Ident, - voter_type: syn::Type, - target_type: syn::Type, - weight_type: syn::Type, - count: usize, -) -> TokenStream2 { - let from_impl = from_impl(count); - let into_impl = into_impl(count, weight_type.clone()); - - quote!( - use _npos::__OrInvalidIndex; - impl #ident { - pub fn from_assignment( - assignments: Vec<_npos::Assignment>, - index_of_voter: FV, - index_of_target: FT, - ) -> Result - where - A: _npos::IdentifierT, - for<'r> FV: Fn(&'r A) -> Option<#voter_type>, - for<'r> FT: Fn(&'r A) -> Option<#target_type>, - { - let mut compact: #ident = Default::default(); - - for _npos::Assignment { who, distribution } in assignments { - match distribution.len() { - 0 => continue, - #from_impl - _ => { - return Err(_npos::Error::CompactTargetOverflow); - } - } - }; - Ok(compact) - } - - pub fn into_assignment( - self, - voter_at: impl Fn(#voter_type) -> Option, - target_at: impl Fn(#target_type) -> Option, - ) -> Result>, _npos::Error> { - let mut assignments: Vec<_npos::Assignment> = Default::default(); - #into_impl - Ok(assignments) - } - } - ) -} diff --git a/primitives/npos-elections/compact/src/lib.rs b/primitives/npos-elections/compact/src/lib.rs index 32397652f9b93..c8008dba51d65 100644 --- a/primitives/npos-elections/compact/src/lib.rs +++ b/primitives/npos-elections/compact/src/lib.rs @@ -95,19 +95,11 @@ pub fn generate_solution_type(item: TokenStream) -> TokenStream { compact_encoding, ).unwrap_or_else(|e| e.to_compile_error()); - let assignment_impls = assignment::assignment( - ident.clone(), - voter_type.clone(), - target_type.clone(), - weight_type.clone(), - count, - ); - quote!( #imports #solution_struct - #assignment_impls - ).into() + ) + .into() } fn struct_def( @@ -125,29 +117,32 @@ fn struct_def( let singles = { let name = field_name_for(1); + // NOTE: we use the visibility of the struct for the fields as well.. could be made better. quote!( - #name: Vec<(#voter_type, #target_type)>, + #vis #name: Vec<(#voter_type, #target_type)>, ) }; let doubles = { let name = field_name_for(2); quote!( - #name: Vec<(#voter_type, (#target_type, #weight_type), #target_type)>, + #vis #name: Vec<(#voter_type, (#target_type, #weight_type), #target_type)>, ) }; - let rest = (3..=count).map(|c| { - let field_name = field_name_for(c); - let array_len = c - 1; - quote!( - #field_name: Vec<( - #voter_type, - [(#target_type, #weight_type); #array_len], - #target_type - )>, - ) - }).collect::(); + let rest = (3..=count) + .map(|c| { + let field_name = field_name_for(c); + let array_len = c - 1; + quote!( + #vis #field_name: Vec<( + #voter_type, + [(#target_type, #weight_type); #array_len], + #target_type + )>, + ) + }) + .collect::(); let len_impl = len_impl(count); let edge_count_impl = edge_count_impl(count); @@ -172,40 +167,39 @@ fn struct_def( quote!(#[derive(Default, PartialEq, Eq, Clone, Debug, _npos::codec::Encode, _npos::codec::Decode)]) }; + let from_impl = assignment::from_impl(count); + let into_impl = assignment::into_impl(count, weight_type.clone()); + Ok(quote! ( /// A struct to encode a election assignment in a compact way. #derives_and_maybe_compact_encoding #vis struct #ident { #singles #doubles #rest } - impl _npos::VotingLimit for #ident { + use _npos::__OrInvalidIndex; + impl _npos::CompactSolution for #ident { const LIMIT: usize = #count; - } + type Voter = #voter_type; + type Target = #target_type; + type Accuracy = #weight_type; - impl #ident { - /// Get the length of all the assignments that this type is encoding. This is basically - /// the same as the number of assignments, or the number of voters in total. - pub fn len(&self) -> usize { + fn voter_count(&self) -> usize { let mut all_len = 0usize; #len_impl all_len } - /// Get the total count of edges. - pub fn edge_count(&self) -> usize { + fn edge_count(&self) -> usize { let mut all_edges = 0usize; #edge_count_impl all_edges } - /// Get the number of unique targets in the whole struct. - /// - /// Once presented with a list of winners, this set and the set of winners must be - /// equal. - /// - /// The resulting indices are sorted. - pub fn unique_targets(&self) -> Vec<#target_type> { - let mut all_targets: Vec<#target_type> = Vec::with_capacity(self.average_edge_count()); - let mut maybe_insert_target = |t: #target_type| { + fn unique_targets(&self) -> Vec { + // NOTE: this implementation returns the targets sorted, but we don't use it yet per + // se, nor is the API enforcing it. + let mut all_targets: Vec = + Vec::with_capacity(self.average_edge_count()); + let mut maybe_insert_target = |t: Self::Target| { match all_targets.binary_search(&t) { Ok(_) => (), Err(pos) => all_targets.insert(pos, t) @@ -217,22 +211,44 @@ fn struct_def( all_targets } - /// Get the average edge count. - pub fn average_edge_count(&self) -> usize { - self.edge_count().checked_div(self.len()).unwrap_or(0) - } - - /// Remove a certain voter. - /// - /// This will only search until the first instance of `to_remove`, and return true. If - /// no instance is found (no-op), then it returns false. - /// - /// In other words, if this return true, exactly one element must have been removed from - /// `self.len()`. - pub fn remove_voter(&mut self, to_remove: #voter_type) -> bool { + fn remove_voter(&mut self, to_remove: Self::Voter) -> bool { #remove_voter_impl return false } + + fn from_assignment( + assignments: Vec<_npos::Assignment>, + index_of_voter: FV, + index_of_target: FT, + ) -> Result + where + A: _npos::IdentifierT, + for<'r> FV: Fn(&'r A) -> Option, + for<'r> FT: Fn(&'r A) -> Option, + { + let mut compact: #ident = Default::default(); + + for _npos::Assignment { who, distribution } in assignments { + match distribution.len() { + 0 => continue, + #from_impl + _ => { + return Err(_npos::Error::CompactTargetOverflow); + } + } + }; + Ok(compact) + } + + fn into_assignment( + self, + voter_at: impl Fn(Self::Voter) -> Option, + target_at: impl Fn(Self::Target) -> Option, + ) -> Result>, _npos::Error> { + let mut assignments: Vec<_npos::Assignment> = Default::default(); + #into_impl + Ok(assignments) + } } )) } @@ -347,7 +363,6 @@ fn imports() -> Result { } } } - struct SolutionDef { vis: syn::Visibility, ident: syn::Ident, diff --git a/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs b/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs index 024b721b222a7..2ba7e409568b3 100644 --- a/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs +++ b/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs @@ -22,8 +22,8 @@ mod common; use common::*; use honggfuzz::fuzz; use sp_npos_elections::{ - assignment_ratio_to_staked_normalized, build_support_map, to_without_backing, VoteWeight, - evaluate_support, is_score_better, seq_phragmen, + assignment_ratio_to_staked_normalized, is_score_better, seq_phragmen, to_supports, + to_without_backing, EvaluateSupport, VoteWeight, }; use sp_runtime::Perbill; use rand::{self, SeedableRng}; @@ -66,11 +66,16 @@ fn main() { }; let unbalanced_score = { - let staked = assignment_ratio_to_staked_normalized(unbalanced.assignments.clone(), &stake_of).unwrap(); + let staked = assignment_ratio_to_staked_normalized( + unbalanced.assignments.clone(), + &stake_of, + ) + .unwrap(); let winners = to_without_backing(unbalanced.winners.clone()); - let support = build_support_map(winners.as_ref(), staked.as_ref()).unwrap(); + let score = to_supports(winners.as_ref(), staked.as_ref()) + .unwrap() + .evaluate(); - let score = evaluate_support(&support); if score[0] == 0 { // such cases cannot be improved by balancing. return; @@ -87,11 +92,13 @@ fn main() { ).unwrap(); let balanced_score = { - let staked = assignment_ratio_to_staked_normalized(balanced.assignments.clone(), &stake_of).unwrap(); + let staked = assignment_ratio_to_staked_normalized( + balanced.assignments.clone(), + &stake_of, + ).unwrap(); let winners = to_without_backing(balanced.winners); - let support = build_support_map(winners.as_ref(), staked.as_ref()).unwrap(); + to_supports(winners.as_ref(), staked.as_ref()).unwrap().evaluate() - evaluate_support(&support) }; let enhance = is_score_better(balanced_score, unbalanced_score, Perbill::zero()); diff --git a/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs b/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs index 868aa67236f41..8ce7e7d415fa2 100644 --- a/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs +++ b/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs @@ -22,8 +22,8 @@ mod common; use common::*; use honggfuzz::fuzz; use sp_npos_elections::{ - assignment_ratio_to_staked_normalized, build_support_map, to_without_backing, VoteWeight, - evaluate_support, is_score_better, phragmms, + assignment_ratio_to_staked_normalized, is_score_better, phragmms, to_supports, + to_without_backing, EvaluateSupport, VoteWeight, }; use sp_runtime::Perbill; use rand::{self, SeedableRng}; @@ -66,11 +66,14 @@ fn main() { }; let unbalanced_score = { - let staked = assignment_ratio_to_staked_normalized(unbalanced.assignments.clone(), &stake_of).unwrap(); + let staked = assignment_ratio_to_staked_normalized( + unbalanced.assignments.clone(), + &stake_of, + ) + .unwrap(); let winners = to_without_backing(unbalanced.winners.clone()); - let support = build_support_map(winners.as_ref(), staked.as_ref()).unwrap(); + let score = to_supports(&winners, &staked).unwrap().evaluate(); - let score = evaluate_support(&support); if score[0] == 0 { // such cases cannot be improved by balancing. return; @@ -86,11 +89,13 @@ fn main() { ).unwrap(); let balanced_score = { - let staked = assignment_ratio_to_staked_normalized(balanced.assignments.clone(), &stake_of).unwrap(); + let staked = + assignment_ratio_to_staked_normalized(balanced.assignments.clone(), &stake_of) + .unwrap(); let winners = to_without_backing(balanced.winners); - let support = build_support_map(winners.as_ref(), staked.as_ref()).unwrap(); - - evaluate_support(&support) + to_supports(winners.as_ref(), staked.as_ref()) + .unwrap() + .evaluate() }; let enhance = is_score_better(balanced_score, unbalanced_score, Perbill::zero()); diff --git a/primitives/npos-elections/fuzzer/src/reduce.rs b/primitives/npos-elections/fuzzer/src/reduce.rs index 074c1546d49d8..4ee2468d9d140 100644 --- a/primitives/npos-elections/fuzzer/src/reduce.rs +++ b/primitives/npos-elections/fuzzer/src/reduce.rs @@ -34,8 +34,8 @@ use honggfuzz::fuzz; mod common; use common::to_range; -use sp_npos_elections::{StakedAssignment, ExtendedBalance, build_support_map, reduce}; -use rand::{self, Rng, SeedableRng, RngCore}; +use sp_npos_elections::{reduce, to_support_map, ExtendedBalance, StakedAssignment}; +use rand::{self, Rng, RngCore, SeedableRng}; type Balance = u128; type AccountId = u64; @@ -109,9 +109,8 @@ fn assert_assignments_equal( ass1: &Vec>, ass2: &Vec>, ) { - - let support_1 = build_support_map::(winners, ass1).unwrap(); - let support_2 = build_support_map::(winners, ass2).unwrap(); + let support_1 = to_support_map::(winners, ass1).unwrap(); + let support_2 = to_support_map::(winners, ass2).unwrap(); for (who, support) in support_1.iter() { assert_eq!(support.total, support_2.get(who).unwrap().total); diff --git a/primitives/npos-elections/src/helpers.rs b/primitives/npos-elections/src/helpers.rs index 6f4400b6748fd..3dbde0e03c386 100644 --- a/primitives/npos-elections/src/helpers.rs +++ b/primitives/npos-elections/src/helpers.rs @@ -18,21 +18,21 @@ //! Helper methods for npos-elections. use crate::{ - Assignment, ExtendedBalance, VoteWeight, IdentifierT, StakedAssignment, WithApprovalOf, Error, + Assignment, Error, ExtendedBalance, IdentifierT, PerThing128, StakedAssignment, VoteWeight, + WithApprovalOf, }; -use sp_arithmetic::{PerThing, InnerOf}; +use sp_arithmetic::{InnerOf, PerThing}; use sp_std::prelude::*; /// Converts a vector of ratio assignments into ones with absolute budget value. /// /// Note that this will NOT attempt at normalizing the result. -pub fn assignment_ratio_to_staked( +pub fn assignment_ratio_to_staked( ratios: Vec>, stake_of: FS, ) -> Vec> where for<'r> FS: Fn(&'r A) -> VoteWeight, - P: sp_std::ops::Mul, ExtendedBalance: From>, { ratios @@ -45,19 +45,22 @@ where } /// Same as [`assignment_ratio_to_staked`] and try and do normalization. -pub fn assignment_ratio_to_staked_normalized( +pub fn assignment_ratio_to_staked_normalized( ratio: Vec>, stake_of: FS, ) -> Result>, Error> where for<'r> FS: Fn(&'r A) -> VoteWeight, - P: sp_std::ops::Mul, ExtendedBalance: From>, { let mut staked = assignment_ratio_to_staked(ratio, &stake_of); - staked.iter_mut().map(|a| - a.try_normalize(stake_of(&a.who).into()).map_err(|err| Error::ArithmeticError(err)) - ).collect::>()?; + staked + .iter_mut() + .map(|a| { + a.try_normalize(stake_of(&a.who).into()) + .map_err(|err| Error::ArithmeticError(err)) + }) + .collect::>()?; Ok(staked) } @@ -74,7 +77,7 @@ where } /// Same as [`assignment_staked_to_ratio`] and try and do normalization. -pub fn assignment_staked_to_ratio_normalized( +pub fn assignment_staked_to_ratio_normalized( staked: Vec>, ) -> Result>, Error> where diff --git a/primitives/npos-elections/src/lib.rs b/primitives/npos-elections/src/lib.rs index 1e3c2707497c2..7966b66e383fc 100644 --- a/primitives/npos-elections/src/lib.rs +++ b/primitives/npos-elections/src/lib.rs @@ -21,8 +21,8 @@ //! - [`phragmms()`]: Implements a hybrid approach inspired by Phragmén which is executed faster but //! it can achieve a constant factor approximation of the maximin problem, similar to that of the //! MMS algorithm. -//! - [`balance`]: Implements the star balancing algorithm. This iterative process can push -//! a solution toward being more `balances`, which in turn can increase its score. +//! - [`balance`]: Implements the star balancing algorithm. This iterative process can push a +//! solution toward being more `balances`, which in turn can increase its score. //! //! ### Terminology //! @@ -57,7 +57,6 @@ //! //! // the combination of the two makes the election result. //! let election_result = ElectionResult { winners, assignments }; -//! //! ``` //! //! The `Assignment` field of the election result is voter-major, i.e. it is from the perspective of @@ -74,18 +73,24 @@ #![cfg_attr(not(feature = "std"), no_std)] -use sp_std::{ - prelude::*, collections::btree_map::BTreeMap, fmt::Debug, cmp::Ordering, rc::Rc, cell::RefCell, -}; use sp_arithmetic::{ - PerThing, Rational128, ThresholdOrd, InnerOf, Normalizable, - traits::{Zero, Bounded}, + traits::{Bounded, UniqueSaturatedInto, Zero}, + InnerOf, Normalizable, PerThing, Rational128, ThresholdOrd, +}; +use sp_std::{ + cell::RefCell, + cmp::Ordering, + collections::btree_map::BTreeMap, + convert::{TryFrom, TryInto}, + fmt::Debug, + ops::Mul, + prelude::*, + rc::Rc, }; +use codec::{Decode, Encode}; #[cfg(feature = "std")] -use serde::{Serialize, Deserialize}; -#[cfg(feature = "std")] -use codec::{Encode, Decode}; +use serde::{Deserialize, Serialize}; #[cfg(test)] mod mock; @@ -125,20 +130,105 @@ impl __OrInvalidIndex for Option { } } -// re-export the compact solution type. -pub use sp_npos_elections_compact::generate_solution_type; - -/// A trait to limit the number of votes per voter. The generated compact type will implement this. -pub trait VotingLimit { +/// A common interface for all compact solutions. +/// +/// See [`sp-npos-elections-compact`] for more info. +pub trait CompactSolution: Sized { + /// The maximum number of votes that are allowed. const LIMIT: usize; + + /// The voter type. + type Voter: UniqueSaturatedInto + TryInto + TryFrom + Debug + Copy + Clone; + + /// The target type + type Target: UniqueSaturatedInto + TryInto + TryFrom + Debug + Copy + Clone; + + /// The weight/accuracy type of each vote. + type Accuracy: PerThing128; + + /// Build self from a `Vec>`. + fn from_assignment( + assignments: Vec>, + voter_index: FV, + target_index: FT, + ) -> Result + where + A: IdentifierT, + for<'r> FV: Fn(&'r A) -> Option, + for<'r> FT: Fn(&'r A) -> Option; + + /// Convert self into a `Vec>` + fn into_assignment( + self, + voter_at: impl Fn(Self::Voter) -> Option, + target_at: impl Fn(Self::Target) -> Option, + ) -> Result>, Error>; + + /// Get the length of all the voters that this type is encoding. + /// + /// This is basically the same as the number of assignments. + fn voter_count(&self) -> usize; + + /// Get the total count of edges. + /// + /// This is effectively in the range of {[`Self::voter_count`], [`Self::voter_count`] * + /// [`Self::LIMIT`]}. + fn edge_count(&self) -> usize; + + /// Get the number of unique targets in the whole struct. + /// + /// Once presented with a list of winners, this set and the set of winners must be + /// equal. + fn unique_targets(&self) -> Vec; + + /// Get the average edge count. + fn average_edge_count(&self) -> usize { + self.edge_count() + .checked_div(self.voter_count()) + .unwrap_or(0) + } + + /// Remove a certain voter. + /// + /// This will only search until the first instance of `to_remove`, and return true. If + /// no instance is found (no-op), then it returns false. + /// + /// In other words, if this return true, exactly one element must have been removed from + /// `self.len()`. + fn remove_voter(&mut self, to_remove: Self::Voter) -> bool; + + /// Compute the score of this compact solution type. + fn score( + self, + winners: &[A], + stake_of: FS, + voter_at: impl Fn(Self::Voter) -> Option, + target_at: impl Fn(Self::Target) -> Option, + ) -> Result + where + for<'r> FS: Fn(&'r A) -> VoteWeight, + A: IdentifierT, + ExtendedBalance: From>, + { + let ratio = self.into_assignment(voter_at, target_at)?; + let staked = helpers::assignment_ratio_to_staked_normalized(ratio, stake_of)?; + let supports = to_supports(winners, &staked)?; + Ok(supports.evaluate()) + } } +// re-export the compact solution type. +pub use sp_npos_elections_compact::generate_solution_type; + /// an aggregator trait for a generic type of a voter/target identifier. This usually maps to /// substrate's account id. pub trait IdentifierT: Clone + Eq + Default + Ord + Debug + codec::Codec {} - impl IdentifierT for T {} +/// Aggregator trait for a PerThing that can be multiplied by u128 (ExtendedBalance). +pub trait PerThing128: PerThing + Mul {} +impl> PerThing128 for T {} + /// The errors that might occur in the this crate and compact. #[derive(Debug, Eq, PartialEq)] pub enum Error { @@ -151,6 +241,8 @@ pub enum Error { CompactInvalidIndex, /// An error occurred in some arithmetic operation. ArithmeticError(&'static str), + /// The data provided to create support map was invalid. + InvalidSupportEdge, } /// A type which is used in the API of this crate as a numeric weight of a vote, most often the @@ -160,7 +252,8 @@ pub type VoteWeight = u64; /// A type in which performing operations on vote weights are safe. pub type ExtendedBalance = u128; -/// The score of an assignment. This can be computed from the support map via [`evaluate_support`]. +/// The score of an assignment. This can be computed from the support map via +/// [`EvaluateSupport::evaluate`]. pub type ElectionScore = [ExtendedBalance; 3]; /// A winner, with their respective approval stake. @@ -331,10 +424,7 @@ pub struct Assignment { pub distribution: Vec<(AccountId, P)>, } -impl Assignment -where - ExtendedBalance: From>, -{ +impl Assignment { /// Convert from a ratio assignment into one with absolute values aka. [`StakedAssignment`]. /// /// It needs `stake` which is the total budget of the voter. If `fill` is set to true, it @@ -344,11 +434,9 @@ where /// /// If an edge ratio is [`Bounded::min_value()`], it is dropped. This edge can never mean /// anything useful. - pub fn into_staked(self, stake: ExtendedBalance) -> StakedAssignment - where - P: sp_std::ops::Mul, - { - let distribution = self.distribution + pub fn into_staked(self, stake: ExtendedBalance) -> StakedAssignment { + let distribution = self + .distribution .into_iter() .filter_map(|(target, p)| { // if this ratio is zero, then skip it. @@ -408,11 +496,8 @@ pub struct StakedAssignment { impl StakedAssignment { /// Converts self into the normal [`Assignment`] type. /// - /// If `fill` is set to true, it _tries_ to ensure that all the potential rounding errors are - /// compensated and the distribution's sum is exactly equal to 100%, by adding or subtracting - /// the remainder from the last distribution. - /// - /// NOTE: it is quite critical that this attempt always works. The data type returned here will + /// NOTE: This will always round down, and thus the results might be less than a full 100% `P`. + /// Use a normalization post-processing to fix this. The data type returned here will /// potentially get used to create a compact type; a compact type requires sum of ratios to be /// less than 100% upon un-compacting. /// @@ -479,8 +564,8 @@ impl StakedAssignment { /// /// This, at the current version, resembles the `Exposure` defined in the Staking pallet, yet they /// do not necessarily have to be the same. -#[derive(Default, Debug)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Eq, PartialEq))] +#[derive(Default, Debug, Encode, Decode, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct Support { /// Total support. pub total: ExtendedBalance, @@ -488,51 +573,43 @@ pub struct Support { pub voters: Vec<(AccountId, ExtendedBalance)>, } -/// A linkage from a candidate and its [`Support`]. -pub type SupportMap = BTreeMap>; - -/// Build the support map from the given election result. It maps a flat structure like -/// -/// ```nocompile -/// assignments: vec![ -/// voter1, vec![(candidate1, w11), (candidate2, w12)], -/// voter2, vec![(candidate1, w21), (candidate2, w22)] -/// ] -/// ``` +/// A target-major representation of the the election outcome. /// -/// into a mapping of candidates and their respective support: +/// Essentially a flat variant of [`SupportMap`]. /// -/// ```nocompile -/// SupportMap { -/// candidate1: Support { -/// own:0, -/// total: w11 + w21, -/// others: vec![(candidate1, w11), (candidate2, w21)] -/// }, -/// candidate2: Support { -/// own:0, -/// total: w12 + w22, -/// others: vec![(candidate1, w12), (candidate2, w22)] -/// }, -/// } -/// ``` +/// The main advantage of this is that it is encodable. +pub type Supports = Vec<(A, Support)>; + +/// Linkage from a winner to their [`Support`]. /// -/// The second returned flag indicates the number of edges who didn't corresponded to an actual -/// winner from the given winner set. A value in this place larger than 0 indicates a potentially -/// faulty assignment. +/// This is more helpful than a normal [`Supports`] as it allows faster error checking. +pub type SupportMap = BTreeMap>; + +/// Helper trait to convert from a support map to a flat support vector. +pub trait FlattenSupportMap { + /// Flatten the support. + fn flatten(self) -> Supports; +} + +impl FlattenSupportMap for SupportMap { + fn flatten(self) -> Supports { + self.into_iter().collect::>() + } +} + +/// Build the support map from the winners and assignments. /// -/// `O(E)` where `E` is the total number of edges. -pub fn build_support_map( - winners: &[AccountId], - assignments: &[StakedAssignment], -) -> Result, AccountId> where - AccountId: IdentifierT, -{ +/// The list of winners is basically a redundancy for error checking only; It ensures that all the +/// targets pointed to by the [`Assignment`] are present in the `winners`. +pub fn to_support_map( + winners: &[A], + assignments: &[StakedAssignment], +) -> Result, Error> { // Initialize the support of each candidate. - let mut supports = >::new(); - winners - .iter() - .for_each(|e| { supports.insert(e.clone(), Default::default()); }); + let mut supports = >::new(); + winners.iter().for_each(|e| { + supports.insert(e.clone(), Default::default()); + }); // build support struct. for StakedAssignment { who, distribution } in assignments.iter() { @@ -541,37 +618,83 @@ pub fn build_support_map( support.total = support.total.saturating_add(*weight_extended); support.voters.push((who.clone(), *weight_extended)); } else { - return Err(c.clone()) + return Err(Error::InvalidSupportEdge) } } } Ok(supports) } -/// Evaluate a support map. The returned tuple contains: +/// Same as [`to_support_map`] except it calls `FlattenSupportMap` on top of the result to return a +/// flat vector. /// -/// - Minimum support. This value must be **maximized**. -/// - Sum of all supports. This value must be **maximized**. -/// - Sum of all supports squared. This value must be **minimized**. +/// Similar to [`to_support_map`], `winners` is used for error checking. +pub fn to_supports( + winners: &[A], + assignments: &[StakedAssignment], +) -> Result, Error> { + to_support_map(winners, assignments).map(FlattenSupportMap::flatten) +} + +/// Extension trait for evaluating a support map or vector. +pub trait EvaluateSupport { + /// Evaluate a support map. The returned tuple contains: + /// + /// - Minimum support. This value must be **maximized**. + /// - Sum of all supports. This value must be **maximized**. + /// - Sum of all supports squared. This value must be **minimized**. + fn evaluate(self) -> ElectionScore; +} + +/// A common wrapper trait for both (&A, &B) and &(A, B). /// -/// `O(E)` where `E` is the total number of edges. -pub fn evaluate_support( - support: &SupportMap, -) -> ElectionScore { - let mut min_support = ExtendedBalance::max_value(); - let mut sum: ExtendedBalance = Zero::zero(); - // NOTE: The third element might saturate but fine for now since this will run on-chain and need - // to be fast. - let mut sum_squared: ExtendedBalance = Zero::zero(); - for (_, support) in support.iter() { - sum = sum.saturating_add(support.total); - let squared = support.total.saturating_mul(support.total); - sum_squared = sum_squared.saturating_add(squared); - if support.total < min_support { - min_support = support.total; +/// This allows us to implemented something for both `Vec<_>` and `BTreeMap<_>`, such as +/// [`EvaluateSupport`]. +pub trait TupleRef { + fn extract(&self) -> (&K, &V); +} + +impl TupleRef for &(K, V) { + fn extract(&self) -> (&K, &V) { + (&self.0, &self.1) + } +} + +impl TupleRef for (K, V) { + fn extract(&self) -> (&K, &V) { + (&self.0, &self.1) + } +} + +impl TupleRef for (&K, &V) { + fn extract(&self) -> (&K, &V) { + (self.0, self.1) + } +} + +impl EvaluateSupport for C +where + C: IntoIterator, + I: TupleRef>, + A: IdentifierT, +{ + fn evaluate(self) -> ElectionScore { + let mut min_support = ExtendedBalance::max_value(); + let mut sum: ExtendedBalance = Zero::zero(); + // NOTE: The third element might saturate but fine for now since this will run on-chain and + // need to be fast. + let mut sum_squared: ExtendedBalance = Zero::zero(); + for item in self { + let (_, support) = item.extract(); + sum = sum.saturating_add(support.total); + let squared = support.total.saturating_mul(support.total); + sum_squared = sum_squared.saturating_add(squared); + if support.total < min_support { + min_support = support.total; + } } + [min_support, sum, sum_squared] } - [min_support, sum, sum_squared] } /// Compares two sets of election scores based on desirability and returns true if `this` is better @@ -582,14 +705,15 @@ pub fn evaluate_support( /// /// Note that the third component should be minimized. pub fn is_score_better(this: ElectionScore, that: ElectionScore, epsilon: P) -> bool - where ExtendedBalance: From> +where + ExtendedBalance: From>, { match this .iter() - .enumerate() - .map(|(i, e)| ( - e.ge(&that[i]), - e.tcmp(&that[i], epsilon.mul_ceil(that[i])), + .zip(that.iter()) + .map(|(thi, tha)| ( + thi.ge(&tha), + thi.tcmp(&tha, epsilon.mul_ceil(*tha)), )) .collect::>() .as_slice() diff --git a/primitives/npos-elections/src/mock.rs b/primitives/npos-elections/src/mock.rs index 410adcc3779e0..57b2204a72b48 100644 --- a/primitives/npos-elections/src/mock.rs +++ b/primitives/npos-elections/src/mock.rs @@ -19,10 +19,13 @@ #![cfg(test)] -use crate::{seq_phragmen, ElectionResult, Assignment, VoteWeight, ExtendedBalance}; -use sp_arithmetic::{PerThing, InnerOf, traits::{SaturatedConversion, Zero, One}}; -use sp_std::collections::btree_map::BTreeMap; +use crate::*; +use sp_arithmetic::{ + traits::{One, SaturatedConversion, Zero}, + InnerOf, PerThing, +}; use sp_runtime::assert_eq_error_rate; +use sp_std::collections::btree_map::BTreeMap; #[derive(Default, Debug)] pub(crate) struct _Candidate { @@ -313,14 +316,13 @@ pub fn check_assignments_sum(assignments: Vec( +pub(crate) fn run_and_compare( candidates: Vec, voters: Vec<(AccountId, Vec)>, stake_of: &Box VoteWeight>, to_elect: usize, ) where ExtendedBalance: From>, - Output: sp_std::ops::Mul, { // run fixed point code. let ElectionResult { winners, assignments } = seq_phragmen::<_, Output>( diff --git a/primitives/npos-elections/src/phragmen.rs b/primitives/npos-elections/src/phragmen.rs index 8f88c45ae6de8..ed45efe1b54f7 100644 --- a/primitives/npos-elections/src/phragmen.rs +++ b/primitives/npos-elections/src/phragmen.rs @@ -21,15 +21,15 @@ //! to the Maximin problem. use crate::{ - IdentifierT, VoteWeight, Voter, CandidatePtr, ExtendedBalance, setup_inputs, ElectionResult, + balancing, setup_inputs, CandidatePtr, ElectionResult, ExtendedBalance, IdentifierT, + PerThing128, VoteWeight, Voter, }; -use sp_std::prelude::*; use sp_arithmetic::{ - PerThing, InnerOf, Rational128, helpers_128bit::multiply_by_rational, - traits::{Zero, Bounded}, + traits::{Bounded, Zero}, + InnerOf, Rational128, }; -use crate::balancing; +use sp_std::prelude::*; /// The denominator used for loads. Since votes are collected as u64, the smallest ratio that we /// might collect is `1/approval_stake` where approval stake is the sum of votes. Hence, some number @@ -63,12 +63,15 @@ const DEN: ExtendedBalance = ExtendedBalance::max_value(); /// `expect` this to return `Ok`. /// /// This can only fail if the normalization fails. -pub fn seq_phragmen( +pub fn seq_phragmen( rounds: usize, initial_candidates: Vec, initial_voters: Vec<(AccountId, VoteWeight, Vec)>, balance: Option<(usize, ExtendedBalance)>, -) -> Result, &'static str> where ExtendedBalance: From> { +) -> Result, crate::Error> +where + ExtendedBalance: From>, +{ let (candidates, voters) = setup_inputs(initial_candidates, initial_voters); let (candidates, mut voters) = seq_phragmen_core::( @@ -93,13 +96,26 @@ pub fn seq_phragmen( // sort winners based on desirability. winners.sort_by_key(|c_ptr| c_ptr.borrow().round); - let mut assignments = voters.into_iter().filter_map(|v| v.into_assignment()).collect::>(); - let _ = assignments.iter_mut().map(|a| a.try_normalize()).collect::>()?; - let winners = winners.into_iter().map(|w_ptr| - (w_ptr.borrow().who.clone(), w_ptr.borrow().backed_stake) - ).collect(); + let mut assignments = voters + .into_iter() + .filter_map(|v| v.into_assignment()) + .collect::>(); + let _ = assignments + .iter_mut() + .map(|a| { + a.try_normalize() + .map_err(|e| crate::Error::ArithmeticError(e)) + }) + .collect::>()?; + let winners = winners + .into_iter() + .map(|w_ptr| (w_ptr.borrow().who.clone(), w_ptr.borrow().backed_stake)) + .collect(); - Ok(ElectionResult { winners, assignments }) + Ok(ElectionResult { + winners, + assignments, + }) } /// Core implementation of seq-phragmen. @@ -114,7 +130,7 @@ pub fn seq_phragmen_core( rounds: usize, candidates: Vec>, mut voters: Vec>, -) -> Result<(Vec>, Vec>), &'static str> { +) -> Result<(Vec>, Vec>), crate::Error> { // we have already checked that we have more candidates than minimum_candidate_count. let to_elect = rounds.min(candidates.len()); @@ -198,7 +214,7 @@ pub fn seq_phragmen_core( // edge of all candidates that eventually have a non-zero weight must be elected. debug_assert!(voter.edges.iter().all(|e| e.candidate.borrow().elected)); // inc budget to sum the budget. - voter.try_normalize_elected()?; + voter.try_normalize_elected().map_err(|e| crate::Error::ArithmeticError(e))?; } Ok((candidates, voters)) diff --git a/primitives/npos-elections/src/phragmms.rs b/primitives/npos-elections/src/phragmms.rs index b0f841e57f245..b37d3432f9d7e 100644 --- a/primitives/npos-elections/src/phragmms.rs +++ b/primitives/npos-elections/src/phragmms.rs @@ -23,7 +23,7 @@ use crate::{ IdentifierT, ElectionResult, ExtendedBalance, setup_inputs, VoteWeight, Voter, CandidatePtr, - balance, + balance, PerThing128, }; use sp_arithmetic::{PerThing, InnerOf, Rational128, traits::Bounded}; use sp_std::{prelude::*, rc::Rc}; @@ -41,13 +41,14 @@ use sp_std::{prelude::*, rc::Rc}; /// assignments, `assignment.distribution.map(|p| p.deconstruct()).sum()` fails to fit inside /// `UpperOf

`. A user of this crate may statically assert that this can never happen and safely /// `expect` this to return `Ok`. -pub fn phragmms( +pub fn phragmms( to_elect: usize, initial_candidates: Vec, initial_voters: Vec<(AccountId, VoteWeight, Vec)>, balancing_config: Option<(usize, ExtendedBalance)>, ) -> Result, &'static str> - where ExtendedBalance: From> +where + ExtendedBalance: From>, { let (candidates, mut voters) = setup_inputs(initial_candidates, initial_voters); diff --git a/primitives/npos-elections/src/tests.rs b/primitives/npos-elections/src/tests.rs index 1d26909911f33..7aac5aae1ddab 100644 --- a/primitives/npos-elections/src/tests.rs +++ b/primitives/npos-elections/src/tests.rs @@ -17,14 +17,13 @@ //! Tests for npos-elections. -use crate::mock::*; use crate::{ - seq_phragmen, balancing, build_support_map, is_score_better, helpers::*, - Support, StakedAssignment, Assignment, ElectionResult, ExtendedBalance, setup_inputs, - seq_phragmen_core, Voter, + balancing, helpers::*, is_score_better, mock::*, seq_phragmen, seq_phragmen_core, setup_inputs, + to_support_map, to_supports, Assignment, ElectionResult, ExtendedBalance, StakedAssignment, + Support, Voter, EvaluateSupport, }; +use sp_arithmetic::{PerU16, Perbill, Percent, Permill}; use substrate_test_utils::assert_eq_uvec; -use sp_arithmetic::{Perbill, Permill, Percent, PerU16}; #[test] fn float_phragmen_poc_works() { @@ -53,22 +52,44 @@ fn float_phragmen_poc_works() { assert_eq!( support_map.get(&2).unwrap(), - &_Support { own: 0.0, total: 25.0, others: vec![(10u64, 10.0), (30u64, 15.0)]} + &_Support { + own: 0.0, + total: 25.0, + others: vec![(10u64, 10.0), (30u64, 15.0)] + } ); assert_eq!( support_map.get(&3).unwrap(), - &_Support { own: 0.0, total: 35.0, others: vec![(20u64, 20.0), (30u64, 15.0)]} + &_Support { + own: 0.0, + total: 35.0, + others: vec![(20u64, 20.0), (30u64, 15.0)] + } ); - equalize_float(phragmen_result.assignments, &mut support_map, 0.0, 2, stake_of); + equalize_float( + phragmen_result.assignments, + &mut support_map, + 0.0, + 2, + stake_of, + ); assert_eq!( support_map.get(&2).unwrap(), - &_Support { own: 0.0, total: 30.0, others: vec![(10u64, 10.0), (30u64, 20.0)]} + &_Support { + own: 0.0, + total: 30.0, + others: vec![(10u64, 10.0), (30u64, 20.0)] + } ); assert_eq!( support_map.get(&3).unwrap(), - &_Support { own: 0.0, total: 30.0, others: vec![(20u64, 20.0), (30u64, 10.0)]} + &_Support { + own: 0.0, + total: 30.0, + others: vec![(20u64, 20.0), (30u64, 10.0)] + } ); } @@ -300,7 +321,7 @@ fn phragmen_poc_works() { let staked = assignment_ratio_to_staked(assignments, &stake_of); let winners = to_without_backing(winners); - let support_map = build_support_map::(&winners, &staked).unwrap(); + let support_map = to_support_map::(&winners, &staked).unwrap(); assert_eq_uvec!( staked, @@ -374,7 +395,7 @@ fn phragmen_poc_works_with_balancing() { let staked = assignment_ratio_to_staked(assignments, &stake_of); let winners = to_without_backing(winners); - let support_map = build_support_map::(&winners, &staked).unwrap(); + let support_map = to_support_map::(&winners, &staked).unwrap(); assert_eq_uvec!( staked, @@ -766,7 +787,7 @@ fn phragmen_self_votes_should_be_kept() { let staked_assignments = assignment_ratio_to_staked(result.assignments, &stake_of); let winners = to_without_backing(result.winners); - let supports = build_support_map::(&winners, &staked_assignments).unwrap(); + let supports = to_support_map::(&winners, &staked_assignments).unwrap(); assert_eq!(supports.get(&5u64), None); assert_eq!( @@ -839,6 +860,34 @@ fn duplicate_target_is_ignored_when_winner() { ); } +#[test] +fn support_map_and_vec_can_be_evaluated() { + let candidates = vec![1, 2, 3]; + let voters = vec![(10, vec![1, 2]), (20, vec![1, 3]), (30, vec![2, 3])]; + + let stake_of = create_stake_of(&[(10, 10), (20, 20), (30, 30)]); + let ElectionResult { + winners, + assignments, + } = seq_phragmen::<_, Perbill>( + 2, + candidates, + voters + .iter() + .map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())) + .collect::>(), + None, + ) + .unwrap(); + + let staked = assignment_ratio_to_staked(assignments, &stake_of); + let winners = to_without_backing(winners); + let support_map = to_support_map::(&winners, &staked).unwrap(); + let support_vec = to_supports(&winners, &staked).unwrap(); + + assert_eq!(support_map.evaluate(), support_vec.evaluate()); +} + mod assignment_convert_normalize { use super::*; #[test] @@ -1112,20 +1161,16 @@ mod score { } mod solution_type { - use codec::{Decode, Encode}; use super::AccountId; + use codec::{Decode, Encode}; // these need to come from the same dev-dependency `sp-npos-elections`, not from the crate. - use crate::{ - generate_solution_type, Assignment, - Error as PhragmenError, - }; - use sp_std::{convert::TryInto, fmt::Debug}; + use crate::{generate_solution_type, Assignment, CompactSolution, Error as PhragmenError}; use sp_arithmetic::Percent; + use sp_std::{convert::TryInto, fmt::Debug}; type TestAccuracy = Percent; generate_solution_type!(pub struct TestSolutionCompact::(16)); - #[allow(dead_code)] mod __private { // This is just to make sure that that the compact can be generated in a scope without any @@ -1136,7 +1181,6 @@ mod solution_type { #[compact] struct InnerTestSolutionCompact::(12) ); - } #[test] @@ -1190,7 +1234,7 @@ mod solution_type { compact, Decode::decode(&mut &encoded[..]).unwrap(), ); - assert_eq!(compact.len(), 4); + assert_eq!(compact.voter_count(), 4); assert_eq!(compact.edge_count(), 2 + 4); assert_eq!(compact.unique_targets(), vec![10, 11, 20, 40, 50, 51]); } @@ -1326,7 +1370,7 @@ mod solution_type { ).unwrap(); // basically number of assignments that it is encoding. - assert_eq!(compacted.len(), assignments.len()); + assert_eq!(compacted.voter_count(), assignments.len()); assert_eq!( compacted.edge_count(), assignments.iter().fold(0, |a, b| a + b.distribution.len()), @@ -1410,9 +1454,12 @@ mod solution_type { ..Default::default() }; - assert_eq!(compact.unique_targets(), vec![1, 2, 3, 4, 7, 8, 11, 12, 13, 66, 67]); + assert_eq!( + compact.unique_targets(), + vec![1, 2, 3, 4, 7, 8, 11, 12, 13, 66, 67] + ); assert_eq!(compact.edge_count(), 2 + (2 * 2) + 3 + 16); - assert_eq!(compact.len(), 6); + assert_eq!(compact.voter_count(), 6); // this one has some duplicates. let compact = TestSolutionCompact { @@ -1429,7 +1476,7 @@ mod solution_type { assert_eq!(compact.unique_targets(), vec![1, 3, 4, 7, 8, 11, 13]); assert_eq!(compact.edge_count(), 2 + (2 * 2) + 3); - assert_eq!(compact.len(), 5); + assert_eq!(compact.voter_count(), 5); } #[test] From 5aea9cc9681890eadc110028a49f9a2d65ab12aa Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 15 Jan 2021 15:17:56 +0000 Subject: [PATCH 02/29] pallet and unsigned phase --- Cargo.lock | 34 + Cargo.toml | 1 + bin/node/runtime/Cargo.toml | 5 + bin/node/runtime/src/constants.rs | 2 +- bin/node/runtime/src/lib.rs | 57 +- frame/babe/Cargo.toml | 1 + frame/babe/src/lib.rs | 12 +- frame/babe/src/mock.rs | 11 +- .../election-provider-multi-phase/Cargo.toml | 67 + .../src/benchmarking.rs | 283 ++++ .../src/helpers.rs | 199 +++ .../election-provider-multi-phase/src/lib.rs | 1495 +++++++++++++++++ .../election-provider-multi-phase/src/mock.rs | 386 +++++ .../src/unsigned.rs | 799 +++++++++ .../src/weights.rs | 147 ++ frame/grandpa/Cargo.toml | 1 + frame/grandpa/src/mock.rs | 9 + frame/offences/benchmarking/Cargo.toml | 2 + frame/offences/benchmarking/src/mock.rs | 10 +- frame/session/benchmarking/Cargo.toml | 2 + frame/session/benchmarking/src/mock.rs | 12 +- frame/session/src/lib.rs | 34 +- frame/staking/Cargo.toml | 5 + frame/staking/fuzzer/Cargo.toml | 1 + frame/staking/fuzzer/src/mock.rs | 18 +- frame/staking/src/lib.rs | 344 +++- frame/staking/src/mock.rs | 51 +- frame/staking/src/offchain_election.rs | 52 +- frame/staking/src/testing_utils.rs | 8 +- frame/staking/src/tests.rs | 98 +- frame/support/src/traits.rs | 65 +- 31 files changed, 4042 insertions(+), 169 deletions(-) create mode 100644 frame/election-provider-multi-phase/Cargo.toml create mode 100644 frame/election-provider-multi-phase/src/benchmarking.rs create mode 100644 frame/election-provider-multi-phase/src/helpers.rs create mode 100644 frame/election-provider-multi-phase/src/lib.rs create mode 100644 frame/election-provider-multi-phase/src/mock.rs create mode 100644 frame/election-provider-multi-phase/src/unsigned.rs create mode 100644 frame/election-provider-multi-phase/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 05f4896e071ff..f02b4183a069f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3954,6 +3954,7 @@ dependencies = [ "pallet-contracts-primitives", "pallet-contracts-rpc-runtime-api", "pallet-democracy", + "pallet-election-provider-multi-phase", "pallet-elections-phragmen", "pallet-grandpa", "pallet-identity", @@ -3992,6 +3993,7 @@ dependencies = [ "sp-inherents", "sp-io", "sp-keyring", + "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-session", @@ -4385,6 +4387,7 @@ dependencies = [ "sp-consensus-babe", "sp-consensus-vrf", "sp-core", + "sp-election-providers", "sp-inherents", "sp-io", "sp-runtime", @@ -4545,6 +4548,32 @@ dependencies = [ "substrate-test-utils", ] +[[package]] +name = "pallet-election-provider-multi-phase" +version = "2.0.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "hex-literal", + "pallet-balances", + "parity-scale-codec", + "parking_lot 0.11.1", + "paste 1.0.3", + "rand 0.7.3", + "serde", + "sp-arithmetic", + "sp-core", + "sp-election-providers", + "sp-io", + "sp-npos-elections", + "sp-runtime", + "sp-std", + "sp-tracing", + "static_assertions", + "substrate-test-utils", +] + [[package]] name = "pallet-elections" version = "2.0.1" @@ -4645,6 +4674,7 @@ dependencies = [ "serde", "sp-application-crypto", "sp-core", + "sp-election-providers", "sp-finality-grandpa", "sp-io", "sp-keyring", @@ -4834,6 +4864,7 @@ dependencies = [ "parity-scale-codec", "serde", "sp-core", + "sp-election-providers", "sp-io", "sp-runtime", "sp-staking", @@ -4955,6 +4986,7 @@ dependencies = [ "rand 0.7.3", "serde", "sp-core", + "sp-election-providers", "sp-io", "sp-runtime", "sp-session", @@ -4996,6 +5028,7 @@ dependencies = [ "serde", "sp-application-crypto", "sp-core", + "sp-election-providers", "sp-io", "sp-npos-elections", "sp-runtime", @@ -5022,6 +5055,7 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "sp-core", + "sp-election-providers", "sp-io", "sp-npos-elections", "sp-runtime", diff --git a/Cargo.toml b/Cargo.toml index 1754f896c8846..e3f04cd996e41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,6 +73,7 @@ members = [ "frame/contracts/rpc/runtime-api", "frame/democracy", "frame/elections", + "frame/election-provider-multi-phase", "frame/example", "frame/example-offchain-worker", "frame/example-parallel", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index d3cc0101e082b..e74600f9501f0 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -35,6 +35,7 @@ sp-keyring = { version = "2.0.0", optional = true, path = "../../../primitives/k sp-session = { version = "2.0.0", default-features = false, path = "../../../primitives/session" } sp-transaction-pool = { version = "2.0.0", default-features = false, path = "../../../primitives/transaction-pool" } sp-version = { version = "2.0.0", default-features = false, path = "../../../primitives/version" } +sp-npos-elections = { version = "2.0.0", default-features = false, path = "../../../primitives/npos-elections" } # frame dependencies frame-executive = { version = "2.0.0", default-features = false, path = "../../../frame/executive" } @@ -71,6 +72,7 @@ pallet-recovery = { version = "2.0.0", default-features = false, path = "../../. pallet-session = { version = "2.0.0", features = ["historical"], path = "../../../frame/session", default-features = false } pallet-session-benchmarking = { version = "2.0.0", path = "../../../frame/session/benchmarking", default-features = false, optional = true } pallet-staking = { version = "2.0.0", default-features = false, path = "../../../frame/staking" } +pallet-election-provider-multi-phase = { version = "2.0.0", default-features = false, path = "../../../frame/election-provider-multi-phase" } pallet-staking-reward-curve = { version = "2.0.0", default-features = false, path = "../../../frame/staking/reward-curve" } pallet-scheduler = { version = "2.0.0", default-features = false, path = "../../../frame/scheduler" } pallet-society = { version = "2.0.0", default-features = false, path = "../../../frame/society" } @@ -114,6 +116,7 @@ std = [ "pallet-im-online/std", "pallet-indices/std", "sp-inherents/std", + "sp-npos-elections/std", "pallet-lottery/std", "pallet-membership/std", "pallet-mmr/std", @@ -140,6 +143,7 @@ std = [ "frame-benchmarking/std", "frame-system-rpc-runtime-api/std", "frame-system/std", + "pallet-election-provider-multi-phase/std", "pallet-timestamp/std", "pallet-tips/std", "pallet-transaction-payment-rpc-runtime-api/std", @@ -156,6 +160,7 @@ runtime-benchmarks = [ "frame-benchmarking", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "pallet-election-provider-multi-phase/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-babe/runtime-benchmarks", diff --git a/bin/node/runtime/src/constants.rs b/bin/node/runtime/src/constants.rs index f447486c7ffc4..c549b1977d376 100644 --- a/bin/node/runtime/src/constants.rs +++ b/bin/node/runtime/src/constants.rs @@ -35,7 +35,7 @@ pub mod time { use node_primitives::{Moment, BlockNumber}; /// Since BABE is probabilistic this is the average expected block time that - /// we are targetting. Blocks will be produced at a minimum duration defined + /// we are targeting. Blocks will be produced at a minimum duration defined /// by `SLOT_DURATION`, but some slots will not be allocated to any /// authority and hence no block will be produced. We expect to have this /// block time on average following the defined slot duration and the value diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index e88484e472958..bc3526af195f7 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -28,7 +28,8 @@ use frame_support::{ construct_runtime, parameter_types, debug, RuntimeDebug, weights::{ Weight, IdentityFee, - constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, DispatchClass, + constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, + DispatchClass, }, traits::{ Currency, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness, LockIdentifier, @@ -50,14 +51,14 @@ pub use node_primitives::{AccountId, Signature}; use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Index, Moment}; use sp_api::impl_runtime_apis; use sp_runtime::{ - Permill, Perbill, Perquintill, Percent, ApplyExtrinsicResult, - impl_opaque_keys, generic, create_runtime_str, ModuleId, FixedPointNumber, + Permill, Perbill, Perquintill, Percent, ApplyExtrinsicResult, impl_opaque_keys, generic, + create_runtime_str, ModuleId, FixedPointNumber, }; use sp_runtime::curve::PiecewiseLinear; use sp_runtime::transaction_validity::{TransactionValidity, TransactionSource, TransactionPriority}; use sp_runtime::traits::{ - self, BlakeTwo256, Block as BlockT, StaticLookup, SaturatedConversion, - ConvertInto, OpaqueKeys, NumberFor, + self, BlakeTwo256, Block as BlockT, StaticLookup, SaturatedConversion, ConvertInto, OpaqueKeys, + NumberFor, }; use sp_version::RuntimeVersion; #[cfg(any(feature = "std", test))] @@ -145,7 +146,7 @@ impl OnUnbalanced for DealWithFees { } } -/// We assume that ~10% of the block weight is consumed by `on_initalize` handlers. +/// We assume that ~10% of the block weight is consumed by `on_initialize` handlers. /// This is used to limit the maximal weight of a single extrinsic. const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); /// We allow `Normal` extrinsics to fill up the block up to 75%, the rest can be used @@ -488,18 +489,56 @@ impl pallet_staking::Config for Runtime { type SessionInterface = Self; type RewardCurve = RewardCurve; type NextNewSession = Session; + type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; type ElectionLookahead = ElectionLookahead; type Call = Call; type MaxIterations = MaxIterations; type MinSolutionScoreBump = MinSolutionScoreBump; - type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; type UnsignedPriority = StakingUnsignedPriority; // The unsigned solution weight targeted by the OCW. We set it to the maximum possible value of // a single extrinsic. type OffchainSolutionWeightLimit = OffchainSolutionWeightLimit; + type ElectionProvider = ElectionProviderMultiPhase; type WeightInfo = pallet_staking::weights::SubstrateWeight; } +use pallet_election_provider_multi_phase::FallbackStrategy; +parameter_types! { + // phase durations + pub const SignedPhase: u32 = 100; + pub const UnsignedPhase: u32 = 100; + + // fallback: no need to do on-chain phragmen initially. + pub const Fallback: FallbackStrategy = FallbackStrategy::Nothing; + + pub SolutionImprovementThreshold: Perbill = Perbill::from_rational_approximation(1u32, 10_000); + + // miner configs + pub const TwoPhaseUnsignedPriority: TransactionPriority = StakingUnsignedPriority::get() - 1u64; + pub const MinerMaxIterations: u32 = 10; + pub MinerMaxWeight: Weight = RuntimeBlockWeights::get() + .get(DispatchClass::Normal) + .max_extrinsic.expect("Normal extrinsics have a weight limit configured; qed") + .saturating_sub(BlockExecutionWeight::get()); +} + +impl pallet_election_provider_multi_phase::Config for Runtime { + type Event = Event; + type Currency = Balances; + type SignedPhase = SignedPhase; + type UnsignedPhase = UnsignedPhase; + type SolutionImprovementThreshold = MinSolutionScoreBump; + type MinerMaxIterations = MinerMaxIterations; + type MinerMaxWeight = MinerMaxWeight; + type UnsignedPriority = TwoPhaseUnsignedPriority; + type DataProvider = Staking; + type OnChainAccuracy = Perbill; + type CompactSolution = pallet_staking::CompactAssignments; + type Fallback = Fallback; + type WeightInfo = pallet_election_provider_multi_phase::weights::SubstrateWeight; + type BenchmarkingConfig = (); +} + parameter_types! { pub const LaunchPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; pub const VotingPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; @@ -546,7 +585,7 @@ impl pallet_democracy::Config for Runtime { >; type BlacklistOrigin = EnsureRoot; // Any single technical committee member may veto a coming council proposal, however they can - // only do it once and it lasts only for the cooloff period. + // only do it once and it lasts only for the cool-off period. type VetoOrigin = pallet_collective::EnsureMember; type CooloffPeriod = CooloffPeriod; type PreimageByteDeposit = PreimageByteDeposit; @@ -1006,6 +1045,7 @@ construct_runtime!( Indices: pallet_indices::{Module, Call, Storage, Config, Event}, Balances: pallet_balances::{Module, Call, Storage, Config, Event}, TransactionPayment: pallet_transaction_payment::{Module, Storage}, + ElectionProviderMultiPhase: pallet_election_provider_multi_phase::{Module, Call, Storage, Event, ValidateUnsigned}, Staking: pallet_staking::{Module, Call, Config, Storage, Event, ValidateUnsigned}, Session: pallet_session::{Module, Call, Storage, Event, Config}, Democracy: pallet_democracy::{Module, Call, Storage, Config, Event}, @@ -1337,6 +1377,7 @@ impl_runtime_apis! { add_benchmark!(params, batches, pallet_treasury, Treasury); add_benchmark!(params, batches, pallet_utility, Utility); add_benchmark!(params, batches, pallet_vesting, Vesting); + add_benchmark!(params, batches, pallet_election_provider_multi_phase, ElectionProviderMultiPhase); if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) } Ok(batches) diff --git a/frame/babe/Cargo.toml b/frame/babe/Cargo.toml index 13ac2e4034c9f..a5e33bbf8f338 100644 --- a/frame/babe/Cargo.toml +++ b/frame/babe/Cargo.toml @@ -39,6 +39,7 @@ pallet-offences = { version = "2.0.0", path = "../offences" } pallet-staking = { version = "2.0.0", path = "../staking" } pallet-staking-reward-curve = { version = "2.0.0", path = "../staking/reward-curve" } sp-core = { version = "2.0.0", path = "../../primitives/core" } +sp-election-providers = { version = "2.0.0", path = "../../primitives/election-providers" } [features] default = ["std"] diff --git a/frame/babe/src/lib.rs b/frame/babe/src/lib.rs index d604bfd57d1a3..31266d64f6323 100644 --- a/frame/babe/src/lib.rs +++ b/frame/babe/src/lib.rs @@ -415,12 +415,14 @@ impl Module { /// In other word, this is only accurate if no slots are missed. Given missed slots, the slot /// number will grow while the block number will not. Hence, the result can be interpreted as an /// upper bound. - // -------------- IMPORTANT NOTE -------------- + // + // ## IMPORTANT NOTE + // // This implementation is linked to how [`should_epoch_change`] is working. This might need to // be updated accordingly, if the underlying mechanics of slot and epochs change. // - // WEIGHT NOTE: This function is tied to the weight of `EstimateNextSessionRotation`. If you update - // this function, you must also update the corresponding weight. + // WEIGHT NOTE: This function is tied to the weight of `EstimateNextSessionRotation`. If you + // update this function, you must also update the corresponding weight. pub fn next_expected_epoch_change(now: T::BlockNumber) -> Option { let next_slot = Self::current_epoch_start().saturating_add(T::EpochDuration::get()); next_slot @@ -748,6 +750,10 @@ impl OnTimestampSet for Module { } impl frame_support::traits::EstimateNextSessionRotation for Module { + fn average_session_length() -> T::BlockNumber { + T::EpochDuration::get().saturated_into() + } + fn estimate_next_session_rotation(now: T::BlockNumber) -> Option { Self::next_expected_epoch_change(now) } diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 58e2af873fd91..a26f6f7f70c56 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -37,8 +37,9 @@ use sp_consensus_babe::{AuthorityId, AuthorityPair, SlotNumber}; use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; use sp_staking::SessionIndex; use pallet_staking::EraIndex; +use sp_election_providers::onchain; -impl_outer_origin!{ +impl_outer_origin! { pub enum Origin for Test where system = frame_system {} } @@ -179,6 +180,13 @@ parameter_types! { pub const StakingUnsignedPriority: u64 = u64::max_value() / 2; } +impl onchain::Config for Test { + type AccountId = ::AccountId; + type BlockNumber = ::BlockNumber; + type Accuracy = Perbill; + type DataProvider = Staking; +} + impl pallet_staking::Config for Test { type RewardRemainder = (); type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; @@ -201,6 +209,7 @@ impl pallet_staking::Config for Test { type MaxIterations = (); type MinSolutionScoreBump = (); type OffchainSolutionWeightLimit = (); + type ElectionProvider = onchain::OnChainSequentialPhragmen; type WeightInfo = (); } diff --git a/frame/election-provider-multi-phase/Cargo.toml b/frame/election-provider-multi-phase/Cargo.toml new file mode 100644 index 0000000000000..c70e68cd36faa --- /dev/null +++ b/frame/election-provider-multi-phase/Cargo.toml @@ -0,0 +1,67 @@ +[package] +name = "pallet-election-provider-multi-phase" +version = "2.0.0" +authors = ["Parity Technologies "] +edition = "2018" +license = "Apache-2.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "PALLET two phase election providers" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +static_assertions = "1.1.0" +serde = { version = "1.0.101", optional = true } +codec = { package = "parity-scale-codec", version = "1.3.4", default-features = false, features = ["derive"] } + +frame-support = { version = "2.0.0", default-features = false, path = "../support" } +frame-system = { version = "2.0.0", default-features = false, path = "../system" } + +sp-io ={ version = "2.0.0", default-features = false, path = "../../primitives/io" } +sp-std = { version = "2.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "2.0.0", default-features = false, path = "../../primitives/runtime" } +sp-npos-elections = { version = "2.0.0", default-features = false, path = "../../primitives/npos-elections" } +sp-arithmetic = { version = "2.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-election-providers = { version = "2.0.0", default-features = false, path = "../../primitives/election-providers" } + +# Optional imports for benchmarking +frame-benchmarking = { version = "2.0.0", default-features = false, path = "../benchmarking", optional = true } +rand = { version = "0.7.3", default-features = false, optional = true, features = ["alloc", "small_rng"] } + +[dev-dependencies] +sp-io = { version = "2.0.0", path = "../../primitives/io" } +hex-literal = "0.3.1" +pallet-balances = { version = "2.0.0", path = "../balances" } +sp-core = { version = "2.0.0", path = "../../primitives/core" } +paste = "1.0.3" +substrate-test-utils = { version = "2.0.0", path = "../../test-utils" } +parking_lot = "0.11.0" +sp-tracing = { version = "2.0.0", path = "../../primitives/tracing" } +rand = { version = "0.7.3" } +frame-benchmarking = { path = "../benchmarking" } +sp-election-providers = { version = "2.0.0", features = ["runtime-benchmarks"], path = "../../primitives/election-providers" } + + +[features] +default = ["std"] +std = [ + "serde", + "codec/std", + + "frame-support/std", + "frame-system/std", + + "sp-io/std", + "sp-std/std", + "sp-runtime/std", + "sp-npos-elections/std", + "sp-arithmetic/std", + "sp-election-providers/std", +] +runtime-benchmarks = [ + "frame-benchmarking", + "rand", +] diff --git a/frame/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs new file mode 100644 index 0000000000000..a7a4eed852850 --- /dev/null +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -0,0 +1,283 @@ +// This file is part of Substrate. + +// Copyright (C) 2020 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. + +//! Two phase election pallet benchmarking. + +use super::*; +use crate::Module as TwoPhase; + +pub use frame_benchmarking::{account, benchmarks, whitelist_account, whitelisted_caller}; +use frame_support::{assert_ok, traits::OnInitialize}; +use frame_system::RawOrigin; +use rand::{prelude::SliceRandom, rngs::SmallRng, SeedableRng}; +use sp_election_providers::Assignment; +use sp_npos_elections::ExtendedBalance; +use sp_runtime::InnerOf; +use sp_arithmetic::traits::One; +use sp_std::convert::TryInto; + +const SEED: u32 = 0; + +/// Creates a **valid** solution with exactly the given size. +/// +/// The snapshot is also created internally. +fn solution_with_size( + size: SolutionSize, + active_voters_count: u32, + desired_targets: u32, +) -> RawSolution> +where + ExtendedBalance: From>>, + ExtendedBalance: From>>, + > as sp_std::convert::TryFrom>::Error: sp_std::fmt::Debug, +{ + assert!(size.targets >= desired_targets, "must have enough targets"); + assert!( + size.targets >= (>::LIMIT * 2) as u32, + "must have enough targets for unique votes." + ); + assert!(size.voters >= active_voters_count, "must have enough voters"); + assert!( + (>::LIMIT as u32) < desired_targets, + "must have enough winners to give them votes." + ); + + let ed: VoteWeight = T::Currency::minimum_balance().saturated_into::(); + let stake: VoteWeight = ed.max(One::one()).saturating_mul(100); + + // first generates random targets. + let targets: Vec = + (0..size.targets).map(|i| account("Targets", i, SEED)).collect(); + + let mut rng = SmallRng::seed_from_u64(999u64); + + // decide who are the winners. + let winners = targets + .as_slice() + .choose_multiple(&mut rng, desired_targets as usize) + .cloned() + .collect::>(); + + // first generate active voters who must vote for a subset of winners. + let active_voters = (0..active_voters_count) + .map(|i| { + // chose a random subset of winners. + let winner_votes = winners + .as_slice() + .choose_multiple(&mut rng, >::LIMIT) + .cloned() + .collect::>(); + let voter = account::("Voter", i, SEED); + (voter, stake, winner_votes) + }) + .collect::>(); + + // rest of the voters. They can only vote for non-winners. + let non_winners = + targets.iter().filter(|t| !winners.contains(t)).cloned().collect::>(); + let rest_voters = (active_voters_count..size.voters) + .map(|i| { + let votes = (&non_winners) + .choose_multiple(&mut rng, >::LIMIT) + .cloned() + .collect::>(); + let voter = account::("Voter", i, SEED); + (voter, stake, votes) + }) + .collect::>(); + + let mut all_voters = active_voters.clone(); + all_voters.extend(rest_voters); + all_voters.shuffle(&mut rng); + + assert_eq!(active_voters.len() as u32, active_voters_count); + assert_eq!(all_voters.len() as u32, size.voters); + assert_eq!(winners.len() as u32, desired_targets); + + >::put(RoundSnapshotMetadata { + voters_len: all_voters.len() as u32, + targets_len: targets.len() as u32, + }); + >::put(desired_targets); + >::put(RoundSnapshot { voters: all_voters.clone(), targets: targets.clone() }); + + // write the snapshot to staking or whoever is the data provider. + T::DataProvider::put_snapshot(all_voters.clone(), targets.clone()); + + let cache = helpers::generate_voter_cache::(&all_voters); + let stake_of = helpers::stake_of_fn::(&all_voters, &cache); + let voter_index = helpers::voter_index_fn::(&cache); + let target_index = helpers::target_index_fn_linear::(&targets); + let voter_at = helpers::voter_at_fn::(&all_voters); + let target_at = helpers::target_at_fn::(&targets); + + let assignments = active_voters + .iter() + .map(|(voter, _stake, votes)| { + let percent_per_edge: InnerOf> = + (100 / votes.len()).try_into().unwrap(); + Assignment { + who: voter.clone(), + distribution: votes + .iter() + .map(|t| (t.clone(), >::from_percent(percent_per_edge))) + .collect::>(), + } + }) + .collect::>(); + + let compact = + >::from_assignment(assignments, &voter_index, &target_index).unwrap(); + let score = compact.clone().score(&winners, stake_of, voter_at, target_at).unwrap(); + let round = >::round(); + RawSolution { compact, score, round } +} + +benchmarks! { + where_clause { + where ExtendedBalance: From>>, + > as sp_std::convert::TryFrom>::Error: sp_std::fmt::Debug, + ExtendedBalance: From>>, + } + + on_initialize_nothing { + assert!(>::current_phase().is_off()); + }: { + >::on_initialize(1u32.into()); + } verify { + assert!(>::current_phase().is_off()); + } + + on_initialize_open_signed { + // NOTE: this benchmark currently doesn't have any components because the length of a db + // read/write is not captured. Otherwise, it is quite influenced by how much data + // `T::ElectionDataProvider` is reading and passing on. + assert!(>::snapshot().is_none()); + assert!(>::current_phase().is_off()); + }: { + >::on_initialize_open_signed(); + } verify { + assert!(>::snapshot().is_some()); + assert!(>::current_phase().is_signed()); + } + + on_initialize_open_unsigned_with_snapshot { + assert!(>::snapshot().is_none()); + assert!(>::current_phase().is_off()); + }: { + >::on_initialize_open_unsigned(true, true, 1u32.into()); + } verify { + assert!(>::snapshot().is_some()); + assert!(>::current_phase().is_unsigned()); + } + + on_initialize_open_unsigned_without_snapshot { + // need to assume signed phase was open before + >::on_initialize_open_signed(); + assert!(>::snapshot().is_some()); + assert!(>::current_phase().is_signed()); + }: { + >::on_initialize_open_unsigned(false, true, 1u32.into()); + } verify { + assert!(>::snapshot().is_some()); + assert!(>::current_phase().is_unsigned()); + } + + #[extra] + create_snapshot { + assert!(>::snapshot().is_none()); + }: { + >::create_snapshot() + } verify { + assert!(>::snapshot().is_some()); + } + + submit_unsigned { + // 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 assignments, i.e. compact.len(). This means the active nominators, thus must be + // a subset of `v` component. + let a in (T::BenchmarkingConfig::ACTIVE_VOTERS[0]) .. T::BenchmarkingConfig::ACTIVE_VOTERS[1]; + // number of desired targets. Must be a subset of `t` component. + let d in (T::BenchmarkingConfig::DESIRED_TARGETS[0]) .. T::BenchmarkingConfig::DESIRED_TARGETS[1]; + + let witness = SolutionSize { voters: v, targets: t }; + let raw_solution = solution_with_size::(witness, a, d); + + assert!(>::queued_solution().is_none()); + >::put(Phase::Unsigned((true, 1u32.into()))); + }: _(RawOrigin::None, raw_solution, witness) + verify { + assert!(>::queued_solution().is_some()); + } + + // This is checking a valid solution. The worse case is indeed a valid solution. + feasibility_check { + // 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 assignments, i.e. compact.len(). This means the active nominators, thus must be + // a subset of `v` component. + let a in (T::BenchmarkingConfig::ACTIVE_VOTERS[0]) .. T::BenchmarkingConfig::ACTIVE_VOTERS[1]; + // number of desired targets. Must be a subset of `t` component. + let d in (T::BenchmarkingConfig::DESIRED_TARGETS[0]) .. T::BenchmarkingConfig::DESIRED_TARGETS[1]; + + let size = SolutionSize { voters: v, targets: t }; + let raw_solution = solution_with_size::(size, a, d); + + assert_eq!(raw_solution.compact.voter_count() as u32, a); + assert_eq!(raw_solution.compact.unique_targets().len() as u32, d); + }: { + assert_ok!(>::feasibility_check(raw_solution, ElectionCompute::Unsigned)); + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::mock::*; + + #[test] + fn test_benchmarks() { + ExtBuilder::default().build_and_execute(|| { + assert_ok!(test_benchmark_feasibility_check::()); + }); + + ExtBuilder::default().build_and_execute(|| { + assert_ok!(test_benchmark_submit_unsigned::()); + }); + + ExtBuilder::default().build_and_execute(|| { + assert_ok!(test_benchmark_on_initialize_open_unsigned_with_snapshot::()); + }); + + ExtBuilder::default().build_and_execute(|| { + assert_ok!(test_benchmark_on_initialize_open_unsigned_without_snapshot::()); + }); + + ExtBuilder::default().build_and_execute(|| { + assert_ok!(test_benchmark_on_initialize_nothing::()); + }); + + ExtBuilder::default().build_and_execute(|| { + assert_ok!(test_benchmark_create_snapshot::()); + }); + } +} diff --git a/frame/election-provider-multi-phase/src/helpers.rs b/frame/election-provider-multi-phase/src/helpers.rs new file mode 100644 index 0000000000000..da4a092653ae7 --- /dev/null +++ b/frame/election-provider-multi-phase/src/helpers.rs @@ -0,0 +1,199 @@ +// This file is part of Substrate. + +// Copyright (C) 2020 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. + +//! Some helper functions/macros for this crate. + +use super::{ + Config, VoteWeight, CompactVoterIndexOf, CompactTargetIndexOf, CompactAccuracyOf, + OnChainAccuracyOf, ExtendedBalance, +}; +use sp_runtime::InnerOf; +use sp_std::{collections::btree_map::BTreeMap, convert::TryInto, boxed::Box, prelude::*}; + +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + frame_support::debug::$level!( + target: $crate::LOG_TARGET, + concat!("🏦 ", $patter) $(, $values)* + ) + }; +} + +/// Generate a btree-map cache of the voters and their indices. +/// +/// This can be used to efficiently build index getter closures. +pub fn generate_voter_cache( + snapshot: &Vec<(T::AccountId, VoteWeight, Vec)>, +) -> BTreeMap +where + ExtendedBalance: From>>, + ExtendedBalance: From>>, +{ + let mut cache: BTreeMap = BTreeMap::new(); + snapshot.iter().enumerate().for_each(|(i, (x, _, _))| { + let _existed = cache.insert(x.clone(), i); + // if a duplicate exists, we only consider the last one. Defensive only, should never + // happen. + debug_assert!(_existed.is_none()); + }); + + cache +} + +/// Create a function the returns the index a voter in the snapshot. +/// +/// The returning index type is the same as the one defined in [`T::CompactSolution::Voter`]. +/// +/// ## Warning +/// +/// The snapshot must be the same is the one used to create `cache`. +pub fn voter_index_fn( + cache: &BTreeMap, +) -> Box Option> + '_> +where + ExtendedBalance: From>>, + ExtendedBalance: From>>, +{ + Box::new(move |who| { + cache.get(who).and_then(|i| >>::try_into(*i).ok()) + }) +} + +/// Same as [`voter_index_fn`], but the returning index is converted into usize, if possible. +/// +/// ## Warning +/// +/// The snapshot must be the same is the one used to create `cache`. +pub fn voter_index_fn_usize( + cache: &BTreeMap, +) -> Box Option + '_> +where + ExtendedBalance: From>>, + ExtendedBalance: From>>, +{ + Box::new(move |who| cache.get(who).cloned()) +} + +/// A non-optimized, linear version of [`voter_index_fn`] that does not need a cache and does a +/// linear search. +/// +/// ## Warning +/// +/// Not meant to be used in production. +pub fn voter_index_fn_linear( + snapshot: &Vec<(T::AccountId, VoteWeight, Vec)>, +) -> Box Option> + '_> +where + ExtendedBalance: From>>, + ExtendedBalance: From>>, +{ + Box::new(move |who| { + snapshot + .iter() + .position(|(x, _, _)| x == who) + .and_then(|i| >>::try_into(i).ok()) + }) +} + +/// Create a function the returns the index a targets in the snapshot. +/// +/// The returning index type is the same as the one defined in [`T::CompactSolution::Target`]. +pub fn target_index_fn_linear( + snapshot: &Vec, +) -> Box Option> + '_> +where + ExtendedBalance: From>>, + ExtendedBalance: From>>, +{ + Box::new(move |who| { + snapshot + .iter() + .position(|x| x == who) + .and_then(|i| >>::try_into(i).ok()) + }) +} + +/// Create a function that can map a voter index ([`CompactVoterIndexOf`]) to the actual voter +/// account using a linearly indexible snapshot. +pub fn voter_at_fn( + snapshot: &Vec<(T::AccountId, VoteWeight, Vec)>, +) -> Box) -> Option + '_> +where + ExtendedBalance: From>>, + ExtendedBalance: From>>, +{ + Box::new(move |i| { + as TryInto>::try_into(i) + .ok() + .and_then(|i| snapshot.get(i).map(|(x, _, _)| x).cloned()) + }) +} + +/// Create a function that can map a target index ([`CompactTargetIndexOf`]) to the actual target +/// account using a linearly indexible snapshot. +pub fn target_at_fn( + snapshot: &Vec, +) -> Box) -> Option + '_> +where + ExtendedBalance: From>>, + ExtendedBalance: From>>, +{ + Box::new(move |i| { + as TryInto>::try_into(i) + .ok() + .and_then(|i| snapshot.get(i).cloned()) + }) +} + +/// Create a function to get the stake of a voter. +/// +/// This is not optimized and uses a linear search. +pub fn stake_of_fn_linear( + snapshot: &Vec<(T::AccountId, VoteWeight, Vec)>, +) -> Box VoteWeight + '_> +where + ExtendedBalance: From>>, + ExtendedBalance: From>>, +{ + Box::new(move |who| { + snapshot.iter().find(|(x, _, _)| x == who).map(|(_, x, _)| *x).unwrap_or_default() + }) +} + +/// Create a function to get the stake of a voter. +/// +/// ## Warning +/// +/// The cache need must be derived from the same snapshot. Zero is returned if a voter is +/// non-existent. +pub fn stake_of_fn<'a, T: Config>( + snapshot: &'a Vec<(T::AccountId, VoteWeight, Vec)>, + cache: &'a BTreeMap, +) -> Box VoteWeight + 'a> +where + ExtendedBalance: From>>, + ExtendedBalance: From>>, +{ + Box::new(move |who| { + if let Some(index) = cache.get(who) { + snapshot.get(*index).map(|(_, x, _)| x).cloned().unwrap_or_default() + } else { + 0 + } + }) +} diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs new file mode 100644 index 0000000000000..79112297f869f --- /dev/null +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -0,0 +1,1495 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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. + +//! # Two phase, offchain election provider pallet. +//! +//! As the name suggests, this election-provider has two distinct phases (see [`Phase`]), signed and +//! unsigned. +//! +//! ## Phases +//! +//! The timeline of pallet is as follows. At each block, +//! [`sp_election_providers::ElectionDataProvider::next_election_prediction`] is used to estimate +//! the time remaining to the next call to [`sp_election_providers::ElectionProvider::elect`]. Based +//! on this, a phase is chosen. The timeline is as follows. +//! +//! ```ignore +//! elect() +//! + <--T::SignedPhase--> + <--T::UnsignedPhase--> + +//! +-------------------------------------------------------------------+ +//! Phase::Off + Phase::Signed + Phase::Unsigned + +//! ``` +//! +//! Note that the unsigned phase starts [`pallet::Config::UnsignedPhase`] blocks before the +//! `next_election_prediction`, but only ends when a call to [`ElectionProvider::elect`] happens. +//! +//! > Given this, it is rather important for the user of this pallet to ensure it always terminates +//! election via `elect` before requesting a new one. +//! +//! Each of the phases can be disabled by essentially setting their length to zero. If both phases +//! have length zero, then the pallet essentially runs only the on-chain backup. +//! +//! ### Signed Phase +//! +//! In the signed phase, solutions (of type [`RawSolution`]) are submitted and queued on chain. A +//! deposit is reserved, based on the size of the solution, for the cost of keeping this solution +//! on-chain for a number of blocks, and the potential weight of the solution upon being checked. A +//! maximum of [`pallet::Config::MaxSignedSubmissions`] solutions are stored. The queue is always +//! sorted based on score (worse to best). +//! +//! Upon arrival of a new solution: +//! +//! 1. If the queue is not full, it is stored in the appropriate sorted index. +//! 2. If the queue is full but the submitted solution is better than one of the queued ones, the +//! worse solution is discarded, the bond of the outgoing solution is returned, and the new +//! solution is stored in the correct index. +//! 3. If the queue is full and the solution is not an improvement compared to any of the queued +//! ones, it is instantly rejected and no additional bond is reserved. +//! +//! A signed solution cannot be reversed, taken back, updated, or retracted. In other words, the +//! origin can not bail out in any way, if their solution is queued. +//! +//! Upon the end of the signed phase, the solutions are examined from best to worse (i.e. `pop()`ed +//! until drained). Each solution undergoes an expensive [`Pallet::feasibility_check`], which +//! ensures the score claimed by this score was correct, and it is valid based on the election data +//! (i.e. votes and candidates). At each step, if the current best solution passes the feasibility +//! check, it is considered to be the best one. The sender of the origin is rewarded, and the rest +//! of the queued solutions get their deposit back and are discarded, without being checked. +//! +//! The following example covers all of the cases at the end of the signed phase: +//! +//! ```ignore +//! Queue +//! +-------------------------------+ +//! |Solution(score=20, valid=false)| +--> Slashed +//! +-------------------------------+ +//! |Solution(score=15, valid=true )| +--> Rewarded, Saved +//! +-------------------------------+ +//! |Solution(score=10, valid=true )| +--> Discarded +//! +-------------------------------+ +//! |Solution(score=05, valid=false)| +--> Discarded +//! +-------------------------------+ +//! | None | +//! +-------------------------------+ +//! ``` +//! +//! Note that both of the bottom solutions end up being discarded and get their deposit back, +//! despite one of them being *invalid*. +//! +//! ## Unsigned Phase +//! +//! The unsigned phase will always follow the signed phase, with the specified duration. In this +//! phase, only validator nodes can submit solutions. A validator node who has offchain workers +//! enabled will start to mine a solution in this phase and submits it back to the chain as an +//! unsigned transaction, thus the name _unsigned_ phase. This unsigned transaction can never be +//! valid if propagated, and it acts similar to an inherent. +//! +//! Validators will only submit solutions if the one that they have computed is sufficiently better +//! than the best queued one (see [`pallet::Config::SolutionImprovementThreshold`]) and will limit +//! the weigh of the solution to [`pallet::Config::MinerMaxWeight`]. +//! +//! ### Fallback +//! +//! If we reach the end of both phases (i.e. call to [`ElectionProvider::elect`] happens) and no +//! good solution is queued, then the fallback strategy [`pallet::Config::Fallback`] is used to +//! determine what needs to be done. The on-chain election is slow, and contains no balancing or +//! reduction post-processing. See [`onchain::OnChainSequentialPhragmen`]. The +//! [`FallbackStrategy::Nothing`] should probably only be used for testing, and returns an error. +//! +//! ## Feasible Solution (correct solution) +//! +//! All submissions must undergo a feasibility check. Signed solutions are checked on by one at the +//! end of the signed phase, and the unsigned solutions are checked on the spot. A feasible solution +//! is as follows: +//! +//! 0. **all** of the used indices must be correct. +//! 1. present *exactly* correct number of winners. +//! 2. any assignment is checked to match with [`RoundSnapshot::voters`]. +//! 3. the claimed score is valid, based on the fixed point arithmetic accuracy. +//! +//! ## Accuracy +//! +//! The accuracy of the election is configured via two trait parameters. namely, +//! [`OnChainAccuracyOf`] dictates the accuracy used to compute the on-chain fallback election and +//! [`CompactAccuracyOf`] is the accuracy that the submitted solutions must adhere to. +//! +//! Note that both accuracies are of great importance. The offchain solution should be as small as +//! possible, reducing solutions size/weight. The on-chain solution can use more space for accuracy, +//! but should still be fast to prevent massively large blocks in case of a fallback. +//! +//! ## Future Plans +//! +//! **Challenge Phase**. We plan adding a third phase to the pallet, called the challenge phase. +//! This is phase in which no further solutions are processed, and the current best solution might +//! be challenged by anyone (signed or unsigned). The main plan here is to enforce the solution to +//! be PJR. Checking PJR on-chain is quite expensive, yet proving that a solution is **not** PJR is +//! rather cheap. If a queued solution is challenged: +//! +//! 1. We must surely slash whoever submitted that solution (might be a challenge for unsigned +//! solutions). +//! 2. It is probably fine to fallback to the on-chain election, as we expect this to happen rarely. +//! +//! **Bailing out**. The functionality of bailing out of a queued solution is nice. A miner can +//! submit a solution as soon as they _think_ it is high probability feasible, and do the checks +//! afterwards, and remove their solution (for a small cost of probably just transaction fees, or a +//! portion of the bond). +//! +//! **Conditionally open unsigned phase**: Currently, the unsigned phase is always opened. This is +//! useful because an honest validation will run our OCW code, which should be good enough to trump +//! a mediocre or malicious signed submission (assuming in the absence of honest signed bots). If an +//! when the signed submissions are checked against an absolute measure (e.g. PJR), then we can only +//! open the unsigned phase in extreme conditions (i.e. "not good signed solution received") to +//! spare some work in the validators +//! +//! **Allow smaller solutions and build up**: For now we only allow solutions that are exactly +//! [`DesiredTargets`], no more, no less. Over time, we can change this to a [min, max] where any +//! solution within this range is acceptable, where bigger solutions are prioritized. +//! +//! **Recursive Fallback**: Currently, the fallback is a separate enum. A different and fancier way +//! of doing this would be to have the fallback be another +//! [`sp_election_providers::ElectionProvider`]. In this case, this pallet can even have the +//! on-chain election provider as fallback, or special _noop_ fallback that simply returns an error, +//! thus replicating [`FallbackStrategy::Nothing`]. +//! +//! **Score based on size**: We should always prioritize small solutions over bigger ones, if there +//! is a tie. Even more harsh should be to enforce the bound of the `reduce` algorithm. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode, HasCompact}; +use frame_support::{ + dispatch::DispatchResultWithPostInfo, + ensure, + traits::{Currency, Get, ReservableCurrency}, + weights::Weight, +}; +use frame_system::{ensure_none, ensure_signed, offchain::SendTransactionTypes}; +use sp_election_providers::{ElectionDataProvider, ElectionProvider, onchain}; +use sp_npos_elections::{ + assignment_ratio_to_staked_normalized, is_score_better, CompactSolution, ElectionScore, + EvaluateSupport, ExtendedBalance, PerThing128, Supports, VoteWeight, +}; +use sp_runtime::{ + transaction_validity::{ + InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity, + TransactionValidityError, ValidTransaction, + }, + DispatchError, InnerOf, PerThing, Perbill, RuntimeDebug, SaturatedConversion, +}; +use sp_std::prelude::*; +use sp_arithmetic::{ + UpperOf, + traits::{Zero, CheckedAdd}, +}; + +#[cfg(any(feature = "runtime-benchmarks", test))] +mod benchmarking; +#[cfg(test)] +mod mock; +#[macro_use] +pub mod helpers; + +const LOG_TARGET: &'static str = "election-provider"; + +// for the helper macros +#[doc(hidden)] +pub use sp_runtime::traits::UniqueSaturatedInto; +#[doc(hidden)] +pub use sp_std; + +pub mod unsigned; +pub mod weights; + +use weights::WeightInfo; + +// pub mod signed; +// use signed::SignedSubmission; + +/// The compact solution type used by this crate. +pub type CompactOf = ::CompactSolution; + +/// The voter index. Derived from [`CompactOf`]. +pub type CompactVoterIndexOf = as CompactSolution>::Voter; +/// The target index. Derived from [`CompactOf`]. +pub type CompactTargetIndexOf = as CompactSolution>::Target; +/// The accuracy of the election, when submitted from offchain. Derived from [`CompactOf`]. +pub type CompactAccuracyOf = as CompactSolution>::Accuracy; +/// The accuracy of the election, when computed on-chain. Equal to [`Config::OnChainAccuracy`]. +pub type OnChainAccuracyOf = ::OnChainAccuracy; + +struct OnChainConfig(sp_std::marker::PhantomData) +where + ExtendedBalance: From>>, + ExtendedBalance: From>>; +impl onchain::Config for OnChainConfig +where + ExtendedBalance: From>>, + ExtendedBalance: From>>, +{ + type AccountId = T::AccountId; + type BlockNumber = T::BlockNumber; + type Accuracy = T::OnChainAccuracy; + type DataProvider = T::DataProvider; +} + +/// 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 active voters. + const ACTIVE_VOTERS: [u32; 2]; + /// Range of desired targets. + const DESIRED_TARGETS: [u32; 2]; +} + +impl BenchmarkingConfig for () { + const VOTERS: [u32; 2] = [4000, 6000]; + const TARGETS: [u32; 2] = [1000, 1600]; + const ACTIVE_VOTERS: [u32; 2] = [1000, 3000]; + const DESIRED_TARGETS: [u32; 2] = [400, 800]; +} + +/// Current phase of the pallet. +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug)] +pub enum Phase { + /// Nothing, the election is not happening. + Off, + /// Signed phase is open. + Signed, + /// Unsigned phase. First element is whether it is open or not, second the starting block + /// number. + Unsigned((bool, Bn)), +} + +impl Default for Phase { + fn default() -> Self { + Phase::Off + } +} + +impl Phase { + /// Weather the phase is signed or not. + pub fn is_signed(&self) -> bool { + matches!(self, Phase::Signed) + } + + /// Weather the phase is unsigned or not. + pub fn is_unsigned(&self) -> bool { + matches!(self, Phase::Unsigned(_)) + } + + /// Weather the phase is unsigned and open or not, with specific start. + pub fn is_unsigned_open_at(&self, at: Bn) -> bool { + matches!(self, Phase::Unsigned((true, real)) if *real == at) + } + + /// Weather the phase is unsigned and open or not. + pub fn is_unsigned_open(&self) -> bool { + matches!(self, Phase::Unsigned((true, _))) + } + + /// Weather the phase is off or not. + pub fn is_off(&self) -> bool { + matches!(self, Phase::Off) + } +} + +/// A configuration for the module to indicate what should happen in the case of a fallback i.e. +/// reaching a call to `elect` with no good solution. +#[cfg_attr(test, derive(Clone))] +pub enum FallbackStrategy { + /// Run a on-chain sequential phragmen. + /// + /// This might burn the chain for a few minutes due to a stall, but is generally a safe + /// approach to maintain a sensible validator set. + OnChain, + /// Nothing. Return an error. + Nothing, +} + +/// The type of `Computation` that provided this election data. +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug)] +pub enum ElectionCompute { + /// Election was computed on-chain. + OnChain, + /// Election was computed with a signed submission. + Signed, + /// Election was computed with an unsigned submission. + Unsigned, +} + +impl Default for ElectionCompute { + fn default() -> Self { + ElectionCompute::OnChain + } +} + +/// A raw, unchecked solution. +/// +/// This is what will get submitted to the chain. +/// +/// Such a solution should never become effective in anyway before being checked by the +/// [`Pallet::feasibility_check`] +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] +pub struct RawSolution { + /// Compact election edges. + compact: C, + /// The _claimed_ score of the solution. + score: ElectionScore, + /// The round at which this solution should be submitted. + round: u32, +} + +impl Default for RawSolution { + fn default() -> Self { + // Round 0 is always invalid, only set this to 1. + Self { round: 1, compact: Default::default(), score: Default::default() } + } +} + +/// A checked solution, ready to be enacted. +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default)] +pub struct ReadySolution { + /// The final supports of the solution. + /// + /// This is target-major vector, storing each winners, total backing, and each individual + /// backer. + supports: Supports, + /// The score of the solution. + /// + /// This is needed to potentially challenge the solution. + score: ElectionScore, + /// How this election was computed. + compute: ElectionCompute, +} + +/// Solution size of the election. +/// +/// This is needed for proper weight calculation. +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, Default)] +pub struct SolutionSize { + /// Number of all voters. + /// + /// This must match the on-chain snapshot. + #[codec(compact)] + voters: u32, + /// Number of all targets. + /// + /// This must match the on-chain snapshot. + #[codec(compact)] + targets: u32, +} + +/// A snapshot of all the data that is needed for en entire round. They are provided by +/// [`ElectionDataProvider`] at the beginning of the signed phase (or the unsigned phase, if signed +/// phase is non-existent) and are kept around until the round is finished. +/// +/// These are stored together because they are often times accessed together. +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default)] +pub struct RoundSnapshot { + /// All of the voters. + pub voters: Vec<(A, VoteWeight, Vec)>, + /// All of the targets. + pub targets: Vec, +} + +/// Some metadata related to snapshot. +/// +/// In this pallet, there are cases where we want to read the whole snapshot (voters, targets, +/// desired), and cases that we are interested in just the length of these values. The former favors +/// the snapshot to be stored in one struct (as it is now) while the latter prefers them to be +/// separate to enable the use of `decode_len`. This approach is a middle ground, storing the +/// snapshot as one struct, whilst storing the lengths separately. +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default)] +pub struct RoundSnapshotMetadata { + /// The length of voters. + voters_len: u32, + /// The length of targets. + targets_len: u32, +} + +/// Internal errors of the pallet. +/// +/// Note that this is different from [`pallet::Error`]. +#[derive(RuntimeDebug, Eq, PartialEq)] +pub enum ElectionError { + /// A feasibility error. + Feasibility(FeasibilityError), + /// An error in the on-chain fallback. + OnChainFallback(onchain::Error), + /// No fallback is configured + NoFallbackConfigured, + /// An internal error in the NPoS elections crate. + NposElections(sp_npos_elections::Error), + /// Snapshot data was unavailable unexpectedly. + SnapshotUnAvailable, + /// Submitting a transaction to the pool failed. + /// + /// This can only happen in the unsigned phase. + PoolSubmissionFailed, +} + +impl From for ElectionError { + fn from(e: onchain::Error) -> Self { + ElectionError::OnChainFallback(e) + } +} + +impl From for ElectionError { + fn from(e: sp_npos_elections::Error) -> Self { + ElectionError::NposElections(e) + } +} + +impl From for ElectionError { + fn from(e: FeasibilityError) -> Self { + ElectionError::Feasibility(e) + } +} + +/// Errors that can happen in the feasibility check. +#[derive(RuntimeDebug, Eq, PartialEq)] +pub enum FeasibilityError { + /// Wrong number of winners presented. + WrongWinnerCount, + /// The snapshot is not available. + /// + /// This must be an internal error of the chain. + SnapshotUnavailable, + /// Internal error from the election crate. + NposElection(sp_npos_elections::Error), + /// A vote is invalid. + InvalidVote, + /// A voter is invalid. + InvalidVoter, + /// A winner is invalid. + InvalidWinner, + /// The given score was invalid. + InvalidScore, + /// The provided round is incorrect. + InvalidRound, +} + +impl From for FeasibilityError { + fn from(e: sp_npos_elections::Error) -> Self { + FeasibilityError::NposElection(e) + } +} + +pub use pallet::*; +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config + SendTransactionTypes> + where + ExtendedBalance: From>>, + ExtendedBalance: From>>, + { + type Event: From> + + Into<::Event> + + IsType<::Event>; + + /// Currency type. + type Currency: ReservableCurrency + Currency; + + /// Duration of the unsigned phase. + #[pallet::constant] + type UnsignedPhase: Get; + /// Duration of the signed phase. + #[pallet::constant] + type SignedPhase: Get; + + /// The minimum amount of improvement to the solution score that defines a solution as + /// "better". + #[pallet::constant] + type SolutionImprovementThreshold: Get; + + /// The priority of the unsigned transaction submitted in the unsigned-phase + type UnsignedPriority: Get; + /// Maximum number of iteration of balancing that will be executed in the embedded miner of + /// the pallet. + type MinerMaxIterations: Get; + /// Maximum weight that the miner should consume. + /// + /// The miner will ensure that the total weight of the unsigned solution will not exceed + /// this values, based on [`WeightInfo::submit_unsigned`]. + type MinerMaxWeight: Get; + + /// Something that will provide the election data. + type DataProvider: ElectionDataProvider; + + /// The compact solution type + type CompactSolution: codec::Codec + + Default + + PartialEq + + Eq + + Clone + + sp_std::fmt::Debug + + CompactSolution; + + /// Accuracy used for fallback on-chain election. + type OnChainAccuracy: PerThing128; + + /// Configuration for the fallback + type Fallback: Get; + + /// The configuration of benchmarking. + type BenchmarkingConfig: BenchmarkingConfig; + + /// The weight of the pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::hooks] + impl Hooks> for Pallet + where + ExtendedBalance: From>>, + ExtendedBalance: From>>, + { + fn on_initialize(now: T::BlockNumber) -> Weight { + let next_election = T::DataProvider::next_election_prediction(now).max(now); + + let signed_deadline = T::SignedPhase::get() + T::UnsignedPhase::get(); + let unsigned_deadline = T::UnsignedPhase::get(); + + let remaining = next_election - now; + let current_phase = Self::current_phase(); + + match current_phase { + Phase::Off if remaining <= signed_deadline && remaining > unsigned_deadline => { + Self::on_initialize_open_signed(); + log!(info, "Starting signed phase at #{:?} , round {}.", now, Self::round()); + T::WeightInfo::on_initialize_open_signed() + } + Phase::Signed | Phase::Off + if remaining <= unsigned_deadline && remaining > 0u32.into() => + { + let (need_snapshot, enabled, additional) = if current_phase == Phase::Signed { + // followed by a signed phase: close the signed phase, no need for snapshot. + // TODO + (false, true, Weight::zero()) + } else { + // no signed phase + (true, true, Weight::zero()) + }; + + Self::on_initialize_open_unsigned(need_snapshot, enabled, now); + log!(info, "Starting unsigned phase({}) at #{:?}.", enabled, now); + let base_weight = if need_snapshot { + T::WeightInfo::on_initialize_open_unsigned_with_snapshot() } + else { + T::WeightInfo::on_initialize_open_unsigned_without_snapshot() + }; + base_weight.saturating_add(additional) + } + _ => T::WeightInfo::on_initialize_nothing(), + } + } + + fn offchain_worker(n: T::BlockNumber) { + // We only run the OCW in the fist block of the unsigned phase. + if Self::current_phase().is_unsigned_open_at(n) { + match Self::set_check_offchain_execution_status(n) { + Ok(_) => match Self::mine_and_submit() { + Ok(_) => { + log!(info, "successfully submitted a solution via OCW at block {:?}", n) + } + Err(e) => log!(error, "error while submitting transaction in OCW: {:?}", e), + }, + Err(why) => log!(error, "Error in unsigned offchain worker: {:?}", why), + } + } + } + + fn integrity_test() { + use sp_std::mem::size_of; + // The index type of both voters and targets need to be smaller than that of usize (very + // unlikely to be the case, but anyhow). + assert!(size_of::>() <= size_of::()); + assert!(size_of::>() <= size_of::()); + + // ---------------------------- + // based on the requirements of [`sp_npos_elections::Assignment::try_normalize`]. + let max_vote: usize = as CompactSolution>::LIMIT; + + // 1. Maximum sum of [ChainAccuracy; 16] must fit into `UpperOf`.. + let maximum_chain_accuracy: Vec>> = + (0..max_vote).map(|_| >::one().deconstruct().into()).collect(); + let _: UpperOf> = maximum_chain_accuracy + .iter() + .fold(Zero::zero(), |acc, x| acc.checked_add(x).unwrap()); + + // 2. Maximum sum of [CompactAccuracy; 16] must fit into `UpperOf`. + let maximum_chain_accuracy: Vec>> = + (0..max_vote).map(|_| >::one().deconstruct().into()).collect(); + let _: UpperOf> = maximum_chain_accuracy + .iter() + .fold(Zero::zero(), |acc, x| acc.checked_add(x).unwrap()); + } + } + + #[pallet::call] + impl Pallet + where + ExtendedBalance: From>>, + ExtendedBalance: From>>, + { + /// Submit a solution for the unsigned phase. + /// + /// The dispatch origin fo this call must be __none__. + /// + /// This submission is checked on the fly, thus it is likely yo be more limited and smaller. + /// Moreover, this unsigned solution is only validated when submitted to the pool from the + /// local process. Effectively, this means that only active validators can submit this + /// transaction when authoring a block. + /// + /// To prevent any incorrect solution (and thus wasted time/weight), this transaction will + /// panic if the solution submitted by the validator is invalid, effectively putting their + /// authoring reward at risk. + /// + /// No deposit or reward is associated with this. + #[pallet::weight(T::WeightInfo::submit_unsigned( + witness.voters, + witness.targets, + solution.compact.voter_count() as u32, + solution.compact.unique_targets().len() as u32 + ))] + pub fn submit_unsigned( + origin: OriginFor, + solution: RawSolution>, + witness: SolutionSize, + ) -> DispatchResultWithPostInfo { + ensure_none(origin)?; + let error_message = "Invalid unsigned submission must produce invalid block and \ + deprive validator from their authoring reward."; + + // check phase and score. + // NOTE: since we do this in pre-dispatch, we can just ignore it here. + Self::unsigned_pre_dispatch_checks(&solution).expect(error_message); + + // ensure witness was correct. + let RoundSnapshotMetadata { voters_len, targets_len } = + Self::snapshot_metadata().expect(error_message); + + // NOTE: we are asserting, not `ensure`ing -- we want to panic here. + assert!(voters_len as u32 == witness.voters, error_message); + assert!(targets_len as u32 == witness.targets, error_message); + + let ready = + Self::feasibility_check(solution, ElectionCompute::Unsigned).expect(error_message); + + // store the newly received solution. + log!(info, "queued unsigned solution with score {:?}", ready.score); + >::put(ready); + Self::deposit_event(Event::SolutionStored(ElectionCompute::Unsigned)); + + Ok(None.into()) + } + } + + #[pallet::event] + #[pallet::metadata(::AccountId = "AccountId")] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event + where + ExtendedBalance: From>>, + ExtendedBalance: From>>, + { + /// A solution was stored with the given compute. + /// + /// If the solution is signed, this means that it hasn't yet been processed. If the + /// solution is unsigned, this means that it has also been processed. + SolutionStored(ElectionCompute), + /// The election has been finalized, with `Some` of the given computation, or else if the + /// election failed, `None`. + ElectionFinalized(Option), + /// An account has been rewarded for their signed submission being finalized. + Rewarded(::AccountId), + /// An account has been slashed for submitting an invalid signed submission. + Slashed(::AccountId), + /// The signed phase of the given round has started. + SignedPhaseStarted(u32), + /// The unsigned phase of the given round has started. + UnsignedPhaseStarted(u32), + } + + #[pallet::error] + pub enum Error { + /// Submission was too early. + EarlySubmission, + /// Wrong number of winners presented. + WrongWinnerCount, + /// Submission was too weak, score-wise. + WeakSubmission, + /// The queue was full, and the solution was not better than any of the existing ones. + QueueFull, + /// The origin failed to pay the deposit. + CannotPayDeposit, + /// witness data to dispatchable is invalid. + InvalidWitness, + /// The signed submission consumes too much weight + TooMuchWeight, + } + + #[pallet::origin] + pub struct Origin(PhantomData); + + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet + where + ExtendedBalance: From>>, + ExtendedBalance: From>>, + { + type Call = Call; + fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity { + if let Call::submit_unsigned(solution, _) = call { + // discard solution not coming from the local OCW. + match source { + TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ } + _ => { + return InvalidTransaction::Call.into(); + } + } + + let _ = Self::unsigned_pre_dispatch_checks(solution) + .map_err(|err| { + log!(error, "unsigned transaction validation failed due to {:?}", err); + err + }) + .map_err(dispatch_error_to_invalid)?; + + ValidTransaction::with_tag_prefix("OffchainElection") + // The higher the score[0], the better a solution is. + .priority( + T::UnsignedPriority::get() + .saturating_add(solution.score[0].saturated_into()), + ) + // used to deduplicate unsigned solutions: each validator should produce one + // solution per round at most, and solutions are not propagate. + .and_provides(solution.round) + // transaction should stay in the pool for the duration of the unsigned phase. + .longevity(T::UnsignedPhase::get().saturated_into::()) + // We don't propagate this. This can never the validated at a remote node. + .propagate(false) + .build() + } else { + InvalidTransaction::Call.into() + } + } + + fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> { + if let Call::submit_unsigned(solution, _) = call { + Self::unsigned_pre_dispatch_checks(solution) + .map_err(dispatch_error_to_invalid) + .map_err(Into::into) + } else { + Err(InvalidTransaction::Call.into()) + } + } + } + + #[pallet::type_value] + pub fn DefaultForRound() -> u32 { + 1 + } + + /// Internal counter for the number of rounds. + /// + /// This is useful for de-duplication of transactions submitted to the pool, and general + /// diagnostics of the module. + /// + /// This is merely incremented once per every time that an upstream `elect` is called. + #[pallet::storage] + #[pallet::getter(fn round)] + pub type Round = StorageValue<_, u32, ValueQuery, DefaultForRound>; + + /// Current phase. + #[pallet::storage] + #[pallet::getter(fn current_phase)] + pub type CurrentPhase = StorageValue<_, Phase, ValueQuery>; + + /// Current best solution, signed or unsigned. + #[pallet::storage] + #[pallet::getter(fn queued_solution)] + pub type QueuedSolution = StorageValue<_, ReadySolution>; + + /// Snapshot data of the round. + /// + /// This is created at the beginning of the signed phase and cleared upon calling `elect`. + #[pallet::storage] + #[pallet::getter(fn snapshot)] + pub type Snapshot = StorageValue<_, RoundSnapshot>; + + /// Desired number of targets to elect for this round. + /// + /// Only exists when [`Snapshot`] is present. + #[pallet::storage] + #[pallet::getter(fn desired_targets)] + pub type DesiredTargets = StorageValue<_, u32>; + + /// The metadata of the [`RoundSnapshot`] + /// + /// Only exists when [`Snapshot`] is present. + #[pallet::storage] + #[pallet::getter(fn snapshot_metadata)] + pub type SnapshotMetadata = StorageValue<_, RoundSnapshotMetadata>; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData); +} + +impl Pallet +where + ExtendedBalance: From>>, + ExtendedBalance: From>>, +{ + /// Logic for `::on_initialize` when signed phase is being opened. + /// + /// This is decoupled for easy weight calculation. + pub fn on_initialize_open_signed() { + >::put(Phase::Signed); + Self::create_snapshot(); + Self::deposit_event(Event::SignedPhaseStarted(Self::round())); + } + + /// Logic for `>::on_initialize` when unsigned phase is being opened. + /// + /// This is decoupled for easy weight calculation. Note that the default weight benchmark of + /// this function will assume an empty signed queue for `finalize_signed_phase`. + pub fn on_initialize_open_unsigned( + need_snapshot: bool, + enabled: bool, + now: T::BlockNumber, + ) { + if need_snapshot { + // if not being followed by a signed phase, then create the snapshots. + debug_assert!(Self::snapshot().is_none()); + Self::create_snapshot(); + } + + // for now always start the unsigned phase. + >::put(Phase::Unsigned((enabled, now))); + Self::deposit_event(Event::UnsignedPhaseStarted(Self::round())); + } + + /// Creates the snapshot. Writes new data to: + /// + /// 1. [`SnapshotMetadata`] + /// 2. [`RoundSnapshot`] + /// 3. [`DesiredTargets`] + pub fn create_snapshot() { + // if any of them don't exist, create all of them. This is a bit conservative. + let targets = T::DataProvider::targets(); + let voters = T::DataProvider::voters(); + let desired_targets = T::DataProvider::desired_targets(); + + >::put(RoundSnapshotMetadata { + voters_len: voters.len() as u32, + targets_len: targets.len() as u32, + }); + >::put(desired_targets); + >::put(RoundSnapshot { voters, targets }); + } + + /// Checks the feasibility of a solution. + /// + /// This checks the solution for the following: + /// + /// 0. **all** of the used indices must be correct. + /// 1. present correct number of winners. + /// 2. any assignment is checked to match with [Snapshot::voters]. + /// 3. for each assignment, the check of `ElectionDataProvider` is also examined. + /// 4. the claimed score is valid. + fn feasibility_check( + solution: RawSolution>, + compute: ElectionCompute, + ) -> Result, FeasibilityError> { + let RawSolution { compact, score, round } = solution; + + // first, check round. + ensure!(Self::round() == round, FeasibilityError::InvalidRound); + + // winners are not directly encoded in the solution. + let winners = compact.unique_targets(); + + let desired_targets = + Self::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?; + + // NOTE: this is a bit of duplicate, but we keep it around for veracity. The unsigned path + // already checked this in `unsigned_per_dispatch_checks`. The signed path *could* check it + // upon arrival. + ensure!(winners.len() as u32 == desired_targets, FeasibilityError::WrongWinnerCount,); + + // read the entire snapshot. + let RoundSnapshot { voters: snapshot_voters, targets: snapshot_targets } = + Self::snapshot().ok_or(FeasibilityError::SnapshotUnavailable)?; + + // ----- Start building. First, we need some closures. + let cache = helpers::generate_voter_cache::(&snapshot_voters); + let voter_at = helpers::voter_at_fn::(&snapshot_voters); + let target_at = helpers::target_at_fn::(&snapshot_targets); + let voter_index = helpers::voter_index_fn_usize::(&cache); + + // first, make sure that all the winners are sane. + let winners = winners + .into_iter() + .map(|i| target_at(i).ok_or(FeasibilityError::InvalidWinner)) + .collect::, FeasibilityError>>()?; + + // Then convert compact -> Assignment. This will fail if any of the indices are gibberish. + // that winner indices are already checked. + let assignments = compact + .into_assignment(voter_at, target_at) + .map_err::(Into::into)?; + + // Ensure that assignments is correct. + let _ = assignments + .iter() + .map(|ref assignment| { + // check that assignment.who is actually a voter (defensive-only). + // NOTE: while using the index map from `voter_index` is better than a blind linear + // search, this *still* has room for optimization. Note that we had the index when + // we did `compact -> assignment` and we lost it. Ideal is to keep the index around. + + // defensive-only: must exist in the snapshot. + let snapshot_index = + voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?; + // defensive-only: index comes from the snapshot, must exist. + let (_voter, _stake, targets) = + snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?; + + // check that all of the targets are valid based on the snapshot. + if assignment.distribution.iter().any(|(d, _)| !targets.contains(d)) { + return Err(FeasibilityError::InvalidVote); + } + Ok(()) + }) + .collect::>()?; + + // ----- Start building support. First, we need one more closure. + let stake_of = helpers::stake_of_fn::(&snapshot_voters, &cache); + + // This might fail if the normalization fails. Very unlikely. See `integrity_test`. + let staked_assignments = assignment_ratio_to_staked_normalized(assignments, stake_of) + .map_err::(Into::into)?; + // This might fail if one of the voter edges is pointing to a non-winner, which is not + // really possible anymore because all the winners come from the same `compact`. + let supports = sp_npos_elections::to_supports(&winners, &staked_assignments) + .map_err::(Into::into)?; + + // Finally, check that the claimed score was indeed correct. + let known_score = (&supports).evaluate(); + ensure!(known_score == score, FeasibilityError::InvalidScore); + + Ok(ReadySolution { supports, compute, score }) + } + + /// Perform the tasks to be done after a new `elect` has been triggered: + /// + /// 1. Increment round. + /// 2. Change phase to [`Phase::Off`] + /// 3. Clear all snapshot data. + fn post_elect() { + // inc round + >::mutate(|r| *r = *r + 1); + + // change phase + >::put(Phase::Off); + + // kill snapshots + >::kill(); + >::kill(); + >::kill(); + } + + /// On-chain fallback of election. + fn onchain_fallback() -> Result, ElectionError> + where + ExtendedBalance: From<::Inner>, + { + > as ElectionProvider< + T::AccountId, + T::BlockNumber, + >>::elect() + .map_err(Into::into) + } + + fn do_elect() -> Result, ElectionError> { + // NOTE: SignedSubmission is guaranteed to be drained by the end of the signed phase too, + // thus no need for a manual cleanup: + // TODO + // debug_assert!(Self::signed_submissions().is_empty()); + >::take() + .map_or_else( + || match T::Fallback::get() { + FallbackStrategy::OnChain => Self::onchain_fallback() + .map(|r| (r, ElectionCompute::OnChain)) + .map_err(Into::into), + FallbackStrategy::Nothing => Err(ElectionError::NoFallbackConfigured), + }, + |ReadySolution { supports, compute, .. }| Ok((supports, compute)), + ) + .map(|(supports, compute)| { + Self::deposit_event(Event::ElectionFinalized(Some(compute))); + log!(info, "Finalized election round with compute {:?}.", compute); + supports + }) + .map_err(|err| { + Self::deposit_event(Event::ElectionFinalized(None)); + log!(warn, "Failed to finalize election round. reason {:?}", err); + err + }) + } +} + +impl ElectionProvider for Pallet +where + ExtendedBalance: From>>, + ExtendedBalance: From>>, +{ + type Error = ElectionError; + type DataProvider = T::DataProvider; + + fn elect() -> Result, Self::Error> { + let outcome = Self::do_elect(); + // cleanup. + Self::post_elect(); + outcome + } +} + +/// convert a DispatchError to a custom InvalidTransaction with the inner code being the error +/// number. +pub fn dispatch_error_to_invalid(error: DispatchError) -> InvalidTransaction { + let error_number = match error { + DispatchError::Module { error, .. } => error, + _ => 0, + }; + InvalidTransaction::Custom(error_number) +} + +#[cfg(test)] +mod feasibility_check { + //! All of the tests here should be dedicated to only testing the feasibility check and nothing + //! more. The best way to audit and review these tests is to try and come up with a solution + //! that is invalid, but gets through the system as valid. + + use super::{mock::*, *}; + + const COMPUTE: ElectionCompute = ElectionCompute::OnChain; + + #[test] + fn snapshot_is_there() { + ExtBuilder::default().build_and_execute(|| { + roll_to(::get() - ::get() - ::get()); + assert!(TwoPhase::current_phase().is_signed()); + let solution = raw_solution(); + + // for whatever reason it might be: + >::kill(); + + assert_noop!( + TwoPhase::feasibility_check(solution, COMPUTE), + FeasibilityError::SnapshotUnavailable + ); + }) + } + + #[test] + fn round() { + ExtBuilder::default().build_and_execute(|| { + roll_to(::get() - ::get() - ::get()); + assert!(TwoPhase::current_phase().is_signed()); + + let mut solution = raw_solution(); + solution.round += 1; + assert_noop!( + TwoPhase::feasibility_check(solution, COMPUTE), + FeasibilityError::InvalidRound + ); + }) + } + + #[test] + fn desired_targets() { + ExtBuilder::default().desired_targets(8).build_and_execute(|| { + roll_to(::get() - ::get() - ::get()); + assert!(TwoPhase::current_phase().is_signed()); + + let solution = raw_solution(); + + assert_eq!(solution.compact.unique_targets().len(), 4); + assert_eq!(TwoPhase::desired_targets().unwrap(), 8); + + assert_noop!( + TwoPhase::feasibility_check(solution, COMPUTE), + FeasibilityError::WrongWinnerCount + ); + }) + } + + #[test] + fn winner_indices() { + ExtBuilder::default().desired_targets(2).build_and_execute(|| { + roll_to(::get() - ::get() - ::get()); + assert!(TwoPhase::current_phase().is_signed()); + + let mut solution = raw_solution(); + assert_eq!(TwoPhase::snapshot().unwrap().targets.len(), 4); + // ----------------------------------------------------^^ valid range is [0..3]. + + // swap all votes from 3 to 4. This will ensure that the number of unique winners + // will still be 4, but one of the indices will be gibberish. Requirement is to make + // sure 3 a winner, which we don't do here. + solution + .compact + .votes1 + .iter_mut() + .filter(|(_, t)| *t == 3u16) + .for_each(|(_, t)| *t += 1); + solution.compact.votes2.iter_mut().for_each(|(_, (t0, _), t1)| { + if *t0 == 3u16 { + *t0 += 1 + }; + if *t1 == 3u16 { + *t1 += 1 + }; + }); + assert_noop!( + TwoPhase::feasibility_check(solution, COMPUTE), + FeasibilityError::InvalidWinner + ); + }) + } + + #[test] + fn voter_indices() { + // should be caught in `compact.into_assignment`. + ExtBuilder::default().desired_targets(2).build_and_execute(|| { + roll_to(::get() - ::get() - ::get()); + assert!(TwoPhase::current_phase().is_signed()); + + let mut solution = raw_solution(); + assert_eq!(TwoPhase::snapshot().unwrap().voters.len(), 8); + // ----------------------------------------------------^^ valid range is [0..7]. + + // check that there is a index 7 in votes1, and flip to 8. + assert!( + solution + .compact + .votes1 + .iter_mut() + .filter(|(v, _)| *v == 7u32) + .map(|(v, _)| *v = 8) + .count() > 0 + ); + assert_noop!( + TwoPhase::feasibility_check(solution, COMPUTE), + FeasibilityError::NposElection(sp_npos_elections::Error::CompactInvalidIndex), + ); + }) + } + + #[test] + fn voter_votes() { + ExtBuilder::default().desired_targets(2).build_and_execute(|| { + roll_to(::get() - ::get() - ::get()); + assert!(TwoPhase::current_phase().is_signed()); + + let mut solution = raw_solution(); + assert_eq!(TwoPhase::snapshot().unwrap().voters.len(), 8); + // ----------------------------------------------------^^ valid range is [0..7]. + + // first, check that voter at index 7 (40) actually voted for 3 (40) -- this is self + // vote. Then, change the vote to 2 (30). + assert_eq!( + solution + .compact + .votes1 + .iter_mut() + .filter(|(v, t)| *v == 7 && *t == 3) + .map(|(_, t)| *t = 2) + .count(), + 1, + ); + assert_noop!( + TwoPhase::feasibility_check(solution, COMPUTE), + FeasibilityError::InvalidVote, + ); + }) + } + + #[test] + fn score() { + ExtBuilder::default().desired_targets(2).build_and_execute(|| { + roll_to(::get() - ::get() - ::get()); + assert!(TwoPhase::current_phase().is_signed()); + + let mut solution = raw_solution(); + assert_eq!(TwoPhase::snapshot().unwrap().voters.len(), 8); + + // simply faff with the score. + solution.score[0] += 1; + + assert_noop!( + TwoPhase::feasibility_check(solution, COMPUTE), + FeasibilityError::InvalidScore, + ); + }) + } +} + +#[cfg(test)] +mod tests { + use super::{mock::*, Event, *}; + use sp_election_providers::ElectionProvider; + use sp_npos_elections::Support; + + #[test] + fn phase_rotation_works() { + ExtBuilder::default().build_and_execute(|| { + // 0 ------- 15 ------- 25 ------- 30 ------- ------- 45 ------- 55 ------- 60 + // | | | | + // Signed Unsigned Signed Unsigned + + assert_eq!(System::block_number(), 0); + assert_eq!(TwoPhase::current_phase(), Phase::Off); + assert_eq!(TwoPhase::round(), 1); + + roll_to(4); + assert_eq!(TwoPhase::current_phase(), Phase::Off); + assert!(TwoPhase::snapshot().is_none()); + assert_eq!(TwoPhase::round(), 1); + + roll_to(15); + assert_eq!(TwoPhase::current_phase(), Phase::Signed); + assert_eq!(two_phase_events(), vec![Event::SignedPhaseStarted(1)]); + assert!(TwoPhase::snapshot().is_some()); + assert_eq!(TwoPhase::round(), 1); + + roll_to(24); + assert_eq!(TwoPhase::current_phase(), Phase::Signed); + assert!(TwoPhase::snapshot().is_some()); + assert_eq!(TwoPhase::round(), 1); + + roll_to(25); + assert_eq!(TwoPhase::current_phase(), Phase::Unsigned((true, 25))); + assert_eq!( + two_phase_events(), + vec![Event::SignedPhaseStarted(1), Event::UnsignedPhaseStarted(1)], + ); + assert!(TwoPhase::snapshot().is_some()); + + roll_to(29); + assert_eq!(TwoPhase::current_phase(), Phase::Unsigned((true, 25))); + assert!(TwoPhase::snapshot().is_some()); + + roll_to(30); + assert_eq!(TwoPhase::current_phase(), Phase::Unsigned((true, 25))); + assert!(TwoPhase::snapshot().is_some()); + + // we close when upstream tells us to elect. + roll_to(32); + assert_eq!(TwoPhase::current_phase(), Phase::Unsigned((true, 25))); + assert!(TwoPhase::snapshot().is_some()); + + TwoPhase::elect().unwrap(); + + assert!(TwoPhase::current_phase().is_off()); + assert!(TwoPhase::snapshot().is_none()); + assert_eq!(TwoPhase::round(), 2); + + roll_to(44); + assert!(TwoPhase::current_phase().is_off()); + + roll_to(45); + assert!(TwoPhase::current_phase().is_signed()); + + roll_to(55); + assert!(TwoPhase::current_phase().is_unsigned_open_at(55)); + }) + } + + #[test] + fn signed_phase_void() { + ExtBuilder::default().phases(0, 10).build_and_execute(|| { + roll_to(15); + assert!(TwoPhase::current_phase().is_off()); + + roll_to(19); + assert!(TwoPhase::current_phase().is_off()); + + roll_to(20); + assert!(TwoPhase::current_phase().is_unsigned_open_at(20)); + assert!(TwoPhase::snapshot().is_some()); + + roll_to(30); + assert!(TwoPhase::current_phase().is_unsigned_open_at(20)); + + TwoPhase::elect().unwrap(); + + assert!(TwoPhase::current_phase().is_off()); + assert!(TwoPhase::snapshot().is_none()); + }); + } + + #[test] + fn unsigned_phase_void() { + ExtBuilder::default().phases(10, 0).build_and_execute(|| { + roll_to(15); + assert!(TwoPhase::current_phase().is_off()); + + roll_to(19); + assert!(TwoPhase::current_phase().is_off()); + + roll_to(20); + assert!(TwoPhase::current_phase().is_signed()); + assert!(TwoPhase::snapshot().is_some()); + + roll_to(30); + assert!(TwoPhase::current_phase().is_signed()); + + let _ = TwoPhase::elect().unwrap(); + + assert!(TwoPhase::current_phase().is_off()); + assert!(TwoPhase::snapshot().is_none()); + }); + } + + #[test] + fn both_phases_void() { + ExtBuilder::default().phases(0, 0).build_and_execute(|| { + roll_to(15); + assert!(TwoPhase::current_phase().is_off()); + + roll_to(19); + assert!(TwoPhase::current_phase().is_off()); + + roll_to(20); + assert!(TwoPhase::current_phase().is_off()); + + roll_to(30); + assert!(TwoPhase::current_phase().is_off()); + + // this module is now only capable of doing on-chain backup. + let _ = TwoPhase::elect().unwrap(); + + assert!(TwoPhase::current_phase().is_off()); + }); + } + + #[test] + fn early_termination() { + // an early termination in the signed phase, with no queued solution. + ExtBuilder::default().build_and_execute(|| { + // signed phase started at block 15 and will end at 25. + roll_to(14); + assert_eq!(TwoPhase::current_phase(), Phase::Off); + + roll_to(15); + assert_eq!(two_phase_events(), vec![Event::SignedPhaseStarted(1)]); + assert_eq!(TwoPhase::current_phase(), Phase::Signed); + assert_eq!(TwoPhase::round(), 1); + + // an unexpected call to elect. + roll_to(20); + TwoPhase::elect().unwrap(); + + // we surely can't have any feasible solutions. This will cause an on-chain election. + assert_eq!( + two_phase_events(), + vec![ + Event::SignedPhaseStarted(1), + Event::ElectionFinalized(Some(ElectionCompute::OnChain)) + ], + ); + // all storage items must be cleared. + assert_eq!(TwoPhase::round(), 2); + assert!(TwoPhase::snapshot().is_none()); + assert!(TwoPhase::snapshot_metadata().is_none()); + assert!(TwoPhase::desired_targets().is_none()); + assert!(TwoPhase::queued_solution().is_none()); + }) + } + + #[test] + fn fallback_strategy_works() { + ExtBuilder::default().fallabck(FallbackStrategy::OnChain).build_and_execute(|| { + roll_to(15); + assert_eq!(TwoPhase::current_phase(), Phase::Signed); + + roll_to(25); + assert_eq!(TwoPhase::current_phase(), Phase::Unsigned((true, 25))); + + // zilch solutions thus far. + let supports = TwoPhase::elect().unwrap(); + + assert_eq!( + supports, + vec![ + (30, Support { total: 40, voters: vec![(2, 5), (4, 5), (30, 30)] }), + (40, Support { total: 60, voters: vec![(2, 5), (3, 10), (4, 5), (40, 40)] }) + ] + ) + }); + + ExtBuilder::default().fallabck(FallbackStrategy::Nothing).build_and_execute(|| { + roll_to(15); + assert_eq!(TwoPhase::current_phase(), Phase::Signed); + + roll_to(25); + assert_eq!(TwoPhase::current_phase(), Phase::Unsigned((true, 25))); + + // zilch solutions thus far. + assert_eq!(TwoPhase::elect().unwrap_err(), ElectionError::NoFallbackConfigured); + }) + } + + #[test] + fn number_of_voters_allowed_2sec_block() { + // Just a rough estimate with the substrate weights. + assert!(!MockWeightInfo::get()); + + let all_voters: u32 = 100_000; + let all_targets: u32 = 2_000; + let desired: u32 = 1_000; + let weight_with = |active| { + ::WeightInfo::submit_unsigned( + all_voters, + all_targets, + active, + desired, + ) + }; + + let mut active = 1; + while weight_with(active) + <= ::BlockWeights::get().max_block + { + active += 1; + } + + println!("can support {} voters to yield a weight of {}", active, weight_with(active)); + } +} diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs new file mode 100644 index 0000000000000..71102f3e6e526 --- /dev/null +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -0,0 +1,386 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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. + +use super::*; +use crate as two_phase; +pub use frame_support::{assert_noop, assert_ok}; +use frame_support::{ + parameter_types, + traits::{Hooks}, + weights::Weight, +}; +use parking_lot::RwLock; +use sp_core::{ + offchain::{ + testing::{PoolState, TestOffchainExt, TestTransactionPoolExt}, + OffchainExt, TransactionPoolExt, + }, + H256, +}; +use sp_election_providers::ElectionDataProvider; +use sp_npos_elections::{ + assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, to_without_backing, + CompactSolution, ElectionResult, EvaluateSupport, +}; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, Block as BlockT, IdentityLookup}, + PerU16, +}; +use std::sync::Arc; + +pub type Block = sp_runtime::generic::Block; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; + +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Module, Call, Event}, + Balances: pallet_balances::{Module, Call, Event, Config}, + TwoPhase: two_phase::{Module, Call, Event}, + } +); + +pub(crate) type Balance = u64; +pub(crate) type AccountId = u64; + +sp_npos_elections::generate_solution_type!( + #[compact] + pub struct TestCompact::(16) +); + +/// All events of this pallet. +pub(crate) fn two_phase_events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let Event::two_phase(inner) = e { Some(inner) } else { None }) + .collect::>() +} + +/// To from `now` to block `n`. +pub fn roll_to(n: u64) { + let now = System::block_number(); + for i in now + 1..=n { + System::set_block_number(i); + TwoPhase::on_initialize(i); + } +} + +pub fn roll_to_with_ocw(n: u64) { + let now = System::block_number(); + for i in now + 1..=n { + System::set_block_number(i); + TwoPhase::on_initialize(i); + TwoPhase::offchain_worker(i); + } +} + +/// Get the free and reserved balance of some account. +pub fn balances(who: &AccountId) -> (Balance, Balance) { + (Balances::free_balance(who), Balances::reserved_balance(who)) +} + +/// Spit out a verifiable raw solution. +/// +/// This is a good example of what an offchain miner would do. +pub fn raw_solution() -> RawSolution> { + let RoundSnapshot { voters, targets } = TwoPhase::snapshot().unwrap(); + let desired_targets = TwoPhase::desired_targets().unwrap(); + + // closures + let cache = helpers::generate_voter_cache::(&voters); + let voter_index = helpers::voter_index_fn_linear::(&voters); + let target_index = helpers::target_index_fn_linear::(&targets); + let stake_of = helpers::stake_of_fn::(&voters, &cache); + + let ElectionResult { winners, assignments } = seq_phragmen::<_, CompactAccuracyOf>( + desired_targets as usize, + targets.clone(), + voters.clone(), + None, + ) + .unwrap(); + + let winners = to_without_backing(winners); + + let score = { + let staked = assignment_ratio_to_staked_normalized(assignments.clone(), &stake_of).unwrap(); + to_supports(&winners, &staked).unwrap().evaluate() + }; + let compact = + >::from_assignment(assignments, &voter_index, &target_index).unwrap(); + + let round = TwoPhase::round(); + RawSolution { compact, score, round } +} + +pub fn witness() -> SolutionSize { + TwoPhase::snapshot() + .map(|snap| SolutionSize { + voters: snap.voters.len() as u32, + targets: snap.targets.len() as u32, + }) + .unwrap_or_default() +} + +impl frame_system::Config for Runtime { + type SS58Prefix = (); + type BaseCallFilter = (); + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Call = Call; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = (); + type DbWeight = (); + type BlockLength = (); + type BlockWeights = BlockWeights; + type Version = (); + type PalletInfo = (); + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); +} + +const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); +parameter_types! { + pub const ExistentialDeposit: u64 = 1; + pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights + ::with_sensible_defaults(2 * frame_support::weights::constants::WEIGHT_PER_SECOND, NORMAL_DISPATCH_RATIO); +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type MaxLocks = (); + type WeightInfo = (); +} + +parameter_types! { + pub static Targets: Vec = vec![10, 20, 30, 40]; + pub static Voters: Vec<(AccountId, VoteWeight, Vec)> = vec![ + (1, 10, vec![10, 20]), + (2, 10, vec![30, 40]), + (3, 10, vec![40]), + (4, 10, vec![10, 20, 30, 40]), + // self votes. + (10, 10, vec![10]), + (20, 20, vec![20]), + (30, 30, vec![30]), + (40, 40, vec![40]), + ]; + + pub static Fallback: FallbackStrategy = FallbackStrategy::OnChain; + pub static DesiredTargets: u32 = 2; + pub static SignedPhase: u64 = 10; + pub static UnsignedPhase: u64 = 5; + pub static MaxSignedSubmissions: u32 = 5; + + pub static MinerMaxIterations: u32 = 5; + pub static UnsignedPriority: u64 = 100; + pub static SolutionImprovementThreshold: Perbill = Perbill::zero(); + pub static MinerMaxWeight: Weight = BlockWeights::get().max_block; + pub static MockWeightInfo: bool = false; + + + pub static EpochLength: u64 = 30; +} + +// Hopefully this won't be too much of a hassle to maintain. +pub struct DualMockWeightInfo; +impl two_phase::weights::WeightInfo for DualMockWeightInfo { + fn on_initialize_nothing() -> Weight { + if MockWeightInfo::get() { + Zero::zero() + } else { + <() as two_phase::weights::WeightInfo>::on_initialize_nothing() + } + } + fn on_initialize_open_signed() -> Weight { + if MockWeightInfo::get() { + Zero::zero() + } else { + <() as two_phase::weights::WeightInfo>::on_initialize_open_signed() + } + } + fn on_initialize_open_unsigned_with_snapshot() -> Weight { + if MockWeightInfo::get() { + Zero::zero() + } else { + <() as two_phase::weights::WeightInfo>::on_initialize_open_unsigned_with_snapshot() + } + } + fn on_initialize_open_unsigned_without_snapshot() -> Weight { + if MockWeightInfo::get() { + Zero::zero() + } else { + <() as two_phase::weights::WeightInfo>::on_initialize_open_unsigned_without_snapshot() + } + } + fn submit_unsigned(v: u32, t: u32, a: u32, d: u32) -> Weight { + if MockWeightInfo::get() { + // 10 base + // 5 per edge. + (10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight)) + } else { + <() as two_phase::weights::WeightInfo>::submit_unsigned(v, t, a, d) + } + } + fn feasibility_check(v: u32, t: u32, a: u32, d: u32) -> Weight { + if MockWeightInfo::get() { + // 10 base + // 5 per edge. + (10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight)) + } else { + <() as two_phase::weights::WeightInfo>::feasibility_check(v, t, a, d) + } + } +} + +impl crate::Config for Runtime { + type Event = Event; + type Currency = Balances; + type SignedPhase = SignedPhase; + type UnsignedPhase = UnsignedPhase; + type SolutionImprovementThreshold = SolutionImprovementThreshold; + type MinerMaxIterations = MinerMaxIterations; + type MinerMaxWeight = MinerMaxWeight; + type UnsignedPriority = UnsignedPriority; + type DataProvider = StakingMock; + type WeightInfo = DualMockWeightInfo; + type BenchmarkingConfig = (); + type OnChainAccuracy = Perbill; + type Fallback = Fallback; + type CompactSolution = TestCompact; +} + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + Call: From, +{ + type OverarchingCall = Call; + type Extrinsic = Extrinsic; +} + +pub type Extrinsic = sp_runtime::testing::TestXt; + +#[derive(Default)] +pub struct ExtBuilder {} + +pub struct StakingMock; +impl ElectionDataProvider for StakingMock { + fn targets() -> Vec { + Targets::get() + } + fn voters() -> Vec<(AccountId, VoteWeight, Vec)> { + Voters::get() + } + fn desired_targets() -> u32 { + DesiredTargets::get() + } + fn next_election_prediction(now: u64) -> u64 { + now + EpochLength::get() - now % EpochLength::get() + } +} + +impl ExtBuilder { + pub fn unsigned_priority(self, p: u64) -> Self { + ::set(p); + self + } + pub fn solution_improvement_threshold(self, p: Perbill) -> Self { + ::set(p); + self + } + pub fn phases(self, signed: u64, unsigned: u64) -> Self { + ::set(signed); + ::set(unsigned); + self + } + pub fn fallabck(self, fallback: FallbackStrategy) -> Self { + ::set(fallback); + self + } + pub fn miner_weight(self, weight: Weight) -> Self { + ::set(weight); + self + } + pub fn mock_weight_info(self, mock: bool) -> Self { + ::set(mock); + self + } + pub fn desired_targets(self, t: u32) -> Self { + ::set(t); + self + } + pub fn add_voter(self, who: AccountId, stake: Balance, targets: Vec) -> Self { + VOTERS.with(|v| v.borrow_mut().push((who, stake, targets))); + self + } + pub fn build(self) -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + let mut storage = + frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let _ = pallet_balances::GenesisConfig:: { + balances: vec![ + // bunch of account for submitting stuff only. + (99, 100), + (999, 100), + (9999, 100), + ], + } + .assimilate_storage(&mut storage); + + sp_io::TestExternalities::from(storage) + } + + pub fn build_offchainify( + self, + iters: u32, + ) -> (sp_io::TestExternalities, Arc>) { + let mut ext = self.build(); + let (offchain, offchain_state) = TestOffchainExt::new(); + let (pool, pool_state) = TestTransactionPoolExt::new(); + + let mut seed = [0_u8; 32]; + seed[0..4].copy_from_slice(&iters.to_le_bytes()); + offchain_state.write().seed = seed; + + ext.register_extension(OffchainExt::new(offchain)); + ext.register_extension(TransactionPoolExt::new(pool)); + + (ext, pool_state) + } + + pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + self.build().execute_with(test) + } +} diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs new file mode 100644 index 0000000000000..e922dc32dfa34 --- /dev/null +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -0,0 +1,799 @@ +// This file is part of Substrate. + +// Copyright (C) 2020 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. + +//! The unsigned phase implementation. + +use crate::*; +use frame_support::dispatch::DispatchResult; +use frame_system::offchain::SubmitTransaction; +use sp_npos_elections::{seq_phragmen, CompactSolution, ElectionResult}; +use sp_runtime::{offchain::storage::StorageValueRef, traits::TrailingZeroInput}; +use sp_std::cmp::Ordering; + +/// Storage key used to store the persistent offchain worker status. +pub(crate) const OFFCHAIN_HEAD_DB: &[u8] = b"parity/multi-phase-unsigned-election/"; +/// The repeat threshold of the offchain worker. This means we won't run the offchain worker twice +/// within a window of 5 blocks. +pub(crate) const OFFCHAIN_REPEAT: u32 = 5; + +impl Pallet +where + ExtendedBalance: From>>, + ExtendedBalance: From>>, +{ + /// Min a new npos solution. + pub fn mine_solution( + iters: usize, + ) -> Result<(RawSolution>, SolutionSize), ElectionError> { + let RoundSnapshot { voters, targets } = + Self::snapshot().ok_or(ElectionError::SnapshotUnAvailable)?; + let desired_targets = Self::desired_targets().ok_or(ElectionError::SnapshotUnAvailable)?; + + seq_phragmen::<_, CompactAccuracyOf>( + desired_targets as usize, + targets, + voters, + Some((iters, 0)), + ) + .map_err(Into::into) + .and_then(|election_result| { + if election_result.winners.len() as u32 == desired_targets { + Ok(election_result) + } else { + Err(ElectionError::Feasibility(FeasibilityError::WrongWinnerCount)) + } + }) + .and_then(Self::prepare_election_result) + } + + /// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which + /// is ready to be submitted to the chain. + /// + /// Will always reduce the solution as well. + pub fn prepare_election_result( + election_result: ElectionResult>, + ) -> Result<(RawSolution>, SolutionSize), ElectionError> { + // storage items. Note: we have already read this from storage, they must be in cache. + let RoundSnapshot { voters, targets } = + Self::snapshot().ok_or(ElectionError::SnapshotUnAvailable)?; + let desired_targets = Self::desired_targets().ok_or(ElectionError::SnapshotUnAvailable)?; + + // closures. + let cache = helpers::generate_voter_cache::(&voters); + let voter_index = helpers::voter_index_fn::(&cache); + let target_index = helpers::target_index_fn_linear::(&targets); + let voter_at = helpers::voter_at_fn::(&voters); + let target_at = helpers::target_at_fn::(&targets); + let stake_of = helpers::stake_of_fn::(&voters, &cache); + + let ElectionResult { assignments, winners } = election_result; + + // convert to staked and reduce. + let mut staked = + sp_npos_elections::assignment_ratio_to_staked_normalized(assignments, &stake_of) + .map_err::(Into::into)?; + sp_npos_elections::reduce(&mut staked); + + // convert back to ration and make compact. + let ratio = sp_npos_elections::assignment_staked_to_ratio_normalized(staked)?; + let compact = >::from_assignment(ratio, &voter_index, &target_index)?; + + let size = SolutionSize { voters: voters.len() as u32, targets: targets.len() as u32 }; + let maximum_allowed_voters = Self::maximum_voter_for_weight::( + desired_targets, + size, + T::MinerMaxWeight::get(), + ); + log!( + debug, + "miner: current compact solution voters = {}, maximum_allowed = {}", + compact.voter_count(), + maximum_allowed_voters, + ); + let compact = Self::trim_compact(maximum_allowed_voters, compact, &voter_index)?; + + // re-calc score. + let winners = sp_npos_elections::to_without_backing(winners); + let score = compact.clone().score(&winners, stake_of, voter_at, target_at)?; + + let round = Self::round(); + Ok((RawSolution { compact, score, round }, size)) + } + + /// Get a random number of iterations to run the balancing in the OCW. + /// + /// Uses the offchain seed to generate a random number, maxed with `T::MinerMaxIterations`. + pub fn get_balancing_iters() -> usize { + match T::MinerMaxIterations::get() { + 0 => 0, + max @ _ => { + let seed = sp_io::offchain::random_seed(); + let random = ::decode(&mut TrailingZeroInput::new(seed.as_ref())) + .expect("input is padded with zeroes; qed") + % max.saturating_add(1); + random as usize + } + } + } + + /// Greedily reduce the size of the a solution to fit into the block, w.r.t. weight. + /// + /// The weight of the solution is foremost a function of the number of voters (i.e. + /// `compact.len()`). Aside from this, the other components of the weight are invariant. The + /// number of winners shall not be changed (otherwise the solution is invalid) and the + /// `ElectionSize` is merely a representation of the total number of stakers. + /// + /// Thus, we reside to stripping away some voters. This means only changing the `compact` + /// struct. + /// + /// Note that the solution is already computed, and the winners are elected based on the merit + /// of the entire stake in the system. Nonetheless, some of the voters will be removed further + /// down the line. + /// + /// Indeed, the score must be computed **after** this step. If this step reduces the score too + /// much, then the solution will be discarded. + pub fn trim_compact( + maximum_allowed_voters: u32, + mut compact: CompactOf, + nominator_index: FN, + ) -> Result, ElectionError> + where + for<'r> FN: Fn(&'r T::AccountId) -> Option>, + { + match compact.voter_count().checked_sub(maximum_allowed_voters as usize) { + Some(to_remove) if to_remove > 0 => { + // grab all voters and sort them by least stake. + let RoundSnapshot { voters, .. } = + Self::snapshot().ok_or(ElectionError::SnapshotUnAvailable)?; + let mut voters_sorted = voters + .into_iter() + .map(|(who, stake, _)| (who.clone(), stake)) + .collect::>(); + voters_sorted.sort_by_key(|(_, y)| *y); + + // start removing from the least stake. Iterate until we know enough have been + // removed. + let mut removed = 0; + for (maybe_index, _stake) in + voters_sorted.iter().map(|(who, stake)| (nominator_index(&who), stake)) + { + let index = maybe_index.ok_or(ElectionError::SnapshotUnAvailable)?; + if compact.remove_voter(index) { + removed += 1 + } + + if removed >= to_remove { + break; + } + } + + Ok(compact) + } + _ => { + // nada, return as-is + Ok(compact) + } + } + } + + /// Find the maximum `len` that a compact can have in order to fit into the block weight. + /// + /// This only returns a value between zero and `size.nominators`. + pub fn maximum_voter_for_weight( + desired_winners: u32, + size: SolutionSize, + max_weight: Weight, + ) -> u32 { + if size.voters < 1 { + return size.voters; + } + + let max_voters = size.voters.max(1); + let mut voters = max_voters; + + // helper closures. + let weight_with = |active_voters: u32| -> Weight { + W::submit_unsigned(size.voters, size.targets, active_voters, desired_winners) + }; + + let next_voters = |current_weight: Weight, voters: u32, step: u32| -> Result { + match current_weight.cmp(&max_weight) { + Ordering::Less => { + let next_voters = voters.checked_add(step); + match next_voters { + Some(voters) if voters < max_voters => Ok(voters), + _ => Err(()), + } + } + Ordering::Greater => voters.checked_sub(step).ok_or(()), + Ordering::Equal => Ok(voters), + } + }; + + // First binary-search the right amount of voters + let mut step = voters / 2; + let mut current_weight = weight_with(voters); + while step > 0 { + match next_voters(current_weight, voters, step) { + // proceed with the binary search + Ok(next) if next != voters => { + voters = next; + } + // we are out of bounds, break out of the loop. + Err(()) => { + break; + } + // we found the right value - early exit the function. + Ok(next) => return next, + } + step = step / 2; + current_weight = weight_with(voters); + } + + // Time to finish. We might have reduced less than expected due to rounding error. Increase + // one last time if we have any room left, the reduce until we are sure we are below limit. + while voters + 1 <= max_voters && weight_with(voters + 1) < max_weight { + voters += 1; + } + while voters.checked_sub(1).is_some() && weight_with(voters) > max_weight { + voters -= 1; + } + + debug_assert!( + weight_with(voters.min(size.voters)) <= max_weight, + "weight_with({}) <= {}", + voters.min(size.voters), + max_weight, + ); + voters.min(size.voters) + } + + /// Checks if an execution of the offchain worker is permitted at the given block number, or + /// not. + /// + /// This essentially makes sure that we don't run on previous blocks in case of a re-org, and we + /// don't run twice within a window of length [`OFFCHAIN_REPEAT`]. + /// + /// Returns `Ok(())` if offchain worker should happen, `Err(reason)` otherwise. + pub(crate) fn set_check_offchain_execution_status( + now: T::BlockNumber, + ) -> Result<(), &'static str> { + let storage = StorageValueRef::persistent(&OFFCHAIN_HEAD_DB); + let threshold = T::BlockNumber::from(OFFCHAIN_REPEAT); + + let mutate_stat = + storage.mutate::<_, &'static str, _>(|maybe_head: Option>| { + match maybe_head { + Some(Some(head)) if now < head => Err("fork."), + Some(Some(head)) if now >= head && now <= head + threshold => { + Err("recently executed.") + } + Some(Some(head)) if now > head + threshold => { + // we can run again now. Write the new head. + Ok(now) + } + _ => { + // value doesn't exists. Probably this node just booted up. Write, and run + Ok(now) + } + } + }); + + match mutate_stat { + // all good + Ok(Ok(_)) => Ok(()), + // failed to write. + Ok(Err(_)) => Err("failed to write to offchain db."), + // fork etc. + Err(why) => Err(why), + } + } + + /// Mine a new solution, and submit it back to the chain as an unsigned transaction. + pub(crate) fn mine_and_submit() -> Result<(), ElectionError> { + let balancing = Self::get_balancing_iters(); + let (raw_solution, witness) = Self::mine_solution(balancing)?; + + // submit the raw solution to the pool. + let call = Call::submit_unsigned(raw_solution, witness).into(); + + SubmitTransaction::>::submit_unsigned_transaction(call) + .map_err(|_| ElectionError::PoolSubmissionFailed) + } + + pub(crate) fn unsigned_pre_dispatch_checks( + solution: &RawSolution>, + ) -> DispatchResult { + // ensure solution is timely. Don't panic yet. This is a cheap check. + ensure!(Self::current_phase().is_unsigned_open(), Error::::EarlySubmission); + + // ensure correct number of winners. + ensure!( + Self::desired_targets().unwrap_or_default() + == solution.compact.unique_targets().len() as u32, + Error::::WrongWinnerCount, + ); + + // ensure score is being improved. Panic henceforth. + ensure!( + Self::queued_solution().map_or(true, |q: ReadySolution<_>| is_score_better::( + solution.score, + q.score, + T::SolutionImprovementThreshold::get() + )), + Error::::WeakSubmission + ); + + Ok(()) + } +} + +#[cfg(test)] +mod max_weight { + #![allow(unused_variables)] + use super::{mock::*, *}; + + struct TestWeight; + impl crate::weights::WeightInfo for TestWeight { + fn on_initialize_nothing() -> Weight { + unreachable!() + } + fn on_initialize_open_signed() -> Weight { + unreachable!() + } + fn on_initialize_open_unsigned_with_snapshot() -> Weight { + unreachable!() + } + fn on_initialize_open_unsigned_without_snapshot() -> Weight { + unreachable!() + } + fn submit_unsigned(v: u32, t: u32, a: u32, d: u32) -> Weight { + (0 * v + 0 * t + 1000 * a + 0 * d) as Weight + } + fn feasibility_check(v: u32, _t: u32, a: u32, d: u32) -> Weight { + unreachable!() + } + } + + #[test] + fn find_max_voter_binary_search_works() { + let w = SolutionSize { voters: 10, targets: 0 }; + + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 0), 0); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1), 0); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 999), 0); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1000), 1); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1001), 1); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1990), 1); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1999), 1); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2000), 2); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2001), 2); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2010), 2); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2990), 2); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2999), 2); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 3000), 3); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 3333), 3); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 5500), 5); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 7777), 7); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 9999), 9); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 10_000), 10); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 10_999), 10); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 11_000), 10); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 22_000), 10); + + let w = SolutionSize { voters: 1, targets: 0 }; + + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 0), 0); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1), 0); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 999), 0); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1000), 1); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1001), 1); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1990), 1); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1999), 1); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2000), 1); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2001), 1); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2010), 1); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 3333), 1); + + let w = SolutionSize { voters: 2, targets: 0 }; + + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 0), 0); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1), 0); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 999), 0); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1000), 1); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1001), 1); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1999), 1); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2000), 2); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2001), 2); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2010), 2); + assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 3333), 2); + } +} + +#[cfg(test)] +mod tests { + use super::{ + mock::{Origin, *}, + Call, *, + }; + use frame_support::{dispatch::Dispatchable, traits::OffchainWorker}; + use mock::Call as OuterCall; + use sp_election_providers::Assignment; + use sp_runtime::{traits::ValidateUnsigned, PerU16}; + + #[test] + fn validate_unsigned_retracts_wrong_phase() { + ExtBuilder::default().desired_targets(0).build_and_execute(|| { + let solution = RawSolution:: { score: [5, 0, 0], ..Default::default() }; + let call = Call::submit_unsigned(solution.clone(), witness()); + + // initial + assert_eq!(TwoPhase::current_phase(), Phase::Off); + assert!(matches!( + ::validate_unsigned(TransactionSource::Local, &call) + .unwrap_err(), + TransactionValidityError::Invalid(InvalidTransaction::Custom(0)) + )); + assert!(matches!( + ::pre_dispatch(&call).unwrap_err(), + TransactionValidityError::Invalid(InvalidTransaction::Custom(0)) + )); + + // signed + roll_to(15); + assert_eq!(TwoPhase::current_phase(), Phase::Signed); + assert!(matches!( + ::validate_unsigned(TransactionSource::Local, &call) + .unwrap_err(), + TransactionValidityError::Invalid(InvalidTransaction::Custom(0)) + )); + assert!(matches!( + ::pre_dispatch(&call).unwrap_err(), + TransactionValidityError::Invalid(InvalidTransaction::Custom(0)) + )); + + // unsigned + roll_to(25); + assert!(TwoPhase::current_phase().is_unsigned()); + + assert!(::validate_unsigned( + TransactionSource::Local, + &call + ) + .is_ok()); + assert!(::pre_dispatch(&call).is_ok()); + }) + } + + #[test] + fn validate_unsigned_retracts_low_score() { + ExtBuilder::default().desired_targets(0).build_and_execute(|| { + roll_to(25); + assert!(TwoPhase::current_phase().is_unsigned()); + + let solution = RawSolution:: { score: [5, 0, 0], ..Default::default() }; + let call = Call::submit_unsigned(solution.clone(), witness()); + + // initial + assert!(::validate_unsigned( + TransactionSource::Local, + &call + ) + .is_ok()); + assert!(::pre_dispatch(&call).is_ok()); + + // set a better score + let ready = ReadySolution { score: [10, 0, 0], ..Default::default() }; + >::put(ready); + + // won't work anymore. + assert!(matches!( + ::validate_unsigned(TransactionSource::Local, &call) + .unwrap_err(), + TransactionValidityError::Invalid(InvalidTransaction::Custom(2)) + )); + assert!(matches!( + ::pre_dispatch(&call).unwrap_err(), + TransactionValidityError::Invalid(InvalidTransaction::Custom(2)) + )); + }) + } + + #[test] + fn validate_unsigned_retracts_incorrect_winner_count() { + ExtBuilder::default().desired_targets(1).build_and_execute(|| { + roll_to(25); + assert!(TwoPhase::current_phase().is_unsigned()); + + let solution = RawSolution:: { score: [5, 0, 0], ..Default::default() }; + let call = Call::submit_unsigned(solution.clone(), witness()); + assert_eq!(solution.compact.unique_targets().len(), 0); + + // won't work anymore. + assert!(matches!( + ::validate_unsigned(TransactionSource::Local, &call) + .unwrap_err(), + TransactionValidityError::Invalid(InvalidTransaction::Custom(1)) + )); + }) + } + + #[test] + fn priority_is_set() { + ExtBuilder::default().unsigned_priority(20).desired_targets(0).build_and_execute(|| { + roll_to(25); + assert!(TwoPhase::current_phase().is_unsigned()); + + let solution = RawSolution:: { score: [5, 0, 0], ..Default::default() }; + let call = Call::submit_unsigned(solution.clone(), witness()); + + assert_eq!( + ::validate_unsigned(TransactionSource::Local, &call) + .unwrap() + .priority, + 25 + ); + }) + } + + #[test] + #[should_panic(expected = "Invalid unsigned submission must produce invalid block and \ + deprive validator from their authoring reward.: \ + DispatchError::Module { index: 0, error: 1, message: \ + Some(\"WrongWinnerCount\") }")] + fn unfeasible_solution_panics() { + ExtBuilder::default().build_and_execute(|| { + roll_to(25); + assert!(TwoPhase::current_phase().is_unsigned()); + + // This is in itself an invalid BS solution. + let solution = RawSolution:: { score: [5, 0, 0], ..Default::default() }; + let call = Call::submit_unsigned(solution.clone(), witness()); + let outer_call: OuterCall = call.into(); + let _ = outer_call.dispatch(Origin::none()); + }) + } + + #[test] + #[should_panic(expected = "Invalid unsigned submission must produce invalid block and \ + deprive validator from their authoring reward.")] + fn wrong_witness_panics() { + ExtBuilder::default().build_and_execute(|| { + roll_to(25); + assert!(TwoPhase::current_phase().is_unsigned()); + + // This solution is unfeasible as well, but we won't even get there. + let solution = RawSolution:: { score: [5, 0, 0], ..Default::default() }; + + let mut correct_witness = witness(); + correct_witness.voters += 1; + correct_witness.targets -= 1; + let call = Call::submit_unsigned(solution.clone(), correct_witness); + let outer_call: OuterCall = call.into(); + let _ = outer_call.dispatch(Origin::none()); + }) + } + + #[test] + fn miner_works() { + ExtBuilder::default().build_and_execute(|| { + roll_to(25); + assert!(TwoPhase::current_phase().is_unsigned()); + + // ensure we have snapshots in place. + assert!(TwoPhase::snapshot().is_some()); + assert_eq!(TwoPhase::desired_targets().unwrap(), 2); + + // mine seq_phragmen solution with 2 iters. + let (solution, witness) = TwoPhase::mine_solution(2).unwrap(); + + // ensure this solution is valid. + assert!(TwoPhase::queued_solution().is_none()); + assert_ok!(TwoPhase::submit_unsigned(Origin::none(), solution, witness)); + assert!(TwoPhase::queued_solution().is_some()); + }) + } + + #[test] + fn miner_trims_weight() { + ExtBuilder::default().miner_weight(100).mock_weight_info(true).build_and_execute(|| { + roll_to(25); + assert!(TwoPhase::current_phase().is_unsigned()); + + let (solution, witness) = TwoPhase::mine_solution(2).unwrap(); + let solution_weight = ::WeightInfo::submit_unsigned( + witness.voters, + witness.targets, + solution.compact.voter_count() as u32, + solution.compact.unique_targets().len() as u32, + ); + // default solution will have 5 edges (5 * 5 + 10) + assert_eq!(solution_weight, 35); + assert_eq!(solution.compact.voter_count(), 5); + + // now reduce the max weight + ::set(25); + + let (solution, witness) = TwoPhase::mine_solution(2).unwrap(); + let solution_weight = ::WeightInfo::submit_unsigned( + witness.voters, + witness.targets, + solution.compact.voter_count() as u32, + solution.compact.unique_targets().len() as u32, + ); + // default solution will have 5 edges (5 * 5 + 10) + assert_eq!(solution_weight, 25); + assert_eq!(solution.compact.voter_count(), 3); + }) + } + + #[test] + fn miner_will_not_submit_if_not_enough_winners() { + ExtBuilder::default().desired_targets(8).build_and_execute(|| { + roll_to(25); + assert!(TwoPhase::current_phase().is_unsigned()); + + // mine seq_phragmen solution with 2 iters. + assert_eq!( + TwoPhase::mine_solution(2).unwrap_err(), + ElectionError::Feasibility(FeasibilityError::WrongWinnerCount), + ); + }) + } + + #[test] + fn unsigned_per_dispatch_checks_can_only_submit_threshold_better() { + ExtBuilder::default() + .desired_targets(1) + .add_voter(7, 2, vec![10]) + .add_voter(8, 5, vec![10]) + .solution_improvement_threshold(Perbill::from_percent(50)) + .build_and_execute(|| { + roll_to(25); + assert!(TwoPhase::current_phase().is_unsigned()); + assert_eq!(TwoPhase::desired_targets().unwrap(), 1); + + // an initial solution + let result = ElectionResult { + // note: This second element of backing stake is not important here. + winners: vec![(10, 10)], + assignments: vec![Assignment { + who: 10, + distribution: vec![(10, PerU16::one())], + }], + }; + let (solution, witness) = TwoPhase::prepare_election_result(result).unwrap(); + assert_ok!(TwoPhase::unsigned_pre_dispatch_checks(&solution)); + assert_ok!(TwoPhase::submit_unsigned(Origin::none(), solution, witness)); + assert_eq!(TwoPhase::queued_solution().unwrap().score[0], 10); + + // trial 1: a solution who's score is only 2, i.e. 20% better in the first element. + let result = ElectionResult { + winners: vec![(10, 12)], + assignments: vec![ + Assignment { who: 10, distribution: vec![(10, PerU16::one())] }, + Assignment { + who: 7, + // note: this percent doesn't even matter, in compact it is 100%. + distribution: vec![(10, PerU16::one())], + }, + ], + }; + let (solution, _) = TwoPhase::prepare_election_result(result).unwrap(); + // 12 is not 50% more than 10 + assert_eq!(solution.score[0], 12); + assert_noop!( + TwoPhase::unsigned_pre_dispatch_checks(&solution), + Error::::WeakSubmission, + ); + // submitting this will actually panic. + + // trial 2: a solution who's score is only 7, i.e. 70% better in the first element. + let result = ElectionResult { + winners: vec![(10, 12)], + assignments: vec![ + Assignment { who: 10, distribution: vec![(10, PerU16::one())] }, + Assignment { who: 7, distribution: vec![(10, PerU16::one())] }, + Assignment { + who: 8, + // note: this percent doesn't even matter, in compact it is 100%. + distribution: vec![(10, PerU16::one())], + }, + ], + }; + let (solution, witness) = TwoPhase::prepare_election_result(result).unwrap(); + assert_eq!(solution.score[0], 17); + + // and it is fine + assert_ok!(TwoPhase::unsigned_pre_dispatch_checks(&solution)); + assert_ok!(TwoPhase::submit_unsigned(Origin::none(), solution, witness)); + }) + } + + #[test] + fn ocw_check_prevent_duplicate() { + let (mut ext, _) = ExtBuilder::default().build_offchainify(0); + ext.execute_with(|| { + roll_to(25); + assert!(TwoPhase::current_phase().is_unsigned()); + + // first execution -- okay. + assert!(TwoPhase::set_check_offchain_execution_status(25).is_ok()); + + // next block: rejected. + assert!(TwoPhase::set_check_offchain_execution_status(26).is_err()); + + // allowed after `OFFCHAIN_REPEAT` + assert!(TwoPhase::set_check_offchain_execution_status((26 + OFFCHAIN_REPEAT).into()) + .is_ok()); + + // a fork like situation: re-execute last 3. + assert!(TwoPhase::set_check_offchain_execution_status( + (26 + OFFCHAIN_REPEAT - 3).into() + ) + .is_err()); + assert!(TwoPhase::set_check_offchain_execution_status( + (26 + OFFCHAIN_REPEAT - 2).into() + ) + .is_err()); + assert!(TwoPhase::set_check_offchain_execution_status( + (26 + OFFCHAIN_REPEAT - 1).into() + ) + .is_err()); + }) + } + + #[test] + fn ocw_only_runs_when_signed_open_now() { + let (mut ext, pool) = ExtBuilder::default().build_offchainify(0); + ext.execute_with(|| { + roll_to(25); + assert_eq!(TwoPhase::current_phase(), Phase::Unsigned((true, 25))); + + // we must clear the offchain storage to ensure the offchain execution check doesn't get + // in the way. + let mut storage = StorageValueRef::persistent(&OFFCHAIN_HEAD_DB); + + TwoPhase::offchain_worker(24); + assert!(pool.read().transactions.len().is_zero()); + storage.clear(); + + TwoPhase::offchain_worker(26); + assert!(pool.read().transactions.len().is_zero()); + storage.clear(); + + // submits! + TwoPhase::offchain_worker(25); + assert!(!pool.read().transactions.len().is_zero()); + }) + } + + #[test] + fn ocw_can_submit_to_pool() { + let (mut ext, pool) = ExtBuilder::default().build_offchainify(0); + ext.execute_with(|| { + roll_to_with_ocw(25); + assert_eq!(TwoPhase::current_phase(), Phase::Unsigned((true, 25))); + // OCW must have submitted now + + let encoded = pool.read().transactions[0].clone(); + let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap(); + let call = extrinsic.call; + assert!(matches!(call, OuterCall::TwoPhase(Call::submit_unsigned(_, _)))); + }) + } +} diff --git a/frame/election-provider-multi-phase/src/weights.rs b/frame/election-provider-multi-phase/src/weights.rs new file mode 100644 index 0000000000000..6070b771593ce --- /dev/null +++ b/frame/election-provider-multi-phase/src/weights.rs @@ -0,0 +1,147 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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_two_phase_election_provider +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.1 +//! DATE: 2021-01-14, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// target/release/substrate +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_two_phase_election_provider +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/two-phase-election-provider/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![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_two_phase_election_provider. +pub trait WeightInfo { + fn on_initialize_nothing() -> Weight; + fn on_initialize_open_signed() -> Weight; + fn on_initialize_open_unsigned_without_snapshot() -> Weight; + fn on_initialize_open_unsigned_with_snapshot() -> Weight; + fn submit_unsigned(v: u32, t: u32, a: u32, d: u32) -> Weight; + fn feasibility_check(v: u32, t: u32, a: u32, d: u32) -> Weight; +} + +/// Weights for pallet_two_phase_election_provider using the Substrate node and recommended +/// hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + fn on_initialize_nothing() -> Weight { + (21_280_000 as Weight).saturating_add(T::DbWeight::get().reads(7 as Weight)) + } + fn on_initialize_open_signed() -> Weight { + (74_221_000 as Weight) + .saturating_add(T::DbWeight::get().reads(7 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + fn on_initialize_open_unsigned_with_snapshot() -> Weight { + (76_100_000 as Weight) + .saturating_add(T::DbWeight::get().reads(8 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + fn on_initialize_open_unsigned_without_snapshot() -> Weight { + (76_100_000 as Weight) + .saturating_add(T::DbWeight::get().reads(8 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + fn submit_unsigned(v: u32, _t: u32, a: u32, d: u32) -> Weight { + (0 as Weight) + // Standard Error: 21_000 + .saturating_add((2_606_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 21_000 + .saturating_add((11_405_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 108_000 + .saturating_add((2_651_000 as Weight).saturating_mul(d as Weight)) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn feasibility_check(v: u32, t: u32, a: u32, d: u32) -> Weight { + (0 as Weight) + // Standard Error: 12_000 + .saturating_add((2_788_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 41_000 + .saturating_add((601_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 12_000 + .saturating_add((9_722_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 61_000 + .saturating_add((3_706_000 as Weight).saturating_mul(d as Weight)) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn on_initialize_nothing() -> Weight { + (21_280_000 as Weight).saturating_add(RocksDbWeight::get().reads(7 as Weight)) + } + fn on_initialize_open_signed() -> Weight { + (74_221_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(7 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + fn on_initialize_open_unsigned_with_snapshot() -> Weight { + (76_100_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(8 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + fn on_initialize_open_unsigned_without_snapshot() -> Weight { + (76_100_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(8 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + fn submit_unsigned(v: u32, _t: u32, a: u32, d: u32) -> Weight { + (0 as Weight) + // Standard Error: 21_000 + .saturating_add((2_606_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 21_000 + .saturating_add((11_405_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 108_000 + .saturating_add((2_651_000 as Weight).saturating_mul(d as Weight)) + .saturating_add(RocksDbWeight::get().reads(6 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn feasibility_check(v: u32, t: u32, a: u32, d: u32) -> Weight { + (0 as Weight) + // Standard Error: 12_000 + .saturating_add((2_788_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 41_000 + .saturating_add((601_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 12_000 + .saturating_add((9_722_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 61_000 + .saturating_add((3_706_000 as Weight).saturating_mul(d as Weight)) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + } +} diff --git a/frame/grandpa/Cargo.toml b/frame/grandpa/Cargo.toml index c6a76de23e454..d12eb6060a1a9 100644 --- a/frame/grandpa/Cargo.toml +++ b/frame/grandpa/Cargo.toml @@ -38,6 +38,7 @@ pallet-offences = { version = "2.0.0", path = "../offences" } pallet-staking = { version = "2.0.0", path = "../staking" } pallet-staking-reward-curve = { version = "2.0.0", path = "../staking/reward-curve" } pallet-timestamp = { version = "2.0.0", path = "../timestamp" } +sp-election-providers = { version = "2.0.0", path = "../../primitives/election-providers" } [features] default = ["std"] diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index bf4ce5a519e7c..2c63424c5abd8 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -40,6 +40,7 @@ use sp_runtime::{ DigestItem, Perbill, }; use sp_staking::SessionIndex; +use sp_election_providers::onchain; impl_outer_origin! { pub enum Origin for Test {} @@ -194,6 +195,13 @@ parameter_types! { pub const StakingUnsignedPriority: u64 = u64::max_value() / 2; } +impl onchain::Config for Test { + type AccountId = ::AccountId; + type BlockNumber = ::BlockNumber; + type Accuracy = Perbill; + type DataProvider = Staking; +} + impl pallet_staking::Config for Test { type RewardRemainder = (); type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; @@ -216,6 +224,7 @@ impl pallet_staking::Config for Test { type MaxIterations = (); type MinSolutionScoreBump = (); type OffchainSolutionWeightLimit = (); + type ElectionProvider = onchain::OnChainSequentialPhragmen; type WeightInfo = (); } diff --git a/frame/offences/benchmarking/Cargo.toml b/frame/offences/benchmarking/Cargo.toml index 80492288d74bf..ede129ce77228 100644 --- a/frame/offences/benchmarking/Cargo.toml +++ b/frame/offences/benchmarking/Cargo.toml @@ -26,6 +26,7 @@ pallet-session = { version = "2.0.0", default-features = false, path = "../../se pallet-staking = { version = "2.0.0", default-features = false, features = ["runtime-benchmarks"], path = "../../staking" } sp-runtime = { version = "2.0.0", default-features = false, path = "../../../primitives/runtime" } sp-staking = { version = "2.0.0", default-features = false, path = "../../../primitives/staking" } +sp-election-providers = { version = "2.0.0", default-features = false, path = "../../../primitives/election-providers" } sp-std = { version = "2.0.0", default-features = false, path = "../../../primitives/std" } [dev-dependencies] @@ -50,6 +51,7 @@ std = [ "pallet-staking/std", "sp-runtime/std", "sp-staking/std", + "sp-election-providers/std", "sp-std/std", "codec/std", ] diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index 8e0bb361e15ce..3ea1bbb8ebd1d 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -29,7 +29,7 @@ use sp_runtime::{ traits::{IdentityLookup, Block as BlockT}, testing::{Header, UintAuthorityId}, }; - +use sp_election_providers::onchain; type AccountId = u64; type AccountIndex = u32; @@ -147,6 +147,13 @@ parameter_types! { pub type Extrinsic = sp_runtime::testing::TestXt; +impl onchain::Config for Test { + type AccountId = AccountId; + type BlockNumber = BlockNumber; + type Accuracy = Perbill; + type DataProvider = Staking; +} + impl pallet_staking::Config for Test { type Currency = Balances; type UnixTime = pallet_timestamp::Module; @@ -169,6 +176,7 @@ impl pallet_staking::Config for Test { type MaxIterations = (); type MinSolutionScoreBump = (); type OffchainSolutionWeightLimit = (); + type ElectionProvider = onchain::OnChainSequentialPhragmen; type WeightInfo = (); } diff --git a/frame/session/benchmarking/Cargo.toml b/frame/session/benchmarking/Cargo.toml index fc3099e1b95cb..061af4d28e47b 100644 --- a/frame/session/benchmarking/Cargo.toml +++ b/frame/session/benchmarking/Cargo.toml @@ -16,6 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] sp-std = { version = "2.0.0", default-features = false, path = "../../../primitives/std" } sp-session = { version = "2.0.0", default-features = false, path = "../../../primitives/session" } sp-runtime = { version = "2.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-election-providers = { version = "2.0.0", default-features = false, path = "../../../primitives/election-providers" } frame-system = { version = "2.0.0", default-features = false, path = "../../system" } frame-benchmarking = { version = "2.0.0", default-features = false, path = "../../benchmarking" } frame-support = { version = "2.0.0", default-features = false, path = "../../support" } @@ -37,6 +38,7 @@ default = ["std"] std = [ "sp-std/std", "sp-session/std", + "sp-election-providers/std", "sp-runtime/std", "frame-system/std", "frame-benchmarking/std", diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index 31593b3da54b3..9519b0bc79459 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -21,6 +21,7 @@ use sp_runtime::traits::IdentityLookup; use frame_support::{impl_outer_origin, impl_outer_dispatch, parameter_types}; +use sp_election_providers::onchain; type AccountId = u64; type AccountIndex = u32; @@ -147,13 +148,21 @@ parameter_types! { pub type Extrinsic = sp_runtime::testing::TestXt; -impl frame_system::offchain::SendTransactionTypes for Test where +impl frame_system::offchain::SendTransactionTypes for Test +where Call: From, { type OverarchingCall = Call; type Extrinsic = Extrinsic; } +impl onchain::Config for Test { + type AccountId = AccountId; + type BlockNumber = BlockNumber; + type Accuracy = sp_runtime::Perbill; + type DataProvider = Staking; +} + impl pallet_staking::Config for Test { type Currency = Balances; type UnixTime = pallet_timestamp::Module; @@ -176,6 +185,7 @@ impl pallet_staking::Config for Test { type MaxIterations = (); type MinSolutionScoreBump = (); type OffchainSolutionWeightLimit = (); + type ElectionProvider = onchain::OnChainSequentialPhragmen; type WeightInfo = (); } diff --git a/frame/session/src/lib.rs b/frame/session/src/lib.rs index 90eba3815a7a5..23ff157b9e813 100644 --- a/frame/session/src/lib.rs +++ b/frame/session/src/lib.rs @@ -158,21 +158,33 @@ impl< } impl< - BlockNumber: Rem + Sub + Zero + PartialOrd + Saturating + Clone, - Period: Get, - Offset: Get, -> EstimateNextSessionRotation for PeriodicSessions { + BlockNumber: Rem + + Sub + + Zero + + PartialOrd + + Saturating + + Clone, + Period: Get, + Offset: Get, + > EstimateNextSessionRotation for PeriodicSessions +{ + fn average_session_length() -> BlockNumber { + Period::get() + } + fn estimate_next_session_rotation(now: BlockNumber) -> Option { let offset = Offset::get(); let period = Period::get(); Some(if now > offset { - let block_after_last_session = (now.clone() - offset) % period.clone(); + let block_after_last_session = (now.clone() - offset.clone()) % period.clone(); if block_after_last_session > Zero::zero() { - now.saturating_add( - period.saturating_sub(block_after_last_session) - ) + now.saturating_add(period.saturating_sub(block_after_last_session)) } else { - now + // this branch happens when the session is already rotated or will rotate in this + // block (depending on being called before or after `session::on_initialize`). Here, + // we assume the latter, namely that this is called after `session::on_initialize`, + // and thus we add period to it as well. + now + period } } else { offset @@ -851,6 +863,10 @@ impl EstimateNextNewSession for Module { T::NextSessionRotation::estimate_next_session_rotation(now) } + fn average_session_length() -> T::BlockNumber { + T::NextSessionRotation::average_session_length() + } + fn weight(now: T::BlockNumber) -> Weight { T::NextSessionRotation::weight(now) } diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index 7c2fc21fde54e..93ec34025bd1e 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -17,6 +17,7 @@ static_assertions = "1.1.0" serde = { version = "1.0.101", optional = true } codec = { package = "parity-scale-codec", version = "1.3.6", default-features = false, features = ["derive"] } sp-std = { version = "2.0.0", default-features = false, path = "../../primitives/std" } +# TWO_PHASE_NOTE:: ideally we should be able to get rid of this. sp-npos-elections = { version = "2.0.0", default-features = false, path = "../../primitives/npos-elections" } sp-io ={ version = "2.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "2.0.0", default-features = false, path = "../../primitives/runtime" } @@ -25,6 +26,7 @@ frame-support = { version = "2.0.0", default-features = false, path = "../suppor frame-system = { version = "2.0.0", default-features = false, path = "../system" } pallet-session = { version = "2.0.0", default-features = false, features = ["historical"], path = "../session" } pallet-authorship = { version = "2.0.0", default-features = false, path = "../authorship" } +sp-election-providers = { version = "2.0.0", default-features = false, path = "../../primitives/election-providers" } sp-application-crypto = { version = "2.0.0", default-features = false, path = "../../primitives/application-crypto" } # Optional imports for benchmarking @@ -40,6 +42,7 @@ pallet-timestamp = { version = "2.0.0", path = "../timestamp" } pallet-staking-reward-curve = { version = "2.0.0", path = "../staking/reward-curve" } substrate-test-utils = { version = "2.0.0", path = "../../test-utils" } frame-benchmarking = { version = "2.0.0", path = "../benchmarking" } +sp-election-providers = { version = "2.0.0", features = ["runtime-benchmarks"], path = "../../primitives/election-providers" } rand_chacha = { version = "0.2" } parking_lot = "0.11.1" hex = "0.4" @@ -59,8 +62,10 @@ std = [ "frame-system/std", "pallet-authorship/std", "sp-application-crypto/std", + "sp-election-providers/std", ] runtime-benchmarks = [ "frame-benchmarking", + "sp-election-providers/runtime-benchmarks", "rand_chacha", ] diff --git a/frame/staking/fuzzer/Cargo.toml b/frame/staking/fuzzer/Cargo.toml index db65e347d8e2a..dd28df6180173 100644 --- a/frame/staking/fuzzer/Cargo.toml +++ b/frame/staking/fuzzer/Cargo.toml @@ -27,6 +27,7 @@ sp-std = { version = "2.0.0", path = "../../../primitives/std" } sp-io ={ version = "2.0.0", path = "../../../primitives/io" } sp-core = { version = "2.0.0", path = "../../../primitives/core" } sp-npos-elections = { version = "2.0.0", path = "../../../primitives/npos-elections" } +sp-election-providers = { version = "2.0.0", path = "../../../primitives/election-providers" } sp-runtime = { version = "2.0.0", path = "../../../primitives/runtime" } [[bin]] diff --git a/frame/staking/fuzzer/src/mock.rs b/frame/staking/fuzzer/src/mock.rs index b3c9dd9f57b60..da6617d0519b3 100644 --- a/frame/staking/fuzzer/src/mock.rs +++ b/frame/staking/fuzzer/src/mock.rs @@ -151,13 +151,28 @@ parameter_types! { pub type Extrinsic = sp_runtime::testing::TestXt; -impl frame_system::offchain::SendTransactionTypes for Test where +impl frame_system::offchain::SendTransactionTypes for Test +where Call: From, { type OverarchingCall = Call; type Extrinsic = Extrinsic; } +pub struct MockElectionProvider; +impl sp_election_providers::ElectionProvider for MockElectionProvider { + type Error = (); + type DataProvider = pallet_staking::Module; + + fn elect() -> Result, Self::Error> { + Err(()) + } + + fn ongoing() -> bool { + false + } +} + impl pallet_staking::Config for Test { type Currency = Balances; type UnixTime = pallet_timestamp::Module; @@ -181,4 +196,5 @@ impl pallet_staking::Config for Test { type UnsignedPriority = (); type OffchainSolutionWeightLimit = (); type WeightInfo = (); + type ElectionProvider = MockElectionProvider; } diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 795f222158e05..109145ede5e31 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -232,10 +232,11 @@ //! //! The controller account can free a portion (or all) of the funds using the //! [`unbond`](enum.Call.html#variant.unbond) call. Note that the funds are not immediately -//! accessible. Instead, a duration denoted by [`BondingDuration`](./trait.Config.html#associatedtype.BondingDuration) -//! (in number of eras) must pass until the funds can actually be removed. Once the -//! `BondingDuration` is over, the [`withdraw_unbonded`](./enum.Call.html#variant.withdraw_unbonded) -//! call can be used to actually withdraw the funds. +//! accessible. Instead, a duration denoted by +//! [`BondingDuration`](./trait.Config.html#associatedtype.BondingDuration) (in number of eras) must +//! pass until the funds can actually be removed. Once the `BondingDuration` is over, the +//! [`withdraw_unbonded`](./enum.Call.html#variant.withdraw_unbonded) call can be used to actually +//! withdraw the funds. //! //! Note that there is a limitation to the number of fund-chunks that can be scheduled to be //! unlocked in the future via [`unbond`](enum.Call.html#variant.unbond). In case this maximum @@ -304,7 +305,7 @@ use frame_support::{ }; use pallet_session::historical; use sp_runtime::{ - Percent, Perbill, PerU16, PerThing, InnerOf, RuntimeDebug, DispatchError, + Percent, Perbill, PerU16, InnerOf, RuntimeDebug, DispatchError, curve::PiecewiseLinear, traits::{ Convert, Zero, StaticLookup, CheckedSub, Saturating, SaturatedConversion, @@ -327,15 +328,14 @@ use frame_system::{ }; use sp_npos_elections::{ ExtendedBalance, Assignment, ElectionScore, ElectionResult as PrimitiveElectionResult, - build_support_map, evaluate_support, seq_phragmen, generate_solution_type, - is_score_better, VotingLimit, SupportMap, VoteWeight, + to_supports, EvaluateSupport, seq_phragmen, generate_solution_type, is_score_better, Supports, + VoteWeight, CompactSolution, PerThing128, }; +use sp_election_providers::ElectionProvider; pub use weights::WeightInfo; const STAKING_ID: LockIdentifier = *b"staking "; pub const MAX_UNLOCKING_CHUNKS: usize = 32; -pub const MAX_NOMINATIONS: usize = ::LIMIT; - pub(crate) const LOG_TARGET: &'static str = "staking"; // syntactic sugar for logging. @@ -344,7 +344,7 @@ macro_rules! log { ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { frame_support::debug::$level!( target: crate::LOG_TARGET, - $patter $(, $values)* + concat!("💸 ", $patter) $(, $values)* ) }; } @@ -364,6 +364,8 @@ static_assertions::const_assert!(size_of::() <= size_of::() /// Maximum number of stakers that can be stored in a snapshot. pub(crate) const MAX_VALIDATORS: usize = ValidatorIndex::max_value() as usize; pub(crate) const MAX_NOMINATORS: usize = NominatorIndex::max_value() as usize; +pub const MAX_NOMINATIONS: usize = + ::LIMIT; /// Counter for the number of eras that have passed. pub type EraIndex = u32; @@ -387,10 +389,12 @@ pub type OffchainAccuracy = PerU16; pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -type PositiveImbalanceOf = - <::Currency as Currency<::AccountId>>::PositiveImbalance; -type NegativeImbalanceOf = - <::Currency as Currency<::AccountId>>::NegativeImbalance; +type PositiveImbalanceOf = <::Currency as Currency< + ::AccountId, +>>::PositiveImbalance; +type NegativeImbalanceOf = <::Currency as Currency< + ::AccountId, +>>::NegativeImbalance; /// Information regarding the active era (era in used in session). #[derive(Encode, Decode, RuntimeDebug)] @@ -772,7 +776,7 @@ impl SessionInterface<::AccountId> for T w pub trait Config: frame_system::Config + SendTransactionTypes> { /// The staking balance. - type Currency: LockableCurrency; + type Currency: LockableCurrency; /// Time used for computing era duration. /// @@ -787,6 +791,14 @@ pub trait Config: frame_system::Config + SendTransactionTypes> { /// [`BalanceOf`]. type CurrencyToVote: CurrencyToVote>; + /// Something that provides the election functionality. + type ElectionProvider: sp_election_providers::ElectionProvider< + Self::AccountId, + Self::BlockNumber, + // we only accept an election provider that has staking as data provider. + DataProvider = Module, + >; + /// Tokens have been minted and are unused for validator-reward. /// See [Era payout](./index.html#era-payout). type RewardRemainder: OnUnbalanced>; @@ -883,7 +895,9 @@ pub enum Forcing { } impl Default for Forcing { - fn default() -> Self { Forcing::NotForcing } + fn default() -> Self { + Forcing::NotForcing + } } // A value placed in storage that represents the current version of the Staking storage. This value @@ -1059,28 +1073,45 @@ decl_storage! { /// The earliest era for which we have a pending, unapplied slash. EarliestUnappliedSlash: Option; + /// The last planned session scheduled by the session pallet. + /// + /// This is basically in sync with the call to [`SessionManager::new_session`]. + pub CurrentPlannedSession get(fn current_planned_session): SessionIndex; + /// Snapshot of validators at the beginning of the current election window. This should only /// have a value when [`EraElectionStatus`] == `ElectionStatus::Open(_)`. + /// + /// TWO_PHASE_NOTE: should be removed once we switch to multi-phase. pub SnapshotValidators get(fn snapshot_validators): Option>; /// Snapshot of nominators at the beginning of the current election window. This should only /// have a value when [`EraElectionStatus`] == `ElectionStatus::Open(_)`. + /// + /// TWO_PHASE_NOTE: should be removed once we switch to multi-phase. pub SnapshotNominators get(fn snapshot_nominators): Option>; /// The next validator set. At the end of an era, if this is available (potentially from the /// result of an offchain worker), it is immediately used. Otherwise, the on-chain election /// is executed. + /// + /// TWO_PHASE_NOTE: should be removed once we switch to multi-phase. pub QueuedElected get(fn queued_elected): Option>>; /// The score of the current [`QueuedElected`]. + /// + /// TWO_PHASE_NOTE: should be removed once we switch to multi-phase. pub QueuedScore get(fn queued_score): Option; /// Flag to control the execution of the offchain election. When `Open(_)`, we accept /// solutions to be submitted. + /// + /// TWO_PHASE_NOTE: should be removed once we switch to multi-phase. pub EraElectionStatus get(fn era_election_status): ElectionStatus; /// True if the current **planned** session is final. Note that this does not take era /// forcing into account. + /// + /// TWO_PHASE_NOTE: should be removed once we switch to multi-phase. pub IsCurrentSessionFinal get(fn is_current_session_final): bool = false; /// True if network has been upgraded to this version. @@ -1298,14 +1329,14 @@ decl_module! { ElectionStatus::::Open(now) ); add_weight(0, 1, 0); - log!(info, "💸 Election window is Open({:?}). Snapshot created", now); + log!(info, "Election window is Open({:?}). Snapshot created", now); } else { - log!(warn, "💸 Failed to create snapshot at {:?}.", now); + log!(warn, "Failed to create snapshot at {:?}.", now); } } } } else { - log!(warn, "💸 Estimating next session change failed."); + log!(warn, "Estimating next session change failed."); } add_weight(0, 0, T::NextNewSession::weight(now)) } @@ -1320,16 +1351,15 @@ decl_module! { /// to open. If so, it runs the offchain worker code. fn offchain_worker(now: T::BlockNumber) { use offchain_election::{set_check_offchain_execution_status, compute_offchain_election}; - if Self::era_election_status().is_open_at(now) { let offchain_status = set_check_offchain_execution_status::(now); if let Err(why) = offchain_status { - log!(warn, "💸 skipping offchain worker in open election window due to [{}]", why); + log!(warn, "skipping offchain worker in open election window due to [{}]", why); } else { if let Err(e) = compute_offchain_election::() { - log!(error, "💸 Error in election offchain worker: {:?}", e); + log!(error, "Error in election offchain worker: {:?}", e); } else { - log!(debug, "💸 Executed offchain worker thread without errors."); + log!(debug, "Executed offchain worker thread without errors."); } } } @@ -2102,7 +2132,7 @@ decl_module! { #[weight = T::WeightInfo::submit_solution_better( size.validators.into(), size.nominators.into(), - compact.len() as u32, + compact.voter_count() as u32, winners.len() as u32, )] pub fn submit_election_solution( @@ -2136,7 +2166,7 @@ decl_module! { #[weight = T::WeightInfo::submit_solution_better( size.validators.into(), size.nominators.into(), - compact.len() as u32, + compact.voter_count() as u32, winners.len() as u32, )] pub fn submit_election_solution_unsigned( @@ -2175,7 +2205,10 @@ impl Module { } /// Internal impl of [`Self::slashable_balance_of`] that returns [`VoteWeight`]. - pub fn slashable_balance_of_vote_weight(stash: &T::AccountId, issuance: BalanceOf) -> VoteWeight { + pub fn slashable_balance_of_vote_weight( + stash: &T::AccountId, + issuance: BalanceOf, + ) -> VoteWeight { T::CurrencyToVote::to_vote(Self::slashable_balance_of(stash), issuance) } @@ -2214,7 +2247,7 @@ impl Module { { log!( warn, - "💸 Snapshot size too big [{} <> {}][{} <> {}].", + "Snapshot size too big [{} <> {}][{} <> {}].", num_validators, MAX_VALIDATORS, num_nominators, @@ -2238,10 +2271,7 @@ impl Module { >::kill(); } - fn do_payout_stakers( - validator_stash: T::AccountId, - era: EraIndex, - ) -> DispatchResult { + fn do_payout_stakers(validator_stash: T::AccountId, era: EraIndex) -> DispatchResult { // Validate input data let current_era = CurrentEra::get().ok_or(Error::::InvalidEraToReward)?; ensure!(era <= current_era, Error::::InvalidEraToReward); @@ -2534,7 +2564,7 @@ impl Module { validator_at, ).map_err(|e| { // log the error since it is not propagated into the runtime error. - log!(warn, "💸 un-compacting solution failed due to {:?}", e); + log!(warn, "un-compacting solution failed due to {:?}", e); Error::::OffchainElectionBogusCompact })?; @@ -2549,7 +2579,7 @@ impl Module { // all of the indices must map to either a validator or a nominator. If this is ever // not the case, then the locking system of staking is most likely faulty, or we // have bigger problems. - log!(error, "💸 detected an error in the staking locking and snapshot."); + log!(error, "detected an error in the staking locking and snapshot."); // abort. return Err(Error::::OffchainElectionBogusNominator.into()); } @@ -2598,20 +2628,19 @@ impl Module { ); // build the support map thereof in order to evaluate. - let supports = build_support_map::( - &winners, - &staked_assignments, - ).map_err(|_| Error::::OffchainElectionBogusEdge)?; + let supports = to_supports(&winners, &staked_assignments) + .map_err(|_| Error::::OffchainElectionBogusEdge)?; // Check if the score is the same as the claimed one. - let submitted_score = evaluate_support(&supports); + let submitted_score = (&supports).evaluate(); ensure!(submitted_score == claimed_score, Error::::OffchainElectionBogusScore); // At last, alles Ok. Exposures and store the result. - let exposures = Self::collect_exposure(supports); + let exposures = Self::collect_exposures(supports); log!( info, - "💸 A better solution (with compute {:?} and score {:?}) has been validated and stored on chain.", + "A better solution (with compute {:?} and score {:?}) has been validated and stored \ + on chain.", compute, submitted_score, ); @@ -2744,6 +2773,7 @@ impl Module { // Set staking information for new era. let maybe_new_validators = Self::select_and_update_validators(current_era); + let _unused_new_validators = Self::enact_election(current_era); maybe_new_validators } @@ -2811,7 +2841,7 @@ impl Module { log!( info, - "💸 new validator set of size {:?} has been elected via {:?} for era {:?}", + "new validator set of size {:?} has been elected via {:?} for staring era {:?}", elected_stashes.len(), compute, current_era, @@ -2860,20 +2890,20 @@ impl Module { Self::slashable_balance_of_fn(), ); - let supports = build_support_map::( + let supports = to_supports( &elected_stashes, &staked_assignments, ) .map_err(|_| log!( error, - "💸 on-chain phragmen is failing due to a problem in the result. This must be a bug." + "on-chain phragmen is failing due to a problem in the result. This must be a bug." ) ) .ok()?; // collect exposures - let exposures = Self::collect_exposure(supports); + let exposures = Self::collect_exposures(supports); // In order to keep the property required by `on_session_ending` that we must return the // new validator set even if it's the same as the old, as long as any underlying @@ -2899,7 +2929,7 @@ impl Module { /// Self votes are added and nominations before the most recent slashing span are ignored. /// /// No storage item is updated. - pub fn do_phragmen( + pub fn do_phragmen( iterations: usize, ) -> Option> where @@ -2938,7 +2968,7 @@ impl Module { // If we don't have enough candidates, nothing to do. log!( warn, - "💸 Chain does not have enough staking candidates to operate. Era {:?}.", + "chain does not have enough staking candidates to operate. Era {:?}.", Self::current_era() ); None @@ -2949,14 +2979,15 @@ impl Module { all_nominators, Some((iterations, 0)), // exactly run `iterations` rounds. ) - .map_err(|err| log!(error, "Call to seq-phragmen failed due to {}", err)) + .map_err(|err| log!(error, "Call to seq-phragmen failed due to {:?}", err)) .ok() } } - /// Consume a set of [`Supports`] from [`sp_npos_elections`] and collect them into a [`Exposure`] - fn collect_exposure( - supports: SupportMap, + /// Consume a set of [`Supports`] from [`sp_npos_elections`] and collect them into a + /// [`Exposure`]. + fn collect_exposures( + supports: Supports, ) -> Vec<(T::AccountId, Exposure>)> { let total_issuance = T::Currency::total_issuance(); let to_currency = |e: ExtendedBalance| T::CurrencyToVote::to_currency(e, total_issuance); @@ -2988,6 +3019,80 @@ impl Module { }).collect::)>>() } + /// Process the output of the election. + /// + /// This ensures enough validators have been elected, converts all supports to exposures and + /// writes them to the associated storage. + /// + /// Returns `Err(())` if less than [`MinimumValidatorCount`] validators have been elected, `Ok` + /// otherwise. + // TWO_PHASE_NOTE: the deadcode + #[allow(dead_code)] + pub fn process_election( + flat_supports: sp_npos_elections::Supports, + current_era: EraIndex, + ) -> Result, ()> { + let exposures = Self::collect_exposures(flat_supports); + let elected_stashes = exposures.iter().cloned().map(|(x, _)| x).collect::>(); + + if (elected_stashes.len() as u32) <= Self::minimum_validator_count() { + log!( + warn, + "chain does not have enough staking candidates to operate for era {:?}", + current_era, + ); + return Err(()); + } + + // Populate Stakers and write slot stake. + let mut total_stake: BalanceOf = Zero::zero(); + exposures.into_iter().for_each(|(stash, exposure)| { + total_stake = total_stake.saturating_add(exposure.total); + >::insert(current_era, &stash, &exposure); + + let mut exposure_clipped = exposure; + let clipped_max_len = T::MaxNominatorRewardedPerValidator::get() as usize; + if exposure_clipped.others.len() > clipped_max_len { + exposure_clipped.others.sort_by(|a, b| a.value.cmp(&b.value).reverse()); + exposure_clipped.others.truncate(clipped_max_len); + } + >::insert(¤t_era, &stash, exposure_clipped); + }); + + // Insert current era staking information + >::insert(¤t_era, total_stake); + + // collect the pref of all winners + for stash in &elected_stashes { + let pref = Self::validators(stash); + >::insert(¤t_era, stash, pref); + } + + // emit event + // TWO_PHASE_NOTE: remove the inner value. + Self::deposit_event(RawEvent::StakingElection(ElectionCompute::Signed)); + + log!( + info, + "new validator set of size {:?} has been processed for era {:?}", + elected_stashes.len(), + current_era, + ); + + Ok(elected_stashes) + } + + /// Enact and process the election using the `ElectionProvider` type. + /// + /// This will also process the election, as noted in [`process_election`]. + fn enact_election(_current_era: EraIndex) -> Option> { + let outcome = T::ElectionProvider::elect().map(|_| ()); + log!(debug, "Experimental election provider outputted {:?}", outcome); + // TWO_PHASE_NOTE: This code path shall not return anything for now. Later on, redirect the + // results to `process_election`. + None + } + /// Remove all associated data of a stash account from the staking system. /// /// Assumes storage is upgraded before calling. @@ -3080,7 +3185,11 @@ impl Module { } #[cfg(feature = "runtime-benchmarks")] - pub fn add_era_stakers(current_era: EraIndex, controller: T::AccountId, exposure: Exposure>) { + pub fn add_era_stakers( + current_era: EraIndex, + controller: T::AccountId, + exposure: Exposure>, + ) { >::insert(¤t_era, &controller, &exposure); } @@ -3093,6 +3202,106 @@ impl Module { pub fn set_slash_reward_fraction(fraction: Perbill) { SlashRewardFraction::put(fraction); } + + /// Get all of the voters that are eligible for the npos election. + /// + /// This will use all on-chain nominators, and all the validators will inject a self vote. + /// + /// ### Slashing + /// + /// All nominations that have been submitted before the last non-zero slash of the validator are + /// auto-chilled. + /// + /// Note that this is VERY expensive. Use with care. + pub fn get_npos_voters() -> Vec<(T::AccountId, VoteWeight, Vec)> { + let weight_of = Self::slashable_balance_of_fn(); + let mut all_voters = Vec::new(); + + for (validator, _) in >::iter() { + // append self vote + let self_vote = (validator.clone(), weight_of(&validator), vec![validator.clone()]); + all_voters.push(self_vote); + } + + for (nominator, nominations) in >::iter() { + let Nominations { submitted_in, mut targets, suppressed: _ } = nominations; + + // Filter out nomination targets which were nominated before the most recent + // slashing span. + targets.retain(|stash| { + Self::slashing_spans(&stash) + .map_or(true, |spans| submitted_in >= spans.last_nonzero_slash()) + }); + + let vote_weight = weight_of(&nominator); + all_voters.push((nominator, vote_weight, targets)) + } + + all_voters + } + + pub fn get_npos_targets() -> Vec { + >::iter().map(|(v, _)| v).collect::>() + } +} + +impl sp_election_providers::ElectionDataProvider + for Module +{ + fn desired_targets() -> u32 { + Self::validator_count() + } + + fn voters() -> Vec<(T::AccountId, VoteWeight, Vec)> { + Self::get_npos_voters() + } + + fn targets() -> Vec { + Self::get_npos_targets() + } + + fn next_election_prediction(now: T::BlockNumber) -> T::BlockNumber { + let current_era = Self::current_era().unwrap_or(0); + let current_session = Self::current_planned_session(); + let current_era_start_session_index = + Self::eras_start_session_index(current_era).unwrap_or(0); + let era_length = current_session + .saturating_sub(current_era_start_session_index) + .min(T::SessionsPerEra::get()); + + let session_length = T::NextNewSession::average_session_length(); + + let until_this_session_end = T::NextNewSession::estimate_next_new_session(now) + .unwrap_or_default() + .saturating_sub(now); + + let sessions_left: T::BlockNumber = T::SessionsPerEra::get() + .saturating_sub(era_length) + // one session is computed in this_session_end. + .saturating_sub(1) + .into(); + + now.saturating_add( + until_this_session_end.saturating_add(sessions_left.saturating_mul(session_length)), + ) + } + + #[cfg(any(feature = "runtime-benchmarks", test))] + fn put_snapshot( + voters: Vec<(T::AccountId, VoteWeight, Vec)>, + targets: Vec, + ) { + targets.into_iter().for_each(|v| { + >::insert(v, ValidatorPrefs { commission: Perbill::zero() }); + }); + + voters.into_iter().for_each(|(v, _s, t)| { + >::insert( + v, + Nominations { targets: t, submitted_in: 0, suppressed: false }, + ); + }); + } } /// In this implementation `new_session(session)` must be called before `end_session(session-1)` @@ -3108,6 +3317,7 @@ impl pallet_session::SessionManager for Module { >::block_number(), new_index ); + CurrentPlannedSession::put(new_index); Self::new_session(new_index) } fn start_session(start_index: SessionIndex) { @@ -3130,10 +3340,12 @@ impl pallet_session::SessionManager for Module { } } -impl historical::SessionManager>> for Module { - fn new_session(new_index: SessionIndex) - -> Option>)>> - { +impl historical::SessionManager>> + for Module +{ + fn new_session( + new_index: SessionIndex, + ) -> Option>)>> { >::new_session(new_index).map(|validators| { let current_era = Self::current_era() // Must be some as a new era has been created. @@ -3158,8 +3370,8 @@ impl historical::SessionManager pallet_authorship::EventHandler for Module - where - T: Config + pallet_authorship::Config + pallet_session::Config +where + T: Config + pallet_authorship::Config + pallet_session::Config, { fn note_author(author: T::AccountId) { Self::reward_by_ids(vec![(author, 20)]) @@ -3202,9 +3414,10 @@ impl Convert } /// This is intended to be used with `FilterHistoricalOffences`. -impl +impl OnOffenceHandler, Weight> -for Module where + for Module +where T: pallet_session::Config::AccountId>, T: pallet_session::historical::Config< FullIdentification = Exposure<::AccountId, BalanceOf>, @@ -3218,12 +3431,15 @@ for Module where >, { fn on_offence( - offenders: &[OffenceDetails>], + offenders: &[OffenceDetails< + T::AccountId, + pallet_session::historical::IdentificationTuple, + >], slash_fraction: &[Perbill], slash_session: SessionIndex, ) -> Result { if !Self::can_report() { - return Err(()) + return Err(()); } let reward_proportion = SlashRewardFraction::get(); @@ -3334,6 +3550,7 @@ for Module where } fn can_report() -> bool { + // TWO_PHASE_NOTE: we can get rid of this API Self::era_election_status().is_closed() } } @@ -3344,7 +3561,8 @@ pub struct FilterHistoricalOffences { } impl ReportOffence - for FilterHistoricalOffences, R> where + for FilterHistoricalOffences, R> +where T: Config, R: ReportOffence, O: Offence, @@ -3401,7 +3619,7 @@ impl frame_support::unsigned::ValidateUnsigned for Module { return invalid.into(); } - log!(debug, "💸 validateUnsigned succeeded for a solution at era {}.", era); + log!(debug, "validateUnsigned succeeded for a solution at era {}.", era); ValidTransaction::with_tag_prefix("StakingOffchain") // The higher the score[0], the better a solution is. diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 048806b062395..d01f8b59a681f 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -27,7 +27,7 @@ use frame_support::{ use sp_core::H256; use sp_io; use sp_npos_elections::{ - build_support_map, evaluate_support, reduce, ExtendedBalance, StakedAssignment, ElectionScore, + to_supports, reduce, ExtendedBalance, StakedAssignment, ElectionScore, EvaluateSupport, }; use sp_runtime::{ curve::PiecewiseLinear, @@ -36,6 +36,7 @@ use sp_runtime::{ }; use sp_staking::offence::{OffenceDetails, OnOffenceHandler}; use std::{cell::RefCell, collections::HashSet}; +use sp_election_providers::onchain; pub const INIT_TIMESTAMP: u64 = 30_000; pub const BLOCK_TIME: u64 = 1000; @@ -252,6 +253,12 @@ impl OnUnbalanced> for RewardRemainderMock { } } +impl onchain::Config for Test { + type AccountId = AccountId; + type BlockNumber = BlockNumber; + type Accuracy = Perbill; + type DataProvider = Staking; +} impl Config for Test { type Currency = Balances; type UnixTime = Timestamp; @@ -274,6 +281,7 @@ impl Config for Test { type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; type UnsignedPriority = UnsignedPriority; type OffchainSolutionWeightLimit = OffchainSolutionWeightLimit; + type ElectionProvider = onchain::OnChainSequentialPhragmen; type WeightInfo = (); } @@ -769,7 +777,7 @@ pub(crate) fn add_slash(who: &AccountId) { on_offence_now( &[ OffenceDetails { - offender: (who.clone(), Staking::eras_stakers(Staking::active_era().unwrap().index, who.clone())), + offender: (who.clone(), Staking::eras_stakers(active_era(), who.clone())), reporters: vec![], }, ], @@ -850,8 +858,8 @@ pub(crate) fn horrible_npos_solution( let score = { let (_, _, better_score) = prepare_submission_with(true, true, 0, |_| {}); - let support = build_support_map::(&winners, &staked_assignment).unwrap(); - let score = evaluate_support(&support); + let support = to_supports::(&winners, &staked_assignment).unwrap(); + let score = support.evaluate(); assert!(sp_npos_elections::is_score_better::( better_score, @@ -950,11 +958,11 @@ pub(crate) fn prepare_submission_with( Staking::slashable_balance_of_fn(), ); - let support_map = build_support_map::( + let support_map = to_supports( winners.as_slice(), staked.as_slice(), ).unwrap(); - evaluate_support::(&support_map) + support_map.evaluate() } else { Default::default() }; @@ -971,9 +979,8 @@ pub(crate) fn prepare_submission_with( /// Make all validator and nominator request their payment pub(crate) fn make_all_reward_payment(era: EraIndex) { - let validators_with_reward = ErasRewardPoints::::get(era).individual.keys() - .cloned() - .collect::>(); + let validators_with_reward = + ErasRewardPoints::::get(era).individual.keys().cloned().collect::>(); // reward validators for validator_controller in validators_with_reward.iter().filter_map(Staking::bonded) { @@ -990,19 +997,19 @@ pub(crate) fn make_all_reward_payment(era: EraIndex) { macro_rules! assert_session_era { ($session:expr, $era:expr) => { assert_eq!( - Session::current_index(), - $session, - "wrong session {} != {}", - Session::current_index(), - $session, - ); - assert_eq!( - Staking::active_era().unwrap().index, - $era, - "wrong active era {} != {}", - Staking::active_era().unwrap().index, - $era, - ); + Session::current_index(), + $session, + "wrong session {} != {}", + Session::current_index(), + $session, + ); + assert_eq!( + Staking::current_era().unwrap(), + $era, + "wrong current era {} != {}", + Staking::current_era().unwrap(), + $era, + ); }; } diff --git a/frame/staking/src/offchain_election.rs b/frame/staking/src/offchain_election.rs index 433e02261cc58..bec4174ad42d3 100644 --- a/frame/staking/src/offchain_election.rs +++ b/frame/staking/src/offchain_election.rs @@ -25,8 +25,8 @@ use codec::Decode; use frame_support::{traits::Get, weights::Weight, IterableStorageMap}; use frame_system::offchain::SubmitTransaction; use sp_npos_elections::{ - build_support_map, evaluate_support, reduce, Assignment, ElectionResult, ElectionScore, - ExtendedBalance, + to_supports, EvaluateSupport, reduce, Assignment, ElectionResult, ElectionScore, + ExtendedBalance, CompactSolution, }; use sp_runtime::{ offchain::storage::StorageValueRef, traits::TrailingZeroInput, PerThing, RuntimeDebug, @@ -127,7 +127,7 @@ pub(crate) fn compute_offchain_election() -> Result<(), OffchainElect crate::log!( info, - "💸 prepared a seq-phragmen solution with {} balancing iterations and score {:?}", + "prepared a seq-phragmen solution with {} balancing iterations and score {:?}", iters, score, ); @@ -265,7 +265,7 @@ pub fn trim_to_weight( where for<'r> FN: Fn(&'r T::AccountId) -> Option, { - match compact.len().checked_sub(maximum_allowed_voters as usize) { + match compact.voter_count().checked_sub(maximum_allowed_voters as usize) { Some(to_remove) if to_remove > 0 => { // grab all voters and sort them by least stake. let balance_of = >::slashable_balance_of_fn(); @@ -284,7 +284,7 @@ where if compact.remove_voter(index) { crate::log!( trace, - "💸 removed a voter at index {} with stake {:?} from compact to reduce the size", + "removed a voter at index {} with stake {:?} from compact to reduce the size", index, _stake, ); @@ -297,19 +297,17 @@ where } crate::log!( - warn, - "💸 {} nominators out of {} had to be removed from compact solution due to size limits.", - removed, - compact.len() + removed, - ); + warn, + "{} nominators out of {} had to be removed from compact solution due to size \ + limits.", + removed, + compact.voter_count() + removed, + ); Ok(compact) } _ => { // nada, return as-is - crate::log!( - info, - "💸 Compact solution did not get trimmed due to block weight limits.", - ); + crate::log!(info, "Compact solution did not get trimmed due to block weight limits.",); Ok(compact) } } @@ -324,12 +322,7 @@ pub fn prepare_submission( do_reduce: bool, maximum_weight: Weight, ) -> Result< - ( - Vec, - CompactAssignments, - ElectionScore, - ElectionSize, - ), + (Vec, CompactAssignments, ElectionScore, ElectionSize), OffchainElectionError, > where @@ -398,16 +391,19 @@ where let maximum_allowed_voters = maximum_compact_len::(winners.len() as u32, size, maximum_weight); - crate::log!(debug, "💸 Maximum weight = {:?} // current weight = {:?} // maximum voters = {:?} // current votes = {:?}", + crate::log!( + debug, + "Maximum weight = {:?} // current weight = {:?} // maximum voters = {:?} // current votes \ + = {:?}", maximum_weight, T::WeightInfo::submit_solution_better( - size.validators.into(), - size.nominators.into(), - compact.len() as u32, - winners.len() as u32, + size.validators.into(), + size.nominators.into(), + compact.voter_count() as u32, + winners.len() as u32, ), maximum_allowed_voters, - compact.len(), + compact.voter_count(), ); let compact = trim_to_weight::(maximum_allowed_voters, compact, &nominator_index)?; @@ -423,9 +419,9 @@ where >::slashable_balance_of_fn(), ); - let support_map = build_support_map::(&winners, &staked) + let support_map = to_supports::(&winners, &staked) .map_err(|_| OffchainElectionError::ElectionFailed)?; - evaluate_support::(&support_map) + support_map.evaluate() }; // winners to index. Use a simple for loop for a more expressive early exit in case of error. diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs index d3139b53e6f97..f96d23ffb5c3b 100644 --- a/frame/staking/src/testing_utils.rs +++ b/frame/staking/src/testing_utils.rs @@ -244,11 +244,9 @@ pub fn get_weak_solution( >::slashable_balance_of_fn(), ); - let support_map = build_support_map::( - winners.as_slice(), - staked.as_slice(), - ).unwrap(); - evaluate_support::(&support_map) + let support_map = + to_supports::(winners.as_slice(), staked.as_slice()).unwrap(); + support_map.evaluate() }; // compact encode the assignment. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index bf0b2bf0da484..56a7e4388927a 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -1788,6 +1788,7 @@ fn bond_with_duplicate_vote_should_be_ignored_by_npos_election() { .minimum_validator_count(1) .build() .execute_with(|| { + // disable the nominator assert_ok!(Staking::chill(Origin::signed(100))); // make stakes equal. @@ -1808,6 +1809,7 @@ fn bond_with_duplicate_vote_should_be_ignored_by_npos_election() { } assert_ok!(Staking::bond(Origin::signed(1), 2, 1000, RewardDestination::Controller)); + // 11 should not be elected. All of these count as ONE vote. assert_ok!(Staking::nominate(Origin::signed(2), vec![11, 11, 11, 21, 31,])); assert_ok!(Staking::bond(Origin::signed(3), 4, 1000, RewardDestination::Controller)); @@ -1861,7 +1863,6 @@ fn bond_with_duplicate_vote_should_be_ignored_by_npos_election_elected() { assert_ok!(Staking::nominate(Origin::signed(4), vec![21, 31])); // winners should be 21 and 31. Otherwise this election is taking duplicates into account. - let sp_npos_elections::ElectionResult { winners, assignments, @@ -2004,7 +2005,7 @@ fn reward_from_authorship_event_handler_works() { fn add_reward_points_fns_works() { ExtBuilder::default().build_and_execute(|| { // Not mandatory but must be coherent with rewards - assert_eq!(Session::validators(), vec![21, 11]); + assert_eq_uvec!(Session::validators(), vec![21, 11]); >::reward_by_ids(vec![ (21, 1), @@ -3023,7 +3024,7 @@ mod offchain_election { assert_eq!(Staking::era_election_status(), ElectionStatus::Open(37)); run_to_block(40); - assert_session_era!(4, 0); + assert_session_era!(4, 1); assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); assert!(Staking::snapshot_nominators().is_none()); assert!(Staking::snapshot_validators().is_none()); @@ -3041,7 +3042,7 @@ mod offchain_election { assert!(Staking::snapshot_validators().is_some()); run_to_block(90); - assert_session_era!(9, 1); + assert_session_era!(9, 2); assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); assert!(Staking::snapshot_nominators().is_none()); assert!(Staking::snapshot_validators().is_none()); @@ -4950,3 +4951,92 @@ fn cannot_bond_extra_to_lower_than_ed() { ); }) } + +mod election_data_provider { + use super::*; + use sp_election_providers::ElectionDataProvider; + + #[test] + fn voters_include_self_vote() { + ExtBuilder::default().nominate(false).build().execute_with(|| { + assert!(>::iter().map(|(x, _)| x).all(|v| Staking::voters() + .into_iter() + .find(|(w, _, t)| { v == *w && t[0] == *w }) + .is_some())) + }) + } + + #[test] + fn voters_exclude_slashed() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + assert_eq!( + >::voters() + .iter() + .find(|x| x.0 == 101) + .unwrap() + .2, + vec![11, 21] + ); + + start_active_era(1); + add_slash(&11); + + // 11 is gone. + start_active_era(2); + assert_eq!( + >::voters() + .iter() + .find(|x| x.0 == 101) + .unwrap() + .2, + vec![21] + ); + + // resubmit and it is back + assert_ok!(Staking::nominate(Origin::signed(100), vec![11, 21])); + assert_eq!( + >::voters() + .iter() + .find(|x| x.0 == 101) + .unwrap() + .2, + vec![11, 21] + ); + }) + } + + #[test] + fn estimate_next_election_works() { + ExtBuilder::default().session_per_era(5).period(5).build().execute_with(|| { + // first session is always length 0. + for b in 1..20 { + run_to_block(b); + assert_eq!(Staking::next_election_prediction(System::block_number()), 20); + } + + // election + run_to_block(20); + assert_eq!(Staking::next_election_prediction(System::block_number()), 45); + assert_eq!(staking_events().len(), 1); + assert_eq!( + *staking_events().last().unwrap(), + RawEvent::StakingElection(ElectionCompute::OnChain) + ); + + for b in 21..45 { + run_to_block(b); + assert_eq!(Staking::next_election_prediction(System::block_number()), 45); + } + + // election + run_to_block(45); + assert_eq!(Staking::next_election_prediction(System::block_number()), 70); + assert_eq!(staking_events().len(), 3); + assert_eq!( + *staking_events().last().unwrap(), + RawEvent::StakingElection(ElectionCompute::OnChain) + ); + }) + } +} diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 0b2d3bceea5ec..eabe93d2ae008 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -347,22 +347,23 @@ impl Happened for () { /// be the default value), or where the account is being removed or reset back to the default value /// where previously it did exist (though may have been in a default state). This works well with /// system module's `CallOnCreatedAccount` and `CallKillAccount`. -pub struct StorageMapShim< - S, - Created, - Removed, - K, - T ->(sp_std::marker::PhantomData<(S, Created, Removed, K, T)>); +pub struct StorageMapShim( + sp_std::marker::PhantomData<(S, Created, Removed, K, T)>, +); impl< - S: StorageMap, - Created: Happened, - Removed: Happened, - K: FullCodec, - T: FullCodec, -> StoredMap for StorageMapShim { - fn get(k: &K) -> T { S::get(k) } - fn is_explicit(k: &K) -> bool { S::contains_key(k) } + S: StorageMap, + Created: Happened, + Removed: Happened, + K: FullCodec, + T: FullCodec, + > StoredMap for StorageMapShim +{ + fn get(k: &K) -> T { + S::get(k) + } + fn is_explicit(k: &K) -> bool { + S::contains_key(k) + } fn insert(k: &K, t: T) { let existed = S::contains_key(&k); S::insert(k, t); @@ -413,10 +414,16 @@ impl< } } -/// Something that can estimate at which block the next session rotation will happen. This should -/// be the same logical unit that dictates `ShouldEndSession` to the session module. No Assumptions -/// are made about the scheduling of the sessions. +/// Something that can estimate at which block the next session rotation will happen. +/// +/// This should be the same logical unit that dictates `ShouldEndSession` to the session module. No +/// Assumptions are made about the scheduling of the sessions. pub trait EstimateNextSessionRotation { + /// Return the average length of a session. + /// + /// This may or may not be accurate. + fn average_session_length() -> BlockNumber; + /// Return the block number at which the next session rotation is estimated to happen. /// /// None should be returned if the estimation fails to come to an answer @@ -426,7 +433,11 @@ pub trait EstimateNextSessionRotation { fn weight(now: BlockNumber) -> Weight; } -impl EstimateNextSessionRotation for () { +impl EstimateNextSessionRotation for () { + fn average_session_length() -> BlockNumber { + Default::default() + } + fn estimate_next_session_rotation(_: BlockNumber) -> Option { Default::default() } @@ -436,9 +447,15 @@ impl EstimateNextSessionRotation for () { } } -/// Something that can estimate at which block the next `new_session` will be triggered. This must -/// always be implemented by the session module. +/// Something that can estimate at which block the next `new_session` will be triggered. +/// +/// This must always be implemented by the session module. pub trait EstimateNextNewSession { + /// Return the average length of a session. + /// + /// This may or may not be accurate. + fn average_session_length() -> BlockNumber; + /// Return the block number at which the next new session is estimated to happen. fn estimate_next_new_session(now: BlockNumber) -> Option; @@ -446,7 +463,11 @@ pub trait EstimateNextNewSession { fn weight(now: BlockNumber) -> Weight; } -impl EstimateNextNewSession for () { +impl EstimateNextNewSession for () { + fn average_session_length() -> BlockNumber { + Default::default() + } + fn estimate_next_new_session(_: BlockNumber) -> Option { Default::default() } From 632e107d46f9493831e3beb4c5036e702cd4e03a Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 15 Jan 2021 20:57:38 +0000 Subject: [PATCH 03/29] Undo bad formattings. --- frame/staking/src/mock.rs | 26 +++++++++++++------------- frame/support/src/traits.rs | 31 +++++++++++++++---------------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index d01f8b59a681f..46f1c16c971e0 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -997,19 +997,19 @@ pub(crate) fn make_all_reward_payment(era: EraIndex) { macro_rules! assert_session_era { ($session:expr, $era:expr) => { assert_eq!( - Session::current_index(), - $session, - "wrong session {} != {}", - Session::current_index(), - $session, - ); - assert_eq!( - Staking::current_era().unwrap(), - $era, - "wrong current era {} != {}", - Staking::current_era().unwrap(), - $era, - ); + Session::current_index(), + $session, + "wrong session {} != {}", + Session::current_index(), + $session, + ); + assert_eq!( + Staking::current_era().unwrap(), + $era, + "wrong current era {} != {}", + Staking::current_era().unwrap(), + $era, + ); }; } diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index eabe93d2ae008..d4dbdbf5ef115 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -347,23 +347,22 @@ impl Happened for () { /// be the default value), or where the account is being removed or reset back to the default value /// where previously it did exist (though may have been in a default state). This works well with /// system module's `CallOnCreatedAccount` and `CallKillAccount`. -pub struct StorageMapShim( - sp_std::marker::PhantomData<(S, Created, Removed, K, T)>, -); +pub struct StorageMapShim< + S, + Created, + Removed, + K, + T +>(sp_std::marker::PhantomData<(S, Created, Removed, K, T)>); impl< - S: StorageMap, - Created: Happened, - Removed: Happened, - K: FullCodec, - T: FullCodec, - > StoredMap for StorageMapShim -{ - fn get(k: &K) -> T { - S::get(k) - } - fn is_explicit(k: &K) -> bool { - S::contains_key(k) - } + S: StorageMap, + Created: Happened, + Removed: Happened, + K: FullCodec, + T: FullCodec, +> StoredMap for StorageMapShim { + fn get(k: &K) -> T { S::get(k) } + fn is_explicit(k: &K) -> bool { S::contains_key(k) } fn insert(k: &K, t: T) { let existed = S::contains_key(&k); S::insert(k, t); From b4fc5e1701ad9960fc90fe0063fb76136c98392f Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 18 Jan 2021 13:21:18 +0000 Subject: [PATCH 04/29] some formatting cleanup. --- bin/node/runtime/src/lib.rs | 6 ++--- frame/session/src/lib.rs | 24 ++++++++------------ primitives/npos-elections/compact/src/lib.rs | 1 + primitives/npos-elections/src/phragmen.rs | 5 +--- primitives/npos-elections/src/tests.rs | 8 +------ 5 files changed, 15 insertions(+), 29 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index bc3526af195f7..d5f0d663e0081 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -504,9 +504,9 @@ impl pallet_staking::Config for Runtime { use pallet_election_provider_multi_phase::FallbackStrategy; parameter_types! { - // phase durations - pub const SignedPhase: u32 = 100; - pub const UnsignedPhase: u32 = 100; + // phase durations. 1/4 of the last session for each. + pub const SignedPhase: u32 = EPOCH_DURATION_IN_BLOCKS / 4; + pub const UnsignedPhase: u32 = EPOCH_DURATION_IN_BLOCKS / 4; // fallback: no need to do on-chain phragmen initially. pub const Fallback: FallbackStrategy = FallbackStrategy::Nothing; diff --git a/frame/session/src/lib.rs b/frame/session/src/lib.rs index 6f619129d8eaa..1ee4d123aa79b 100644 --- a/frame/session/src/lib.rs +++ b/frame/session/src/lib.rs @@ -158,25 +158,15 @@ impl< } impl< - BlockNumber: Rem - + Sub - + Zero - + PartialOrd - + Saturating - + Clone, - Period: Get, - Offset: Get, - > EstimateNextSessionRotation for PeriodicSessions -{ - fn average_session_length() -> BlockNumber { - Period::get() - } - + BlockNumber: Rem + Sub + Zero + PartialOrd + Saturating + Clone, + Period: Get, + Offset: Get, +> EstimateNextSessionRotation for PeriodicSessions { fn estimate_next_session_rotation(now: BlockNumber) -> Option { let offset = Offset::get(); let period = Period::get(); Some(if now > offset { - let block_after_last_session = (now.clone() - offset.clone()) % period.clone(); + let block_after_last_session = (now.clone() - offset) % period.clone(); if block_after_last_session > Zero::zero() { now.saturating_add(period.saturating_sub(block_after_last_session)) } else { @@ -198,6 +188,10 @@ impl< // reasonable to come back here and properly calculate the weight of this function. 0 } + + fn average_session_length() -> BlockNumber { + Period::get() + } } /// A trait for managing creation of new validator set. diff --git a/primitives/npos-elections/compact/src/lib.rs b/primitives/npos-elections/compact/src/lib.rs index 01b59c1a43549..191998a341924 100644 --- a/primitives/npos-elections/compact/src/lib.rs +++ b/primitives/npos-elections/compact/src/lib.rs @@ -362,6 +362,7 @@ fn imports() -> Result { } } } + struct SolutionDef { vis: syn::Visibility, ident: syn::Ident, diff --git a/primitives/npos-elections/src/phragmen.rs b/primitives/npos-elections/src/phragmen.rs index bdd64503a56d7..24a6b81af31a7 100644 --- a/primitives/npos-elections/src/phragmen.rs +++ b/primitives/npos-elections/src/phragmen.rs @@ -107,10 +107,7 @@ where .map(|w_ptr| (w_ptr.borrow().who.clone(), w_ptr.borrow().backed_stake)) .collect(); - Ok(ElectionResult { - winners, - assignments, - }) + Ok(ElectionResult { winners, assignments }) } /// Core implementation of seq-phragmen. diff --git a/primitives/npos-elections/src/tests.rs b/primitives/npos-elections/src/tests.rs index a6c50b2fcba30..bc148f118ce42 100644 --- a/primitives/npos-elections/src/tests.rs +++ b/primitives/npos-elections/src/tests.rs @@ -59,13 +59,7 @@ fn float_phragmen_poc_works() { &_Support { own: 0.0, total: 35.0, others: vec![(20u64, 20.0), (30u64, 15.0)] } ); - equalize_float( - phragmen_result.assignments, - &mut support_map, - 0.0, - 2, - stake_of, - ); + equalize_float(phragmen_result.assignments, &mut support_map, 0.0, 2, stake_of); assert_eq!( support_map.get(&2).unwrap(), From cc26881f03f799fe49f5f96f657073cd9d597ff8 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 18 Jan 2021 15:44:08 +0000 Subject: [PATCH 05/29] Small self-cleanup. --- .../election-provider-multi-phase/src/lib.rs | 130 ++++++++---------- .../election-provider-multi-phase/src/mock.rs | 13 +- .../src/unsigned.rs | 66 ++++++--- 3 files changed, 112 insertions(+), 97 deletions(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 79112297f869f..3028ee9ee9b06 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -15,10 +15,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # Two phase, offchain election provider pallet. +//! # Multi phase, offchain election provider pallet. //! -//! As the name suggests, this election-provider has two distinct phases (see [`Phase`]), signed and -//! unsigned. +//! Currently, this election-provider has two distinct phases (see [`Phase`]), **signed** and +//! **unsigned**. //! //! ## Phases //! @@ -35,13 +35,15 @@ //! ``` //! //! Note that the unsigned phase starts [`pallet::Config::UnsignedPhase`] blocks before the -//! `next_election_prediction`, but only ends when a call to [`ElectionProvider::elect`] happens. +//! `next_election_prediction`, but only ends when a call to [`ElectionProvider::elect`] happens. If +//! no `elect` happens, the signed phase is extended. //! //! > Given this, it is rather important for the user of this pallet to ensure it always terminates //! election via `elect` before requesting a new one. //! //! Each of the phases can be disabled by essentially setting their length to zero. If both phases -//! have length zero, then the pallet essentially runs only the on-chain backup. +//! have length zero, then the pallet essentially runs only the fallback strategy, denoted by +//! [`Config::FallbackStrategy`]. //! //! ### Signed Phase //! @@ -102,6 +104,10 @@ //! than the best queued one (see [`pallet::Config::SolutionImprovementThreshold`]) and will limit //! the weigh of the solution to [`pallet::Config::MinerMaxWeight`]. //! +//! The unsigned phase can be made passive depending on how the previous signed phase went, by +//! setting the first inner value of [`Phase`] to `false`. For now, the signed phase is always +//! active. +//! //! ### Fallback //! //! If we reach the end of both phases (i.e. call to [`ElectionProvider::elect`] happens) and no @@ -165,19 +171,19 @@ //! on-chain election provider as fallback, or special _noop_ fallback that simply returns an error, //! thus replicating [`FallbackStrategy::Nothing`]. //! -//! **Score based on size**: We should always prioritize small solutions over bigger ones, if there -//! is a tie. Even more harsh should be to enforce the bound of the `reduce` algorithm. +//! **Score based on (byte) size**: We should always prioritize small solutions over bigger ones, if +//! there is a tie. Even more harsh should be to enforce the bound of the `reduce` algorithm. #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Decode, Encode, HasCompact}; +use codec::{Decode, Encode}; use frame_support::{ dispatch::DispatchResultWithPostInfo, ensure, traits::{Currency, Get, ReservableCurrency}, weights::Weight, }; -use frame_system::{ensure_none, ensure_signed, offchain::SendTransactionTypes}; +use frame_system::{ensure_none, offchain::SendTransactionTypes}; use sp_election_providers::{ElectionDataProvider, ElectionProvider, onchain}; use sp_npos_elections::{ assignment_ratio_to_staked_normalized, is_score_better, CompactSolution, ElectionScore, @@ -205,20 +211,11 @@ pub mod helpers; const LOG_TARGET: &'static str = "election-provider"; -// for the helper macros -#[doc(hidden)] -pub use sp_runtime::traits::UniqueSaturatedInto; -#[doc(hidden)] -pub use sp_std; - pub mod unsigned; pub mod weights; use weights::WeightInfo; -// pub mod signed; -// use signed::SignedSubmission; - /// The compact solution type used by this crate. pub type CompactOf = ::CompactSolution; @@ -231,6 +228,7 @@ pub type CompactAccuracyOf = as CompactSolution>::Accuracy; /// The accuracy of the election, when computed on-chain. Equal to [`Config::OnChainAccuracy`]. pub type OnChainAccuracyOf = ::OnChainAccuracy; +/// Wrapper type that implements the configurations needed for the on-chain backup. struct OnChainConfig(sp_std::marker::PhantomData) where ExtendedBalance: From>>, @@ -310,7 +308,7 @@ impl Phase { } } -/// A configuration for the module to indicate what should happen in the case of a fallback i.e. +/// A configuration for the pallet to indicate what should happen in the case of a fallback i.e. /// reaching a call to `elect` with no good solution. #[cfg_attr(test, derive(Clone))] pub enum FallbackStrategy { @@ -379,7 +377,7 @@ pub struct ReadySolution { compute: ElectionCompute, } -/// Solution size of the election. +/// Size of the snapshot from which the solution was derived. /// /// This is needed for proper weight calculation. #[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, Default)] @@ -397,10 +395,9 @@ pub struct SolutionSize { } /// A snapshot of all the data that is needed for en entire round. They are provided by -/// [`ElectionDataProvider`] at the beginning of the signed phase (or the unsigned phase, if signed -/// phase is non-existent) and are kept around until the round is finished. +/// [`ElectionDataProvider`] and are kept around until the round is finished. /// -/// These are stored together because they are often times accessed together. +/// These are stored together because they are often accessed together. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default)] pub struct RoundSnapshot { /// All of the voters. @@ -433,16 +430,16 @@ pub enum ElectionError { Feasibility(FeasibilityError), /// An error in the on-chain fallback. OnChainFallback(onchain::Error), - /// No fallback is configured + /// No fallback is configured. NoFallbackConfigured, /// An internal error in the NPoS elections crate. NposElections(sp_npos_elections::Error), /// Snapshot data was unavailable unexpectedly. SnapshotUnAvailable, /// Submitting a transaction to the pool failed. - /// - /// This can only happen in the unsigned phase. PoolSubmissionFailed, + /// The pre-dispatch checks failed for the mined solution. + PreDispatchChecksFailed, } impl From for ElectionError { @@ -470,7 +467,8 @@ pub enum FeasibilityError { WrongWinnerCount, /// The snapshot is not available. /// - /// This must be an internal error of the chain. + /// Kinda defensive: The pallet should technically never attempt to do a feasibility check when + /// no snapshot is present. SnapshotUnavailable, /// Internal error from the election crate. NposElection(sp_npos_elections::Error), @@ -520,12 +518,12 @@ pub mod pallet { type SignedPhase: Get; /// The minimum amount of improvement to the solution score that defines a solution as - /// "better". + /// "better" (in any phase). #[pallet::constant] type SolutionImprovementThreshold: Get; /// The priority of the unsigned transaction submitted in the unsigned-phase - type UnsignedPriority: Get; + type MinerTxPriority: Get; /// Maximum number of iteration of balancing that will be executed in the embedded miner of /// the pallet. type MinerMaxIterations: Get; @@ -586,15 +584,17 @@ pub mod pallet { { let (need_snapshot, enabled, additional) = if current_phase == Phase::Signed { // followed by a signed phase: close the signed phase, no need for snapshot. - // TODO + // NOTE: SIGNED_PHASE (false, true, Weight::zero()) } else { - // no signed phase + // no signed phase: create a new snapshot, definitely `enable` the unsigned + // phase. (true, true, Weight::zero()) }; Self::on_initialize_open_unsigned(need_snapshot, enabled, now); log!(info, "Starting unsigned phase({}) at #{:?}.", enabled, now); + let base_weight = if need_snapshot { T::WeightInfo::on_initialize_open_unsigned_with_snapshot() } else { @@ -616,7 +616,7 @@ pub mod pallet { } Err(e) => log!(error, "error while submitting transaction in OCW: {:?}", e), }, - Err(why) => log!(error, "Error in unsigned offchain worker: {:?}", why), + Err(why) => log!(warn, "denied offchain worker: {:?}", why), } } } @@ -658,16 +658,16 @@ pub mod pallet { /// /// The dispatch origin fo this call must be __none__. /// - /// This submission is checked on the fly, thus it is likely yo be more limited and smaller. - /// Moreover, this unsigned solution is only validated when submitted to the pool from the - /// local process. Effectively, this means that only active validators can submit this - /// transaction when authoring a block. + /// This submission is checked on the fly. Moreover, this unsigned solution is only + /// validated when submitted to the pool from the **local** node. Effectively, this means + /// that only active validators can submit this transaction when authoring a block (similar + /// to an inherent). /// /// To prevent any incorrect solution (and thus wasted time/weight), this transaction will - /// panic if the solution submitted by the validator is invalid, effectively putting their - /// authoring reward at risk. + /// panic if the solution submitted by the validator is invalid in any way, effectively + /// putting their authoring reward at risk. /// - /// No deposit or reward is associated with this. + /// No deposit or reward is associated with this submission. #[pallet::weight(T::WeightInfo::submit_unsigned( witness.voters, witness.targets, @@ -683,8 +683,7 @@ pub mod pallet { let error_message = "Invalid unsigned submission must produce invalid block and \ deprive validator from their authoring reward."; - // check phase and score. - // NOTE: since we do this in pre-dispatch, we can just ignore it here. + // NOTE: since we do this in pre-dispatch, we _could_ just ignore it here. Self::unsigned_pre_dispatch_checks(&solution).expect(error_message); // ensure witness was correct. @@ -733,6 +732,7 @@ pub mod pallet { UnsignedPhaseStarted(u32), } + /// Error of the pallet that can be returned in response to dispatches. #[pallet::error] pub enum Error { /// Submission was too early. @@ -781,15 +781,14 @@ pub mod pallet { ValidTransaction::with_tag_prefix("OffchainElection") // The higher the score[0], the better a solution is. .priority( - T::UnsignedPriority::get() - .saturating_add(solution.score[0].saturated_into()), + T::MinerTxPriority::get().saturating_add(solution.score[0].saturated_into()), ) // used to deduplicate unsigned solutions: each validator should produce one // solution per round at most, and solutions are not propagate. .and_provides(solution.round) // transaction should stay in the pool for the duration of the unsigned phase. .longevity(T::UnsignedPhase::get().saturated_into::()) - // We don't propagate this. This can never the validated at a remote node. + // We don't propagate this. This can never be validated at a remote node. .propagate(false) .build() } else { @@ -816,7 +815,7 @@ pub mod pallet { /// Internal counter for the number of rounds. /// /// This is useful for de-duplication of transactions submitted to the pool, and general - /// diagnostics of the module. + /// diagnostics of the pallet. /// /// This is merely incremented once per every time that an upstream `elect` is called. #[pallet::storage] @@ -828,7 +827,7 @@ pub mod pallet { #[pallet::getter(fn current_phase)] pub type CurrentPhase = StorageValue<_, Phase, ValueQuery>; - /// Current best solution, signed or unsigned. + /// Current best solution, signed or unsigned, queued to be returned upon `elect`. #[pallet::storage] #[pallet::getter(fn queued_solution)] pub type QueuedSolution = StorageValue<_, ReadySolution>; @@ -867,7 +866,7 @@ where /// Logic for `::on_initialize` when signed phase is being opened. /// /// This is decoupled for easy weight calculation. - pub fn on_initialize_open_signed() { + pub(crate) fn on_initialize_open_signed() { >::put(Phase::Signed); Self::create_snapshot(); Self::deposit_event(Event::SignedPhaseStarted(Self::round())); @@ -877,7 +876,7 @@ where /// /// This is decoupled for easy weight calculation. Note that the default weight benchmark of /// this function will assume an empty signed queue for `finalize_signed_phase`. - pub fn on_initialize_open_unsigned( + pub(crate) fn on_initialize_open_unsigned( need_snapshot: bool, enabled: bool, now: T::BlockNumber, @@ -888,7 +887,6 @@ where Self::create_snapshot(); } - // for now always start the unsigned phase. >::put(Phase::Unsigned((enabled, now))); Self::deposit_event(Event::UnsignedPhaseStarted(Self::round())); } @@ -898,7 +896,7 @@ where /// 1. [`SnapshotMetadata`] /// 2. [`RoundSnapshot`] /// 3. [`DesiredTargets`] - pub fn create_snapshot() { + pub(crate) fn create_snapshot() { // if any of them don't exist, create all of them. This is a bit conservative. let targets = T::DataProvider::targets(); let voters = T::DataProvider::voters(); @@ -912,15 +910,14 @@ where >::put(RoundSnapshot { voters, targets }); } + /// Kill everything created by [`Pallet::create_snapshot`]. + pub(crate) fn kill_snapshot() { + >::kill(); + >::kill(); + >::kill(); + } + /// Checks the feasibility of a solution. - /// - /// This checks the solution for the following: - /// - /// 0. **all** of the used indices must be correct. - /// 1. present correct number of winners. - /// 2. any assignment is checked to match with [Snapshot::voters]. - /// 3. for each assignment, the check of `ElectionDataProvider` is also examined. - /// 4. the claimed score is valid. fn feasibility_check( solution: RawSolution>, compute: ElectionCompute, @@ -938,7 +935,7 @@ where // NOTE: this is a bit of duplicate, but we keep it around for veracity. The unsigned path // already checked this in `unsigned_per_dispatch_checks`. The signed path *could* check it - // upon arrival. + // upon arrival, thus we would then remove it here. Given overlay it is cheap anyhow ensure!(winners.len() as u32 == desired_targets, FeasibilityError::WrongWinnerCount,); // read the entire snapshot. @@ -957,8 +954,7 @@ where .map(|i| target_at(i).ok_or(FeasibilityError::InvalidWinner)) .collect::, FeasibilityError>>()?; - // Then convert compact -> Assignment. This will fail if any of the indices are gibberish. - // that winner indices are already checked. + // Then convert compact -> assignment. This will fail if any of the indices are gibberish. let assignments = compact .into_assignment(voter_at, target_at) .map_err::(Into::into)?; @@ -993,6 +989,7 @@ where // This might fail if the normalization fails. Very unlikely. See `integrity_test`. let staked_assignments = assignment_ratio_to_staked_normalized(assignments, stake_of) .map_err::(Into::into)?; + // This might fail if one of the voter edges is pointing to a non-winner, which is not // really possible anymore because all the winners come from the same `compact`. let supports = sp_npos_elections::to_supports(&winners, &staked_assignments) @@ -1018,9 +1015,7 @@ where >::put(Phase::Off); // kill snapshots - >::kill(); - >::kill(); - >::kill(); + Self::kill_snapshot(); } /// On-chain fallback of election. @@ -1036,10 +1031,6 @@ where } fn do_elect() -> Result, ElectionError> { - // NOTE: SignedSubmission is guaranteed to be drained by the end of the signed phase too, - // thus no need for a manual cleanup: - // TODO - // debug_assert!(Self::signed_submissions().is_empty()); >::take() .map_or_else( || match T::Fallback::get() { @@ -1073,7 +1064,6 @@ where fn elect() -> Result, Self::Error> { let outcome = Self::do_elect(); - // cleanup. Self::post_elect(); outcome } @@ -1144,7 +1134,7 @@ mod feasibility_check { assert_noop!( TwoPhase::feasibility_check(solution, COMPUTE), - FeasibilityError::WrongWinnerCount + FeasibilityError::WrongWinnerCount, ); }) } diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 71102f3e6e526..6fcebe7f08eaf 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -93,11 +93,6 @@ pub fn roll_to_with_ocw(n: u64) { } } -/// Get the free and reserved balance of some account. -pub fn balances(who: &AccountId) -> (Balance, Balance) { - (Balances::free_balance(who), Balances::reserved_balance(who)) -} - /// Spit out a verifiable raw solution. /// /// This is a good example of what an offchain miner would do. @@ -204,7 +199,7 @@ parameter_types! { pub static MaxSignedSubmissions: u32 = 5; pub static MinerMaxIterations: u32 = 5; - pub static UnsignedPriority: u64 = 100; + pub static MinerTxPriority: u64 = 100; pub static SolutionImprovementThreshold: Perbill = Perbill::zero(); pub static MinerMaxWeight: Weight = BlockWeights::get().max_block; pub static MockWeightInfo: bool = false; @@ -272,7 +267,7 @@ impl crate::Config for Runtime { type SolutionImprovementThreshold = SolutionImprovementThreshold; type MinerMaxIterations = MinerMaxIterations; type MinerMaxWeight = MinerMaxWeight; - type UnsignedPriority = UnsignedPriority; + type MinerTxPriority = MinerTxPriority; type DataProvider = StakingMock; type WeightInfo = DualMockWeightInfo; type BenchmarkingConfig = (); @@ -311,8 +306,8 @@ impl ElectionDataProvider for StakingMock { } impl ExtBuilder { - pub fn unsigned_priority(self, p: u64) -> Self { - ::set(p); + pub fn miner_tx_priority(self, p: u64) -> Self { + ::set(p); self } pub fn solution_improvement_threshold(self, p: Perbill) -> Self { diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index e922dc32dfa34..4907d3f0e761a 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -20,14 +20,20 @@ use crate::*; use frame_support::dispatch::DispatchResult; use frame_system::offchain::SubmitTransaction; -use sp_npos_elections::{seq_phragmen, CompactSolution, ElectionResult}; +use sp_npos_elections::{ + seq_phragmen, CompactSolution, ElectionResult, assignment_ratio_to_staked_normalized, reduce, + assignment_staked_to_ratio_normalized, +}; use sp_runtime::{offchain::storage::StorageValueRef, traits::TrailingZeroInput}; use sp_std::cmp::Ordering; /// Storage key used to store the persistent offchain worker status. pub(crate) const OFFCHAIN_HEAD_DB: &[u8] = b"parity/multi-phase-unsigned-election/"; + /// The repeat threshold of the offchain worker. This means we won't run the offchain worker twice /// within a window of 5 blocks. +// TODO: this should go into config, and we should store the solution an repeat with this threshold +// until we can submit it, or if the election happened. Okay for now though pub(crate) const OFFCHAIN_REPEAT: u32 = 5; impl Pallet @@ -35,7 +41,7 @@ where ExtendedBalance: From>>, ExtendedBalance: From>>, { - /// Min a new npos solution. + /// Mine a new npos solution. pub fn mine_solution( iters: usize, ) -> Result<(RawSolution>, SolutionSize), ElectionError> { @@ -50,14 +56,15 @@ where Some((iters, 0)), ) .map_err(Into::into) - .and_then(|election_result| { - if election_result.winners.len() as u32 == desired_targets { - Ok(election_result) - } else { - Err(ElectionError::Feasibility(FeasibilityError::WrongWinnerCount)) - } - }) .and_then(Self::prepare_election_result) + .and_then(|(raw_solution, size)| { + Self::unsigned_pre_dispatch_checks(&raw_solution) + .map_err(|e| { + log!(warn, "pre-disaptch-checks failed for mined solution: {:?}", e); + ElectionError::PreDispatchChecksFailed + })?; + Ok((raw_solution, size)) + }) } /// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which @@ -67,6 +74,9 @@ where pub fn prepare_election_result( election_result: ElectionResult>, ) -> Result<(RawSolution>, SolutionSize), ElectionError> { + // NOTE: This code path is generally not optimized as it is run offchain. Could use some at + // some point though. + // storage items. Note: we have already read this from storage, they must be in cache. let RoundSnapshot { voters, targets } = Self::snapshot().ok_or(ElectionError::SnapshotUnAvailable)?; @@ -83,13 +93,12 @@ where let ElectionResult { assignments, winners } = election_result; // convert to staked and reduce. - let mut staked = - sp_npos_elections::assignment_ratio_to_staked_normalized(assignments, &stake_of) - .map_err::(Into::into)?; + let mut staked = assignment_ratio_to_staked_normalized(assignments, &stake_of) + .map_err::(Into::into)?; sp_npos_elections::reduce(&mut staked); // convert back to ration and make compact. - let ratio = sp_npos_elections::assignment_staked_to_ratio_normalized(staked)?; + let ratio = assignment_staked_to_ratio_normalized(staked)?; let compact = >::from_assignment(ratio, &voter_index, &target_index)?; let size = SolutionSize { voters: voters.len() as u32, targets: targets.len() as u32 }; @@ -116,7 +125,8 @@ where /// Get a random number of iterations to run the balancing in the OCW. /// - /// Uses the offchain seed to generate a random number, maxed with `T::MinerMaxIterations`. + /// Uses the offchain seed to generate a random number, maxed with + /// [`Config::MinerMaxIterations`]. pub fn get_balancing_iters() -> usize { match T::MinerMaxIterations::get() { 0 => 0, @@ -315,11 +325,18 @@ where .map_err(|_| ElectionError::PoolSubmissionFailed) } + /// Do the basics checks that MUST happen during the validation and pre-dispatch of an unsigned + /// transaction. + /// + /// Can optionally also be called during dispatch, if needed. + /// + /// NOTE: Ideally, these tests should move more and more outside of this and more to the miner's + /// code, so that we do less and less storage reads here. pub(crate) fn unsigned_pre_dispatch_checks( solution: &RawSolution>, ) -> DispatchResult { // ensure solution is timely. Don't panic yet. This is a cheap check. - ensure!(Self::current_phase().is_unsigned_open(), Error::::EarlySubmission); + ensure!(Self::current_phase().is_unsigned_open(), Error::::EarlySubmission,); // ensure correct number of winners. ensure!( @@ -335,7 +352,7 @@ where q.score, T::SolutionImprovementThreshold::get() )), - Error::::WeakSubmission + Error::::WeakSubmission, ); Ok(()) @@ -476,6 +493,19 @@ mod tests { ) .is_ok()); assert!(::pre_dispatch(&call).is_ok()); + + // unsigned -- but not enabled. + >::put(Phase::Unsigned((false, 25))); + assert!(TwoPhase::current_phase().is_unsigned()); + assert!(matches!( + ::validate_unsigned(TransactionSource::Local, &call) + .unwrap_err(), + TransactionValidityError::Invalid(InvalidTransaction::Custom(0)) + )); + assert!(matches!( + ::pre_dispatch(&call).unwrap_err(), + TransactionValidityError::Invalid(InvalidTransaction::Custom(0)) + )); }) } @@ -534,7 +564,7 @@ mod tests { #[test] fn priority_is_set() { - ExtBuilder::default().unsigned_priority(20).desired_targets(0).build_and_execute(|| { + ExtBuilder::default().miner_tx_priority(20).desired_targets(0).build_and_execute(|| { roll_to(25); assert!(TwoPhase::current_phase().is_unsigned()); @@ -650,7 +680,7 @@ mod tests { // mine seq_phragmen solution with 2 iters. assert_eq!( TwoPhase::mine_solution(2).unwrap_err(), - ElectionError::Feasibility(FeasibilityError::WrongWinnerCount), + ElectionError::PreDispatchChecksFailed, ); }) } From 75eca3f450076eee1e022945ce928a57e496412f Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 27 Jan 2021 09:33:39 +0000 Subject: [PATCH 06/29] Make it all build --- bin/node/runtime/src/lib.rs | 2 +- .../src/benchmarking.rs | 18 +--- .../src/helpers.rs | 60 +++---------- .../election-provider-multi-phase/src/lib.rs | 90 +++++++------------ .../election-provider-multi-phase/src/mock.rs | 2 +- .../src/unsigned.rs | 26 ++---- frame/staking/src/lib.rs | 5 +- 7 files changed, 62 insertions(+), 141 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 636e0b4b784cc..0636817e456d4 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -530,7 +530,7 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type SolutionImprovementThreshold = MinSolutionScoreBump; type MinerMaxIterations = MinerMaxIterations; type MinerMaxWeight = MinerMaxWeight; - type UnsignedPriority = TwoPhaseUnsignedPriority; + type MinerTxPriority = TwoPhaseUnsignedPriority; type DataProvider = Staking; type OnChainAccuracy = Perbill; type CompactSolution = pallet_staking::CompactAssignments; diff --git a/frame/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs index a7a4eed852850..4cd6bdf60a4ef 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -25,9 +25,8 @@ use frame_support::{assert_ok, traits::OnInitialize}; use frame_system::RawOrigin; use rand::{prelude::SliceRandom, rngs::SmallRng, SeedableRng}; use sp_election_providers::Assignment; -use sp_npos_elections::ExtendedBalance; -use sp_runtime::InnerOf; use sp_arithmetic::traits::One; +use sp_runtime::InnerOf; use sp_std::convert::TryInto; const SEED: u32 = 0; @@ -39,12 +38,7 @@ fn solution_with_size( size: SolutionSize, active_voters_count: u32, desired_targets: u32, -) -> RawSolution> -where - ExtendedBalance: From>>, - ExtendedBalance: From>>, - > as sp_std::convert::TryFrom>::Error: sp_std::fmt::Debug, -{ +) -> RawSolution> { assert!(size.targets >= desired_targets, "must have enough targets"); assert!( size.targets >= (>::LIMIT * 2) as u32, @@ -129,7 +123,7 @@ where .iter() .map(|(voter, _stake, votes)| { let percent_per_edge: InnerOf> = - (100 / votes.len()).try_into().unwrap(); + (100 / votes.len()).try_into().unwrap_or_else(|_| panic!("failed to convert")); Assignment { who: voter.clone(), distribution: votes @@ -148,12 +142,6 @@ where } benchmarks! { - where_clause { - where ExtendedBalance: From>>, - > as sp_std::convert::TryFrom>::Error: sp_std::fmt::Debug, - ExtendedBalance: From>>, - } - on_initialize_nothing { assert!(>::current_phase().is_off()); }: { diff --git a/frame/election-provider-multi-phase/src/helpers.rs b/frame/election-provider-multi-phase/src/helpers.rs index da4a092653ae7..9b2f817f1ca09 100644 --- a/frame/election-provider-multi-phase/src/helpers.rs +++ b/frame/election-provider-multi-phase/src/helpers.rs @@ -17,11 +17,7 @@ //! Some helper functions/macros for this crate. -use super::{ - Config, VoteWeight, CompactVoterIndexOf, CompactTargetIndexOf, CompactAccuracyOf, - OnChainAccuracyOf, ExtendedBalance, -}; -use sp_runtime::InnerOf; +use super::{Config, VoteWeight, CompactVoterIndexOf, CompactTargetIndexOf}; use sp_std::{collections::btree_map::BTreeMap, convert::TryInto, boxed::Box, prelude::*}; #[macro_export] @@ -39,11 +35,7 @@ macro_rules! log { /// This can be used to efficiently build index getter closures. pub fn generate_voter_cache( snapshot: &Vec<(T::AccountId, VoteWeight, Vec)>, -) -> BTreeMap -where - ExtendedBalance: From>>, - ExtendedBalance: From>>, -{ +) -> BTreeMap { let mut cache: BTreeMap = BTreeMap::new(); snapshot.iter().enumerate().for_each(|(i, (x, _, _))| { let _existed = cache.insert(x.clone(), i); @@ -64,11 +56,7 @@ where /// The snapshot must be the same is the one used to create `cache`. pub fn voter_index_fn( cache: &BTreeMap, -) -> Box Option> + '_> -where - ExtendedBalance: From>>, - ExtendedBalance: From>>, -{ +) -> Box Option> + '_> { Box::new(move |who| { cache.get(who).and_then(|i| >>::try_into(*i).ok()) }) @@ -81,11 +69,7 @@ where /// The snapshot must be the same is the one used to create `cache`. pub fn voter_index_fn_usize( cache: &BTreeMap, -) -> Box Option + '_> -where - ExtendedBalance: From>>, - ExtendedBalance: From>>, -{ +) -> Box Option + '_> { Box::new(move |who| cache.get(who).cloned()) } @@ -97,11 +81,7 @@ where /// Not meant to be used in production. pub fn voter_index_fn_linear( snapshot: &Vec<(T::AccountId, VoteWeight, Vec)>, -) -> Box Option> + '_> -where - ExtendedBalance: From>>, - ExtendedBalance: From>>, -{ +) -> Box Option> + '_> { Box::new(move |who| { snapshot .iter() @@ -115,11 +95,7 @@ where /// The returning index type is the same as the one defined in [`T::CompactSolution::Target`]. pub fn target_index_fn_linear( snapshot: &Vec, -) -> Box Option> + '_> -where - ExtendedBalance: From>>, - ExtendedBalance: From>>, -{ +) -> Box Option> + '_> { Box::new(move |who| { snapshot .iter() @@ -132,11 +108,7 @@ where /// account using a linearly indexible snapshot. pub fn voter_at_fn( snapshot: &Vec<(T::AccountId, VoteWeight, Vec)>, -) -> Box) -> Option + '_> -where - ExtendedBalance: From>>, - ExtendedBalance: From>>, -{ +) -> Box) -> Option + '_> { Box::new(move |i| { as TryInto>::try_into(i) .ok() @@ -148,11 +120,7 @@ where /// account using a linearly indexible snapshot. pub fn target_at_fn( snapshot: &Vec, -) -> Box) -> Option + '_> -where - ExtendedBalance: From>>, - ExtendedBalance: From>>, -{ +) -> Box) -> Option + '_> { Box::new(move |i| { as TryInto>::try_into(i) .ok() @@ -165,11 +133,7 @@ where /// This is not optimized and uses a linear search. pub fn stake_of_fn_linear( snapshot: &Vec<(T::AccountId, VoteWeight, Vec)>, -) -> Box VoteWeight + '_> -where - ExtendedBalance: From>>, - ExtendedBalance: From>>, -{ +) -> Box VoteWeight + '_> { Box::new(move |who| { snapshot.iter().find(|(x, _, _)| x == who).map(|(_, x, _)| *x).unwrap_or_default() }) @@ -184,11 +148,7 @@ where pub fn stake_of_fn<'a, T: Config>( snapshot: &'a Vec<(T::AccountId, VoteWeight, Vec)>, cache: &'a BTreeMap, -) -> Box VoteWeight + 'a> -where - ExtendedBalance: From>>, - ExtendedBalance: From>>, -{ +) -> Box VoteWeight + 'a> { Box::new(move |who| { if let Some(index) = cache.get(who) { snapshot.get(*index).map(|(_, x, _)| x).cloned().unwrap_or_default() diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 3028ee9ee9b06..f528f37a82fbf 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -173,6 +173,10 @@ //! //! **Score based on (byte) size**: We should always prioritize small solutions over bigger ones, if //! there is a tie. Even more harsh should be to enforce the bound of the `reduce` algorithm. +//! +//! **Offchain resubmit**: Essentially port https://github.com/paritytech/substrate/pull/7976 to +//! this pallet as well. The `OFFCHAIN_REPEAT` also needs to become an adjustable parameter of the +//! pallet. #![cfg_attr(not(feature = "std"), no_std)] @@ -187,14 +191,14 @@ use frame_system::{ensure_none, offchain::SendTransactionTypes}; use sp_election_providers::{ElectionDataProvider, ElectionProvider, onchain}; use sp_npos_elections::{ assignment_ratio_to_staked_normalized, is_score_better, CompactSolution, ElectionScore, - EvaluateSupport, ExtendedBalance, PerThing128, Supports, VoteWeight, + EvaluateSupport, PerThing128, Supports, VoteWeight, }; use sp_runtime::{ transaction_validity::{ InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity, TransactionValidityError, ValidTransaction, }, - DispatchError, InnerOf, PerThing, Perbill, RuntimeDebug, SaturatedConversion, + DispatchError, PerThing, Perbill, RuntimeDebug, SaturatedConversion, }; use sp_std::prelude::*; use sp_arithmetic::{ @@ -229,15 +233,8 @@ pub type CompactAccuracyOf = as CompactSolution>::Accuracy; pub type OnChainAccuracyOf = ::OnChainAccuracy; /// Wrapper type that implements the configurations needed for the on-chain backup. -struct OnChainConfig(sp_std::marker::PhantomData) -where - ExtendedBalance: From>>, - ExtendedBalance: From>>; -impl onchain::Config for OnChainConfig -where - ExtendedBalance: From>>, - ExtendedBalance: From>>, -{ +struct OnChainConfig(sp_std::marker::PhantomData); +impl onchain::Config for OnChainConfig { type AccountId = T::AccountId; type BlockNumber = T::BlockNumber; type Accuracy = T::OnChainAccuracy; @@ -498,11 +495,7 @@ pub mod pallet { use frame_system::pallet_prelude::*; #[pallet::config] - pub trait Config: frame_system::Config + SendTransactionTypes> - where - ExtendedBalance: From>>, - ExtendedBalance: From>>, - { + pub trait Config: frame_system::Config + SendTransactionTypes> { type Event: From> + Into<::Event> + IsType<::Event>; @@ -559,11 +552,7 @@ pub mod pallet { } #[pallet::hooks] - impl Hooks> for Pallet - where - ExtendedBalance: From>>, - ExtendedBalance: From>>, - { + impl Hooks> for Pallet { fn on_initialize(now: T::BlockNumber) -> Weight { let next_election = T::DataProvider::next_election_prediction(now).max(now); @@ -609,7 +598,7 @@ pub mod pallet { fn offchain_worker(n: T::BlockNumber) { // We only run the OCW in the fist block of the unsigned phase. if Self::current_phase().is_unsigned_open_at(n) { - match Self::set_check_offchain_execution_status(n) { + match Self::try_acquire_offchain_lock(n) { Ok(_) => match Self::mine_and_submit() { Ok(_) => { log!(info, "successfully submitted a solution via OCW at block {:?}", n) @@ -633,15 +622,25 @@ pub mod pallet { let max_vote: usize = as CompactSolution>::LIMIT; // 1. Maximum sum of [ChainAccuracy; 16] must fit into `UpperOf`.. - let maximum_chain_accuracy: Vec>> = - (0..max_vote).map(|_| >::one().deconstruct().into()).collect(); + let maximum_chain_accuracy: Vec>> = (0..max_vote) + .map(|_| { + >>::from( + >::one().deconstruct(), + ) + }) + .collect(); let _: UpperOf> = maximum_chain_accuracy .iter() .fold(Zero::zero(), |acc, x| acc.checked_add(x).unwrap()); // 2. Maximum sum of [CompactAccuracy; 16] must fit into `UpperOf`. - let maximum_chain_accuracy: Vec>> = - (0..max_vote).map(|_| >::one().deconstruct().into()).collect(); + let maximum_chain_accuracy: Vec>> = (0..max_vote) + .map(|_| { + >>::from( + >::one().deconstruct(), + ) + }) + .collect(); let _: UpperOf> = maximum_chain_accuracy .iter() .fold(Zero::zero(), |acc, x| acc.checked_add(x).unwrap()); @@ -649,11 +648,7 @@ pub mod pallet { } #[pallet::call] - impl Pallet - where - ExtendedBalance: From>>, - ExtendedBalance: From>>, - { + impl Pallet { /// Submit a solution for the unsigned phase. /// /// The dispatch origin fo this call must be __none__. @@ -709,11 +704,7 @@ pub mod pallet { #[pallet::event] #[pallet::metadata(::AccountId = "AccountId")] #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event - where - ExtendedBalance: From>>, - ExtendedBalance: From>>, - { + pub enum Event { /// A solution was stored with the given compute. /// /// If the solution is signed, this means that it hasn't yet been processed. If the @@ -755,11 +746,7 @@ pub mod pallet { pub struct Origin(PhantomData); #[pallet::validate_unsigned] - impl ValidateUnsigned for Pallet - where - ExtendedBalance: From>>, - ExtendedBalance: From>>, - { + impl ValidateUnsigned for Pallet { type Call = Call; fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity { if let Call::submit_unsigned(solution, _) = call { @@ -781,7 +768,9 @@ pub mod pallet { ValidTransaction::with_tag_prefix("OffchainElection") // The higher the score[0], the better a solution is. .priority( - T::MinerTxPriority::get().saturating_add(solution.score[0].saturated_into()), + T::MinerTxPriority::get().saturating_add( + solution.score[0].saturated_into() + ), ) // used to deduplicate unsigned solutions: each validator should produce one // solution per round at most, and solutions are not propagate. @@ -858,11 +847,7 @@ pub mod pallet { pub struct Pallet(PhantomData); } -impl Pallet -where - ExtendedBalance: From>>, - ExtendedBalance: From>>, -{ +impl Pallet { /// Logic for `::on_initialize` when signed phase is being opened. /// /// This is decoupled for easy weight calculation. @@ -1019,10 +1004,7 @@ where } /// On-chain fallback of election. - fn onchain_fallback() -> Result, ElectionError> - where - ExtendedBalance: From<::Inner>, - { + fn onchain_fallback() -> Result, ElectionError> { > as ElectionProvider< T::AccountId, T::BlockNumber, @@ -1054,11 +1036,7 @@ where } } -impl ElectionProvider for Pallet -where - ExtendedBalance: From>>, - ExtendedBalance: From>>, -{ +impl ElectionProvider for Pallet { type Error = ElectionError; type DataProvider = T::DataProvider; diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 6fcebe7f08eaf..8f9c6b58d4268 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -38,7 +38,7 @@ use sp_npos_elections::{ }; use sp_runtime::{ testing::Header, - traits::{BlakeTwo256, Block as BlockT, IdentityLookup}, + traits::{BlakeTwo256, IdentityLookup}, PerU16, }; use std::sync::Arc; diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index 4907d3f0e761a..01a9fe3d6e4f0 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -21,7 +21,7 @@ use crate::*; use frame_support::dispatch::DispatchResult; use frame_system::offchain::SubmitTransaction; use sp_npos_elections::{ - seq_phragmen, CompactSolution, ElectionResult, assignment_ratio_to_staked_normalized, reduce, + seq_phragmen, CompactSolution, ElectionResult, assignment_ratio_to_staked_normalized, assignment_staked_to_ratio_normalized, }; use sp_runtime::{offchain::storage::StorageValueRef, traits::TrailingZeroInput}; @@ -32,15 +32,9 @@ pub(crate) const OFFCHAIN_HEAD_DB: &[u8] = b"parity/multi-phase-unsigned-electio /// The repeat threshold of the offchain worker. This means we won't run the offchain worker twice /// within a window of 5 blocks. -// TODO: this should go into config, and we should store the solution an repeat with this threshold -// until we can submit it, or if the election happened. Okay for now though pub(crate) const OFFCHAIN_REPEAT: u32 = 5; -impl Pallet -where - ExtendedBalance: From>>, - ExtendedBalance: From>>, -{ +impl Pallet { /// Mine a new npos solution. pub fn mine_solution( iters: usize, @@ -279,9 +273,7 @@ where /// don't run twice within a window of length [`OFFCHAIN_REPEAT`]. /// /// Returns `Ok(())` if offchain worker should happen, `Err(reason)` otherwise. - pub(crate) fn set_check_offchain_execution_status( - now: T::BlockNumber, - ) -> Result<(), &'static str> { + pub(crate) fn try_acquire_offchain_lock(now: T::BlockNumber) -> Result<(), &'static str> { let storage = StorageValueRef::persistent(&OFFCHAIN_HEAD_DB); let threshold = T::BlockNumber::from(OFFCHAIN_REPEAT); @@ -762,25 +754,25 @@ mod tests { assert!(TwoPhase::current_phase().is_unsigned()); // first execution -- okay. - assert!(TwoPhase::set_check_offchain_execution_status(25).is_ok()); + assert!(TwoPhase::try_acquire_offchain_lock(25).is_ok()); // next block: rejected. - assert!(TwoPhase::set_check_offchain_execution_status(26).is_err()); + assert!(TwoPhase::try_acquire_offchain_lock(26).is_err()); // allowed after `OFFCHAIN_REPEAT` - assert!(TwoPhase::set_check_offchain_execution_status((26 + OFFCHAIN_REPEAT).into()) + assert!(TwoPhase::try_acquire_offchain_lock((26 + OFFCHAIN_REPEAT).into()) .is_ok()); // a fork like situation: re-execute last 3. - assert!(TwoPhase::set_check_offchain_execution_status( + assert!(TwoPhase::try_acquire_offchain_lock( (26 + OFFCHAIN_REPEAT - 3).into() ) .is_err()); - assert!(TwoPhase::set_check_offchain_execution_status( + assert!(TwoPhase::try_acquire_offchain_lock( (26 + OFFCHAIN_REPEAT - 2).into() ) .is_err()); - assert!(TwoPhase::set_check_offchain_execution_status( + assert!(TwoPhase::try_acquire_offchain_lock( (26 + OFFCHAIN_REPEAT - 1).into() ) .is_err()); diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 685b9fd380293..f7ea7d5f98b58 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -3381,7 +3381,10 @@ impl sp_election_providers::ElectionDataProvider, ) { targets.into_iter().for_each(|v| { - >::insert(v, ValidatorPrefs { commission: Perbill::zero() }); + >::insert( + v, + ValidatorPrefs { commission: Perbill::zero(), blocked: false }, + ); }); voters.into_iter().for_each(|(v, _s, t)| { From 8daec3a9e38a5ebe717a5c71f673fb35afd1ae9e Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 27 Jan 2021 10:14:56 +0000 Subject: [PATCH 07/29] self-review --- .../src/benchmarking.rs | 12 +- .../election-provider-multi-phase/src/lib.rs | 127 ++++++++---------- .../election-provider-multi-phase/src/mock.rs | 4 +- .../src/unsigned.rs | 67 +++++---- 4 files changed, 106 insertions(+), 104 deletions(-) diff --git a/frame/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs index 4cd6bdf60a4ef..98a874967fc03 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -35,7 +35,7 @@ const SEED: u32 = 0; /// /// The snapshot is also created internally. fn solution_with_size( - size: SolutionSize, + size: SolutionOrSnapshotSize, active_voters_count: u32, desired_targets: u32, ) -> RawSolution> { @@ -102,9 +102,9 @@ fn solution_with_size( assert_eq!(all_voters.len() as u32, size.voters); assert_eq!(winners.len() as u32, desired_targets); - >::put(RoundSnapshotMetadata { - voters_len: all_voters.len() as u32, - targets_len: targets.len() as u32, + >::put(SolutionOrSnapshotSize { + voters: all_voters.len() as u32, + targets: targets.len() as u32, }); >::put(desired_targets); >::put(RoundSnapshot { voters: all_voters.clone(), targets: targets.clone() }); @@ -205,7 +205,7 @@ benchmarks! { // number of desired targets. Must be a subset of `t` component. let d in (T::BenchmarkingConfig::DESIRED_TARGETS[0]) .. T::BenchmarkingConfig::DESIRED_TARGETS[1]; - let witness = SolutionSize { voters: v, targets: t }; + let witness = SolutionOrSnapshotSize { voters: v, targets: t }; let raw_solution = solution_with_size::(witness, a, d); assert!(>::queued_solution().is_none()); @@ -227,7 +227,7 @@ benchmarks! { // number of desired targets. Must be a subset of `t` component. let d in (T::BenchmarkingConfig::DESIRED_TARGETS[0]) .. T::BenchmarkingConfig::DESIRED_TARGETS[1]; - let size = SolutionSize { voters: v, targets: t }; + let size = SolutionOrSnapshotSize { voters: v, targets: t }; let raw_solution = solution_with_size::(size, a, d); assert_eq!(raw_solution.compact.voter_count() as u32, a); diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index f528f37a82fbf..567b2aadb1de8 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -44,7 +44,6 @@ //! Each of the phases can be disabled by essentially setting their length to zero. If both phases //! have length zero, then the pallet essentially runs only the fallback strategy, denoted by //! [`Config::FallbackStrategy`]. -//! //! ### Signed Phase //! //! In the signed phase, solutions (of type [`RawSolution`]) are submitted and queued on chain. A @@ -137,6 +136,24 @@ //! possible, reducing solutions size/weight. The on-chain solution can use more space for accuracy, //! but should still be fast to prevent massively large blocks in case of a fallback. //! +//! ## Error types +//! +//! This pallet provides a verbose error system to ease future debugging and debugging. The +//! overall hierarchy of errors is as follows: +//! +//! 1. [`pallet::Error`]: These are the errors that can be returned in the dispatchables of the +//! pallet, either signed or unsigned. Since decomposition with nested enums is not possible +//! here, they are prefixed with the logical sub-system to which they belong. +//! 2. [`ElectionError`]: These are the errors that can be generated while the pallet is doing +//! something in automatic scenarios, such as `offchain_worker` or `on_initialize`. These errors +//! are helpful for logging and are thus nested as: +//! - [`ElectionError::Miner`]: wraps a [`unsigned::MinerError`]. +//! - [`ElectionError::Feasibility`]: wraps a [`FeasibilityError`]. +//! - [`ElectionError::OnChainFallback`]: wraps a [`sp_election_providers::onchain::Error`]. +//! +//! Note that there could be an overlap between these sub-errors. For example, A +//! `SnapshotUnavailable` can happen in both miner and feasibility check phase. +//! //! ## Future Plans //! //! **Challenge Phase**. We plan adding a third phase to the pallet, called the challenge phase. @@ -374,23 +391,6 @@ pub struct ReadySolution { compute: ElectionCompute, } -/// Size of the snapshot from which the solution was derived. -/// -/// This is needed for proper weight calculation. -#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, Default)] -pub struct SolutionSize { - /// Number of all voters. - /// - /// This must match the on-chain snapshot. - #[codec(compact)] - voters: u32, - /// Number of all targets. - /// - /// This must match the on-chain snapshot. - #[codec(compact)] - targets: u32, -} - /// A snapshot of all the data that is needed for en entire round. They are provided by /// [`ElectionDataProvider`] and are kept around until the round is finished. /// @@ -403,40 +403,34 @@ pub struct RoundSnapshot { pub targets: Vec, } -/// Some metadata related to snapshot. +/// Encodes the length of a solution or a snapshot. /// -/// In this pallet, there are cases where we want to read the whole snapshot (voters, targets, -/// desired), and cases that we are interested in just the length of these values. The former favors -/// the snapshot to be stored in one struct (as it is now) while the latter prefers them to be -/// separate to enable the use of `decode_len`. This approach is a middle ground, storing the -/// snapshot as one struct, whilst storing the lengths separately. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default)] -pub struct RoundSnapshotMetadata { +/// This is stored automatically on-chain, and it contains the **size of the entire snapshot**. +/// This is also used in dispatchables as weight witness data and should **only contain the size of +/// the presented solution**, not the entire snapshot. +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, Default)] +pub struct SolutionOrSnapshotSize { /// The length of voters. - voters_len: u32, + #[codec(compact)] + voters: u32, /// The length of targets. - targets_len: u32, + #[codec(compact)] + targets: u32, } /// Internal errors of the pallet. /// /// Note that this is different from [`pallet::Error`]. -#[derive(RuntimeDebug, Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq)] pub enum ElectionError { - /// A feasibility error. + /// An error happened in the feasibility check sub-system. Feasibility(FeasibilityError), + /// An error in the miner (offchain) sub-system. + Miner(unsigned::MinerError), /// An error in the on-chain fallback. OnChainFallback(onchain::Error), - /// No fallback is configured. + /// No fallback is configured. This is a special case. NoFallbackConfigured, - /// An internal error in the NPoS elections crate. - NposElections(sp_npos_elections::Error), - /// Snapshot data was unavailable unexpectedly. - SnapshotUnAvailable, - /// Submitting a transaction to the pool failed. - PoolSubmissionFailed, - /// The pre-dispatch checks failed for the mined solution. - PreDispatchChecksFailed, } impl From for ElectionError { @@ -445,20 +439,20 @@ impl From for ElectionError { } } -impl From for ElectionError { - fn from(e: sp_npos_elections::Error) -> Self { - ElectionError::NposElections(e) - } -} - impl From for ElectionError { fn from(e: FeasibilityError) -> Self { ElectionError::Feasibility(e) } } +impl From for ElectionError { + fn from(e: unsigned::MinerError) -> Self { + ElectionError::Miner(e) + } +} + /// Errors that can happen in the feasibility check. -#[derive(RuntimeDebug, Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq)] pub enum FeasibilityError { /// Wrong number of winners presented. WrongWinnerCount, @@ -599,12 +593,10 @@ pub mod pallet { // We only run the OCW in the fist block of the unsigned phase. if Self::current_phase().is_unsigned_open_at(n) { match Self::try_acquire_offchain_lock(n) { - Ok(_) => match Self::mine_and_submit() { - Ok(_) => { - log!(info, "successfully submitted a solution via OCW at block {:?}", n) - } - Err(e) => log!(error, "error while submitting transaction in OCW: {:?}", e), - }, + Ok(_) => { + let outcome = Self::mine_and_submit().map_err(ElectionError::from); + log!(info, "miner exeuction done: {:?}", outcome); + } Err(why) => log!(warn, "denied offchain worker: {:?}", why), } } @@ -672,7 +664,7 @@ pub mod pallet { pub fn submit_unsigned( origin: OriginFor, solution: RawSolution>, - witness: SolutionSize, + witness: SolutionOrSnapshotSize, ) -> DispatchResultWithPostInfo { ensure_none(origin)?; let error_message = "Invalid unsigned submission must produce invalid block and \ @@ -682,12 +674,12 @@ pub mod pallet { Self::unsigned_pre_dispatch_checks(&solution).expect(error_message); // ensure witness was correct. - let RoundSnapshotMetadata { voters_len, targets_len } = + let SolutionOrSnapshotSize { voters, targets } = Self::snapshot_metadata().expect(error_message); // NOTE: we are asserting, not `ensure`ing -- we want to panic here. - assert!(voters_len as u32 == witness.voters, error_message); - assert!(targets_len as u32 == witness.targets, error_message); + assert!(voters as u32 == witness.voters, error_message); + assert!(targets as u32 == witness.targets, error_message); let ready = Self::feasibility_check(solution, ElectionCompute::Unsigned).expect(error_message); @@ -727,24 +719,15 @@ pub mod pallet { #[pallet::error] pub enum Error { /// Submission was too early. - EarlySubmission, + PreDispatchEarlySubmission, /// Wrong number of winners presented. - WrongWinnerCount, + PreDispatchWrongWinnerCount, /// Submission was too weak, score-wise. - WeakSubmission, - /// The queue was full, and the solution was not better than any of the existing ones. - QueueFull, - /// The origin failed to pay the deposit. - CannotPayDeposit, - /// witness data to dispatchable is invalid. - InvalidWitness, - /// The signed submission consumes too much weight - TooMuchWeight, + PreDispatchWeakSubmission, } #[pallet::origin] pub struct Origin(PhantomData); - #[pallet::validate_unsigned] impl ValidateUnsigned for Pallet { type Call = Call; @@ -840,7 +823,7 @@ pub mod pallet { /// Only exists when [`Snapshot`] is present. #[pallet::storage] #[pallet::getter(fn snapshot_metadata)] - pub type SnapshotMetadata = StorageValue<_, RoundSnapshotMetadata>; + pub type SnapshotMetadata = StorageValue<_, SolutionOrSnapshotSize>; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] @@ -887,9 +870,9 @@ impl Pallet { let voters = T::DataProvider::voters(); let desired_targets = T::DataProvider::desired_targets(); - >::put(RoundSnapshotMetadata { - voters_len: voters.len() as u32, - targets_len: targets.len() as u32, + >::put(SolutionOrSnapshotSize { + voters: voters.len() as u32, + targets: targets.len() as u32, }); >::put(desired_targets); >::put(RoundSnapshot { voters, targets }); diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 8f9c6b58d4268..402886703a1e7 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -127,9 +127,9 @@ pub fn raw_solution() -> RawSolution> { RawSolution { compact, score, round } } -pub fn witness() -> SolutionSize { +pub fn witness() -> SolutionOrSnapshotSize { TwoPhase::snapshot() - .map(|snap| SolutionSize { + .map(|snap| SolutionOrSnapshotSize { voters: snap.voters.len() as u32, targets: snap.targets.len() as u32, }) diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index 01a9fe3d6e4f0..a796986bd12c9 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -34,14 +34,32 @@ pub(crate) const OFFCHAIN_HEAD_DB: &[u8] = b"parity/multi-phase-unsigned-electio /// within a window of 5 blocks. pub(crate) const OFFCHAIN_REPEAT: u32 = 5; +#[derive(Debug, Eq, PartialEq)] +pub enum MinerError { + /// An internal error in the NPoS elections crate. + NposElections(sp_npos_elections::Error), + /// Snapshot data was unavailable unexpectedly. + SnapshotUnAvailable, + /// Submitting a transaction to the pool failed. + PoolSubmissionFailed, + /// The pre-dispatch checks failed for the mined solution. + PreDispatchChecksFailed, +} + +impl From for MinerError { + fn from(e: sp_npos_elections::Error) -> Self { + MinerError::NposElections(e) + } +} + impl Pallet { /// Mine a new npos solution. pub fn mine_solution( iters: usize, - ) -> Result<(RawSolution>, SolutionSize), ElectionError> { + ) -> Result<(RawSolution>, SolutionOrSnapshotSize), MinerError> { let RoundSnapshot { voters, targets } = - Self::snapshot().ok_or(ElectionError::SnapshotUnAvailable)?; - let desired_targets = Self::desired_targets().ok_or(ElectionError::SnapshotUnAvailable)?; + Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?; + let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?; seq_phragmen::<_, CompactAccuracyOf>( desired_targets as usize, @@ -55,7 +73,7 @@ impl Pallet { Self::unsigned_pre_dispatch_checks(&raw_solution) .map_err(|e| { log!(warn, "pre-disaptch-checks failed for mined solution: {:?}", e); - ElectionError::PreDispatchChecksFailed + MinerError::PreDispatchChecksFailed })?; Ok((raw_solution, size)) }) @@ -67,14 +85,14 @@ impl Pallet { /// Will always reduce the solution as well. pub fn prepare_election_result( election_result: ElectionResult>, - ) -> Result<(RawSolution>, SolutionSize), ElectionError> { + ) -> Result<(RawSolution>, SolutionOrSnapshotSize), MinerError> { // NOTE: This code path is generally not optimized as it is run offchain. Could use some at // some point though. // storage items. Note: we have already read this from storage, they must be in cache. let RoundSnapshot { voters, targets } = - Self::snapshot().ok_or(ElectionError::SnapshotUnAvailable)?; - let desired_targets = Self::desired_targets().ok_or(ElectionError::SnapshotUnAvailable)?; + Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?; + let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?; // closures. let cache = helpers::generate_voter_cache::(&voters); @@ -88,14 +106,15 @@ impl Pallet { // convert to staked and reduce. let mut staked = assignment_ratio_to_staked_normalized(assignments, &stake_of) - .map_err::(Into::into)?; + .map_err::(Into::into)?; sp_npos_elections::reduce(&mut staked); // convert back to ration and make compact. let ratio = assignment_staked_to_ratio_normalized(staked)?; let compact = >::from_assignment(ratio, &voter_index, &target_index)?; - let size = SolutionSize { voters: voters.len() as u32, targets: targets.len() as u32 }; + let size = + SolutionOrSnapshotSize { voters: voters.len() as u32, targets: targets.len() as u32 }; let maximum_allowed_voters = Self::maximum_voter_for_weight::( desired_targets, size, @@ -154,7 +173,7 @@ impl Pallet { maximum_allowed_voters: u32, mut compact: CompactOf, nominator_index: FN, - ) -> Result, ElectionError> + ) -> Result, MinerError> where for<'r> FN: Fn(&'r T::AccountId) -> Option>, { @@ -162,7 +181,7 @@ impl Pallet { Some(to_remove) if to_remove > 0 => { // grab all voters and sort them by least stake. let RoundSnapshot { voters, .. } = - Self::snapshot().ok_or(ElectionError::SnapshotUnAvailable)?; + Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?; let mut voters_sorted = voters .into_iter() .map(|(who, stake, _)| (who.clone(), stake)) @@ -175,7 +194,7 @@ impl Pallet { for (maybe_index, _stake) in voters_sorted.iter().map(|(who, stake)| (nominator_index(&who), stake)) { - let index = maybe_index.ok_or(ElectionError::SnapshotUnAvailable)?; + let index = maybe_index.ok_or(MinerError::SnapshotUnAvailable)?; if compact.remove_voter(index) { removed += 1 } @@ -199,7 +218,7 @@ impl Pallet { /// This only returns a value between zero and `size.nominators`. pub fn maximum_voter_for_weight( desired_winners: u32, - size: SolutionSize, + size: SolutionOrSnapshotSize, max_weight: Weight, ) -> u32 { if size.voters < 1 { @@ -306,7 +325,7 @@ impl Pallet { } /// Mine a new solution, and submit it back to the chain as an unsigned transaction. - pub(crate) fn mine_and_submit() -> Result<(), ElectionError> { + pub(crate) fn mine_and_submit() -> Result<(), MinerError> { let balancing = Self::get_balancing_iters(); let (raw_solution, witness) = Self::mine_solution(balancing)?; @@ -314,7 +333,7 @@ impl Pallet { let call = Call::submit_unsigned(raw_solution, witness).into(); SubmitTransaction::>::submit_unsigned_transaction(call) - .map_err(|_| ElectionError::PoolSubmissionFailed) + .map_err(|_| MinerError::PoolSubmissionFailed) } /// Do the basics checks that MUST happen during the validation and pre-dispatch of an unsigned @@ -328,13 +347,13 @@ impl Pallet { solution: &RawSolution>, ) -> DispatchResult { // ensure solution is timely. Don't panic yet. This is a cheap check. - ensure!(Self::current_phase().is_unsigned_open(), Error::::EarlySubmission,); + ensure!(Self::current_phase().is_unsigned_open(), Error::::PreDispatchEarlySubmission); // ensure correct number of winners. ensure!( Self::desired_targets().unwrap_or_default() == solution.compact.unique_targets().len() as u32, - Error::::WrongWinnerCount, + Error::::PreDispatchWrongWinnerCount, ); // ensure score is being improved. Panic henceforth. @@ -344,7 +363,7 @@ impl Pallet { q.score, T::SolutionImprovementThreshold::get() )), - Error::::WeakSubmission, + Error::::PreDispatchWeakSubmission, ); Ok(()) @@ -380,7 +399,7 @@ mod max_weight { #[test] fn find_max_voter_binary_search_works() { - let w = SolutionSize { voters: 10, targets: 0 }; + let w = SolutionOrSnapshotSize { voters: 10, targets: 0 }; assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 0), 0); assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1), 0); @@ -404,7 +423,7 @@ mod max_weight { assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 11_000), 10); assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 22_000), 10); - let w = SolutionSize { voters: 1, targets: 0 }; + let w = SolutionOrSnapshotSize { voters: 1, targets: 0 }; assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 0), 0); assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1), 0); @@ -418,7 +437,7 @@ mod max_weight { assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2010), 1); assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 3333), 1); - let w = SolutionSize { voters: 2, targets: 0 }; + let w = SolutionOrSnapshotSize { voters: 2, targets: 0 }; assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 0), 0); assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1), 0); @@ -576,7 +595,7 @@ mod tests { #[should_panic(expected = "Invalid unsigned submission must produce invalid block and \ deprive validator from their authoring reward.: \ DispatchError::Module { index: 0, error: 1, message: \ - Some(\"WrongWinnerCount\") }")] + Some(\"PreDispatchWrongWinnerCount\") }")] fn unfeasible_solution_panics() { ExtBuilder::default().build_and_execute(|| { roll_to(25); @@ -672,7 +691,7 @@ mod tests { // mine seq_phragmen solution with 2 iters. assert_eq!( TwoPhase::mine_solution(2).unwrap_err(), - ElectionError::PreDispatchChecksFailed, + MinerError::PreDispatchChecksFailed, ); }) } @@ -720,7 +739,7 @@ mod tests { assert_eq!(solution.score[0], 12); assert_noop!( TwoPhase::unsigned_pre_dispatch_checks(&solution), - Error::::WeakSubmission, + Error::::PreDispatchWeakSubmission, ); // submitting this will actually panic. From 49613edd1df346f6a82184d7075ecc228828cdf0 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 27 Jan 2021 10:42:35 +0000 Subject: [PATCH 08/29] Some doc tests. --- Cargo.lock | 1 - bin/node/runtime/Cargo.toml | 2 -- frame/election-provider-multi-phase/src/lib.rs | 8 +++++++- frame/staking/src/lib.rs | 2 +- frame/staking/src/tests.rs | 1 - primitives/npos-elections/src/tests.rs | 1 + 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a50ab923b0f04..91af7e22e2c19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3986,7 +3986,6 @@ dependencies = [ "sp-inherents", "sp-io", "sp-keyring", - "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-session", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index e1f87cd3cc6ca..96e110ea1ec50 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -35,7 +35,6 @@ sp-keyring = { version = "2.0.0", optional = true, path = "../../../primitives/k sp-session = { version = "2.0.0", default-features = false, path = "../../../primitives/session" } sp-transaction-pool = { version = "2.0.0", default-features = false, path = "../../../primitives/transaction-pool" } sp-version = { version = "2.0.0", default-features = false, path = "../../../primitives/version" } -sp-npos-elections = { version = "2.0.0", default-features = false, path = "../../../primitives/npos-elections" } # frame dependencies frame-executive = { version = "2.0.0", default-features = false, path = "../../../frame/executive" } @@ -116,7 +115,6 @@ std = [ "pallet-im-online/std", "pallet-indices/std", "sp-inherents/std", - "sp-npos-elections/std", "pallet-lottery/std", "pallet-membership/std", "pallet-mmr/std", diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 567b2aadb1de8..236a3db8ce1c7 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -186,7 +186,8 @@ //! of doing this would be to have the fallback be another //! [`sp_election_providers::ElectionProvider`]. In this case, this pallet can even have the //! on-chain election provider as fallback, or special _noop_ fallback that simply returns an error, -//! thus replicating [`FallbackStrategy::Nothing`]. +//! thus replicating [`FallbackStrategy::Nothing`]. In this case, we won't need the additional +//! config OnChainAccuracy either. //! //! **Score based on (byte) size**: We should always prioritize small solutions over bigger ones, if //! there is a tie. Even more harsh should be to enforce the bound of the `reduce` algorithm. @@ -194,6 +195,11 @@ //! **Offchain resubmit**: Essentially port https://github.com/paritytech/substrate/pull/7976 to //! this pallet as well. The `OFFCHAIN_REPEAT` also needs to become an adjustable parameter of the //! pallet. +//! +//! **Make the number of nominators configurable from the runtime**. Remove `sp_npos_elections` +//! dependency from staking and the compact solution type. It should be generated at runtime, there +//! it should be encoded how many votes each nominators have. Essentially translate +//! https://github.com/paritytech/substrate/pull/7929 to this pallet. #![cfg_attr(not(feature = "std"), no_std)] diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index f7ea7d5f98b58..c69a680a5267f 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -3115,7 +3115,7 @@ impl Module { /// /// Returns `Err(())` if less than [`MinimumValidatorCount`] validators have been elected, `Ok` /// otherwise. - // TWO_PHASE_NOTE: the deadcode + // TWO_PHASE_NOTE: remove the dead code. #[allow(dead_code)] pub fn process_election( flat_supports: sp_npos_elections::Supports, diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 4f17d05694b88..3d90f412943af 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -1813,7 +1813,6 @@ fn bond_with_duplicate_vote_should_be_ignored_by_npos_election() { .minimum_validator_count(1) .build() .execute_with(|| { - // disable the nominator assert_ok!(Staking::chill(Origin::signed(100))); // make stakes equal. diff --git a/primitives/npos-elections/src/tests.rs b/primitives/npos-elections/src/tests.rs index bc148f118ce42..edfea038ebc50 100644 --- a/primitives/npos-elections/src/tests.rs +++ b/primitives/npos-elections/src/tests.rs @@ -1149,6 +1149,7 @@ mod solution_type { type TestAccuracy = Percent; generate_solution_type!(pub struct TestSolutionCompact::(16)); + #[allow(dead_code)] mod __private { // This is just to make sure that that the compact can be generated in a scope without any From 35f1fafe91026c73803a1f2a3349e6d559b54041 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 27 Jan 2021 12:24:25 +0000 Subject: [PATCH 09/29] Some changes from other PR --- .../src/unsigned.rs | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index a796986bd12c9..d0b7400cb0f2b 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -53,6 +53,24 @@ impl From for MinerError { } impl Pallet { + /// Mine a new solution, and submit it back to the chain as an unsigned transaction. + pub(crate) fn mine_and_submit() -> Result<(), MinerError> { + let balancing = Self::get_balancing_iters(); + let (raw_solution, witness) = Self::mine_solution(balancing)?; + + // ensure that this will pass the pre-dispatch checks + Self::unsigned_pre_dispatch_checks(&raw_solution).map_err(|e| { + log!(warn, "pre-disaptch-checks failed for mined solution: {:?}", e); + MinerError::PreDispatchChecksFailed + })?; + + // submit the raw solution to the pool. + let call = Call::submit_unsigned(raw_solution, witness).into(); + + SubmitTransaction::>::submit_unsigned_transaction(call) + .map_err(|_| MinerError::PoolSubmissionFailed) + } + /// Mine a new npos solution. pub fn mine_solution( iters: usize, @@ -69,14 +87,6 @@ impl Pallet { ) .map_err(Into::into) .and_then(Self::prepare_election_result) - .and_then(|(raw_solution, size)| { - Self::unsigned_pre_dispatch_checks(&raw_solution) - .map_err(|e| { - log!(warn, "pre-disaptch-checks failed for mined solution: {:?}", e); - MinerError::PreDispatchChecksFailed - })?; - Ok((raw_solution, size)) - }) } /// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which @@ -324,18 +334,6 @@ impl Pallet { } } - /// Mine a new solution, and submit it back to the chain as an unsigned transaction. - pub(crate) fn mine_and_submit() -> Result<(), MinerError> { - let balancing = Self::get_balancing_iters(); - let (raw_solution, witness) = Self::mine_solution(balancing)?; - - // submit the raw solution to the pool. - let call = Call::submit_unsigned(raw_solution, witness).into(); - - SubmitTransaction::>::submit_unsigned_transaction(call) - .map_err(|_| MinerError::PoolSubmissionFailed) - } - /// Do the basics checks that MUST happen during the validation and pre-dispatch of an unsigned /// transaction. /// @@ -684,13 +682,14 @@ mod tests { #[test] fn miner_will_not_submit_if_not_enough_winners() { - ExtBuilder::default().desired_targets(8).build_and_execute(|| { + let (mut ext, _) = ExtBuilder::default().desired_targets(8).build_offchainify(0); + ext.execute_with(|| { roll_to(25); assert!(TwoPhase::current_phase().is_unsigned()); // mine seq_phragmen solution with 2 iters. assert_eq!( - TwoPhase::mine_solution(2).unwrap_err(), + TwoPhase::mine_and_submit().unwrap_err(), MinerError::PreDispatchChecksFailed, ); }) From e01cacc54a41bcec3d2a79f20b3f4e5a6aa6030d Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 27 Jan 2021 13:04:31 +0000 Subject: [PATCH 10/29] Fix session test --- frame/session/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/session/src/tests.rs b/frame/session/src/tests.rs index 7c1d3c9dcdd24..3a665a5cbea17 100644 --- a/frame/session/src/tests.rs +++ b/frame/session/src/tests.rs @@ -274,7 +274,7 @@ fn periodic_session_works() { } assert!(P::should_end_session(13u64)); - assert_eq!(P::estimate_next_session_rotation(13u64).unwrap(), 13); + assert_eq!(P::estimate_next_session_rotation(13u64).unwrap(), 23); assert!(!P::should_end_session(14u64)); assert_eq!(P::estimate_next_session_rotation(14u64).unwrap(), 23); From 4c516cb420731b08e1241ce0b34cff035b7caf8a Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Thu, 4 Feb 2021 19:25:09 -0400 Subject: [PATCH 11/29] Update Cargo.lock --- Cargo.lock | 351 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 199 insertions(+), 152 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c39ba87321b65..5451bb6d77e0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -455,6 +455,16 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "bitvec" +version = "0.17.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c" +dependencies = [ + "either", + "radium 0.3.0", +] + [[package]] name = "bitvec" version = "0.20.1" @@ -462,7 +472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5011ffc90248764d7005b0e10c7294f5aa1bd87d9dd7248f4ad475b347c294d" dependencies = [ "funty", - "radium", + "radium 0.6.2", "tap", "wyz", ] @@ -618,6 +628,12 @@ version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9" +[[package]] +name = "byte-slice-cast" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3" + [[package]] name = "byte-slice-cast" version = "1.0.0" @@ -1575,7 +1591,7 @@ dependencies = [ "futures-timer 3.0.2", "log", "num-traits", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "rand 0.8.3", ] @@ -1621,7 +1637,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" name = "fork-tree" version = "2.0.1" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", ] [[package]] @@ -1642,7 +1658,7 @@ dependencies = [ "frame-system", "hex-literal", "linregress", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "paste 1.0.4", "sp-api", "sp-io", @@ -1660,7 +1676,7 @@ dependencies = [ "chrono", "frame-benchmarking", "handlebars", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sc-cli", "sc-client-db", "sc-executor", @@ -1684,7 +1700,7 @@ dependencies = [ "pallet-balances", "pallet-indices", "pallet-transaction-payment", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -1698,7 +1714,7 @@ dependencies = [ name = "frame-metadata" version = "12.0.1" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-std", @@ -1715,7 +1731,7 @@ dependencies = [ "impl-trait-for-tuples 0.2.0", "log", "once_cell", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parity-util-mem", "paste 1.0.4", "pretty_assertions", @@ -1772,7 +1788,7 @@ dependencies = [ "frame-metadata", "frame-support", "frame-system", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "pretty_assertions", "rustversion", "serde", @@ -1792,7 +1808,7 @@ dependencies = [ "criterion", "frame-support", "impl-trait-for-tuples 0.2.0", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-externalities", @@ -1810,7 +1826,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -1822,7 +1838,7 @@ dependencies = [ name = "frame-system-rpc-runtime-api" version = "2.0.1" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-api", ] @@ -2530,7 +2546,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df170efa359aebdd5cb7fe78edcc67107748e4737bdca8a8fb40d15ea7a877ed" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", ] [[package]] @@ -3883,7 +3899,7 @@ dependencies = [ "pallet-staking", "pallet-timestamp", "pallet-transaction-payment", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "platforms", "rand 0.7.3", @@ -3953,7 +3969,7 @@ dependencies = [ "pallet-timestamp", "pallet-transaction-payment", "pallet-treasury", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sc-executor", "sp-application-crypto", "sp-core", @@ -3974,7 +3990,7 @@ version = "0.8.0" dependencies = [ "derive_more", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sc-cli", "sc-client-api", "sc-service", @@ -3989,7 +4005,7 @@ name = "node-primitives" version = "2.0.0" dependencies = [ "frame-system", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "pretty_assertions", "sp-application-crypto", "sp-core", @@ -4093,7 +4109,7 @@ dependencies = [ "pallet-treasury", "pallet-utility", "pallet-vesting", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-api", "sp-authority-discovery", @@ -4171,7 +4187,7 @@ dependencies = [ "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-api", "sp-block-builder", @@ -4210,7 +4226,7 @@ dependencies = [ "pallet-timestamp", "pallet-transaction-payment", "pallet-treasury", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sc-block-builder", "sc-cli", "sc-client-api", @@ -4398,7 +4414,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4413,7 +4429,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4430,7 +4446,7 @@ dependencies = [ "lazy_static", "pallet-session", "pallet-timestamp", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "serde", "sp-application-crypto", @@ -4449,7 +4465,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-session", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-application-crypto", "sp-authority-discovery", @@ -4467,7 +4483,7 @@ dependencies = [ "frame-support", "frame-system", "impl-trait-for-tuples 0.2.0", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-authorship", "sp-core", @@ -4491,7 +4507,7 @@ dependencies = [ "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-application-crypto", "sp-consensus-babe", @@ -4515,7 +4531,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-transaction-payment", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4532,7 +4548,7 @@ dependencies = [ "frame-system", "pallet-balances", "pallet-treasury", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4550,7 +4566,7 @@ dependencies = [ "frame-system", "hex-literal", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4572,7 +4588,7 @@ dependencies = [ "pallet-contracts-proc-macro", "pallet-randomness-collective-flip", "pallet-timestamp", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parity-wasm 0.41.0", "paste 1.0.4", "pretty_assertions", @@ -4594,7 +4610,7 @@ name = "pallet-contracts-primitives" version = "2.0.1" dependencies = [ "bitflags", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-runtime", "sp-std", ] @@ -4617,7 +4633,7 @@ dependencies = [ "jsonrpc-derive", "pallet-contracts-primitives", "pallet-contracts-rpc-runtime-api", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "serde_json", "sp-api", @@ -4632,7 +4648,7 @@ name = "pallet-contracts-rpc-runtime-api" version = "0.8.1" dependencies = [ "pallet-contracts-primitives", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-api", "sp-runtime", "sp-std", @@ -4648,7 +4664,7 @@ dependencies = [ "hex-literal", "pallet-balances", "pallet-scheduler", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4667,9 +4683,9 @@ dependencies = [ "frame-system", "hex-literal", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 1.3.6", "parking_lot 0.11.1", - "paste 1.0.3", + "paste 1.0.4", "rand 0.7.3", "serde", "sp-arithmetic", @@ -4692,7 +4708,7 @@ dependencies = [ "frame-system", "hex-literal", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4709,7 +4725,7 @@ dependencies = [ "frame-system", "hex-literal", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4727,7 +4743,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4742,7 +4758,7 @@ dependencies = [ "frame-support", "frame-system", "lite-json", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4757,7 +4773,7 @@ version = "2.0.1" dependencies = [ "frame-support", "frame-system", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-core", "sp-io", "sp-runtime", @@ -4780,7 +4796,7 @@ dependencies = [ "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-application-crypto", "sp-core", @@ -4803,7 +4819,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4820,7 +4836,7 @@ dependencies = [ "frame-system", "pallet-authorship", "pallet-session", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-application-crypto", "sp-core", @@ -4838,7 +4854,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4855,7 +4871,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4869,7 +4885,7 @@ version = "2.0.1" dependencies = [ "frame-support", "frame-system", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4888,7 +4904,7 @@ dependencies = [ "frame-system", "hex-literal", "pallet-mmr-primitives", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4903,7 +4919,7 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-api", "sp-core", @@ -4919,7 +4935,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4934,7 +4950,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4948,7 +4964,7 @@ version = "2.0.0" dependencies = [ "frame-support", "frame-system", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4963,7 +4979,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -4988,7 +5004,7 @@ dependencies = [ "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-election-providers", @@ -5007,7 +5023,7 @@ dependencies = [ "frame-system", "pallet-balances", "pallet-utility", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -5021,7 +5037,7 @@ version = "2.0.1" dependencies = [ "frame-support", "frame-system", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "safe-mix", "serde", "sp-core", @@ -5038,7 +5054,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -5053,7 +5069,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -5069,7 +5085,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -5086,7 +5102,7 @@ dependencies = [ "impl-trait-for-tuples 0.1.3", "lazy_static", "pallet-timestamp", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-application-crypto", "sp-core", @@ -5110,7 +5126,7 @@ dependencies = [ "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "rand 0.7.3", "serde", "sp-core", @@ -5128,7 +5144,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "rand_chacha 0.2.2", "serde", "sp-core", @@ -5150,7 +5166,7 @@ dependencies = [ "pallet-session", "pallet-staking-reward-curve", "pallet-timestamp", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "rand_chacha 0.2.2", "serde", @@ -5181,7 +5197,7 @@ dependencies = [ "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-core", "sp-election-providers", "sp-io", @@ -5207,7 +5223,7 @@ version = "2.0.1" dependencies = [ "frame-support", "frame-system", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -5221,7 +5237,7 @@ version = "2.0.0" dependencies = [ "frame-support", "frame-system", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -5236,7 +5252,7 @@ dependencies = [ "frame-support", "frame-system", "impl-trait-for-tuples 0.2.0", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-inherents", @@ -5255,7 +5271,7 @@ dependencies = [ "frame-system", "pallet-balances", "pallet-treasury", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -5271,7 +5287,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "serde_json", "smallvec 1.6.1", @@ -5290,7 +5306,7 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "pallet-transaction-payment-rpc-runtime-api", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-api", "sp-blockchain", "sp-core", @@ -5303,7 +5319,7 @@ name = "pallet-transaction-payment-rpc-runtime-api" version = "2.0.1" dependencies = [ "pallet-transaction-payment", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-api", "sp-runtime", ] @@ -5317,7 +5333,7 @@ dependencies = [ "frame-system", "impl-trait-for-tuples 0.2.0", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -5334,7 +5350,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -5352,7 +5368,7 @@ dependencies = [ "frame-system", "hex-literal", "pallet-balances", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -5395,6 +5411,19 @@ dependencies = [ "url 2.2.0", ] +[[package]] +name = "parity-scale-codec" +version = "1.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79602888a81ace83e3d1d4b2873286c1f5f906c84db667594e8db8da3506c383" +dependencies = [ + "arrayvec 0.5.2", + "bitvec 0.17.4", + "byte-slice-cast 0.3.5", + "parity-scale-codec-derive 1.2.2", + "serde", +] + [[package]] name = "parity-scale-codec" version = "2.0.0" @@ -5402,12 +5431,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75c823fdae1bb5ff5708ee61a62697e6296175dc671710876871c853f48592b3" dependencies = [ "arrayvec 0.5.2", - "bitvec", - "byte-slice-cast", - "parity-scale-codec-derive", + "bitvec 0.20.1", + "byte-slice-cast 1.0.0", + "parity-scale-codec-derive 2.0.0", "serde", ] +[[package]] +name = "parity-scale-codec-derive" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "198db82bb1c18fc00176004462dd809b2a6d851669550aa17af6dacd21ae0c14" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-scale-codec-derive" version = "2.0.0" @@ -6079,6 +6120,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" + [[package]] name = "radium" version = "0.6.2" @@ -6559,7 +6606,7 @@ dependencies = [ "futures-timer 3.0.2", "libp2p", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "prost", "prost-build", "quickcheck", @@ -6586,7 +6633,7 @@ dependencies = [ "futures 0.3.12", "futures-timer 3.0.2", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "sc-block-builder", "sc-client-api", @@ -6608,7 +6655,7 @@ dependencies = [ name = "sc-block-builder" version = "0.8.1" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sc-client-api", "sp-api", "sp-block-builder", @@ -6627,7 +6674,7 @@ name = "sc-chain-spec" version = "2.0.1" dependencies = [ "impl-trait-for-tuples 0.2.0", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sc-chain-spec-derive", "sc-consensus-babe", "sc-consensus-epochs", @@ -6663,7 +6710,7 @@ dependencies = [ "libp2p", "log", "names", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "rand 0.7.3", "regex", "rpassword", @@ -6702,7 +6749,7 @@ dependencies = [ "kvdb-memorydb", "lazy_static", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "sc-executor", "sp-api", @@ -6739,7 +6786,7 @@ dependencies = [ "linked-hash-map", "log", "parity-db", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parity-util-mem", "parking_lot 0.11.1", "quickcheck", @@ -6780,7 +6827,7 @@ dependencies = [ "futures-timer 3.0.2", "getrandom 0.2.2", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "sc-block-builder", "sc-client-api", @@ -6825,7 +6872,7 @@ dependencies = [ "num-bigint", "num-rational", "num-traits", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "pdqselect", "rand 0.7.3", @@ -6901,7 +6948,7 @@ name = "sc-consensus-epochs" version = "0.8.1" dependencies = [ "fork-tree", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "sc-client-api", "sp-blockchain", @@ -6919,7 +6966,7 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "sc-basic-authorship", "sc-client-api", @@ -6954,7 +7001,7 @@ dependencies = [ "futures 0.3.12", "futures-timer 3.0.2", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "sc-client-api", "sp-api", @@ -6976,7 +7023,7 @@ dependencies = [ "futures 0.3.12", "futures-timer 3.0.2", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "sc-client-api", "sc-telemetry", @@ -7018,7 +7065,7 @@ dependencies = [ "lazy_static", "libsecp256k1", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parity-wasm 0.41.0", "parking_lot 0.11.1", "paste 1.0.4", @@ -7053,7 +7100,7 @@ name = "sc-executor-common" version = "0.8.1" dependencies = [ "derive_more", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parity-wasm 0.41.0", "sp-allocator", "sp-core", @@ -7068,7 +7115,7 @@ name = "sc-executor-wasmi" version = "0.8.1" dependencies = [ "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sc-executor-common", "sp-allocator", "sp-core", @@ -7083,7 +7130,7 @@ version = "0.8.1" dependencies = [ "assert_matches", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parity-wasm 0.41.0", "pwasm-utils 0.14.0", "sc-executor-common", @@ -7107,7 +7154,7 @@ dependencies = [ "futures-timer 3.0.2", "linked-hash-map", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "pin-project 1.0.4", "rand 0.7.3", @@ -7154,7 +7201,7 @@ dependencies = [ "jsonrpc-pubsub", "lazy_static", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sc-block-builder", "sc-client-api", "sc-finality-grandpa", @@ -7179,7 +7226,7 @@ dependencies = [ "futures 0.3.12", "log", "num-traits", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "prost", "sc-client-api", @@ -7233,7 +7280,7 @@ version = "2.0.1" dependencies = [ "hash-db", "lazy_static", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "sc-client-api", "sc-executor", @@ -7272,7 +7319,7 @@ dependencies = [ "log", "lru", "nohash-hasher", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "pin-project 1.0.4", "prost", @@ -7364,7 +7411,7 @@ dependencies = [ "lazy_static", "log", "num_cpus", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "rand 0.7.3", "sc-block-builder", @@ -7419,7 +7466,7 @@ dependencies = [ "jsonrpc-pubsub", "lazy_static", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "sc-block-builder", "sc-cli", @@ -7460,7 +7507,7 @@ dependencies = [ "jsonrpc-derive", "jsonrpc-pubsub", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "serde", "serde_json", @@ -7518,7 +7565,7 @@ dependencies = [ "jsonrpc-pubsub", "lazy_static", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parity-util-mem", "parking_lot 0.11.1", "pin-project 1.0.4", @@ -7582,7 +7629,7 @@ dependencies = [ "futures 0.3.12", "hex-literal", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "sc-block-builder", "sc-client-api", @@ -7614,7 +7661,7 @@ name = "sc-state-db" version = "0.8.1" dependencies = [ "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parity-util-mem", "parity-util-mem-derive", "parking_lot 0.11.1", @@ -7710,7 +7757,7 @@ dependencies = [ "futures 0.3.12", "linked-hash-map", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parity-util-mem", "parking_lot 0.11.1", "retain_mut", @@ -7735,7 +7782,7 @@ dependencies = [ "hex", "intervalier", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parity-util-mem", "parking_lot 0.11.1", "sc-block-builder", @@ -8152,7 +8199,7 @@ name = "sp-api" version = "2.0.1" dependencies = [ "hash-db", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-api-proc-macro", "sp-core", "sp-runtime", @@ -8179,7 +8226,7 @@ name = "sp-api-test" version = "2.0.1" dependencies = [ "criterion", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "rustversion", "sc-block-builder", "sp-api", @@ -8197,7 +8244,7 @@ dependencies = [ name = "sp-application-crypto" version = "2.0.1" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-core", "sp-io", @@ -8223,7 +8270,7 @@ dependencies = [ "criterion", "integer-sqrt", "num-traits", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "primitive-types", "rand 0.7.3", "serde", @@ -8247,7 +8294,7 @@ dependencies = [ name = "sp-authority-discovery" version = "2.0.1" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-api", "sp-application-crypto", "sp-runtime", @@ -8258,7 +8305,7 @@ dependencies = [ name = "sp-authorship" version = "2.0.1" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-inherents", "sp-runtime", "sp-std", @@ -8268,7 +8315,7 @@ dependencies = [ name = "sp-block-builder" version = "2.0.1" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-api", "sp-inherents", "sp-runtime", @@ -8282,7 +8329,7 @@ dependencies = [ "futures 0.3.12", "log", "lru", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "sp-api", "sp-consensus", @@ -8308,7 +8355,7 @@ dependencies = [ "futures-timer 3.0.2", "libp2p", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "serde", "sp-api", @@ -8330,7 +8377,7 @@ dependencies = [ name = "sp-consensus-aura" version = "0.8.1" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-api", "sp-application-crypto", "sp-consensus-slots", @@ -8345,7 +8392,7 @@ name = "sp-consensus-babe" version = "0.8.1" dependencies = [ "merlin", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-api", "sp-application-crypto", "sp-consensus", @@ -8363,7 +8410,7 @@ dependencies = [ name = "sp-consensus-pow" version = "0.8.1" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-api", "sp-core", "sp-runtime", @@ -8374,7 +8421,7 @@ dependencies = [ name = "sp-consensus-slots" version = "0.8.1" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-arithmetic", "sp-runtime", ] @@ -8383,7 +8430,7 @@ dependencies = [ name = "sp-consensus-vrf" version = "0.8.1" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", "schnorrkel", "sp-core", "sp-runtime", @@ -8411,7 +8458,7 @@ dependencies = [ "log", "merlin", "num-traits", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parity-util-mem", "parking_lot 0.11.1", "pretty_assertions", @@ -8460,7 +8507,7 @@ dependencies = [ name = "sp-election-providers" version = "2.0.0" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-arithmetic", "sp-npos-elections", "sp-runtime", @@ -8472,7 +8519,7 @@ name = "sp-externalities" version = "0.8.1" dependencies = [ "environmental", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-std", "sp-storage", ] @@ -8483,7 +8530,7 @@ version = "2.0.1" dependencies = [ "finality-grandpa", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-api", "sp-application-crypto", @@ -8497,7 +8544,7 @@ dependencies = [ name = "sp-inherents" version = "2.0.1" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "sp-core", "sp-std", @@ -8512,7 +8559,7 @@ dependencies = [ "hash-db", "libsecp256k1", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "sp-core", "sp-externalities", @@ -8545,7 +8592,7 @@ dependencies = [ "derive_more", "futures 0.3.12", "merlin", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "rand 0.7.3", "rand_chacha 0.2.2", @@ -8559,7 +8606,7 @@ dependencies = [ name = "sp-npos-elections" version = "2.0.1" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", "rand 0.7.3", "serde", "sp-arithmetic", @@ -8585,7 +8632,7 @@ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ "honggfuzz", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "rand 0.7.3", "sp-npos-elections", "sp-runtime", @@ -8626,7 +8673,7 @@ dependencies = [ "hash256-std-hasher", "impl-trait-for-tuples 0.2.0", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parity-util-mem", "paste 1.0.4", "rand 0.7.3", @@ -8645,7 +8692,7 @@ name = "sp-runtime-interface" version = "2.0.1" dependencies = [ "impl-trait-for-tuples 0.2.0", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "primitive-types", "rustversion", "sp-core", @@ -8716,7 +8763,7 @@ name = "sp-sandbox" version = "0.8.1" dependencies = [ "assert_matches", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-core", "sp-io", "sp-std", @@ -8737,7 +8784,7 @@ dependencies = [ name = "sp-session" version = "2.0.1" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-api", "sp-core", "sp-runtime", @@ -8749,7 +8796,7 @@ dependencies = [ name = "sp-staking" version = "2.0.1" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-runtime", "sp-std", ] @@ -8762,7 +8809,7 @@ dependencies = [ "hex-literal", "log", "num-traits", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "pretty_assertions", "rand 0.7.3", @@ -8787,7 +8834,7 @@ name = "sp-storage" version = "2.0.1" dependencies = [ "impl-serde", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "ref-cast", "serde", "sp-debug-derive", @@ -8799,7 +8846,7 @@ name = "sp-tasks" version = "2.0.0" dependencies = [ "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-core", "sp-externalities", "sp-io", @@ -8811,7 +8858,7 @@ dependencies = [ name = "sp-test-primitives" version = "2.0.0" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parity-util-mem", "serde", "sp-application-crypto", @@ -8824,7 +8871,7 @@ name = "sp-timestamp" version = "2.0.1" dependencies = [ "impl-trait-for-tuples 0.2.0", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-api", "sp-inherents", "sp-runtime", @@ -8837,7 +8884,7 @@ name = "sp-tracing" version = "2.0.1" dependencies = [ "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-std", "tracing", "tracing-core", @@ -8851,7 +8898,7 @@ dependencies = [ "derive_more", "futures 0.3.12", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-api", "sp-blockchain", @@ -8867,7 +8914,7 @@ dependencies = [ "hash-db", "hex-literal", "memory-db", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-core", "sp-runtime", "sp-std", @@ -8893,7 +8940,7 @@ name = "sp-version" version = "2.0.1" dependencies = [ "impl-serde", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "serde", "sp-runtime", "sp-std", @@ -8904,7 +8951,7 @@ name = "sp-wasm-interface" version = "2.0.1" dependencies = [ "impl-trait-for-tuples 0.2.0", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sp-std", "wasmi", ] @@ -9080,7 +9127,7 @@ dependencies = [ "futures 0.3.12", "jsonrpc-client-transports", "jsonrpc-core", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sc-rpc-api", "serde", "sp-storage", @@ -9097,7 +9144,7 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "log", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sc-client-api", "sc-rpc-api", "sc-transaction-pool", @@ -9133,7 +9180,7 @@ dependencies = [ "futures 0.3.12", "hash-db", "hex", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sc-client-api", "sc-client-db", "sc-consensus", @@ -9164,7 +9211,7 @@ dependencies = [ "memory-db", "pallet-babe", "pallet-timestamp", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parity-util-mem", "sc-block-builder", "sc-executor", @@ -9200,7 +9247,7 @@ name = "substrate-test-runtime-client" version = "2.0.0" dependencies = [ "futures 0.3.12", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "sc-block-builder", "sc-client-api", "sc-consensus", @@ -9221,7 +9268,7 @@ version = "2.0.0" dependencies = [ "derive_more", "futures 0.3.12", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "parking_lot 0.11.1", "sc-transaction-graph", "sp-blockchain", @@ -9851,7 +9898,7 @@ dependencies = [ "hash-db", "keccak-hasher", "memory-db", - "parity-scale-codec", + "parity-scale-codec 2.0.0", "trie-db", "trie-root", "trie-standardmap", From 8c8d1e62b120e037dd9726d9d1a7c2b6aeb804fe Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Thu, 11 Feb 2021 16:01:28 +0000 Subject: [PATCH 12/29] Update frame/election-provider-multi-phase/src/lib.rs Co-authored-by: Guillaume Thiolliere --- frame/election-provider-multi-phase/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 236a3db8ce1c7..d2648225e7f47 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -910,7 +910,7 @@ impl Pallet { // NOTE: this is a bit of duplicate, but we keep it around for veracity. The unsigned path // already checked this in `unsigned_per_dispatch_checks`. The signed path *could* check it // upon arrival, thus we would then remove it here. Given overlay it is cheap anyhow - ensure!(winners.len() as u32 == desired_targets, FeasibilityError::WrongWinnerCount,); + ensure!(winners.len() as u32 == desired_targets, FeasibilityError::WrongWinnerCount); // read the entire snapshot. let RoundSnapshot { voters: snapshot_voters, targets: snapshot_targets } = From 4b58c91f3b98999b9964333a17862ab52fff3051 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 11 Feb 2021 16:14:29 +0000 Subject: [PATCH 13/29] Some review comments --- bin/node/runtime/src/lib.rs | 2 +- frame/election-provider-multi-phase/src/lib.rs | 10 ++++------ frame/election-provider-multi-phase/src/mock.rs | 4 ++-- frame/election-provider-multi-phase/src/unsigned.rs | 6 +++--- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 0636817e456d4..37fb51fa8e8de 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1368,6 +1368,7 @@ impl_runtime_apis! { add_benchmark!(params, batches, pallet_contracts, Contracts); add_benchmark!(params, batches, pallet_democracy, Democracy); add_benchmark!(params, batches, pallet_elections_phragmen, Elections); + add_benchmark!(params, batches, pallet_election_provider_multi_phase, ElectionProviderMultiPhase); add_benchmark!(params, batches, pallet_grandpa, Grandpa); add_benchmark!(params, batches, pallet_identity, Identity); add_benchmark!(params, batches, pallet_im_online, ImOnline); @@ -1386,7 +1387,6 @@ impl_runtime_apis! { add_benchmark!(params, batches, pallet_treasury, Treasury); add_benchmark!(params, batches, pallet_utility, Utility); add_benchmark!(params, batches, pallet_vesting, Vesting); - add_benchmark!(params, batches, pallet_election_provider_multi_phase, ElectionProviderMultiPhase); if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) } Ok(batches) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 236a3db8ce1c7..5fb60afc66835 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -496,9 +496,7 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config + SendTransactionTypes> { - type Event: From> - + Into<::Event> - + IsType<::Event>; + type Event: From> + IsType<::Event>; /// Currency type. type Currency: ReservableCurrency + Currency; @@ -585,8 +583,8 @@ pub mod pallet { log!(info, "Starting unsigned phase({}) at #{:?}.", enabled, now); let base_weight = if need_snapshot { - T::WeightInfo::on_initialize_open_unsigned_with_snapshot() } - else { + T::WeightInfo::on_initialize_open_unsigned_with_snapshot() + } else { T::WeightInfo::on_initialize_open_unsigned_without_snapshot() }; base_weight.saturating_add(additional) @@ -596,7 +594,7 @@ pub mod pallet { } fn offchain_worker(n: T::BlockNumber) { - // We only run the OCW in the fist block of the unsigned phase. + // We only run the OCW in the first block of the unsigned phase. if Self::current_phase().is_unsigned_open_at(n) { match Self::try_acquire_offchain_lock(n) { Ok(_) => { diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 402886703a1e7..0b9fed1d21cf2 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -52,7 +52,7 @@ frame_support::construct_runtime!( NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic { - System: frame_system::{Module, Call, Event}, + System: frame_system::{Module, Call, Event, Config}, Balances: pallet_balances::{Module, Call, Event, Config}, TwoPhase: two_phase::{Module, Call, Event}, } @@ -154,7 +154,7 @@ impl frame_system::Config for Runtime { type BlockLength = (); type BlockWeights = BlockWeights; type Version = (); - type PalletInfo = (); + type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index d0b7400cb0f2b..31d3aaf030066 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -28,7 +28,7 @@ use sp_runtime::{offchain::storage::StorageValueRef, traits::TrailingZeroInput}; use sp_std::cmp::Ordering; /// Storage key used to store the persistent offchain worker status. -pub(crate) const OFFCHAIN_HEAD_DB: &[u8] = b"parity/multi-phase-unsigned-election/"; +pub(crate) const OFFCHAIN_HEAD_DB: &[u8] = b"parity/multi-phase-unsigned-election"; /// The repeat threshold of the offchain worker. This means we won't run the offchain worker twice /// within a window of 5 blocks. @@ -182,7 +182,7 @@ impl Pallet { pub fn trim_compact( maximum_allowed_voters: u32, mut compact: CompactOf, - nominator_index: FN, + voter_index: FN, ) -> Result, MinerError> where for<'r> FN: Fn(&'r T::AccountId) -> Option>, @@ -202,7 +202,7 @@ impl Pallet { // removed. let mut removed = 0; for (maybe_index, _stake) in - voters_sorted.iter().map(|(who, stake)| (nominator_index(&who), stake)) + voters_sorted.iter().map(|(who, stake)| (voter_index(&who), stake)) { let index = maybe_index.ok_or(MinerError::SnapshotUnAvailable)?; if compact.remove_voter(index) { From 528917e6adf7e558e0d7e9384e9363a408cd4604 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 12 Feb 2021 10:17:06 +0000 Subject: [PATCH 14/29] Rename + make encode/decode --- bin/node/runtime/src/lib.rs | 4 +- .../src/benchmarking.rs | 69 ++--- .../election-provider-multi-phase/src/lib.rs | 177 ++++++------- .../election-provider-multi-phase/src/mock.rs | 36 +-- .../src/unsigned.rs | 235 +++++++++--------- .../src/weights.rs | 8 +- 6 files changed, 273 insertions(+), 256 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 5e472bdb0359f..6d014193cec88 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -516,7 +516,7 @@ parameter_types! { pub SolutionImprovementThreshold: Perbill = Perbill::from_rational_approximation(1u32, 10_000); // miner configs - pub const TwoPhaseUnsignedPriority: TransactionPriority = StakingUnsignedPriority::get() - 1u64; + pub const MultiPhaseUnsignedPriority: TransactionPriority = StakingUnsignedPriority::get() - 1u64; pub const MinerMaxIterations: u32 = 10; pub MinerMaxWeight: Weight = RuntimeBlockWeights::get() .get(DispatchClass::Normal) @@ -532,7 +532,7 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type SolutionImprovementThreshold = MinSolutionScoreBump; type MinerMaxIterations = MinerMaxIterations; type MinerMaxWeight = MinerMaxWeight; - type MinerTxPriority = TwoPhaseUnsignedPriority; + type MinerTxPriority = MultiPhaseUnsignedPriority; type DataProvider = Staking; type OnChainAccuracy = Perbill; type CompactSolution = pallet_staking::CompactAssignments; diff --git a/frame/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs index 98a874967fc03..daac85f06790a 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -18,7 +18,7 @@ //! Two phase election pallet benchmarking. use super::*; -use crate::Module as TwoPhase; +use crate::Module as MultiPhase; pub use frame_benchmarking::{account, benchmarks, whitelist_account, whitelisted_caller}; use frame_support::{assert_ok, traits::OnInitialize}; @@ -137,61 +137,61 @@ fn solution_with_size( let compact = >::from_assignment(assignments, &voter_index, &target_index).unwrap(); let score = compact.clone().score(&winners, stake_of, voter_at, target_at).unwrap(); - let round = >::round(); + let round = >::round(); RawSolution { compact, score, round } } benchmarks! { on_initialize_nothing { - assert!(>::current_phase().is_off()); + assert!(>::current_phase().is_off()); }: { - >::on_initialize(1u32.into()); + >::on_initialize(1u32.into()); } verify { - assert!(>::current_phase().is_off()); + assert!(>::current_phase().is_off()); } on_initialize_open_signed { // NOTE: this benchmark currently doesn't have any components because the length of a db // read/write is not captured. Otherwise, it is quite influenced by how much data // `T::ElectionDataProvider` is reading and passing on. - assert!(>::snapshot().is_none()); - assert!(>::current_phase().is_off()); + assert!(>::snapshot().is_none()); + assert!(>::current_phase().is_off()); }: { - >::on_initialize_open_signed(); + >::on_initialize_open_signed(); } verify { - assert!(>::snapshot().is_some()); - assert!(>::current_phase().is_signed()); + assert!(>::snapshot().is_some()); + assert!(>::current_phase().is_signed()); } on_initialize_open_unsigned_with_snapshot { - assert!(>::snapshot().is_none()); - assert!(>::current_phase().is_off()); + assert!(>::snapshot().is_none()); + assert!(>::current_phase().is_off()); }: { - >::on_initialize_open_unsigned(true, true, 1u32.into()); + >::on_initialize_open_unsigned(true, true, 1u32.into()); } verify { - assert!(>::snapshot().is_some()); - assert!(>::current_phase().is_unsigned()); + assert!(>::snapshot().is_some()); + assert!(>::current_phase().is_unsigned()); } on_initialize_open_unsigned_without_snapshot { // need to assume signed phase was open before - >::on_initialize_open_signed(); - assert!(>::snapshot().is_some()); - assert!(>::current_phase().is_signed()); + >::on_initialize_open_signed(); + assert!(>::snapshot().is_some()); + assert!(>::current_phase().is_signed()); }: { - >::on_initialize_open_unsigned(false, true, 1u32.into()); + >::on_initialize_open_unsigned(false, true, 1u32.into()); } verify { - assert!(>::snapshot().is_some()); - assert!(>::current_phase().is_unsigned()); + assert!(>::snapshot().is_some()); + assert!(>::current_phase().is_unsigned()); } #[extra] create_snapshot { - assert!(>::snapshot().is_none()); + assert!(>::snapshot().is_none()); }: { - >::create_snapshot() + >::create_snapshot() } verify { - assert!(>::snapshot().is_some()); + assert!(>::snapshot().is_some()); } submit_unsigned { @@ -208,11 +208,17 @@ benchmarks! { let witness = SolutionOrSnapshotSize { voters: v, targets: t }; let raw_solution = solution_with_size::(witness, a, d); - assert!(>::queued_solution().is_none()); + assert!(>::queued_solution().is_none()); >::put(Phase::Unsigned((true, 1u32.into()))); - }: _(RawOrigin::None, raw_solution, witness) - verify { - assert!(>::queued_solution().is_some()); + + // encode the most significant storage item that needs to be decoded in the dispatch. + let encoded_snapshot = >::snapshot().encode(); + }: { + assert_ok!(>::submit_unsigned(RawOrigin::None.into(), raw_solution, witness)); + let _decoded = as Decode>::decode(&mut &*encoded_snapshot).unwrap(); + // NOTE: assert that this line is optimized away by the compiler in any way! + } verify { + assert!(>::queued_solution().is_some()); } // This is checking a valid solution. The worse case is indeed a valid solution. @@ -232,8 +238,13 @@ benchmarks! { assert_eq!(raw_solution.compact.voter_count() as u32, a); assert_eq!(raw_solution.compact.unique_targets().len() as u32, d); + + // encode the most significant storage item that needs to be decoded in the dispatch. + let encoded_snapshot = >::snapshot().encode(); }: { - assert_ok!(>::feasibility_check(raw_solution, ElectionCompute::Unsigned)); + assert_ok!(>::feasibility_check(raw_solution, ElectionCompute::Unsigned)); + let _decoded = as Decode>::decode(&mut &*encoded_snapshot).unwrap(); + // NOTE: assert that this line is optimized away by the compiler in any way! } } diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 5c78c9a1c59ea..ee89beb04a10b 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -1058,14 +1058,14 @@ mod feasibility_check { fn snapshot_is_there() { ExtBuilder::default().build_and_execute(|| { roll_to(::get() - ::get() - ::get()); - assert!(TwoPhase::current_phase().is_signed()); + assert!(MultiPhase::current_phase().is_signed()); let solution = raw_solution(); // for whatever reason it might be: >::kill(); assert_noop!( - TwoPhase::feasibility_check(solution, COMPUTE), + MultiPhase::feasibility_check(solution, COMPUTE), FeasibilityError::SnapshotUnavailable ); }) @@ -1075,12 +1075,12 @@ mod feasibility_check { fn round() { ExtBuilder::default().build_and_execute(|| { roll_to(::get() - ::get() - ::get()); - assert!(TwoPhase::current_phase().is_signed()); + assert!(MultiPhase::current_phase().is_signed()); let mut solution = raw_solution(); solution.round += 1; assert_noop!( - TwoPhase::feasibility_check(solution, COMPUTE), + MultiPhase::feasibility_check(solution, COMPUTE), FeasibilityError::InvalidRound ); }) @@ -1090,15 +1090,15 @@ mod feasibility_check { fn desired_targets() { ExtBuilder::default().desired_targets(8).build_and_execute(|| { roll_to(::get() - ::get() - ::get()); - assert!(TwoPhase::current_phase().is_signed()); + assert!(MultiPhase::current_phase().is_signed()); let solution = raw_solution(); assert_eq!(solution.compact.unique_targets().len(), 4); - assert_eq!(TwoPhase::desired_targets().unwrap(), 8); + assert_eq!(MultiPhase::desired_targets().unwrap(), 8); assert_noop!( - TwoPhase::feasibility_check(solution, COMPUTE), + MultiPhase::feasibility_check(solution, COMPUTE), FeasibilityError::WrongWinnerCount, ); }) @@ -1108,10 +1108,10 @@ mod feasibility_check { fn winner_indices() { ExtBuilder::default().desired_targets(2).build_and_execute(|| { roll_to(::get() - ::get() - ::get()); - assert!(TwoPhase::current_phase().is_signed()); + assert!(MultiPhase::current_phase().is_signed()); let mut solution = raw_solution(); - assert_eq!(TwoPhase::snapshot().unwrap().targets.len(), 4); + assert_eq!(MultiPhase::snapshot().unwrap().targets.len(), 4); // ----------------------------------------------------^^ valid range is [0..3]. // swap all votes from 3 to 4. This will ensure that the number of unique winners @@ -1132,7 +1132,7 @@ mod feasibility_check { }; }); assert_noop!( - TwoPhase::feasibility_check(solution, COMPUTE), + MultiPhase::feasibility_check(solution, COMPUTE), FeasibilityError::InvalidWinner ); }) @@ -1143,10 +1143,10 @@ mod feasibility_check { // should be caught in `compact.into_assignment`. ExtBuilder::default().desired_targets(2).build_and_execute(|| { roll_to(::get() - ::get() - ::get()); - assert!(TwoPhase::current_phase().is_signed()); + assert!(MultiPhase::current_phase().is_signed()); let mut solution = raw_solution(); - assert_eq!(TwoPhase::snapshot().unwrap().voters.len(), 8); + assert_eq!(MultiPhase::snapshot().unwrap().voters.len(), 8); // ----------------------------------------------------^^ valid range is [0..7]. // check that there is a index 7 in votes1, and flip to 8. @@ -1160,7 +1160,7 @@ mod feasibility_check { .count() > 0 ); assert_noop!( - TwoPhase::feasibility_check(solution, COMPUTE), + MultiPhase::feasibility_check(solution, COMPUTE), FeasibilityError::NposElection(sp_npos_elections::Error::CompactInvalidIndex), ); }) @@ -1170,10 +1170,10 @@ mod feasibility_check { fn voter_votes() { ExtBuilder::default().desired_targets(2).build_and_execute(|| { roll_to(::get() - ::get() - ::get()); - assert!(TwoPhase::current_phase().is_signed()); + assert!(MultiPhase::current_phase().is_signed()); let mut solution = raw_solution(); - assert_eq!(TwoPhase::snapshot().unwrap().voters.len(), 8); + assert_eq!(MultiPhase::snapshot().unwrap().voters.len(), 8); // ----------------------------------------------------^^ valid range is [0..7]. // first, check that voter at index 7 (40) actually voted for 3 (40) -- this is self @@ -1189,7 +1189,7 @@ mod feasibility_check { 1, ); assert_noop!( - TwoPhase::feasibility_check(solution, COMPUTE), + MultiPhase::feasibility_check(solution, COMPUTE), FeasibilityError::InvalidVote, ); }) @@ -1199,16 +1199,16 @@ mod feasibility_check { fn score() { ExtBuilder::default().desired_targets(2).build_and_execute(|| { roll_to(::get() - ::get() - ::get()); - assert!(TwoPhase::current_phase().is_signed()); + assert!(MultiPhase::current_phase().is_signed()); let mut solution = raw_solution(); - assert_eq!(TwoPhase::snapshot().unwrap().voters.len(), 8); + assert_eq!(MultiPhase::snapshot().unwrap().voters.len(), 8); // simply faff with the score. solution.score[0] += 1; assert_noop!( - TwoPhase::feasibility_check(solution, COMPUTE), + MultiPhase::feasibility_check(solution, COMPUTE), FeasibilityError::InvalidScore, ); }) @@ -1229,60 +1229,60 @@ mod tests { // Signed Unsigned Signed Unsigned assert_eq!(System::block_number(), 0); - assert_eq!(TwoPhase::current_phase(), Phase::Off); - assert_eq!(TwoPhase::round(), 1); + assert_eq!(MultiPhase::current_phase(), Phase::Off); + assert_eq!(MultiPhase::round(), 1); roll_to(4); - assert_eq!(TwoPhase::current_phase(), Phase::Off); - assert!(TwoPhase::snapshot().is_none()); - assert_eq!(TwoPhase::round(), 1); + assert_eq!(MultiPhase::current_phase(), Phase::Off); + assert!(MultiPhase::snapshot().is_none()); + assert_eq!(MultiPhase::round(), 1); roll_to(15); - assert_eq!(TwoPhase::current_phase(), Phase::Signed); - assert_eq!(two_phase_events(), vec![Event::SignedPhaseStarted(1)]); - assert!(TwoPhase::snapshot().is_some()); - assert_eq!(TwoPhase::round(), 1); + assert_eq!(MultiPhase::current_phase(), Phase::Signed); + assert_eq!(multi_phase_events(), vec![Event::SignedPhaseStarted(1)]); + assert!(MultiPhase::snapshot().is_some()); + assert_eq!(MultiPhase::round(), 1); roll_to(24); - assert_eq!(TwoPhase::current_phase(), Phase::Signed); - assert!(TwoPhase::snapshot().is_some()); - assert_eq!(TwoPhase::round(), 1); + assert_eq!(MultiPhase::current_phase(), Phase::Signed); + assert!(MultiPhase::snapshot().is_some()); + assert_eq!(MultiPhase::round(), 1); roll_to(25); - assert_eq!(TwoPhase::current_phase(), Phase::Unsigned((true, 25))); + assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25))); assert_eq!( - two_phase_events(), + multi_phase_events(), vec![Event::SignedPhaseStarted(1), Event::UnsignedPhaseStarted(1)], ); - assert!(TwoPhase::snapshot().is_some()); + assert!(MultiPhase::snapshot().is_some()); roll_to(29); - assert_eq!(TwoPhase::current_phase(), Phase::Unsigned((true, 25))); - assert!(TwoPhase::snapshot().is_some()); + assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25))); + assert!(MultiPhase::snapshot().is_some()); roll_to(30); - assert_eq!(TwoPhase::current_phase(), Phase::Unsigned((true, 25))); - assert!(TwoPhase::snapshot().is_some()); + assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25))); + assert!(MultiPhase::snapshot().is_some()); // we close when upstream tells us to elect. roll_to(32); - assert_eq!(TwoPhase::current_phase(), Phase::Unsigned((true, 25))); - assert!(TwoPhase::snapshot().is_some()); + assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25))); + assert!(MultiPhase::snapshot().is_some()); - TwoPhase::elect().unwrap(); + MultiPhase::elect().unwrap(); - assert!(TwoPhase::current_phase().is_off()); - assert!(TwoPhase::snapshot().is_none()); - assert_eq!(TwoPhase::round(), 2); + assert!(MultiPhase::current_phase().is_off()); + assert!(MultiPhase::snapshot().is_none()); + assert_eq!(MultiPhase::round(), 2); roll_to(44); - assert!(TwoPhase::current_phase().is_off()); + assert!(MultiPhase::current_phase().is_off()); roll_to(45); - assert!(TwoPhase::current_phase().is_signed()); + assert!(MultiPhase::current_phase().is_signed()); roll_to(55); - assert!(TwoPhase::current_phase().is_unsigned_open_at(55)); + assert!(MultiPhase::current_phase().is_unsigned_open_at(55)); }) } @@ -1290,22 +1290,22 @@ mod tests { fn signed_phase_void() { ExtBuilder::default().phases(0, 10).build_and_execute(|| { roll_to(15); - assert!(TwoPhase::current_phase().is_off()); + assert!(MultiPhase::current_phase().is_off()); roll_to(19); - assert!(TwoPhase::current_phase().is_off()); + assert!(MultiPhase::current_phase().is_off()); roll_to(20); - assert!(TwoPhase::current_phase().is_unsigned_open_at(20)); - assert!(TwoPhase::snapshot().is_some()); + assert!(MultiPhase::current_phase().is_unsigned_open_at(20)); + assert!(MultiPhase::snapshot().is_some()); roll_to(30); - assert!(TwoPhase::current_phase().is_unsigned_open_at(20)); + assert!(MultiPhase::current_phase().is_unsigned_open_at(20)); - TwoPhase::elect().unwrap(); + MultiPhase::elect().unwrap(); - assert!(TwoPhase::current_phase().is_off()); - assert!(TwoPhase::snapshot().is_none()); + assert!(MultiPhase::current_phase().is_off()); + assert!(MultiPhase::snapshot().is_none()); }); } @@ -1313,22 +1313,22 @@ mod tests { fn unsigned_phase_void() { ExtBuilder::default().phases(10, 0).build_and_execute(|| { roll_to(15); - assert!(TwoPhase::current_phase().is_off()); + assert!(MultiPhase::current_phase().is_off()); roll_to(19); - assert!(TwoPhase::current_phase().is_off()); + assert!(MultiPhase::current_phase().is_off()); roll_to(20); - assert!(TwoPhase::current_phase().is_signed()); - assert!(TwoPhase::snapshot().is_some()); + assert!(MultiPhase::current_phase().is_signed()); + assert!(MultiPhase::snapshot().is_some()); roll_to(30); - assert!(TwoPhase::current_phase().is_signed()); + assert!(MultiPhase::current_phase().is_signed()); - let _ = TwoPhase::elect().unwrap(); + let _ = MultiPhase::elect().unwrap(); - assert!(TwoPhase::current_phase().is_off()); - assert!(TwoPhase::snapshot().is_none()); + assert!(MultiPhase::current_phase().is_off()); + assert!(MultiPhase::snapshot().is_none()); }); } @@ -1336,21 +1336,21 @@ mod tests { fn both_phases_void() { ExtBuilder::default().phases(0, 0).build_and_execute(|| { roll_to(15); - assert!(TwoPhase::current_phase().is_off()); + assert!(MultiPhase::current_phase().is_off()); roll_to(19); - assert!(TwoPhase::current_phase().is_off()); + assert!(MultiPhase::current_phase().is_off()); roll_to(20); - assert!(TwoPhase::current_phase().is_off()); + assert!(MultiPhase::current_phase().is_off()); roll_to(30); - assert!(TwoPhase::current_phase().is_off()); + assert!(MultiPhase::current_phase().is_off()); // this module is now only capable of doing on-chain backup. - let _ = TwoPhase::elect().unwrap(); + let _ = MultiPhase::elect().unwrap(); - assert!(TwoPhase::current_phase().is_off()); + assert!(MultiPhase::current_phase().is_off()); }); } @@ -1360,31 +1360,31 @@ mod tests { ExtBuilder::default().build_and_execute(|| { // signed phase started at block 15 and will end at 25. roll_to(14); - assert_eq!(TwoPhase::current_phase(), Phase::Off); + assert_eq!(MultiPhase::current_phase(), Phase::Off); roll_to(15); - assert_eq!(two_phase_events(), vec![Event::SignedPhaseStarted(1)]); - assert_eq!(TwoPhase::current_phase(), Phase::Signed); - assert_eq!(TwoPhase::round(), 1); + assert_eq!(multi_phase_events(), vec![Event::SignedPhaseStarted(1)]); + assert_eq!(MultiPhase::current_phase(), Phase::Signed); + assert_eq!(MultiPhase::round(), 1); // an unexpected call to elect. roll_to(20); - TwoPhase::elect().unwrap(); + MultiPhase::elect().unwrap(); // we surely can't have any feasible solutions. This will cause an on-chain election. assert_eq!( - two_phase_events(), + multi_phase_events(), vec![ Event::SignedPhaseStarted(1), Event::ElectionFinalized(Some(ElectionCompute::OnChain)) ], ); // all storage items must be cleared. - assert_eq!(TwoPhase::round(), 2); - assert!(TwoPhase::snapshot().is_none()); - assert!(TwoPhase::snapshot_metadata().is_none()); - assert!(TwoPhase::desired_targets().is_none()); - assert!(TwoPhase::queued_solution().is_none()); + assert_eq!(MultiPhase::round(), 2); + assert!(MultiPhase::snapshot().is_none()); + assert!(MultiPhase::snapshot_metadata().is_none()); + assert!(MultiPhase::desired_targets().is_none()); + assert!(MultiPhase::queued_solution().is_none()); }) } @@ -1392,13 +1392,13 @@ mod tests { fn fallback_strategy_works() { ExtBuilder::default().fallabck(FallbackStrategy::OnChain).build_and_execute(|| { roll_to(15); - assert_eq!(TwoPhase::current_phase(), Phase::Signed); + assert_eq!(MultiPhase::current_phase(), Phase::Signed); roll_to(25); - assert_eq!(TwoPhase::current_phase(), Phase::Unsigned((true, 25))); + assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25))); // zilch solutions thus far. - let supports = TwoPhase::elect().unwrap(); + let supports = MultiPhase::elect().unwrap(); assert_eq!( supports, @@ -1411,13 +1411,13 @@ mod tests { ExtBuilder::default().fallabck(FallbackStrategy::Nothing).build_and_execute(|| { roll_to(15); - assert_eq!(TwoPhase::current_phase(), Phase::Signed); + assert_eq!(MultiPhase::current_phase(), Phase::Signed); roll_to(25); - assert_eq!(TwoPhase::current_phase(), Phase::Unsigned((true, 25))); + assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25))); // zilch solutions thus far. - assert_eq!(TwoPhase::elect().unwrap_err(), ElectionError::NoFallbackConfigured); + assert_eq!(MultiPhase::elect().unwrap_err(), ElectionError::NoFallbackConfigured); }) } @@ -1426,8 +1426,8 @@ mod tests { // Just a rough estimate with the substrate weights. assert!(!MockWeightInfo::get()); - let all_voters: u32 = 100_000; - let all_targets: u32 = 2_000; + let all_voters: u32 = 10_000; + let all_targets: u32 = 5_000; let desired: u32 = 1_000; let weight_with = |active| { ::WeightInfo::submit_unsigned( @@ -1441,6 +1441,7 @@ mod tests { let mut active = 1; while weight_with(active) <= ::BlockWeights::get().max_block + || active == all_voters { active += 1; } diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 0b9fed1d21cf2..eb38a4cd52e95 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -16,7 +16,7 @@ // limitations under the License. use super::*; -use crate as two_phase; +use crate as multi_phase; pub use frame_support::{assert_noop, assert_ok}; use frame_support::{ parameter_types, @@ -54,7 +54,7 @@ frame_support::construct_runtime!( { System: frame_system::{Module, Call, Event, Config}, Balances: pallet_balances::{Module, Call, Event, Config}, - TwoPhase: two_phase::{Module, Call, Event}, + MultiPhase: multi_phase::{Module, Call, Event}, } ); @@ -67,11 +67,11 @@ sp_npos_elections::generate_solution_type!( ); /// All events of this pallet. -pub(crate) fn two_phase_events() -> Vec> { +pub(crate) fn multi_phase_events() -> Vec> { System::events() .into_iter() .map(|r| r.event) - .filter_map(|e| if let Event::two_phase(inner) = e { Some(inner) } else { None }) + .filter_map(|e| if let Event::multi_phase(inner) = e { Some(inner) } else { None }) .collect::>() } @@ -80,7 +80,7 @@ pub fn roll_to(n: u64) { let now = System::block_number(); for i in now + 1..=n { System::set_block_number(i); - TwoPhase::on_initialize(i); + MultiPhase::on_initialize(i); } } @@ -88,8 +88,8 @@ pub fn roll_to_with_ocw(n: u64) { let now = System::block_number(); for i in now + 1..=n { System::set_block_number(i); - TwoPhase::on_initialize(i); - TwoPhase::offchain_worker(i); + MultiPhase::on_initialize(i); + MultiPhase::offchain_worker(i); } } @@ -97,8 +97,8 @@ pub fn roll_to_with_ocw(n: u64) { /// /// This is a good example of what an offchain miner would do. pub fn raw_solution() -> RawSolution> { - let RoundSnapshot { voters, targets } = TwoPhase::snapshot().unwrap(); - let desired_targets = TwoPhase::desired_targets().unwrap(); + let RoundSnapshot { voters, targets } = MultiPhase::snapshot().unwrap(); + let desired_targets = MultiPhase::desired_targets().unwrap(); // closures let cache = helpers::generate_voter_cache::(&voters); @@ -123,12 +123,12 @@ pub fn raw_solution() -> RawSolution> { let compact = >::from_assignment(assignments, &voter_index, &target_index).unwrap(); - let round = TwoPhase::round(); + let round = MultiPhase::round(); RawSolution { compact, score, round } } pub fn witness() -> SolutionOrSnapshotSize { - TwoPhase::snapshot() + MultiPhase::snapshot() .map(|snap| SolutionOrSnapshotSize { voters: snap.voters.len() as u32, targets: snap.targets.len() as u32, @@ -210,33 +210,33 @@ parameter_types! { // Hopefully this won't be too much of a hassle to maintain. pub struct DualMockWeightInfo; -impl two_phase::weights::WeightInfo for DualMockWeightInfo { +impl multi_phase::weights::WeightInfo for DualMockWeightInfo { fn on_initialize_nothing() -> Weight { if MockWeightInfo::get() { Zero::zero() } else { - <() as two_phase::weights::WeightInfo>::on_initialize_nothing() + <() as multi_phase::weights::WeightInfo>::on_initialize_nothing() } } fn on_initialize_open_signed() -> Weight { if MockWeightInfo::get() { Zero::zero() } else { - <() as two_phase::weights::WeightInfo>::on_initialize_open_signed() + <() as multi_phase::weights::WeightInfo>::on_initialize_open_signed() } } fn on_initialize_open_unsigned_with_snapshot() -> Weight { if MockWeightInfo::get() { Zero::zero() } else { - <() as two_phase::weights::WeightInfo>::on_initialize_open_unsigned_with_snapshot() + <() as multi_phase::weights::WeightInfo>::on_initialize_open_unsigned_with_snapshot() } } fn on_initialize_open_unsigned_without_snapshot() -> Weight { if MockWeightInfo::get() { Zero::zero() } else { - <() as two_phase::weights::WeightInfo>::on_initialize_open_unsigned_without_snapshot() + <() as multi_phase::weights::WeightInfo>::on_initialize_open_unsigned_without_snapshot() } } fn submit_unsigned(v: u32, t: u32, a: u32, d: u32) -> Weight { @@ -245,7 +245,7 @@ impl two_phase::weights::WeightInfo for DualMockWeightInfo { // 5 per edge. (10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight)) } else { - <() as two_phase::weights::WeightInfo>::submit_unsigned(v, t, a, d) + <() as multi_phase::weights::WeightInfo>::submit_unsigned(v, t, a, d) } } fn feasibility_check(v: u32, t: u32, a: u32, d: u32) -> Weight { @@ -254,7 +254,7 @@ impl two_phase::weights::WeightInfo for DualMockWeightInfo { // 5 per edge. (10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight)) } else { - <() as two_phase::weights::WeightInfo>::feasibility_check(v, t, a, d) + <() as multi_phase::weights::WeightInfo>::feasibility_check(v, t, a, d) } } } diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index ecc48557124a6..de66e2d77347e 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -399,54 +399,54 @@ mod max_weight { fn find_max_voter_binary_search_works() { let w = SolutionOrSnapshotSize { voters: 10, targets: 0 }; - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 0), 0); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1), 0); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 999), 0); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1000), 1); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1001), 1); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1990), 1); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1999), 1); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2000), 2); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2001), 2); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2010), 2); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2990), 2); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2999), 2); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 3000), 3); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 3333), 3); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 5500), 5); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 7777), 7); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 9999), 9); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 10_000), 10); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 10_999), 10); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 11_000), 10); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 22_000), 10); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 0), 0); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1), 0); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 999), 0); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1000), 1); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1001), 1); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1990), 1); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1999), 1); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2000), 2); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2001), 2); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2010), 2); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2990), 2); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2999), 2); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 3000), 3); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 3333), 3); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 5500), 5); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 7777), 7); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 9999), 9); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 10_000), 10); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 10_999), 10); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 11_000), 10); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 22_000), 10); let w = SolutionOrSnapshotSize { voters: 1, targets: 0 }; - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 0), 0); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1), 0); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 999), 0); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1000), 1); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1001), 1); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1990), 1); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1999), 1); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2000), 1); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2001), 1); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2010), 1); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 3333), 1); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 0), 0); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1), 0); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 999), 0); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1000), 1); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1001), 1); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1990), 1); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1999), 1); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2000), 1); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2001), 1); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2010), 1); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 3333), 1); let w = SolutionOrSnapshotSize { voters: 2, targets: 0 }; - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 0), 0); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1), 0); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 999), 0); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1000), 1); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1001), 1); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 1999), 1); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2000), 2); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2001), 2); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 2010), 2); - assert_eq!(TwoPhase::maximum_voter_for_weight::(0, w, 3333), 2); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 0), 0); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1), 0); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 999), 0); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1000), 1); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1001), 1); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1999), 1); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2000), 2); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2001), 2); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2010), 2); + assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 3333), 2); } } @@ -468,51 +468,51 @@ mod tests { let call = Call::submit_unsigned(solution.clone(), witness()); // initial - assert_eq!(TwoPhase::current_phase(), Phase::Off); + assert_eq!(MultiPhase::current_phase(), Phase::Off); assert!(matches!( - ::validate_unsigned(TransactionSource::Local, &call) + ::validate_unsigned(TransactionSource::Local, &call) .unwrap_err(), TransactionValidityError::Invalid(InvalidTransaction::Custom(0)) )); assert!(matches!( - ::pre_dispatch(&call).unwrap_err(), + ::pre_dispatch(&call).unwrap_err(), TransactionValidityError::Invalid(InvalidTransaction::Custom(0)) )); // signed roll_to(15); - assert_eq!(TwoPhase::current_phase(), Phase::Signed); + assert_eq!(MultiPhase::current_phase(), Phase::Signed); assert!(matches!( - ::validate_unsigned(TransactionSource::Local, &call) + ::validate_unsigned(TransactionSource::Local, &call) .unwrap_err(), TransactionValidityError::Invalid(InvalidTransaction::Custom(0)) )); assert!(matches!( - ::pre_dispatch(&call).unwrap_err(), + ::pre_dispatch(&call).unwrap_err(), TransactionValidityError::Invalid(InvalidTransaction::Custom(0)) )); // unsigned roll_to(25); - assert!(TwoPhase::current_phase().is_unsigned()); + assert!(MultiPhase::current_phase().is_unsigned()); - assert!(::validate_unsigned( + assert!(::validate_unsigned( TransactionSource::Local, &call ) .is_ok()); - assert!(::pre_dispatch(&call).is_ok()); + assert!(::pre_dispatch(&call).is_ok()); // unsigned -- but not enabled. >::put(Phase::Unsigned((false, 25))); - assert!(TwoPhase::current_phase().is_unsigned()); + assert!(MultiPhase::current_phase().is_unsigned()); assert!(matches!( - ::validate_unsigned(TransactionSource::Local, &call) + ::validate_unsigned(TransactionSource::Local, &call) .unwrap_err(), TransactionValidityError::Invalid(InvalidTransaction::Custom(0)) )); assert!(matches!( - ::pre_dispatch(&call).unwrap_err(), + ::pre_dispatch(&call).unwrap_err(), TransactionValidityError::Invalid(InvalidTransaction::Custom(0)) )); }) @@ -522,18 +522,18 @@ mod tests { fn validate_unsigned_retracts_low_score() { ExtBuilder::default().desired_targets(0).build_and_execute(|| { roll_to(25); - assert!(TwoPhase::current_phase().is_unsigned()); + assert!(MultiPhase::current_phase().is_unsigned()); let solution = RawSolution:: { score: [5, 0, 0], ..Default::default() }; let call = Call::submit_unsigned(solution.clone(), witness()); // initial - assert!(::validate_unsigned( + assert!(::validate_unsigned( TransactionSource::Local, &call ) .is_ok()); - assert!(::pre_dispatch(&call).is_ok()); + assert!(::pre_dispatch(&call).is_ok()); // set a better score let ready = ReadySolution { score: [10, 0, 0], ..Default::default() }; @@ -541,12 +541,15 @@ mod tests { // won't work anymore. assert!(matches!( - ::validate_unsigned(TransactionSource::Local, &call) - .unwrap_err(), + ::validate_unsigned( + TransactionSource::Local, + &call + ) + .unwrap_err(), TransactionValidityError::Invalid(InvalidTransaction::Custom(2)) )); assert!(matches!( - ::pre_dispatch(&call).unwrap_err(), + ::pre_dispatch(&call).unwrap_err(), TransactionValidityError::Invalid(InvalidTransaction::Custom(2)) )); }) @@ -556,7 +559,7 @@ mod tests { fn validate_unsigned_retracts_incorrect_winner_count() { ExtBuilder::default().desired_targets(1).build_and_execute(|| { roll_to(25); - assert!(TwoPhase::current_phase().is_unsigned()); + assert!(MultiPhase::current_phase().is_unsigned()); let solution = RawSolution:: { score: [5, 0, 0], ..Default::default() }; let call = Call::submit_unsigned(solution.clone(), witness()); @@ -564,8 +567,11 @@ mod tests { // won't work anymore. assert!(matches!( - ::validate_unsigned(TransactionSource::Local, &call) - .unwrap_err(), + ::validate_unsigned( + TransactionSource::Local, + &call + ) + .unwrap_err(), TransactionValidityError::Invalid(InvalidTransaction::Custom(1)) )); }) @@ -575,15 +581,18 @@ mod tests { fn priority_is_set() { ExtBuilder::default().miner_tx_priority(20).desired_targets(0).build_and_execute(|| { roll_to(25); - assert!(TwoPhase::current_phase().is_unsigned()); + assert!(MultiPhase::current_phase().is_unsigned()); let solution = RawSolution:: { score: [5, 0, 0], ..Default::default() }; let call = Call::submit_unsigned(solution.clone(), witness()); assert_eq!( - ::validate_unsigned(TransactionSource::Local, &call) - .unwrap() - .priority, + ::validate_unsigned( + TransactionSource::Local, + &call + ) + .unwrap() + .priority, 25 ); }) @@ -597,7 +606,7 @@ mod tests { fn unfeasible_solution_panics() { ExtBuilder::default().build_and_execute(|| { roll_to(25); - assert!(TwoPhase::current_phase().is_unsigned()); + assert!(MultiPhase::current_phase().is_unsigned()); // This is in itself an invalid BS solution. let solution = RawSolution:: { score: [5, 0, 0], ..Default::default() }; @@ -613,7 +622,7 @@ mod tests { fn wrong_witness_panics() { ExtBuilder::default().build_and_execute(|| { roll_to(25); - assert!(TwoPhase::current_phase().is_unsigned()); + assert!(MultiPhase::current_phase().is_unsigned()); // This solution is unfeasible as well, but we won't even get there. let solution = RawSolution:: { score: [5, 0, 0], ..Default::default() }; @@ -631,19 +640,19 @@ mod tests { fn miner_works() { ExtBuilder::default().build_and_execute(|| { roll_to(25); - assert!(TwoPhase::current_phase().is_unsigned()); + assert!(MultiPhase::current_phase().is_unsigned()); // ensure we have snapshots in place. - assert!(TwoPhase::snapshot().is_some()); - assert_eq!(TwoPhase::desired_targets().unwrap(), 2); + assert!(MultiPhase::snapshot().is_some()); + assert_eq!(MultiPhase::desired_targets().unwrap(), 2); // mine seq_phragmen solution with 2 iters. - let (solution, witness) = TwoPhase::mine_solution(2).unwrap(); + let (solution, witness) = MultiPhase::mine_solution(2).unwrap(); // ensure this solution is valid. - assert!(TwoPhase::queued_solution().is_none()); - assert_ok!(TwoPhase::submit_unsigned(Origin::none(), solution, witness)); - assert!(TwoPhase::queued_solution().is_some()); + assert!(MultiPhase::queued_solution().is_none()); + assert_ok!(MultiPhase::submit_unsigned(Origin::none(), solution, witness)); + assert!(MultiPhase::queued_solution().is_some()); }) } @@ -651,9 +660,9 @@ mod tests { fn miner_trims_weight() { ExtBuilder::default().miner_weight(100).mock_weight_info(true).build_and_execute(|| { roll_to(25); - assert!(TwoPhase::current_phase().is_unsigned()); + assert!(MultiPhase::current_phase().is_unsigned()); - let (solution, witness) = TwoPhase::mine_solution(2).unwrap(); + let (solution, witness) = MultiPhase::mine_solution(2).unwrap(); let solution_weight = ::WeightInfo::submit_unsigned( witness.voters, witness.targets, @@ -667,7 +676,7 @@ mod tests { // now reduce the max weight ::set(25); - let (solution, witness) = TwoPhase::mine_solution(2).unwrap(); + let (solution, witness) = MultiPhase::mine_solution(2).unwrap(); let solution_weight = ::WeightInfo::submit_unsigned( witness.voters, witness.targets, @@ -685,11 +694,11 @@ mod tests { let (mut ext, _) = ExtBuilder::default().desired_targets(8).build_offchainify(0); ext.execute_with(|| { roll_to(25); - assert!(TwoPhase::current_phase().is_unsigned()); + assert!(MultiPhase::current_phase().is_unsigned()); // mine seq_phragmen solution with 2 iters. assert_eq!( - TwoPhase::mine_and_submit().unwrap_err(), + MultiPhase::mine_and_submit().unwrap_err(), MinerError::PreDispatchChecksFailed, ); }) @@ -704,8 +713,8 @@ mod tests { .solution_improvement_threshold(Perbill::from_percent(50)) .build_and_execute(|| { roll_to(25); - assert!(TwoPhase::current_phase().is_unsigned()); - assert_eq!(TwoPhase::desired_targets().unwrap(), 1); + assert!(MultiPhase::current_phase().is_unsigned()); + assert_eq!(MultiPhase::desired_targets().unwrap(), 1); // an initial solution let result = ElectionResult { @@ -716,10 +725,10 @@ mod tests { distribution: vec![(10, PerU16::one())], }], }; - let (solution, witness) = TwoPhase::prepare_election_result(result).unwrap(); - assert_ok!(TwoPhase::unsigned_pre_dispatch_checks(&solution)); - assert_ok!(TwoPhase::submit_unsigned(Origin::none(), solution, witness)); - assert_eq!(TwoPhase::queued_solution().unwrap().score[0], 10); + let (solution, witness) = MultiPhase::prepare_election_result(result).unwrap(); + assert_ok!(MultiPhase::unsigned_pre_dispatch_checks(&solution)); + assert_ok!(MultiPhase::submit_unsigned(Origin::none(), solution, witness)); + assert_eq!(MultiPhase::queued_solution().unwrap().score[0], 10); // trial 1: a solution who's score is only 2, i.e. 20% better in the first element. let result = ElectionResult { @@ -733,11 +742,11 @@ mod tests { }, ], }; - let (solution, _) = TwoPhase::prepare_election_result(result).unwrap(); + let (solution, _) = MultiPhase::prepare_election_result(result).unwrap(); // 12 is not 50% more than 10 assert_eq!(solution.score[0], 12); assert_noop!( - TwoPhase::unsigned_pre_dispatch_checks(&solution), + MultiPhase::unsigned_pre_dispatch_checks(&solution), Error::::PreDispatchWeakSubmission, ); // submitting this will actually panic. @@ -755,12 +764,12 @@ mod tests { }, ], }; - let (solution, witness) = TwoPhase::prepare_election_result(result).unwrap(); + let (solution, witness) = MultiPhase::prepare_election_result(result).unwrap(); assert_eq!(solution.score[0], 17); // and it is fine - assert_ok!(TwoPhase::unsigned_pre_dispatch_checks(&solution)); - assert_ok!(TwoPhase::submit_unsigned(Origin::none(), solution, witness)); + assert_ok!(MultiPhase::unsigned_pre_dispatch_checks(&solution)); + assert_ok!(MultiPhase::submit_unsigned(Origin::none(), solution, witness)); }) } @@ -769,31 +778,27 @@ mod tests { let (mut ext, _) = ExtBuilder::default().build_offchainify(0); ext.execute_with(|| { roll_to(25); - assert!(TwoPhase::current_phase().is_unsigned()); + assert!(MultiPhase::current_phase().is_unsigned()); // first execution -- okay. - assert!(TwoPhase::try_acquire_offchain_lock(25).is_ok()); + assert!(MultiPhase::try_acquire_offchain_lock(25).is_ok()); // next block: rejected. - assert!(TwoPhase::try_acquire_offchain_lock(26).is_err()); + assert!(MultiPhase::try_acquire_offchain_lock(26).is_err()); // allowed after `OFFCHAIN_REPEAT` - assert!(TwoPhase::try_acquire_offchain_lock((26 + OFFCHAIN_REPEAT).into()) - .is_ok()); + assert!(MultiPhase::try_acquire_offchain_lock((26 + OFFCHAIN_REPEAT).into()).is_ok()); // a fork like situation: re-execute last 3. - assert!(TwoPhase::try_acquire_offchain_lock( - (26 + OFFCHAIN_REPEAT - 3).into() - ) - .is_err()); - assert!(TwoPhase::try_acquire_offchain_lock( - (26 + OFFCHAIN_REPEAT - 2).into() - ) - .is_err()); - assert!(TwoPhase::try_acquire_offchain_lock( - (26 + OFFCHAIN_REPEAT - 1).into() - ) - .is_err()); + assert!( + MultiPhase::try_acquire_offchain_lock((26 + OFFCHAIN_REPEAT - 3).into()).is_err() + ); + assert!( + MultiPhase::try_acquire_offchain_lock((26 + OFFCHAIN_REPEAT - 2).into()).is_err() + ); + assert!( + MultiPhase::try_acquire_offchain_lock((26 + OFFCHAIN_REPEAT - 1).into()).is_err() + ); }) } @@ -802,22 +807,22 @@ mod tests { let (mut ext, pool) = ExtBuilder::default().build_offchainify(0); ext.execute_with(|| { roll_to(25); - assert_eq!(TwoPhase::current_phase(), Phase::Unsigned((true, 25))); + assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25))); // we must clear the offchain storage to ensure the offchain execution check doesn't get // in the way. let mut storage = StorageValueRef::persistent(&OFFCHAIN_HEAD_DB); - TwoPhase::offchain_worker(24); + MultiPhase::offchain_worker(24); assert!(pool.read().transactions.len().is_zero()); storage.clear(); - TwoPhase::offchain_worker(26); + MultiPhase::offchain_worker(26); assert!(pool.read().transactions.len().is_zero()); storage.clear(); // submits! - TwoPhase::offchain_worker(25); + MultiPhase::offchain_worker(25); assert!(!pool.read().transactions.len().is_zero()); }) } @@ -827,13 +832,13 @@ mod tests { let (mut ext, pool) = ExtBuilder::default().build_offchainify(0); ext.execute_with(|| { roll_to_with_ocw(25); - assert_eq!(TwoPhase::current_phase(), Phase::Unsigned((true, 25))); + assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25))); // OCW must have submitted now let encoded = pool.read().transactions[0].clone(); let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap(); let call = extrinsic.call; - assert!(matches!(call, OuterCall::TwoPhase(Call::submit_unsigned(_, _)))); + assert!(matches!(call, OuterCall::MultiPhase(Call::submit_unsigned(_, _)))); }) } } diff --git a/frame/election-provider-multi-phase/src/weights.rs b/frame/election-provider-multi-phase/src/weights.rs index 6070b771593ce..02fdd3531279f 100644 --- a/frame/election-provider-multi-phase/src/weights.rs +++ b/frame/election-provider-multi-phase/src/weights.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Autogenerated weights for pallet_two_phase_election_provider +//! Autogenerated weights for pallet_multi_phase_election_provider //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.1 //! DATE: 2021-01-14, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] @@ -27,7 +27,7 @@ // --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_two_phase_election_provider +// --pallet=pallet_multi_phase_election_provider // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -44,7 +44,7 @@ use frame_support::{ }; use sp_std::marker::PhantomData; -/// Weight functions needed for pallet_two_phase_election_provider. +/// Weight functions needed for pallet_multi_phase_election_provider. pub trait WeightInfo { fn on_initialize_nothing() -> Weight; fn on_initialize_open_signed() -> Weight; @@ -54,7 +54,7 @@ pub trait WeightInfo { fn feasibility_check(v: u32, t: u32, a: u32, d: u32) -> Weight; } -/// Weights for pallet_two_phase_election_provider using the Substrate node and recommended +/// Weights for pallet_multi_phase_election_provider using the Substrate node and recommended /// hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { From 1a5794a6c076be2881f55aaf50ecae3d11a73b28 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 12 Feb 2021 10:33:31 +0000 Subject: [PATCH 15/29] Do an assert as well, just in case. --- .../src/benchmarking.rs | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/frame/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs index daac85f06790a..b1ab6bfe0e9e3 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -212,11 +212,16 @@ benchmarks! { >::put(Phase::Unsigned((true, 1u32.into()))); // encode the most significant storage item that needs to be decoded in the dispatch. - let encoded_snapshot = >::snapshot().encode(); + let snapshot = >::snapshot(); + let encoded_snapshot = snapshot.encode(); + let voters_len = snapshot.voters.len(); }: { assert_ok!(>::submit_unsigned(RawOrigin::None.into(), raw_solution, witness)); - let _decoded = as Decode>::decode(&mut &*encoded_snapshot).unwrap(); - // NOTE: assert that this line is optimized away by the compiler in any way! + assert_eq!( + as Decode>::decode(&mut &*encoded_snapshot).unwrap().voters.len(), + voters_len, + ); + // This whole assert is to ensure that the statement is not optimized away. } verify { assert!(>::queued_solution().is_some()); } @@ -240,11 +245,17 @@ benchmarks! { assert_eq!(raw_solution.compact.unique_targets().len() as u32, d); // encode the most significant storage item that needs to be decoded in the dispatch. - let encoded_snapshot = >::snapshot().encode(); + let snapshot = >::snapshot(); + let encoded_snapshot = snapshot.encode(); + let voters_len = snapshot.voters.len(); }: { assert_ok!(>::feasibility_check(raw_solution, ElectionCompute::Unsigned)); - let _decoded = as Decode>::decode(&mut &*encoded_snapshot).unwrap(); - // NOTE: assert that this line is optimized away by the compiler in any way! + assert_eq!( + as Decode>::decode(&mut &*encoded_snapshot).unwrap().voters.len(), + voters_len, + ); + // This whole assert is to ensure that the statement is not optimized away. + } } From 01e63ed222c4e467e8873637f346655ea781e7e6 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 12 Feb 2021 10:44:33 +0000 Subject: [PATCH 16/29] Fix build --- frame/election-provider-multi-phase/src/benchmarking.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs index b1ab6bfe0e9e3..c069ade162fdc 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -212,7 +212,7 @@ benchmarks! { >::put(Phase::Unsigned((true, 1u32.into()))); // encode the most significant storage item that needs to be decoded in the dispatch. - let snapshot = >::snapshot(); + let snapshot = >::snapshot().unwrap(); let encoded_snapshot = snapshot.encode(); let voters_len = snapshot.voters.len(); }: { @@ -245,7 +245,7 @@ benchmarks! { assert_eq!(raw_solution.compact.unique_targets().len() as u32, d); // encode the most significant storage item that needs to be decoded in the dispatch. - let snapshot = >::snapshot(); + let snapshot = >::snapshot().unwrap(); let encoded_snapshot = snapshot.encode(); let voters_len = snapshot.voters.len(); }: { From 4ccecdf1d30cd775349c451492320dc29ca83e56 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 12 Feb 2021 11:48:07 +0000 Subject: [PATCH 17/29] Update frame/election-provider-multi-phase/src/unsigned.rs Co-authored-by: Guillaume Thiolliere --- frame/election-provider-multi-phase/src/unsigned.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index de66e2d77347e..be143e55694b6 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -178,7 +178,7 @@ impl Pallet { /// down the line. /// /// Indeed, the score must be computed **after** this step. If this step reduces the score too - /// much, then the solution will be discarded. + /// much or remove a winner, then the solution must be discarded **after** this step. pub fn trim_compact( maximum_allowed_voters: u32, mut compact: CompactOf, From 83a789ece85c49383e1e68656894ece52f853cfc Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 12 Feb 2021 11:48:41 +0000 Subject: [PATCH 18/29] Las comment --- 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 ee89beb04a10b..935b8c1fb7c9a 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -921,6 +921,10 @@ impl Pallet { let voter_index = helpers::voter_index_fn_usize::(&cache); // first, make sure that all the winners are sane. + // OPTIMIZATION: we could first build the assignments, and then extract the winners directly + // from that, as that would eliminate a little bit of duplicate work. For now, we keep them + // separate: First extract winners separately from compact, and then assignments. This is + // also better, because we can reject solutions that don't meet `desired_targets` early on. let winners = winners .into_iter() .map(|i| target_at(i).ok_or(FeasibilityError::InvalidWinner)) From 7c71df031cd63d5428b8a7d0bddea5ca63af80c7 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 12 Feb 2021 12:20:52 +0000 Subject: [PATCH 19/29] fix staking fuzzer. --- frame/staking/fuzzer/src/submit_solution.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/fuzzer/src/submit_solution.rs b/frame/staking/fuzzer/src/submit_solution.rs index d94ee49b96db4..b661a83a1bdd0 100644 --- a/frame/staking/fuzzer/src/submit_solution.rs +++ b/frame/staking/fuzzer/src/submit_solution.rs @@ -164,7 +164,7 @@ fn main() { assert_eq!( call.dispatch_bypass_filter(origin.into()).unwrap_err().error, DispatchError::Module { - index: 0, + index: 2, error: 16, message: Some("OffchainElectionWeakSubmission"), }, From 12b1640963edde54da77da31496277142e8e79a1 Mon Sep 17 00:00:00 2001 From: Parity Benchmarking Bot Date: Fri, 12 Feb 2021 13:18:20 +0000 Subject: [PATCH 20/29] cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_election_provider_multi_phase --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/election-provider-multi-phase/src/weights.rs --template=./.maintain/frame-weight-template.hbs --- .../src/weights.rs | 113 +++++++++--------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/frame/election-provider-multi-phase/src/weights.rs b/frame/election-provider-multi-phase/src/weights.rs index 02fdd3531279f..e34b3b4d7f226 100644 --- a/frame/election-provider-multi-phase/src/weights.rs +++ b/frame/election-provider-multi-phase/src/weights.rs @@ -15,10 +15,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Autogenerated weights for pallet_multi_phase_election_provider +//! Autogenerated weights for pallet_election_provider_multi_phase //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.1 -//! DATE: 2021-01-14, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 +//! DATE: 2021-02-12, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: @@ -27,76 +27,76 @@ // --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_multi_phase_election_provider +// --pallet=pallet_election_provider_multi_phase // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --output=./frame/two-phase-election-provider/src/weights.rs +// --output=./frame/election-provider-multi-phase/src/weights.rs // --template=./.maintain/frame-weight-template.hbs + #![allow(unused_parens)] #![allow(unused_imports)] -use frame_support::{ - traits::Get, - weights::{Weight, constants::RocksDbWeight}, -}; +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use sp_std::marker::PhantomData; -/// Weight functions needed for pallet_multi_phase_election_provider. +/// Weight functions needed for pallet_election_provider_multi_phase. pub trait WeightInfo { fn on_initialize_nothing() -> Weight; fn on_initialize_open_signed() -> Weight; - fn on_initialize_open_unsigned_without_snapshot() -> Weight; fn on_initialize_open_unsigned_with_snapshot() -> Weight; - fn submit_unsigned(v: u32, t: u32, a: u32, d: u32) -> Weight; - fn feasibility_check(v: u32, t: u32, a: u32, d: u32) -> Weight; + fn on_initialize_open_unsigned_without_snapshot() -> Weight; + fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight; + fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight; } -/// Weights for pallet_multi_phase_election_provider using the Substrate node and recommended -/// hardware. +/// Weights for pallet_election_provider_multi_phase using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { fn on_initialize_nothing() -> Weight { - (21_280_000 as Weight).saturating_add(T::DbWeight::get().reads(7 as Weight)) + (23_272_000 as Weight) + .saturating_add(T::DbWeight::get().reads(7 as Weight)) } fn on_initialize_open_signed() -> Weight { - (74_221_000 as Weight) + (78_018_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn on_initialize_open_unsigned_with_snapshot() -> Weight { - (76_100_000 as Weight) - .saturating_add(T::DbWeight::get().reads(8 as Weight)) + (76_963_000 as Weight) + .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn on_initialize_open_unsigned_without_snapshot() -> Weight { - (76_100_000 as Weight) - .saturating_add(T::DbWeight::get().reads(8 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + (21_235_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) } - fn submit_unsigned(v: u32, _t: u32, a: u32, d: u32) -> Weight { + fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) - // Standard Error: 21_000 - .saturating_add((2_606_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 21_000 - .saturating_add((11_405_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 108_000 - .saturating_add((2_651_000 as Weight).saturating_mul(d as Weight)) + // Standard Error: 22_000 + .saturating_add((4_261_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 73_000 + .saturating_add((311_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 22_000 + .saturating_add((13_490_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 110_000 + .saturating_add((4_677_000 as Weight).saturating_mul(d as Weight)) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } - fn feasibility_check(v: u32, t: u32, a: u32, d: u32) -> Weight { + fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) // Standard Error: 12_000 - .saturating_add((2_788_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((4_335_000 as Weight).saturating_mul(v as Weight)) // Standard Error: 41_000 - .saturating_add((601_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((564_000 as Weight).saturating_mul(t as Weight)) // Standard Error: 12_000 - .saturating_add((9_722_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 61_000 - .saturating_add((3_706_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((10_563_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 62_000 + .saturating_add((4_750_000 as Weight).saturating_mul(d as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) } } @@ -104,44 +104,47 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { fn on_initialize_nothing() -> Weight { - (21_280_000 as Weight).saturating_add(RocksDbWeight::get().reads(7 as Weight)) + (23_272_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(7 as Weight)) } fn on_initialize_open_signed() -> Weight { - (74_221_000 as Weight) + (78_018_000 as Weight) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } fn on_initialize_open_unsigned_with_snapshot() -> Weight { - (76_100_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(8 as Weight)) + (76_963_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } fn on_initialize_open_unsigned_without_snapshot() -> Weight { - (76_100_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(8 as Weight)) - .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + (21_235_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } - fn submit_unsigned(v: u32, _t: u32, a: u32, d: u32) -> Weight { + fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) - // Standard Error: 21_000 - .saturating_add((2_606_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 21_000 - .saturating_add((11_405_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 108_000 - .saturating_add((2_651_000 as Weight).saturating_mul(d as Weight)) + // Standard Error: 22_000 + .saturating_add((4_261_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 73_000 + .saturating_add((311_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 22_000 + .saturating_add((13_490_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 110_000 + .saturating_add((4_677_000 as Weight).saturating_mul(d as Weight)) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } - fn feasibility_check(v: u32, t: u32, a: u32, d: u32) -> Weight { + fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) // Standard Error: 12_000 - .saturating_add((2_788_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((4_335_000 as Weight).saturating_mul(v as Weight)) // Standard Error: 41_000 - .saturating_add((601_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((564_000 as Weight).saturating_mul(t as Weight)) // Standard Error: 12_000 - .saturating_add((9_722_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 61_000 - .saturating_add((3_706_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((10_563_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 62_000 + .saturating_add((4_750_000 as Weight).saturating_mul(d as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) } } From 6397d4a0dc182f8ad98ce5787ce44cc98e2b6ff1 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 12 Feb 2021 13:25:36 +0000 Subject: [PATCH 21/29] Add one last layer of feasibility check as well. --- .../election-provider-multi-phase/src/lib.rs | 2 +- .../src/unsigned.rs | 39 +++++++++++++++---- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 935b8c1fb7c9a..1c28921338d8d 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -236,7 +236,7 @@ mod mock; #[macro_use] pub mod helpers; -const LOG_TARGET: &'static str = "election-provider"; +const LOG_TARGET: &'static str = "runtime::election-provider"; pub mod unsigned; pub mod weights; diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index be143e55694b6..b7589ca2d327c 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -44,6 +44,8 @@ pub enum MinerError { PoolSubmissionFailed, /// The pre-dispatch checks failed for the mined solution. PreDispatchChecksFailed, + /// The solution generated from the miner is not feasible. + Feasibility(FeasibilityError), } impl From for MinerError { @@ -52,23 +54,46 @@ impl From for MinerError { } } +impl From for MinerError { + fn from(e: FeasibilityError) -> Self { + MinerError::Feasibility(e) + } +} + impl Pallet { /// Mine a new solution, and submit it back to the chain as an unsigned transaction. - pub(crate) fn mine_and_submit() -> Result<(), MinerError> { - let balancing = Self::get_balancing_iters(); + pub fn mine_check_and_submit() -> Result<(), MinerError> { + let iters = Self::get_balancing_iters(); + // get the solution, with a load of checks to ensure if submitted, IT IS ABSOLUTELY VALID. + let (raw_solution, witness) = Self::mine_and_check(iters)?; + + let call = Call::submit_unsigned(raw_solution, witness).into(); + SubmitTransaction::>::submit_unsigned_transaction(call) + .map_err(|_| MinerError::PoolSubmissionFailed) + } + + /// Mine a new npos solution, with all the relevant checks to make sure that it will be accepted + /// to the chain. + /// + /// If you want an unchecked solution, use [`Pallet::mine_solution`]. + /// If you want a checked solution and submit it at the same time, use + /// [`Pallet::mine_check_and_submit`]. + pub fn mine_and_check(iters: usize) -> Result<(RawSolution>, SolutionOrSnapshotSize), MinerError> { let (raw_solution, witness) = Self::mine_solution(balancing)?; // ensure that this will pass the pre-dispatch checks Self::unsigned_pre_dispatch_checks(&raw_solution).map_err(|e| { - log!(warn, "pre-disaptch-checks failed for mined solution: {:?}", e); + log!(warn, "pre-dispatch-checks failed for mined solution: {:?}", e); MinerError::PreDispatchChecksFailed })?; - // submit the raw solution to the pool. - let call = Call::submit_unsigned(raw_solution, witness).into(); + // ensure that this is a feasible solution + let _ = Self::feasibility_check(solution.clone(), ElectionCompute::Unsigned).map_err(|e| { + log!(warn, "feasibility-check failed for mined solution: {:?}", e); + e.into() + })?; - SubmitTransaction::>::submit_unsigned_transaction(call) - .map_err(|_| MinerError::PoolSubmissionFailed) + Ok((raw_solution, witness)) } /// Mine a new npos solution. From 7d6f6adc8312da71d9b2418a80f80e5424555798 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 12 Feb 2021 13:33:32 +0000 Subject: [PATCH 22/29] Last fixes to benchmarks --- .../src/benchmarking.rs | 23 +++++-------------- .../election-provider-multi-phase/src/lib.rs | 2 +- .../src/unsigned.rs | 23 +++++++++++++------ 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/frame/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs index c069ade162fdc..74db28c6e3929 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -212,16 +212,12 @@ benchmarks! { >::put(Phase::Unsigned((true, 1u32.into()))); // encode the most significant storage item that needs to be decoded in the dispatch. - let snapshot = >::snapshot().unwrap(); - let encoded_snapshot = snapshot.encode(); - let voters_len = snapshot.voters.len(); + let encoded_snapshot = >::snapshot().unwrap().encode(); + let encoded_call = >::submit_unsigned(raw_solution.clone(), witness).encode(); }: { assert_ok!(>::submit_unsigned(RawOrigin::None.into(), raw_solution, witness)); - assert_eq!( - as Decode>::decode(&mut &*encoded_snapshot).unwrap().voters.len(), - voters_len, - ); - // This whole assert is to ensure that the statement is not optimized away. + let _decoded_snap = as Decode>::decode(&mut &*encoded_snapshot).unwrap(); + let _decoded_call = as Decode>::decode(&mut &*encoded_call).unwrap(); } verify { assert!(>::queued_solution().is_some()); } @@ -245,17 +241,10 @@ benchmarks! { assert_eq!(raw_solution.compact.unique_targets().len() as u32, d); // encode the most significant storage item that needs to be decoded in the dispatch. - let snapshot = >::snapshot().unwrap(); - let encoded_snapshot = snapshot.encode(); - let voters_len = snapshot.voters.len(); + let encoded_snapshot = >::snapshot().unwrap().encode(); }: { assert_ok!(>::feasibility_check(raw_solution, ElectionCompute::Unsigned)); - assert_eq!( - as Decode>::decode(&mut &*encoded_snapshot).unwrap().voters.len(), - voters_len, - ); - // This whole assert is to ensure that the statement is not optimized away. - + let _decoded_snap = as Decode>::decode(&mut &*encoded_snapshot).unwrap(); } } diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 1c28921338d8d..ce6ad9ffac0ba 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -598,7 +598,7 @@ pub mod pallet { if Self::current_phase().is_unsigned_open_at(n) { match Self::try_acquire_offchain_lock(n) { Ok(_) => { - let outcome = Self::mine_and_submit().map_err(ElectionError::from); + let outcome = Self::mine_check_and_submit().map_err(ElectionError::from); log!(info, "miner exeuction done: {:?}", outcome); } Err(why) => log!(warn, "denied offchain worker: {:?}", why), diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index b7589ca2d327c..770eab98c1471 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -78,8 +78,10 @@ impl Pallet { /// If you want an unchecked solution, use [`Pallet::mine_solution`]. /// If you want a checked solution and submit it at the same time, use /// [`Pallet::mine_check_and_submit`]. - pub fn mine_and_check(iters: usize) -> Result<(RawSolution>, SolutionOrSnapshotSize), MinerError> { - let (raw_solution, witness) = Self::mine_solution(balancing)?; + pub fn mine_and_check( + iters: usize, + ) -> Result<(RawSolution>, SolutionOrSnapshotSize), MinerError> { + let (raw_solution, witness) = Self::mine_solution(iters)?; // ensure that this will pass the pre-dispatch checks Self::unsigned_pre_dispatch_checks(&raw_solution).map_err(|e| { @@ -88,10 +90,12 @@ impl Pallet { })?; // ensure that this is a feasible solution - let _ = Self::feasibility_check(solution.clone(), ElectionCompute::Unsigned).map_err(|e| { - log!(warn, "feasibility-check failed for mined solution: {:?}", e); - e.into() - })?; + let _ = Self::feasibility_check(raw_solution.clone(), ElectionCompute::Unsigned).map_err( + |e| { + log!(warn, "feasibility-check failed for mined solution: {:?}", e); + MinerError::from(e) + }, + )?; Ok((raw_solution, witness)) } @@ -723,12 +727,17 @@ mod tests { // mine seq_phragmen solution with 2 iters. assert_eq!( - MultiPhase::mine_and_submit().unwrap_err(), + MultiPhase::mine_check_and_submit().unwrap_err(), MinerError::PreDispatchChecksFailed, ); }) } + #[test] + fn miner_will_not_submit_if_not_feasible() { + todo!() + } + #[test] fn unsigned_per_dispatch_checks_can_only_submit_threshold_better() { ExtBuilder::default() From 2e284374fb803b39c087e7cc0646e6df7601ca1c Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 12 Feb 2021 14:03:44 +0000 Subject: [PATCH 23/29] Some more docs. --- frame/election-provider-multi-phase/src/lib.rs | 3 ++- frame/staking/src/lib.rs | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index ce6ad9ffac0ba..757ef7bdebdfd 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -571,7 +571,8 @@ pub mod pallet { { let (need_snapshot, enabled, additional) = if current_phase == Phase::Signed { // followed by a signed phase: close the signed phase, no need for snapshot. - // NOTE: SIGNED_PHASE + // TWO_PHASE_NOTE: later on once we have signed phase, this should return + // something else. (false, true, Weight::zero()) } else { // no signed phase: create a new snapshot, definitely `enable` the unsigned diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index c69a680a5267f..53f20d6c3b400 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -2865,6 +2865,7 @@ impl Module { // Set staking information for new era. let maybe_new_validators = Self::select_and_update_validators(current_era); + // TWO_PHASE_NOTE: use this later on. let _unused_new_validators = Self::enact_election(current_era); maybe_new_validators @@ -3175,8 +3176,8 @@ impl Module { /// /// This will also process the election, as noted in [`process_election`]. fn enact_election(_current_era: EraIndex) -> Option> { - let outcome = T::ElectionProvider::elect().map(|_| ()); - log!(debug, "Experimental election provider outputted {:?}", outcome); + let _outcome = T::ElectionProvider::elect().map(|_| ()); + log!(debug, "Experimental election provider outputted {:?}", _outcome); // TWO_PHASE_NOTE: This code path shall not return anything for now. Later on, redirect the // results to `process_election`. None From 5fda74464bfc34d9914626043b32dc832d203d16 Mon Sep 17 00:00:00 2001 From: Parity Benchmarking Bot Date: Fri, 12 Feb 2021 14:21:22 +0000 Subject: [PATCH 24/29] cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_election_provider_multi_phase --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/election-provider-multi-phase/src/weights.rs --template=./.maintain/frame-weight-template.hbs --- .../src/weights.rs | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/frame/election-provider-multi-phase/src/weights.rs b/frame/election-provider-multi-phase/src/weights.rs index e34b3b4d7f226..a53f6f022ee41 100644 --- a/frame/election-provider-multi-phase/src/weights.rs +++ b/frame/election-provider-multi-phase/src/weights.rs @@ -56,47 +56,47 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { fn on_initialize_nothing() -> Weight { - (23_272_000 as Weight) + (23_608_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) } fn on_initialize_open_signed() -> Weight { - (78_018_000 as Weight) + (80_022_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn on_initialize_open_unsigned_with_snapshot() -> Weight { - (76_963_000 as Weight) + (79_071_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn on_initialize_open_unsigned_without_snapshot() -> Weight { - (21_235_000 as Weight) + (21_407_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) - // Standard Error: 22_000 - .saturating_add((4_261_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 21_000 + .saturating_add((4_113_000 as Weight).saturating_mul(v as Weight)) // Standard Error: 73_000 - .saturating_add((311_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 22_000 - .saturating_add((13_490_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 110_000 - .saturating_add((4_677_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((48_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 21_000 + .saturating_add((13_747_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 109_000 + .saturating_add((4_526_000 as Weight).saturating_mul(d as Weight)) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) // Standard Error: 12_000 - .saturating_add((4_335_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 41_000 - .saturating_add((564_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((4_190_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 40_000 + .saturating_add((494_000 as Weight).saturating_mul(t as Weight)) // Standard Error: 12_000 - .saturating_add((10_563_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 62_000 - .saturating_add((4_750_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((10_391_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 60_000 + .saturating_add((4_573_000 as Weight).saturating_mul(d as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) } } @@ -104,47 +104,47 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { fn on_initialize_nothing() -> Weight { - (23_272_000 as Weight) + (23_608_000 as Weight) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) } fn on_initialize_open_signed() -> Weight { - (78_018_000 as Weight) + (80_022_000 as Weight) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } fn on_initialize_open_unsigned_with_snapshot() -> Weight { - (76_963_000 as Weight) + (79_071_000 as Weight) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } fn on_initialize_open_unsigned_without_snapshot() -> Weight { - (21_235_000 as Weight) + (21_407_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) - // Standard Error: 22_000 - .saturating_add((4_261_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 21_000 + .saturating_add((4_113_000 as Weight).saturating_mul(v as Weight)) // Standard Error: 73_000 - .saturating_add((311_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 22_000 - .saturating_add((13_490_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 110_000 - .saturating_add((4_677_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((48_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 21_000 + .saturating_add((13_747_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 109_000 + .saturating_add((4_526_000 as Weight).saturating_mul(d as Weight)) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) // Standard Error: 12_000 - .saturating_add((4_335_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 41_000 - .saturating_add((564_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((4_190_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 40_000 + .saturating_add((494_000 as Weight).saturating_mul(t as Weight)) // Standard Error: 12_000 - .saturating_add((10_563_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 62_000 - .saturating_add((4_750_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((10_391_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 60_000 + .saturating_add((4_573_000 as Weight).saturating_mul(d as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) } } From 1ce6c8e8a040d0d601bf0051235790724c83c744 Mon Sep 17 00:00:00 2001 From: Parity Benchmarking Bot Date: Fri, 12 Feb 2021 15:05:57 +0000 Subject: [PATCH 25/29] cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_election_provider_multi_phase --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/election-provider-multi-phase/src/weights.rs --template=./.maintain/frame-weight-template.hbs --- .../src/weights.rs | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/frame/election-provider-multi-phase/src/weights.rs b/frame/election-provider-multi-phase/src/weights.rs index a53f6f022ee41..cbdc5b39bf3ee 100644 --- a/frame/election-provider-multi-phase/src/weights.rs +++ b/frame/election-provider-multi-phase/src/weights.rs @@ -56,47 +56,47 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { fn on_initialize_nothing() -> Weight { - (23_608_000 as Weight) + (23_401_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) } fn on_initialize_open_signed() -> Weight { - (80_022_000 as Weight) + (79_260_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn on_initialize_open_unsigned_with_snapshot() -> Weight { - (79_071_000 as Weight) + (77_745_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn on_initialize_open_unsigned_without_snapshot() -> Weight { - (21_407_000 as Weight) + (21_764_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) - // Standard Error: 21_000 - .saturating_add((4_113_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 73_000 - .saturating_add((48_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 21_000 - .saturating_add((13_747_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 109_000 - .saturating_add((4_526_000 as Weight).saturating_mul(d as Weight)) + // Standard Error: 23_000 + .saturating_add((4_171_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 78_000 + .saturating_add((229_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 23_000 + .saturating_add((13_661_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 117_000 + .saturating_add((4_499_000 as Weight).saturating_mul(d as Weight)) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) // Standard Error: 12_000 - .saturating_add((4_190_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 40_000 - .saturating_add((494_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((4_232_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 42_000 + .saturating_add((636_000 as Weight).saturating_mul(t as Weight)) // Standard Error: 12_000 - .saturating_add((10_391_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 60_000 - .saturating_add((4_573_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((10_294_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 64_000 + .saturating_add((4_428_000 as Weight).saturating_mul(d as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) } } @@ -104,47 +104,47 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { fn on_initialize_nothing() -> Weight { - (23_608_000 as Weight) + (23_401_000 as Weight) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) } fn on_initialize_open_signed() -> Weight { - (80_022_000 as Weight) + (79_260_000 as Weight) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } fn on_initialize_open_unsigned_with_snapshot() -> Weight { - (79_071_000 as Weight) + (77_745_000 as Weight) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } fn on_initialize_open_unsigned_without_snapshot() -> Weight { - (21_407_000 as Weight) + (21_764_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) - // Standard Error: 21_000 - .saturating_add((4_113_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 73_000 - .saturating_add((48_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 21_000 - .saturating_add((13_747_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 109_000 - .saturating_add((4_526_000 as Weight).saturating_mul(d as Weight)) + // Standard Error: 23_000 + .saturating_add((4_171_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 78_000 + .saturating_add((229_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 23_000 + .saturating_add((13_661_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 117_000 + .saturating_add((4_499_000 as Weight).saturating_mul(d as Weight)) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) // Standard Error: 12_000 - .saturating_add((4_190_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 40_000 - .saturating_add((494_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((4_232_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 42_000 + .saturating_add((636_000 as Weight).saturating_mul(t as Weight)) // Standard Error: 12_000 - .saturating_add((10_391_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 60_000 - .saturating_add((4_573_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((10_294_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 64_000 + .saturating_add((4_428_000 as Weight).saturating_mul(d as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) } } From f63c5b83989c7482ef06e88ab9b3575c897f47cf Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 12 Feb 2021 16:11:52 +0000 Subject: [PATCH 26/29] Some nits --- bin/node/runtime/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 6d014193cec88..207ac1f236458 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -504,14 +504,14 @@ impl pallet_staking::Config for Runtime { type WeightInfo = pallet_staking::weights::SubstrateWeight; } -use pallet_election_provider_multi_phase::FallbackStrategy; parameter_types! { // phase durations. 1/4 of the last session for each. pub const SignedPhase: u32 = EPOCH_DURATION_IN_BLOCKS / 4; pub const UnsignedPhase: u32 = EPOCH_DURATION_IN_BLOCKS / 4; // fallback: no need to do on-chain phragmen initially. - pub const Fallback: FallbackStrategy = FallbackStrategy::Nothing; + pub const Fallback: pallet_election_provider_multi_phase::FallbackStrategy = + pallet_election_provider_multi_phase::FallbackStrategy::Nothing; pub SolutionImprovementThreshold: Perbill = Perbill::from_rational_approximation(1u32, 10_000); From 701e4a228b07917e236ec4fa8c9c382baa562e18 Mon Sep 17 00:00:00 2001 From: Parity Benchmarking Bot Date: Sat, 13 Feb 2021 10:09:53 +0000 Subject: [PATCH 27/29] cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_staking --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/staking/src/weights.rs --template=./.maintain/frame-weight-template.hbs --- frame/staking/src/weights.rs | 232 +++++++++++++++++------------------ 1 file changed, 116 insertions(+), 116 deletions(-) diff --git a/frame/staking/src/weights.rs b/frame/staking/src/weights.rs index b70563ccf41b3..c7b7edad5518a 100644 --- a/frame/staking/src/weights.rs +++ b/frame/staking/src/weights.rs @@ -17,8 +17,8 @@ //! Autogenerated weights for pallet_staking //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.1 -//! DATE: 2021-01-19, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 +//! DATE: 2021-02-13, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: @@ -75,171 +75,171 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { fn bond() -> Weight { - (76_281_000 as Weight) + (81_642_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn bond_extra() -> Weight { - (62_062_000 as Weight) + (66_025_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn unbond() -> Weight { - (57_195_000 as Weight) + (60_810_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } fn withdraw_unbonded_update(s: u32, ) -> Weight { - (58_043_000 as Weight) + (61_537_000 as Weight) // Standard Error: 1_000 - .saturating_add((52_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((60_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } fn withdraw_unbonded_kill(s: u32, ) -> Weight { - (89_920_000 as Weight) - // Standard Error: 3_000 - .saturating_add((2_526_000 as Weight).saturating_mul(s as Weight)) + (95_741_000 as Weight) + // Standard Error: 1_000 + .saturating_add((2_754_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(8 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) } fn validate() -> Weight { - (20_228_000 as Weight) + (21_009_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn kick(k: u32, ) -> Weight { - (31_066_000 as Weight) - // Standard Error: 11_000 - .saturating_add((17_754_000 as Weight).saturating_mul(k as Weight)) + (31_832_000 as Weight) + // Standard Error: 15_000 + .saturating_add((19_418_000 as Weight).saturating_mul(k as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(k as Weight))) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(k as Weight))) } fn nominate(n: u32, ) -> Weight { - (33_494_000 as Weight) - // Standard Error: 23_000 - .saturating_add((5_253_000 as Weight).saturating_mul(n as Weight)) + (34_304_000 as Weight) + // Standard Error: 20_000 + .saturating_add((5_643_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn chill() -> Weight { - (19_396_000 as Weight) + (20_103_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn set_payee() -> Weight { - (13_449_000 as Weight) + (13_858_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn set_controller() -> Weight { - (29_184_000 as Weight) + (30_269_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } fn set_validator_count() -> Weight { - (2_266_000 as Weight) + (2_444_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn force_no_eras() -> Weight { - (2_462_000 as Weight) + (2_766_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn force_new_era() -> Weight { - (2_483_000 as Weight) + (2_724_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn force_new_era_always() -> Weight { - (2_495_000 as Weight) + (2_702_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn set_invulnerables(v: u32, ) -> Weight { - (2_712_000 as Weight) + (2_914_000 as Weight) // Standard Error: 0 - .saturating_add((9_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((35_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn force_unstake(s: u32, ) -> Weight { - (60_508_000 as Weight) - // Standard Error: 1_000 - .saturating_add((2_525_000 as Weight).saturating_mul(s as Weight)) + (64_032_000 as Weight) + // Standard Error: 2_000 + .saturating_add((2_787_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(8 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) } fn cancel_deferred_slash(s: u32, ) -> Weight { - (5_886_772_000 as Weight) - // Standard Error: 393_000 - .saturating_add((34_849_000 as Weight).saturating_mul(s as Weight)) + (5_903_394_000 as Weight) + // Standard Error: 391_000 + .saturating_add((34_834_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn payout_stakers_dead_controller(n: u32, ) -> Weight { - (127_627_000 as Weight) - // Standard Error: 27_000 - .saturating_add((49_354_000 as Weight).saturating_mul(n as Weight)) + (141_724_000 as Weight) + // Standard Error: 24_000 + .saturating_add((53_018_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(11 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(n as Weight))) } fn payout_stakers_alive_staked(n: u32, ) -> Weight { - (156_838_000 as Weight) - // Standard Error: 24_000 - .saturating_add((62_653_000 as Weight).saturating_mul(n as Weight)) + (159_994_000 as Weight) + // Standard Error: 28_000 + .saturating_add((67_746_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(12 as Weight)) .saturating_add(T::DbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(n as Weight))) } fn rebond(l: u32, ) -> Weight { - (40_110_000 as Weight) + (42_177_000 as Weight) // Standard Error: 1_000 - .saturating_add((78_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((82_000 as Weight).saturating_mul(l as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } fn set_history_depth(e: u32, ) -> Weight { (0 as Weight) - // Standard Error: 70_000 - .saturating_add((32_883_000 as Weight).saturating_mul(e as Weight)) + // Standard Error: 65_000 + .saturating_add((34_151_000 as Weight).saturating_mul(e as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) .saturating_add(T::DbWeight::get().writes((7 as Weight).saturating_mul(e as Weight))) } fn reap_stash(s: u32, ) -> Weight { - (64_605_000 as Weight) - // Standard Error: 1_000 - .saturating_add((2_506_000 as Weight).saturating_mul(s as Weight)) + (68_377_000 as Weight) + // Standard Error: 0 + .saturating_add((2_757_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(8 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) } fn new_era(v: u32, n: u32, ) -> Weight { (0 as Weight) - // Standard Error: 926_000 - .saturating_add((548_212_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 46_000 - .saturating_add((78_343_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(7 as Weight)) + // Standard Error: 908_000 + .saturating_add((588_562_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 45_000 + .saturating_add((83_485_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(9 as Weight)) .saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(v as Weight))) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) - .saturating_add(T::DbWeight::get().writes(8 as Weight)) + .saturating_add(T::DbWeight::get().writes(13 as Weight)) .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) } fn submit_solution_better(v: u32, n: u32, a: u32, w: u32, ) -> Weight { (0 as Weight) - // Standard Error: 48_000 - .saturating_add((937_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 19_000 - .saturating_add((657_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 48_000 - .saturating_add((70_669_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 101_000 - .saturating_add((7_658_000 as Weight).saturating_mul(w as Weight)) + // Standard Error: 52_000 + .saturating_add((750_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 20_000 + .saturating_add((556_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 52_000 + .saturating_add((76_201_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 108_000 + .saturating_add((7_271_000 as Weight).saturating_mul(w as Weight)) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(a as Weight))) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(w as Weight))) @@ -250,171 +250,171 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { fn bond() -> Weight { - (76_281_000 as Weight) + (81_642_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } fn bond_extra() -> Weight { - (62_062_000 as Weight) + (66_025_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } fn unbond() -> Weight { - (57_195_000 as Weight) + (60_810_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } fn withdraw_unbonded_update(s: u32, ) -> Weight { - (58_043_000 as Weight) + (61_537_000 as Weight) // Standard Error: 1_000 - .saturating_add((52_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((60_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } fn withdraw_unbonded_kill(s: u32, ) -> Weight { - (89_920_000 as Weight) - // Standard Error: 3_000 - .saturating_add((2_526_000 as Weight).saturating_mul(s as Weight)) + (95_741_000 as Weight) + // Standard Error: 1_000 + .saturating_add((2_754_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(8 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) } fn validate() -> Weight { - (20_228_000 as Weight) + (21_009_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } fn kick(k: u32, ) -> Weight { - (31_066_000 as Weight) - // Standard Error: 11_000 - .saturating_add((17_754_000 as Weight).saturating_mul(k as Weight)) + (31_832_000 as Weight) + // Standard Error: 15_000 + .saturating_add((19_418_000 as Weight).saturating_mul(k as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(k as Weight))) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(k as Weight))) } fn nominate(n: u32, ) -> Weight { - (33_494_000 as Weight) - // Standard Error: 23_000 - .saturating_add((5_253_000 as Weight).saturating_mul(n as Weight)) + (34_304_000 as Weight) + // Standard Error: 20_000 + .saturating_add((5_643_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } fn chill() -> Weight { - (19_396_000 as Weight) + (20_103_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } fn set_payee() -> Weight { - (13_449_000 as Weight) + (13_858_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn set_controller() -> Weight { - (29_184_000 as Weight) + (30_269_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } fn set_validator_count() -> Weight { - (2_266_000 as Weight) + (2_444_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn force_no_eras() -> Weight { - (2_462_000 as Weight) + (2_766_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn force_new_era() -> Weight { - (2_483_000 as Weight) + (2_724_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn force_new_era_always() -> Weight { - (2_495_000 as Weight) + (2_702_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn set_invulnerables(v: u32, ) -> Weight { - (2_712_000 as Weight) + (2_914_000 as Weight) // Standard Error: 0 - .saturating_add((9_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((35_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn force_unstake(s: u32, ) -> Weight { - (60_508_000 as Weight) - // Standard Error: 1_000 - .saturating_add((2_525_000 as Weight).saturating_mul(s as Weight)) + (64_032_000 as Weight) + // Standard Error: 2_000 + .saturating_add((2_787_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(8 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) } fn cancel_deferred_slash(s: u32, ) -> Weight { - (5_886_772_000 as Weight) - // Standard Error: 393_000 - .saturating_add((34_849_000 as Weight).saturating_mul(s as Weight)) + (5_903_394_000 as Weight) + // Standard Error: 391_000 + .saturating_add((34_834_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn payout_stakers_dead_controller(n: u32, ) -> Weight { - (127_627_000 as Weight) - // Standard Error: 27_000 - .saturating_add((49_354_000 as Weight).saturating_mul(n as Weight)) + (141_724_000 as Weight) + // Standard Error: 24_000 + .saturating_add((53_018_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(11 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(n as Weight))) } fn payout_stakers_alive_staked(n: u32, ) -> Weight { - (156_838_000 as Weight) - // Standard Error: 24_000 - .saturating_add((62_653_000 as Weight).saturating_mul(n as Weight)) + (159_994_000 as Weight) + // Standard Error: 28_000 + .saturating_add((67_746_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(12 as Weight)) .saturating_add(RocksDbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(n as Weight))) } fn rebond(l: u32, ) -> Weight { - (40_110_000 as Weight) + (42_177_000 as Weight) // Standard Error: 1_000 - .saturating_add((78_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((82_000 as Weight).saturating_mul(l as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } fn set_history_depth(e: u32, ) -> Weight { (0 as Weight) - // Standard Error: 70_000 - .saturating_add((32_883_000 as Weight).saturating_mul(e as Weight)) + // Standard Error: 65_000 + .saturating_add((34_151_000 as Weight).saturating_mul(e as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) .saturating_add(RocksDbWeight::get().writes((7 as Weight).saturating_mul(e as Weight))) } fn reap_stash(s: u32, ) -> Weight { - (64_605_000 as Weight) - // Standard Error: 1_000 - .saturating_add((2_506_000 as Weight).saturating_mul(s as Weight)) + (68_377_000 as Weight) + // Standard Error: 0 + .saturating_add((2_757_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(8 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) } fn new_era(v: u32, n: u32, ) -> Weight { (0 as Weight) - // Standard Error: 926_000 - .saturating_add((548_212_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 46_000 - .saturating_add((78_343_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(7 as Weight)) + // Standard Error: 908_000 + .saturating_add((588_562_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 45_000 + .saturating_add((83_485_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(9 as Weight)) .saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(v as Weight))) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) - .saturating_add(RocksDbWeight::get().writes(8 as Weight)) + .saturating_add(RocksDbWeight::get().writes(13 as Weight)) .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) } fn submit_solution_better(v: u32, n: u32, a: u32, w: u32, ) -> Weight { (0 as Weight) - // Standard Error: 48_000 - .saturating_add((937_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 19_000 - .saturating_add((657_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 48_000 - .saturating_add((70_669_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 101_000 - .saturating_add((7_658_000 as Weight).saturating_mul(w as Weight)) + // Standard Error: 52_000 + .saturating_add((750_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 20_000 + .saturating_add((556_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 52_000 + .saturating_add((76_201_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 108_000 + .saturating_add((7_271_000 as Weight).saturating_mul(w as Weight)) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(a as Weight))) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(w as Weight))) From ea531e17c73aeec611943e08206340ecc9529d5d Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 17 Feb 2021 09:21:20 +0000 Subject: [PATCH 28/29] Fix doc --- frame/election-provider-multi-phase/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 757ef7bdebdfd..5e1bc8df9a7c7 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -675,7 +675,7 @@ pub mod pallet { let error_message = "Invalid unsigned submission must produce invalid block and \ deprive validator from their authoring reward."; - // NOTE: since we do this in pre-dispatch, we _could_ just ignore it here. + // Check score being an improvement, phase, and desired targets. Self::unsigned_pre_dispatch_checks(&solution).expect(error_message); // ensure witness was correct. From 2c6ac8002e22db55d9b8abae8ceb32808eec9f3d Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 17 Feb 2021 16:26:08 +0000 Subject: [PATCH 29/29] Mkae ci green --- Cargo.lock | 1 + frame/election-provider-multi-phase/src/helpers.rs | 2 +- frame/election-provider-multi-phase/src/unsigned.rs | 5 ----- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7735ac36c21f9..57a505f309e7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4511,6 +4511,7 @@ dependencies = [ "sp-consensus-babe", "sp-consensus-vrf", "sp-core", + "sp-election-providers", "sp-io", "sp-runtime", "sp-session", diff --git a/frame/election-provider-multi-phase/src/helpers.rs b/frame/election-provider-multi-phase/src/helpers.rs index 9b2f817f1ca09..be074594e6603 100644 --- a/frame/election-provider-multi-phase/src/helpers.rs +++ b/frame/election-provider-multi-phase/src/helpers.rs @@ -25,7 +25,7 @@ macro_rules! log { ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { frame_support::debug::$level!( target: $crate::LOG_TARGET, - concat!("🏦 ", $patter) $(, $values)* + concat!("🗳 ", $patter) $(, $values)* ) }; } diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index 770eab98c1471..2039e5d9f0754 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -733,11 +733,6 @@ mod tests { }) } - #[test] - fn miner_will_not_submit_if_not_feasible() { - todo!() - } - #[test] fn unsigned_per_dispatch_checks_can_only_submit_threshold_better() { ExtBuilder::default()