Create trait for NPoS election algorithms (#9664)

* build the template, hand it over to zeke now.

* Tests working

* save wip

* Some updates

* Some cleanup

* mo cleanin

* Link to issue

* Apply suggestions from code review

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Bound accuracy for prepare_election_result

* Use npos_election::Error for phragmms

* save

* Apply suggestions from code review

* Simplify test to use Balancing::set

* Cargo.lock after build

* Revert "Cargo.lock after build"

This reverts commit 7d726c8efa687c09e4f377196b106eb9e9760487.

* Try reduce cargo.lock diff

* Update bin/node/runtime/src/lib.rs

* Comment

* Apply suggestions from code review

* Set balancing directly

* Document som pub items

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

* Apply suggestions from code review

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Improve some comments

* Revert accidental change to random file

* tiney

* revert

Co-authored-by: kianenigma <kian@parity.io>
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
This commit is contained in:
Zeke Mostov
2021-09-09 12:46:24 -07:00
committed by GitHub
parent d1c281461d
commit 6bfcfeed4c
10 changed files with 203 additions and 105 deletions
+2 -3
View File
@@ -39,6 +39,7 @@ sp-session = { version = "4.0.0-dev", default-features = false, path = "../../..
sp-transaction-pool = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/transaction-pool" } sp-transaction-pool = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/transaction-pool" }
sp-version = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/version" } sp-version = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/version" }
sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/npos-elections" } sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/npos-elections" }
sp-io = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/io" }
# frame dependencies # frame dependencies
frame-executive = { version = "4.0.0-dev", default-features = false, path = "../../../frame/executive" } frame-executive = { version = "4.0.0-dev", default-features = false, path = "../../../frame/executive" }
@@ -98,9 +99,6 @@ pallet-vesting = { version = "4.0.0-dev", default-features = false, path = "../.
[build-dependencies] [build-dependencies]
substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" }
[dev-dependencies]
sp-io = { version = "4.0.0-dev", path = "../../../primitives/io" }
[features] [features]
default = ["std"] default = ["std"]
with-tracing = ["frame-executive/with-tracing"] with-tracing = ["frame-executive/with-tracing"]
@@ -169,6 +167,7 @@ std = [
"log/std", "log/std",
"frame-try-runtime/std", "frame-try-runtime/std",
"sp-npos-elections/std", "sp-npos-elections/std",
"sp-io/std"
] ]
runtime-benchmarks = [ runtime-benchmarks = [
"frame-benchmarking", "frame-benchmarking",
+31 -2
View File
@@ -534,7 +534,6 @@ parameter_types! {
// miner configs // miner configs
pub const MultiPhaseUnsignedPriority: TransactionPriority = StakingUnsignedPriority::get() - 1u64; pub const MultiPhaseUnsignedPriority: TransactionPriority = StakingUnsignedPriority::get() - 1u64;
pub const MinerMaxIterations: u32 = 10;
pub MinerMaxWeight: Weight = RuntimeBlockWeights::get() pub MinerMaxWeight: Weight = RuntimeBlockWeights::get()
.get(DispatchClass::Normal) .get(DispatchClass::Normal)
.max_extrinsic.expect("Normal extrinsics have a weight limit configured; qed") .max_extrinsic.expect("Normal extrinsics have a weight limit configured; qed")
@@ -570,6 +569,32 @@ impl pallet_election_provider_multi_phase::BenchmarkingConfig for BenchmarkConfi
const MAXIMUM_TARGETS: u32 = 2000; const MAXIMUM_TARGETS: u32 = 2000;
} }
/// Maximum number of iterations for balancing that will be executed in the embedded OCW
/// miner of election provider multi phase.
pub const MINER_MAX_ITERATIONS: u32 = 10;
/// A source of random balance for NposSolver, which is meant to be run by the OCW election miner.
pub struct OffchainRandomBalancing;
impl frame_support::pallet_prelude::Get<Option<(usize, sp_npos_elections::ExtendedBalance)>>
for OffchainRandomBalancing
{
fn get() -> Option<(usize, sp_npos_elections::ExtendedBalance)> {
use sp_runtime::traits::TrailingZeroInput;
let iters = match MINER_MAX_ITERATIONS {
0 => 0,
max @ _ => {
let seed = sp_io::offchain::random_seed();
let random = <u32>::decode(&mut TrailingZeroInput::new(&seed))
.expect("input is padded with zeroes; qed") %
max.saturating_add(1);
random as usize
},
};
Some((iters, 0))
}
}
impl pallet_election_provider_multi_phase::Config for Runtime { impl pallet_election_provider_multi_phase::Config for Runtime {
type Event = Event; type Event = Event;
type Currency = Balances; type Currency = Balances;
@@ -578,7 +603,6 @@ impl pallet_election_provider_multi_phase::Config for Runtime {
type UnsignedPhase = UnsignedPhase; type UnsignedPhase = UnsignedPhase;
type SolutionImprovementThreshold = SolutionImprovementThreshold; type SolutionImprovementThreshold = SolutionImprovementThreshold;
type OffchainRepeat = OffchainRepeat; type OffchainRepeat = OffchainRepeat;
type MinerMaxIterations = MinerMaxIterations;
type MinerMaxWeight = MinerMaxWeight; type MinerMaxWeight = MinerMaxWeight;
type MinerMaxLength = MinerMaxLength; type MinerMaxLength = MinerMaxLength;
type MinerTxPriority = MultiPhaseUnsignedPriority; type MinerTxPriority = MultiPhaseUnsignedPriority;
@@ -594,6 +618,11 @@ impl pallet_election_provider_multi_phase::Config for Runtime {
type OnChainAccuracy = Perbill; type OnChainAccuracy = Perbill;
type Solution = NposSolution16; type Solution = NposSolution16;
type Fallback = Fallback; type Fallback = Fallback;
type Solver = frame_election_provider_support::SequentialPhragmen<
AccountId,
pallet_election_provider_multi_phase::SolutionAccuracyOf<Runtime>,
OffchainRandomBalancing,
>;
type WeightInfo = pallet_election_provider_multi_phase::weights::SubstrateWeight<Runtime>; type WeightInfo = pallet_election_provider_multi_phase::weights::SubstrateWeight<Runtime>;
type ForceOrigin = EnsureRootOrHalfCouncil; type ForceOrigin = EnsureRootOrHalfCouncil;
type BenchmarkingConfig = BenchmarkConfig; type BenchmarkingConfig = BenchmarkConfig;
@@ -485,13 +485,13 @@ pub struct SolutionOrSnapshotSize {
/// Internal errors of the pallet. /// Internal errors of the pallet.
/// ///
/// Note that this is different from [`pallet::Error`]. /// Note that this is different from [`pallet::Error`].
#[derive(Debug, Eq, PartialEq)] #[derive(frame_support::DebugNoBound, frame_support::PartialEqNoBound)]
#[cfg_attr(feature = "runtime-benchmarks", derive(strum_macros::IntoStaticStr))] #[cfg_attr(feature = "runtime-benchmarks", derive(strum_macros::IntoStaticStr))]
pub enum ElectionError { pub enum ElectionError<T: Config> {
/// An error happened in the feasibility check sub-system. /// An error happened in the feasibility check sub-system.
Feasibility(FeasibilityError), Feasibility(FeasibilityError),
/// An error in the miner (offchain) sub-system. /// An error in the miner (offchain) sub-system.
Miner(unsigned::MinerError), Miner(unsigned::MinerError<T>),
/// An error in the on-chain fallback. /// An error in the on-chain fallback.
OnChainFallback(onchain::Error), OnChainFallback(onchain::Error),
/// An error happened in the data provider. /// An error happened in the data provider.
@@ -500,20 +500,20 @@ pub enum ElectionError {
NoFallbackConfigured, NoFallbackConfigured,
} }
impl From<onchain::Error> for ElectionError { impl<T: Config> From<onchain::Error> for ElectionError<T> {
fn from(e: onchain::Error) -> Self { fn from(e: onchain::Error) -> Self {
ElectionError::OnChainFallback(e) ElectionError::OnChainFallback(e)
} }
} }
impl From<FeasibilityError> for ElectionError { impl<T: Config> From<FeasibilityError> for ElectionError<T> {
fn from(e: FeasibilityError) -> Self { fn from(e: FeasibilityError) -> Self {
ElectionError::Feasibility(e) ElectionError::Feasibility(e)
} }
} }
impl From<unsigned::MinerError> for ElectionError { impl<T: Config> From<unsigned::MinerError<T>> for ElectionError<T> {
fn from(e: unsigned::MinerError) -> Self { fn from(e: unsigned::MinerError<T>) -> Self {
ElectionError::Miner(e) ElectionError::Miner(e)
} }
} }
@@ -555,6 +555,7 @@ pub use pallet::*;
#[frame_support::pallet] #[frame_support::pallet]
pub mod pallet { pub mod pallet {
use super::*; use super::*;
use frame_election_provider_support::NposSolver;
use frame_support::{pallet_prelude::*, traits::EstimateCallFee}; use frame_support::{pallet_prelude::*, traits::EstimateCallFee};
use frame_system::pallet_prelude::*; use frame_system::pallet_prelude::*;
@@ -592,10 +593,6 @@ pub mod pallet {
/// The priority of the unsigned transaction submitted in the unsigned-phase /// The priority of the unsigned transaction submitted in the unsigned-phase
#[pallet::constant] #[pallet::constant]
type MinerTxPriority: Get<TransactionPriority>; type MinerTxPriority: Get<TransactionPriority>;
/// Maximum number of iteration of balancing that will be executed in the embedded miner of
/// the pallet.
#[pallet::constant]
type MinerMaxIterations: Get<u32>;
/// Maximum weight that the miner should consume. /// Maximum weight that the miner should consume.
/// ///
@@ -668,6 +665,9 @@ pub mod pallet {
/// Configuration for the fallback /// Configuration for the fallback
type Fallback: Get<FallbackStrategy>; type Fallback: Get<FallbackStrategy>;
/// OCW election solution miner algorithm implementation.
type Solver: NposSolver<AccountId = Self::AccountId>;
/// Origin that can control this pallet. Note that any action taken by this origin (such) /// Origin that can control this pallet. Note that any action taken by this origin (such)
/// as providing an emergency solution is not checked. Thus, it must be a trusted origin. /// as providing an emergency solution is not checked. Thus, it must be a trusted origin.
type ForceOrigin: EnsureOrigin<Self::Origin>; type ForceOrigin: EnsureOrigin<Self::Origin>;
@@ -1298,7 +1298,7 @@ impl<T: Config> Pallet<T> {
/// ///
/// Extracted for easier weight calculation. /// Extracted for easier weight calculation.
fn create_snapshot_external( fn create_snapshot_external(
) -> Result<(Vec<T::AccountId>, Vec<crate::unsigned::Voter<T>>, u32), ElectionError> { ) -> Result<(Vec<T::AccountId>, Vec<crate::unsigned::Voter<T>>, u32), ElectionError<T>> {
let target_limit = <SolutionTargetIndexOf<T>>::max_value().saturated_into::<usize>(); let target_limit = <SolutionTargetIndexOf<T>>::max_value().saturated_into::<usize>();
let voter_limit = <SolutionVoterIndexOf<T>>::max_value().saturated_into::<usize>(); let voter_limit = <SolutionVoterIndexOf<T>>::max_value().saturated_into::<usize>();
@@ -1328,7 +1328,7 @@ impl<T: Config> Pallet<T> {
/// ///
/// This is a *self-weighing* function, it will register its own extra weight as /// This is a *self-weighing* function, it will register its own extra weight as
/// [`DispatchClass::Mandatory`] with the system pallet. /// [`DispatchClass::Mandatory`] with the system pallet.
pub fn create_snapshot() -> Result<(), ElectionError> { pub fn create_snapshot() -> Result<(), ElectionError<T>> {
// this is self-weighing itself.. // this is self-weighing itself..
let (targets, voters, desired_targets) = Self::create_snapshot_external()?; let (targets, voters, desired_targets) = Self::create_snapshot_external()?;
@@ -1471,7 +1471,7 @@ impl<T: Config> Pallet<T> {
} }
/// On-chain fallback of election. /// On-chain fallback of election.
fn onchain_fallback() -> Result<Supports<T::AccountId>, ElectionError> { fn onchain_fallback() -> Result<Supports<T::AccountId>, ElectionError<T>> {
<onchain::OnChainSequentialPhragmen<OnChainConfig<T>> as ElectionProvider< <onchain::OnChainSequentialPhragmen<OnChainConfig<T>> as ElectionProvider<
T::AccountId, T::AccountId,
T::BlockNumber, T::BlockNumber,
@@ -1479,7 +1479,7 @@ impl<T: Config> Pallet<T> {
.map_err(Into::into) .map_err(Into::into)
} }
fn do_elect() -> Result<Supports<T::AccountId>, ElectionError> { fn do_elect() -> Result<Supports<T::AccountId>, ElectionError<T>> {
// We have to unconditionally try finalizing the signed phase here. There are only two // We have to unconditionally try finalizing the signed phase here. There are only two
// possibilities: // possibilities:
// //
@@ -1530,7 +1530,7 @@ impl<T: Config> Pallet<T> {
} }
impl<T: Config> ElectionProvider<T::AccountId, T::BlockNumber> for Pallet<T> { impl<T: Config> ElectionProvider<T::AccountId, T::BlockNumber> for Pallet<T> {
type Error = ElectionError; type Error = ElectionError<T>;
type DataProvider = T::DataProvider; type DataProvider = T::DataProvider;
fn elect() -> Result<Supports<T::AccountId>, Self::Error> { fn elect() -> Result<Supports<T::AccountId>, Self::Error> {
@@ -2013,7 +2013,10 @@ mod tests {
roll_to(15); roll_to(15);
assert_eq!(MultiPhase::current_phase(), Phase::Signed); assert_eq!(MultiPhase::current_phase(), Phase::Signed);
let (solution, _) = MultiPhase::mine_solution(2).unwrap(); // set the solution balancing to get the desired score.
crate::mock::Balancing::set(Some((2, 0)));
let (solution, _) = MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
// Default solution has a score of [50, 100, 5000]. // Default solution has a score of [50, 100, 5000].
assert_eq!(solution.score, [50, 100, 5000]); assert_eq!(solution.score, [50, 100, 5000]);
@@ -17,7 +17,7 @@
use super::*; use super::*;
use crate as multi_phase; use crate as multi_phase;
use frame_election_provider_support::{data_provider, ElectionDataProvider}; use frame_election_provider_support::{data_provider, ElectionDataProvider, SequentialPhragmen};
pub use frame_support::{assert_noop, assert_ok}; pub use frame_support::{assert_noop, assert_ok};
use frame_support::{parameter_types, traits::Hooks, weights::Weight}; use frame_support::{parameter_types, traits::Hooks, weights::Weight};
use multi_phase::unsigned::{IndexAssignmentOf, Voter}; use multi_phase::unsigned::{IndexAssignmentOf, Voter};
@@ -31,7 +31,7 @@ use sp_core::{
}; };
use sp_npos_elections::{ use sp_npos_elections::{
assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, to_without_backing, assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, to_without_backing,
ElectionResult, EvaluateSupport, NposSolution, ElectionResult, EvaluateSupport, ExtendedBalance, NposSolution,
}; };
use sp_runtime::{ use sp_runtime::{
testing::Header, testing::Header,
@@ -262,7 +262,6 @@ parameter_types! {
pub static SignedDepositWeight: Balance = 0; pub static SignedDepositWeight: Balance = 0;
pub static SignedRewardBase: Balance = 7; pub static SignedRewardBase: Balance = 7;
pub static SignedMaxWeight: Weight = BlockWeights::get().max_block; pub static SignedMaxWeight: Weight = BlockWeights::get().max_block;
pub static MinerMaxIterations: u32 = 5;
pub static MinerTxPriority: u64 = 100; pub static MinerTxPriority: u64 = 100;
pub static SolutionImprovementThreshold: Perbill = Perbill::zero(); pub static SolutionImprovementThreshold: Perbill = Perbill::zero();
pub static OffchainRepeat: BlockNumber = 5; pub static OffchainRepeat: BlockNumber = 5;
@@ -352,6 +351,10 @@ impl multi_phase::weights::WeightInfo for DualMockWeightInfo {
} }
} }
parameter_types! {
pub static Balancing: Option<(usize, ExtendedBalance)> = Some((0, 0));
}
impl crate::Config for Runtime { impl crate::Config for Runtime {
type Event = Event; type Event = Event;
type Currency = Balances; type Currency = Balances;
@@ -360,7 +363,6 @@ impl crate::Config for Runtime {
type UnsignedPhase = UnsignedPhase; type UnsignedPhase = UnsignedPhase;
type SolutionImprovementThreshold = SolutionImprovementThreshold; type SolutionImprovementThreshold = SolutionImprovementThreshold;
type OffchainRepeat = OffchainRepeat; type OffchainRepeat = OffchainRepeat;
type MinerMaxIterations = MinerMaxIterations;
type MinerMaxWeight = MinerMaxWeight; type MinerMaxWeight = MinerMaxWeight;
type MinerMaxLength = MinerMaxLength; type MinerMaxLength = MinerMaxLength;
type MinerTxPriority = MinerTxPriority; type MinerTxPriority = MinerTxPriority;
@@ -379,6 +381,7 @@ impl crate::Config for Runtime {
type Fallback = Fallback; type Fallback = Fallback;
type ForceOrigin = frame_system::EnsureRoot<AccountId>; type ForceOrigin = frame_system::EnsureRoot<AccountId>;
type Solution = TestNposSolution; type Solution = TestNposSolution;
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, Balancing>;
} }
impl<LocalCall> frame_system::offchain::SendTransactionTypes<LocalCall> for Runtime impl<LocalCall> frame_system::offchain::SendTransactionTypes<LocalCall> for Runtime
@@ -836,7 +836,8 @@ mod tests {
roll_to(15); roll_to(15);
assert!(MultiPhase::current_phase().is_signed()); assert!(MultiPhase::current_phase().is_signed());
let (raw, witness) = MultiPhase::mine_solution(2).unwrap(); let (raw, witness) =
MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
let solution_weight = <Runtime as Config>::WeightInfo::feasibility_check( let solution_weight = <Runtime as Config>::WeightInfo::feasibility_check(
witness.voters, witness.voters,
witness.targets, witness.targets,
@@ -22,17 +22,17 @@ use crate::{
ReadySolution, RoundSnapshot, SolutionAccuracyOf, SolutionOf, SolutionOrSnapshotSize, Weight, ReadySolution, RoundSnapshot, SolutionAccuracyOf, SolutionOf, SolutionOrSnapshotSize, Weight,
WeightInfo, WeightInfo,
}; };
use codec::{Decode, Encode}; use codec::Encode;
use frame_election_provider_support::{NposSolver, PerThing128};
use frame_support::{dispatch::DispatchResult, ensure, traits::Get}; use frame_support::{dispatch::DispatchResult, ensure, traits::Get};
use frame_system::offchain::SubmitTransaction; use frame_system::offchain::SubmitTransaction;
use sp_arithmetic::Perbill; use sp_arithmetic::Perbill;
use sp_npos_elections::{ use sp_npos_elections::{
assignment_ratio_to_staked_normalized, assignment_staked_to_ratio_normalized, is_score_better, assignment_ratio_to_staked_normalized, assignment_staked_to_ratio_normalized, is_score_better,
seq_phragmen, ElectionResult, NposSolution, ElectionResult, NposSolution,
}; };
use sp_runtime::{ use sp_runtime::{
offchain::storage::{MutateStorageError, StorageValueRef}, offchain::storage::{MutateStorageError, StorageValueRef},
traits::TrailingZeroInput,
DispatchError, SaturatedConversion, DispatchError, SaturatedConversion,
}; };
use sp_std::{boxed::Box, cmp::Ordering, convert::TryFrom, vec::Vec}; use sp_std::{boxed::Box, cmp::Ordering, convert::TryFrom, vec::Vec};
@@ -61,8 +61,11 @@ pub type Assignment<T> =
/// runtime `T`. /// runtime `T`.
pub type IndexAssignmentOf<T> = sp_npos_elections::IndexAssignmentOf<SolutionOf<T>>; pub type IndexAssignmentOf<T> = sp_npos_elections::IndexAssignmentOf<SolutionOf<T>>;
#[derive(Debug, Eq, PartialEq)] /// Error type of the pallet's [`crate::Config::Solver`].
pub enum MinerError { pub type SolverErrorOf<T> = <<T as Config>::Solver as NposSolver>::Error;
/// Error type for operations related to the OCW npos solution miner.
#[derive(frame_support::DebugNoBound, frame_support::PartialEqNoBound)]
pub enum MinerError<T: Config> {
/// An internal error in the NPoS elections crate. /// An internal error in the NPoS elections crate.
NposElections(sp_npos_elections::Error), NposElections(sp_npos_elections::Error),
/// Snapshot data was unavailable unexpectedly. /// Snapshot data was unavailable unexpectedly.
@@ -83,22 +86,24 @@ pub enum MinerError {
FailedToStoreSolution, FailedToStoreSolution,
/// There are no more voters to remove to trim the solution. /// There are no more voters to remove to trim the solution.
NoMoreVoters, NoMoreVoters,
/// An error from the solver.
Solver(SolverErrorOf<T>),
} }
impl From<sp_npos_elections::Error> for MinerError { impl<T: Config> From<sp_npos_elections::Error> for MinerError<T> {
fn from(e: sp_npos_elections::Error) -> Self { fn from(e: sp_npos_elections::Error) -> Self {
MinerError::NposElections(e) MinerError::NposElections(e)
} }
} }
impl From<FeasibilityError> for MinerError { impl<T: Config> From<FeasibilityError> for MinerError<T> {
fn from(e: FeasibilityError) -> Self { fn from(e: FeasibilityError) -> Self {
MinerError::Feasibility(e) MinerError::Feasibility(e)
} }
} }
/// Save a given call into OCW storage. /// Save a given call into OCW storage.
fn save_solution<T: Config>(call: &Call<T>) -> Result<(), MinerError> { fn save_solution<T: Config>(call: &Call<T>) -> Result<(), MinerError<T>> {
log!(debug, "saving a call to the offchain storage."); log!(debug, "saving a call to the offchain storage.");
let storage = StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL); let storage = StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL);
match storage.mutate::<_, (), _>(|_| Ok(call.clone())) { match storage.mutate::<_, (), _>(|_| Ok(call.clone())) {
@@ -116,7 +121,7 @@ fn save_solution<T: Config>(call: &Call<T>) -> Result<(), MinerError> {
} }
/// Get a saved solution from OCW storage if it exists. /// Get a saved solution from OCW storage if it exists.
fn restore_solution<T: Config>() -> Result<Call<T>, MinerError> { fn restore_solution<T: Config>() -> Result<Call<T>, MinerError<T>> {
StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL) StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL)
.get() .get()
.ok() .ok()
@@ -149,7 +154,7 @@ fn ocw_solution_exists<T: Config>() -> bool {
impl<T: Config> Pallet<T> { impl<T: Config> Pallet<T> {
/// Attempt to restore a solution from cache. Otherwise, compute it fresh. Either way, submit /// Attempt to restore a solution from cache. Otherwise, compute it fresh. Either way, submit
/// if our call's score is greater than that of the cached solution. /// if our call's score is greater than that of the cached solution.
pub fn restore_or_compute_then_maybe_submit() -> Result<(), MinerError> { pub fn restore_or_compute_then_maybe_submit() -> Result<(), MinerError<T>> {
log!(debug, "miner attempting to restore or compute an unsigned solution."); log!(debug, "miner attempting to restore or compute an unsigned solution.");
let call = restore_solution::<T>() let call = restore_solution::<T>()
@@ -163,7 +168,7 @@ impl<T: Config> Pallet<T> {
Err(MinerError::SolutionCallInvalid) Err(MinerError::SolutionCallInvalid)
} }
}) })
.or_else::<MinerError, _>(|error| { .or_else::<MinerError<T>, _>(|error| {
log!(debug, "restoring solution failed due to {:?}", error); log!(debug, "restoring solution failed due to {:?}", error);
match error { match error {
MinerError::NoStoredSolution => { MinerError::NoStoredSolution => {
@@ -194,7 +199,7 @@ impl<T: Config> Pallet<T> {
} }
/// Mine a new solution, cache it, and submit it back to the chain as an unsigned transaction. /// Mine a new solution, cache it, and submit it back to the chain as an unsigned transaction.
pub fn mine_check_save_submit() -> Result<(), MinerError> { pub fn mine_check_save_submit() -> Result<(), MinerError<T>> {
log!(debug, "miner attempting to compute an unsigned solution."); log!(debug, "miner attempting to compute an unsigned solution.");
let call = Self::mine_checked_call()?; let call = Self::mine_checked_call()?;
@@ -203,10 +208,9 @@ impl<T: Config> Pallet<T> {
} }
/// Mine a new solution as a call. Performs all checks. /// Mine a new solution as a call. Performs all checks.
pub fn mine_checked_call() -> Result<Call<T>, MinerError> { pub fn mine_checked_call() -> Result<Call<T>, MinerError<T>> {
let iters = Self::get_balancing_iters();
// get the solution, with a load of checks to ensure if submitted, IT IS ABSOLUTELY VALID. // 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 (raw_solution, witness) = Self::mine_and_check()?;
let score = raw_solution.score.clone(); let score = raw_solution.score.clone();
let call: Call<T> = Call::submit_unsigned(Box::new(raw_solution), witness).into(); let call: Call<T> = Call::submit_unsigned(Box::new(raw_solution), witness).into();
@@ -221,7 +225,7 @@ impl<T: Config> Pallet<T> {
Ok(call) Ok(call)
} }
fn submit_call(call: Call<T>) -> Result<(), MinerError> { fn submit_call(call: Call<T>) -> Result<(), MinerError<T>> {
log!(debug, "miner submitting a solution as an unsigned transaction"); log!(debug, "miner submitting a solution as an unsigned transaction");
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into()) SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
@@ -234,7 +238,7 @@ impl<T: Config> Pallet<T> {
pub fn basic_checks( pub fn basic_checks(
raw_solution: &RawSolution<SolutionOf<T>>, raw_solution: &RawSolution<SolutionOf<T>>,
solution_type: &str, solution_type: &str,
) -> Result<(), MinerError> { ) -> Result<(), MinerError<T>> {
Self::unsigned_pre_dispatch_checks(raw_solution).map_err(|err| { Self::unsigned_pre_dispatch_checks(raw_solution).map_err(|err| {
log!(debug, "pre-dispatch checks failed for {} solution: {:?}", solution_type, err); log!(debug, "pre-dispatch checks failed for {} solution: {:?}", solution_type, err);
MinerError::PreDispatchChecksFailed(err) MinerError::PreDispatchChecksFailed(err)
@@ -257,38 +261,37 @@ impl<T: Config> Pallet<T> {
/// If you want a checked solution and submit it at the same time, use /// If you want a checked solution and submit it at the same time, use
/// [`Pallet::mine_check_save_submit`]. /// [`Pallet::mine_check_save_submit`].
pub fn mine_and_check( pub fn mine_and_check(
iters: usize, ) -> Result<(RawSolution<SolutionOf<T>>, SolutionOrSnapshotSize), MinerError<T>> {
) -> Result<(RawSolution<SolutionOf<T>>, SolutionOrSnapshotSize), MinerError> { let (raw_solution, witness) = Self::mine_solution::<T::Solver>()?;
let (raw_solution, witness) = Self::mine_solution(iters)?;
Self::basic_checks(&raw_solution, "mined")?; Self::basic_checks(&raw_solution, "mined")?;
Ok((raw_solution, witness)) Ok((raw_solution, witness))
} }
/// Mine a new npos solution. /// Mine a new npos solution.
pub fn mine_solution( ///
iters: usize, /// The Npos Solver type, `S`, must have the same AccountId and Error type as the
) -> Result<(RawSolution<SolutionOf<T>>, SolutionOrSnapshotSize), MinerError> { /// [`crate::Config::Solver`] in order to create a unified return type.
pub fn mine_solution<S>(
) -> Result<(RawSolution<SolutionOf<T>>, SolutionOrSnapshotSize), MinerError<T>>
where
S: NposSolver<AccountId = T::AccountId, Error = SolverErrorOf<T>>,
{
let RoundSnapshot { voters, targets } = let RoundSnapshot { voters, targets } =
Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?; Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?;
let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?; let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?;
seq_phragmen::<_, SolutionAccuracyOf<T>>( S::solve(desired_targets as usize, targets, voters)
desired_targets as usize, .map_err(|e| MinerError::Solver::<T>(e))
targets, .and_then(|e| Self::prepare_election_result::<S::Accuracy>(e))
voters,
Some((iters, 0)),
)
.map_err(Into::into)
.and_then(Self::prepare_election_result)
} }
/// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which /// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which
/// is ready to be submitted to the chain. /// is ready to be submitted to the chain.
/// ///
/// Will always reduce the solution as well. /// Will always reduce the solution as well.
pub fn prepare_election_result( pub fn prepare_election_result<Accuracy: PerThing128>(
election_result: ElectionResult<T::AccountId, SolutionAccuracyOf<T>>, election_result: ElectionResult<T::AccountId, Accuracy>,
) -> Result<(RawSolution<SolutionOf<T>>, SolutionOrSnapshotSize), MinerError> { ) -> Result<(RawSolution<SolutionOf<T>>, SolutionOrSnapshotSize), MinerError<T>> {
// NOTE: This code path is generally not optimized as it is run offchain. Could use some at // NOTE: This code path is generally not optimized as it is run offchain. Could use some at
// some point though. // some point though.
@@ -378,23 +381,6 @@ impl<T: Config> Pallet<T> {
Ok((RawSolution { solution, score, round }, size)) Ok((RawSolution { solution, 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
/// [`Config::MinerMaxIterations`].
pub fn get_balancing_iters() -> usize {
match T::MinerMaxIterations::get() {
0 => 0,
max @ _ => {
let seed = sp_io::offchain::random_seed();
let random = <u32>::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 solution to fit into the block w.r.t. weight. /// Greedily reduce the size of the 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. /// The weight of the solution is foremost a function of the number of voters (i.e.
@@ -448,7 +434,7 @@ impl<T: Config> Pallet<T> {
max_allowed_length: u32, max_allowed_length: u32,
assignments: &mut Vec<IndexAssignmentOf<T>>, assignments: &mut Vec<IndexAssignmentOf<T>>,
encoded_size_of: impl Fn(&[IndexAssignmentOf<T>]) -> Result<usize, sp_npos_elections::Error>, encoded_size_of: impl Fn(&[IndexAssignmentOf<T>]) -> Result<usize, sp_npos_elections::Error>,
) -> Result<(), MinerError> { ) -> Result<(), MinerError<T>> {
// Perform a binary search for the max subset of which can fit into the allowed // Perform a binary search for the max subset of which can fit into the allowed
// length. Having discovered that, we can truncate efficiently. // length. Having discovered that, we can truncate efficiently.
let max_allowed_length: usize = max_allowed_length.saturated_into(); let max_allowed_length: usize = max_allowed_length.saturated_into();
@@ -584,7 +570,7 @@ impl<T: Config> Pallet<T> {
/// ///
/// Returns `Ok(())` if offchain worker limit is respected, `Err(reason)` otherwise. If `Ok()` /// Returns `Ok(())` if offchain worker limit is respected, `Err(reason)` otherwise. If `Ok()`
/// is returned, `now` is written in storage and will be used in further calls as the baseline. /// is returned, `now` is written in storage and will be used in further calls as the baseline.
pub fn ensure_offchain_repeat_frequency(now: T::BlockNumber) -> Result<(), MinerError> { pub fn ensure_offchain_repeat_frequency(now: T::BlockNumber) -> Result<(), MinerError<T>> {
let threshold = T::OffchainRepeat::get(); let threshold = T::OffchainRepeat::get();
let last_block = StorageValueRef::persistent(&OFFCHAIN_LAST_BLOCK); let last_block = StorageValueRef::persistent(&OFFCHAIN_LAST_BLOCK);
@@ -761,6 +747,7 @@ mod tests {
CurrentPhase, InvalidTransaction, Phase, QueuedSolution, TransactionSource, CurrentPhase, InvalidTransaction, Phase, QueuedSolution, TransactionSource,
TransactionValidityError, TransactionValidityError,
}; };
use codec::Decode;
use frame_benchmarking::Zero; use frame_benchmarking::Zero;
use frame_support::{assert_noop, assert_ok, dispatch::Dispatchable, traits::OffchainWorker}; use frame_support::{assert_noop, assert_ok, dispatch::Dispatchable, traits::OffchainWorker};
use sp_npos_elections::IndexAssignment; use sp_npos_elections::IndexAssignment;
@@ -975,7 +962,8 @@ mod tests {
assert_eq!(MultiPhase::desired_targets().unwrap(), 2); assert_eq!(MultiPhase::desired_targets().unwrap(), 2);
// mine seq_phragmen solution with 2 iters. // mine seq_phragmen solution with 2 iters.
let (solution, witness) = MultiPhase::mine_solution(2).unwrap(); let (solution, witness) =
MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
// ensure this solution is valid. // ensure this solution is valid.
assert!(MultiPhase::queued_solution().is_none()); assert!(MultiPhase::queued_solution().is_none());
@@ -993,7 +981,8 @@ mod tests {
roll_to(25); roll_to(25);
assert!(MultiPhase::current_phase().is_unsigned()); assert!(MultiPhase::current_phase().is_unsigned());
let (raw, witness) = MultiPhase::mine_solution(2).unwrap(); let (raw, witness) =
MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
let solution_weight = <Runtime as Config>::WeightInfo::submit_unsigned( let solution_weight = <Runtime as Config>::WeightInfo::submit_unsigned(
witness.voters, witness.voters,
witness.targets, witness.targets,
@@ -1007,7 +996,8 @@ mod tests {
// now reduce the max weight // now reduce the max weight
<MinerMaxWeight>::set(25); <MinerMaxWeight>::set(25);
let (raw, witness) = MultiPhase::mine_solution(2).unwrap(); let (raw, witness) =
MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
let solution_weight = <Runtime as Config>::WeightInfo::submit_unsigned( let solution_weight = <Runtime as Config>::WeightInfo::submit_unsigned(
witness.voters, witness.voters,
witness.targets, witness.targets,
@@ -1359,7 +1349,7 @@ mod tests {
// OCW must have submitted now // OCW must have submitted now
let encoded = pool.read().transactions[0].clone(); let encoded = pool.read().transactions[0].clone();
let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap(); let extrinsic: Extrinsic = codec::Decode::decode(&mut &*encoded).unwrap();
let call = extrinsic.call; let call = extrinsic.call;
assert!(matches!(call, OuterCall::MultiPhase(Call::submit_unsigned(..)))); assert!(matches!(call, OuterCall::MultiPhase(Call::submit_unsigned(..))));
}) })
@@ -1534,14 +1524,14 @@ mod tests {
roll_to(25); roll_to(25);
// how long would the default solution be? // how long would the default solution be?
let solution = MultiPhase::mine_solution(0).unwrap(); let solution = MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
let max_length = <Runtime as Config>::MinerMaxLength::get(); let max_length = <Runtime as Config>::MinerMaxLength::get();
let solution_size = solution.0.solution.encoded_size(); let solution_size = solution.0.solution.encoded_size();
assert!(solution_size <= max_length as usize); assert!(solution_size <= max_length as usize);
// now set the max size to less than the actual size and regenerate // now set the max size to less than the actual size and regenerate
<Runtime as Config>::MinerMaxLength::set(solution_size as u32 - 1); <Runtime as Config>::MinerMaxLength::set(solution_size as u32 - 1);
let solution = MultiPhase::mine_solution(0).unwrap(); let solution = MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
let max_length = <Runtime as Config>::MinerMaxLength::get(); let max_length = <Runtime as Config>::MinerMaxLength::get();
let solution_size = solution.0.solution.encoded_size(); let solution_size = solution.0.solution.encoded_size();
assert!(solution_size <= max_length as usize); assert!(solution_size <= max_length as usize);
@@ -161,12 +161,14 @@
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
pub mod onchain; pub mod onchain;
use frame_support::traits::Get;
use sp_std::{fmt::Debug, prelude::*}; use sp_std::{fmt::Debug, prelude::*};
/// Re-export some type as they are used in the interface. /// Re-export some type as they are used in the interface.
pub use sp_arithmetic::PerThing; pub use sp_arithmetic::PerThing;
pub use sp_npos_elections::{ pub use sp_npos_elections::{
Assignment, ExtendedBalance, PerThing128, Support, Supports, VoteWeight, Assignment, ElectionResult, ExtendedBalance, IdentifierT, PerThing128, Support, Supports,
VoteWeight,
}; };
/// Types that are used by the data provider trait. /// Types that are used by the data provider trait.
@@ -294,3 +296,69 @@ impl<AccountId, BlockNumber> ElectionProvider<AccountId, BlockNumber> for () {
Err("<() as ElectionProvider> cannot do anything.") Err("<() as ElectionProvider> cannot do anything.")
} }
} }
/// Something that can compute the result to an NPoS solution.
pub trait NposSolver {
/// The account identifier type of this solver.
type AccountId: sp_npos_elections::IdentifierT;
/// The accuracy of this solver. This will affect the accuracy of the output.
type Accuracy: PerThing128;
/// The error type of this implementation.
type Error: sp_std::fmt::Debug + sp_std::cmp::PartialEq;
/// Solve an NPoS solution with the given `voters`, `targets`, and select `to_elect` count
/// of `targets`.
fn solve(
to_elect: usize,
targets: Vec<Self::AccountId>,
voters: Vec<(Self::AccountId, VoteWeight, Vec<Self::AccountId>)>,
) -> Result<ElectionResult<Self::AccountId, Self::Accuracy>, Self::Error>;
}
/// A wrapper for [`sp_npos_elections::seq_phragmen`] that implements [`super::NposSolver`]. See the
/// documentation of [`sp_npos_elections::seq_phragmen`] for more info.
pub struct SequentialPhragmen<AccountId, Accuracy, Balancing = ()>(
sp_std::marker::PhantomData<(AccountId, Accuracy, Balancing)>,
);
impl<
AccountId: IdentifierT,
Accuracy: PerThing128,
Balancing: Get<Option<(usize, ExtendedBalance)>>,
> NposSolver for SequentialPhragmen<AccountId, Accuracy, Balancing>
{
type AccountId = AccountId;
type Accuracy = Accuracy;
type Error = sp_npos_elections::Error;
fn solve(
winners: usize,
targets: Vec<Self::AccountId>,
voters: Vec<(Self::AccountId, VoteWeight, Vec<Self::AccountId>)>,
) -> Result<ElectionResult<Self::AccountId, Self::Accuracy>, Self::Error> {
sp_npos_elections::seq_phragmen(winners, targets, voters, Balancing::get())
}
}
/// A wrapper for [`sp_npos_elections::phragmms`] that implements [`NposSolver`]. See the
/// documentation of [`sp_npos_elections::phragmms`] for more info.
pub struct PhragMMS<AccountId, Accuracy, Balancing = ()>(
sp_std::marker::PhantomData<(AccountId, Accuracy, Balancing)>,
);
impl<
AccountId: IdentifierT,
Accuracy: PerThing128,
Balancing: Get<Option<(usize, ExtendedBalance)>>,
> NposSolver for PhragMMS<AccountId, Accuracy, Balancing>
{
type AccountId = AccountId;
type Accuracy = Accuracy;
type Error = sp_npos_elections::Error;
fn solve(
winners: usize,
targets: Vec<Self::AccountId>,
voters: Vec<(Self::AccountId, VoteWeight, Vec<Self::AccountId>)>,
) -> Result<ElectionResult<Self::AccountId, Self::Accuracy>, Self::Error> {
sp_npos_elections::phragmms(winners, targets, voters, Balancing::get())
}
}
@@ -19,11 +19,11 @@ sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" }
sp-npos-elections-solution-type = { version = "4.0.0-dev", path = "./solution-type" } sp-npos-elections-solution-type = { version = "4.0.0-dev", path = "./solution-type" }
sp-arithmetic = { version = "4.0.0-dev", default-features = false, path = "../arithmetic" } sp-arithmetic = { version = "4.0.0-dev", default-features = false, path = "../arithmetic" }
sp-core = { version = "4.0.0-dev", default-features = false, path = "../core" } sp-core = { version = "4.0.0-dev", default-features = false, path = "../core" }
sp-runtime = { version = "4.0.0-dev", path = "../runtime", default-features = false }
[dev-dependencies] [dev-dependencies]
substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" }
rand = "0.7.3" rand = "0.7.3"
sp-runtime = { version = "4.0.0-dev", path = "../runtime" }
[features] [features]
default = ["std"] default = ["std"]
@@ -34,4 +34,5 @@ std = [
"sp-std/std", "sp-std/std",
"sp-arithmetic/std", "sp-arithmetic/std",
"sp-core/std", "sp-core/std",
"sp-runtime/std",
] ]
@@ -68,16 +68,16 @@ const DEN: ExtendedBalance = ExtendedBalance::max_value();
/// check where t is the standard threshold. The underlying algorithm is sound, but the conversions /// check where t is the standard threshold. The underlying algorithm is sound, but the conversions
/// between numeric types can be lossy. /// between numeric types can be lossy.
pub fn seq_phragmen<AccountId: IdentifierT, P: PerThing128>( pub fn seq_phragmen<AccountId: IdentifierT, P: PerThing128>(
rounds: usize, to_elect: usize,
initial_candidates: Vec<AccountId>, candidates: Vec<AccountId>,
initial_voters: Vec<(AccountId, VoteWeight, Vec<AccountId>)>, voters: Vec<(AccountId, VoteWeight, Vec<AccountId>)>,
balance: Option<(usize, ExtendedBalance)>, balancing: Option<(usize, ExtendedBalance)>,
) -> Result<ElectionResult<AccountId, P>, crate::Error> { ) -> Result<ElectionResult<AccountId, P>, crate::Error> {
let (candidates, voters) = setup_inputs(initial_candidates, initial_voters); let (candidates, voters) = setup_inputs(candidates, voters);
let (candidates, mut voters) = seq_phragmen_core::<AccountId>(rounds, candidates, voters)?; let (candidates, mut voters) = seq_phragmen_core::<AccountId>(to_elect, candidates, voters)?;
if let Some((iterations, tolerance)) = balance { if let Some((iterations, tolerance)) = balancing {
// NOTE: might create zero-edges, but we will strip them again when we convert voter into // NOTE: might create zero-edges, but we will strip them again when we convert voter into
// assignment. // assignment.
let _iters = balancing::balance::<AccountId>(&mut voters, iterations, tolerance); let _iters = balancing::balance::<AccountId>(&mut voters, iterations, tolerance);
@@ -87,7 +87,7 @@ pub fn seq_phragmen<AccountId: IdentifierT, P: PerThing128>(
.into_iter() .into_iter()
.filter(|c_ptr| c_ptr.borrow().elected) .filter(|c_ptr| c_ptr.borrow().elected)
// defensive only: seq-phragmen-core returns only up to rounds. // defensive only: seq-phragmen-core returns only up to rounds.
.take(rounds) .take(to_elect)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// sort winners based on desirability. // sort winners based on desirability.
@@ -116,12 +116,12 @@ pub fn seq_phragmen<AccountId: IdentifierT, P: PerThing128>(
/// This can only fail if the normalization fails. /// This can only fail if the normalization fails.
// To create the inputs needed for this function, see [`crate::setup_inputs`]. // To create the inputs needed for this function, see [`crate::setup_inputs`].
pub fn seq_phragmen_core<AccountId: IdentifierT>( pub fn seq_phragmen_core<AccountId: IdentifierT>(
rounds: usize, to_elect: usize,
candidates: Vec<CandidatePtr<AccountId>>, candidates: Vec<CandidatePtr<AccountId>>,
mut voters: Vec<Voter<AccountId>>, mut voters: Vec<Voter<AccountId>>,
) -> Result<(Vec<CandidatePtr<AccountId>>, Vec<Voter<AccountId>>), crate::Error> { ) -> Result<(Vec<CandidatePtr<AccountId>>, Vec<Voter<AccountId>>), crate::Error> {
// we have already checked that we have more candidates than minimum_candidate_count. // we have already checked that we have more candidates than minimum_candidate_count.
let to_elect = rounds.min(candidates.len()); let to_elect = to_elect.min(candidates.len());
// main election loop // main election loop
for round in 0..to_elect { for round in 0..to_elect {
@@ -43,11 +43,11 @@ use sp_std::{prelude::*, rc::Rc};
/// `expect` this to return `Ok`. /// `expect` this to return `Ok`.
pub fn phragmms<AccountId: IdentifierT, P: PerThing128>( pub fn phragmms<AccountId: IdentifierT, P: PerThing128>(
to_elect: usize, to_elect: usize,
initial_candidates: Vec<AccountId>, candidates: Vec<AccountId>,
initial_voters: Vec<(AccountId, VoteWeight, Vec<AccountId>)>, voters: Vec<(AccountId, VoteWeight, Vec<AccountId>)>,
balancing_config: Option<(usize, ExtendedBalance)>, balancing: Option<(usize, ExtendedBalance)>,
) -> Result<ElectionResult<AccountId, P>, &'static str> { ) -> Result<ElectionResult<AccountId, P>, crate::Error> {
let (candidates, mut voters) = setup_inputs(initial_candidates, initial_voters); let (candidates, mut voters) = setup_inputs(candidates, voters);
let mut winners = vec![]; let mut winners = vec![];
for round in 0..to_elect { for round in 0..to_elect {
@@ -58,7 +58,7 @@ pub fn phragmms<AccountId: IdentifierT, P: PerThing128>(
round_winner.borrow_mut().elected = true; round_winner.borrow_mut().elected = true;
winners.push(round_winner); winners.push(round_winner);
if let Some((iterations, tolerance)) = balancing_config { if let Some((iterations, tolerance)) = balancing {
balance(&mut voters, iterations, tolerance); balance(&mut voters, iterations, tolerance);
} }
} else { } else {
@@ -68,7 +68,11 @@ pub fn phragmms<AccountId: IdentifierT, P: PerThing128>(
let mut assignments = let mut assignments =
voters.into_iter().filter_map(|v| v.into_assignment()).collect::<Vec<_>>(); voters.into_iter().filter_map(|v| v.into_assignment()).collect::<Vec<_>>();
let _ = assignments.iter_mut().map(|a| a.try_normalize()).collect::<Result<(), _>>()?; let _ = assignments
.iter_mut()
.map(|a| a.try_normalize())
.collect::<Result<(), _>>()
.map_err(|e| crate::Error::ArithmeticError(e))?;
let winners = winners let winners = winners
.into_iter() .into_iter()
.map(|w_ptr| (w_ptr.borrow().who.clone(), w_ptr.borrow().backed_stake)) .map(|w_ptr| (w_ptr.borrow().who.clone(), w_ptr.borrow().backed_stake))