From a1d624d0e8f25081fff2843e9ff794ee463a8eab Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 27 Aug 2019 16:57:49 +0200 Subject: [PATCH 01/11] Move phragmen to primitives --- core/sr-primitives/src/lib.rs | 3 +- .../sr-primitives}/src/phragmen.rs | 292 +++++++++++------- srml/staking/src/benches.rs | 79 ++--- srml/staking/src/lib.rs | 119 ++++--- srml/staking/src/mock.rs | 6 +- srml/staking/src/tests.rs | 13 +- 6 files changed, 277 insertions(+), 235 deletions(-) rename {srml/staking => core/sr-primitives}/src/phragmen.rs (56%) diff --git a/core/sr-primitives/src/lib.rs b/core/sr-primitives/src/lib.rs index a6a66b80d4c1e..62c60eefd9708 100644 --- a/core/sr-primitives/src/lib.rs +++ b/core/sr-primitives/src/lib.rs @@ -40,13 +40,14 @@ pub use runtime_io::{StorageOverlay, ChildrenStorageOverlay}; use rstd::{prelude::*, ops, convert::{TryInto, TryFrom}}; use primitives::{crypto, ed25519, sr25519, hash::{H256, H512}}; use codec::{Encode, Decode, CompactAs}; +use traits::{SaturatedConversion, UniqueSaturatedInto, Saturating, Bounded, CheckedSub, CheckedAdd}; #[cfg(feature = "std")] pub mod testing; pub mod weights; +pub mod phragmen; pub mod traits; -use traits::{SaturatedConversion, UniqueSaturatedInto, Saturating, Bounded, CheckedSub, CheckedAdd}; pub mod generic; pub mod transaction_validity; diff --git a/srml/staking/src/phragmen.rs b/core/sr-primitives/src/phragmen.rs similarity index 56% rename from srml/staking/src/phragmen.rs rename to core/sr-primitives/src/phragmen.rs index 14b8a3845f2c4..70a74b30bb129 100644 --- a/srml/staking/src/phragmen.rs +++ b/core/sr-primitives/src/phragmen.rs @@ -17,10 +17,10 @@ //! Rust implementation of the Phragmén election algorithm. use rstd::{prelude::*, collections::btree_map::BTreeMap}; -use sr_primitives::{PerU128}; -use sr_primitives::traits::{Zero, Convert, Saturating}; -use crate::{BalanceOf, RawAssignment, ExpoMap, Trait, ValidatorPrefs, IndividualExposure}; +use crate::PerU128; +use crate::traits::{Zero, Convert, Member, SimpleArithmetic}; +/// Type used as the fraction. type Fraction = PerU128; /// Wrapper around the type used as the _safe_ wrapper around a `balance`. pub type ExtendedBalance = u128; @@ -47,21 +47,21 @@ pub struct Candidate { elected: bool, } -/// Wrapper around the nomination info of a single nominator for a group of validators. +/// Wrapper around the nomination info of a single voter for a group of validators. #[derive(Clone, Default)] #[cfg_attr(feature = "std", derive(Debug))] -pub struct Nominator { - /// The nominator's account. +pub struct Voter { + /// The voter's account. who: AccountId, - /// List of validators proposed by this nominator. + /// List of validators proposed by this voter. edges: Vec>, - /// the stake amount proposed by the nominator as a part of the vote. + /// the stake amount proposed by the voter as a part of the vote. budget: ExtendedBalance, - /// Incremented each time a nominee that this nominator voted for has been elected. + /// Incremented each time a nominee that this voter voted for has been elected. load: Fraction, } -/// Wrapper around a nominator vote and the load of that vote. +/// Wrapper around a voter's vote and the load of that vote. #[derive(Clone, Default)] #[cfg_attr(feature = "std", derive(Debug))] pub struct Edge { @@ -75,80 +75,123 @@ pub struct Edge { candidate_index: usize, } +/// A ratio of a voter's vote, indicated by `ExtendedBalance` / `ACCURACY`. +pub type PhragmenAssignment = (AccountId, ExtendedBalance); + +/// Final result of the phragmen election. +pub struct PhragmenResult { + /// Just winners. + pub winners: Vec, + /// individual assignments. for each tuple, the first elements is a voter and the second + /// is the list of candidates that it supports. + pub assignments: Vec<(AccountId, Vec>)> +} + +/// A structure to demonstrate the phragmen result from the perspective of the candidate, i.e. how +/// much support each candidate is receiving. +/// +/// This complements the [`PhragmenResult`] and is needed to run the equalize post-processing. +/// +/// This, at the current version, resembles the `Exposure` defined in the staking SRML module, yet +/// they do not necessarily have to be the same. +#[derive(Default)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct Support { + /// The amount of support as the effect of self-vote. + pub own: ExtendedBalance, + /// Total support. + pub total: ExtendedBalance, + /// Support from voters. + pub others: Vec>, +} + +/// A linkage from a candidate and its [`Support`]. +pub type SupportMap = BTreeMap>; + /// Perform election based on Phragmén algorithm. /// /// Reference implementation: https://github.com/w3f/consensus /// -/// Returns an Option of elected candidates, if election is performed. -/// Returns None if not enough candidates exist. +/// Returns an `Option` the set of winners and their detailed support ratio from each voter if +/// enough candidates are provided. Returns `None` otherwise. +/// /// -/// The returned Option is a tuple consisting of: -/// - The list of elected candidates. -/// - The list of nominators and their associated vote weights. -pub fn elect( +pub fn elect( validator_count: usize, minimum_validator_count: usize, - validator_iter: FV, - nominator_iter: FN, + initial_candidates: Vec, + initial_voters: Vec<(AccountId, Vec)>, slashable_balance_of: FS, -) -> Option<(Vec, Vec<(T::AccountId, Vec>)>)> where - FV: Iterator>)>, - FN: Iterator)>, - for <'r> FS: Fn(&'r T::AccountId) -> BalanceOf, + self_vote: bool, +) -> Option> where + AccountId: Default + Ord + Member, + Balance: Default + Copy + SimpleArithmetic, + for <'r> FS: Fn(&'r AccountId) -> Balance, + C: Convert + Convert, { - let to_votes = |b: BalanceOf| , u64>>::convert(b) as ExtendedBalance; + let to_votes = |b: Balance| + >::convert(b) as ExtendedBalance; // return structures - let mut elected_candidates: Vec; - let mut assigned: Vec<(T::AccountId, Vec>)>; - let mut c_idx_cache = BTreeMap::::new(); + let mut elected_candidates: Vec; + let mut assigned: Vec<(AccountId, Vec>)>; + + let mut c_idx_cache = BTreeMap::::new(); // 1- Pre-process candidates and place them in a container, optimisation and add phantom votes. // Candidates who have 0 stake => have no votes or all null-votes. Kick them out not. - let mut nominators: Vec> = - Vec::with_capacity(validator_iter.size_hint().0 + nominator_iter.size_hint().0); - let mut candidates = validator_iter.map(|(who, _)| { - let stash_balance = slashable_balance_of(&who); - (Candidate { who, ..Default::default() }, stash_balance) - }) - .filter_map(|(mut c, s)| { - c.approval_stake += to_votes(s); - if c.approval_stake.is_zero() { - None - } else { - Some((c, s)) - } - }) - .enumerate() - .map(|(idx, (c, s))| { - nominators.push(Nominator { - who: c.who.clone(), - edges: vec![ Edge { who: c.who.clone(), candidate_index: idx, ..Default::default() }], - budget: to_votes(s), - load: Fraction::zero(), - }); - c_idx_cache.insert(c.who.clone(), idx); - c - }) - .collect::>>(); - - // 2- Collect the nominators with the associated votes. + let mut voters: Vec> = Vec::with_capacity(initial_candidates.len() + initial_voters.len()); + let mut candidates = if self_vote { + initial_candidates.into_iter().map(|who| { + let stake = slashable_balance_of(&who); + Candidate { who, approval_stake: to_votes(stake), ..Default::default() } + }) + .filter_map(|c| { + if c.approval_stake.is_zero() { + None + } else { + Some(c) + } + }) + .enumerate() + .map(|(idx, c)| { + voters.push(Voter { + who: c.who.clone(), + edges: vec![ Edge { who: c.who.clone(), candidate_index: idx, ..Default::default() }], + budget: c.approval_stake, + load: Fraction::zero(), + }); + c_idx_cache.insert(c.who.clone(), idx); + c + }) + .collect::>>() + } else { + initial_candidates.into_iter() + .enumerate() + .map(|(idx, who)| { + c_idx_cache.insert(who.clone(), idx); + Candidate { who, ..Default::default() } + }) + .collect::>>() + }; + + // 2- collect the voters with the associated votes. // Also collect approval stake along the way. - nominators.extend(nominator_iter.map(|(who, nominees)| { - let nominator_stake = slashable_balance_of(&who); - let mut edges: Vec> = Vec::with_capacity(nominees.len()); - for n in &nominees { - if let Some(idx) = c_idx_cache.get(n) { + voters.extend(initial_voters.into_iter().map(|(who, nominees)| { + let voter_stake = slashable_balance_of(&who); + let mut edges: Vec> = Vec::with_capacity(nominees.len()); + for n in nominees { + if let Some(idx) = c_idx_cache.get(&n) { // This candidate is valid + already cached. candidates[*idx].approval_stake = candidates[*idx].approval_stake - .saturating_add(to_votes(nominator_stake)); + .saturating_add(to_votes(voter_stake)); edges.push(Edge { who: n.clone(), candidate_index: *idx, ..Default::default() }); } // else {} would be wrong votes. We don't really care about it. } - Nominator { + Voter { who, edges: edges, - budget: to_votes(nominator_stake), + budget: to_votes(voter_stake), load: Fraction::zero(), } })); @@ -156,9 +199,9 @@ pub fn elect( // 4- If we have more candidates then needed, run Phragmén. if candidates.len() >= minimum_validator_count { let validator_count = validator_count.min(candidates.len()); - elected_candidates = Vec::with_capacity(validator_count); assigned = Vec::with_capacity(validator_count); + // Main election loop for _round in 0..validator_count { // Loop 1: initialize score @@ -168,7 +211,7 @@ pub fn elect( } } // Loop 2: increment score. - for n in &nominators { + for n in &voters { for e in &n.edges { let c = &mut candidates[e.candidate_index]; if !c.elected && !c.approval_stake.is_zero() { @@ -191,9 +234,9 @@ pub fn elect( .filter(|c| !c.elected) .min_by_key(|c| *c.score) { - // loop 3: update nominator and edge load + // loop 3: update voter and edge load winner.elected = true; - for n in &mut nominators { + for n in &mut voters { for e in &mut n.edges { if e.who == winner.who { e.load = Fraction::from_parts(*winner.score - *n.load); @@ -208,12 +251,12 @@ pub fn elect( } } // end of all rounds - // 4.1- Update backing stake of candidates and nominators - for n in &mut nominators { + // 4.1- Update backing stake of candidates and voters + for n in &mut voters { let mut assignment = (n.who.clone(), vec![]); for e in &mut n.edges { - if let Some(c) = elected_candidates.iter().find(|c| **c == e.who) { - if *c != n.who { + if let Some(c) = elected_candidates.iter().cloned().find(|c| *c == e.who) { + if c != n.who { let ratio = { // Full support. No need to calculate. if *n.load == *e.load { ACCURACY } @@ -234,8 +277,8 @@ pub fn elect( } if assignment.1.len() > 0 { - // To ensure an assertion indicating: no stake from the nominator going to waste, - // we add a minimal post-processing to equally assign all of the leftover stake ratios. + // To ensure an assertion indicating: no stake from the voter going to waste, we add + // a minimal post-processing to equally assign all of the leftover stake ratios. let vote_count = assignment.1.len() as ExtendedBalance; let l = assignment.1.len(); let sum = assignment.1.iter().map(|a| a.1).sum::(); @@ -250,7 +293,7 @@ pub fn elect( } } - // `remainder` is set to be less than maximum votes of a nominator (currently 16). + // `remainder` is set to be less than maximum votes of a voter (currently 16). // safe to cast it to usize. let remainder = diff - diff_per_vote * vote_count; for i in 0..remainder as usize { @@ -266,7 +309,11 @@ pub fn elect( // if we have less than minimum, use the previous validator set. return None } - Some((elected_candidates, assigned)) + + Some(PhragmenResult { + winners: elected_candidates, + assignments: assigned, + }) } /// Performs equalize post-processing to the output of the election algorithm @@ -274,57 +321,72 @@ pub fn elect( /// the elected candidates. /// /// No value is returned from the function and the `expo_map` parameter is updated. -pub fn equalize( - assignments: &mut Vec<(T::AccountId, BalanceOf, Vec<(T::AccountId, ExtendedBalance, ExtendedBalance)>)>, - expo_map: &mut ExpoMap, +pub fn equalize( + mut assignments: Vec<(AccountId, Vec>)>, + supports: &mut SupportMap, tolerance: ExtendedBalance, iterations: usize, -) { + slashable_balance_of: FS, +) where + C: Convert + Convert, + for <'r> FS: Fn(&'r AccountId) -> Balance, + AccountId: Ord + Clone, +{ + // prepare the data for equalise for _i in 0..iterations { let mut max_diff = 0; - assignments.iter_mut().for_each(|(n, budget, assignment)| { - let diff = do_equalize::(&n, *budget, assignment, expo_map, tolerance); - if diff > max_diff { - max_diff = diff; - } - }); + + for (voter, assignment) in assignments.iter_mut() { + let voter_budget = slashable_balance_of(&voter); + + let diff = do_equalize::<_, _, C>( + voter, + voter_budget, + assignment, + supports, + tolerance + ); + if diff > max_diff { max_diff = diff; } + } + if max_diff < tolerance { break; } } } -fn do_equalize( - nominator: &T::AccountId, - budget_balance: BalanceOf, - elected_edges: &mut Vec<(T::AccountId, ExtendedBalance, ExtendedBalance)>, - expo_map: &mut ExpoMap, +fn do_equalize( + voter: &AccountId, + budget_balance: Balance, + elected_edges: &mut Vec<(AccountId, ExtendedBalance)>, + support_map: &mut SupportMap, tolerance: ExtendedBalance -) -> ExtendedBalance { - let to_votes = |b: BalanceOf| - , u64>>::convert(b) as ExtendedBalance; - let to_balance = |v: ExtendedBalance| - >>::convert(v); +) -> ExtendedBalance where + C: Convert + Convert, + AccountId: Ord + Clone, +{ + let to_votes = |b: Balance| + >::convert(b) as ExtendedBalance; let budget = to_votes(budget_balance); - // Nothing to do. This nominator had nothing useful. + // Nothing to do. This voter had nothing useful. // Defensive only. Assignment list should always be populated. if elected_edges.is_empty() { return 0; } let stake_used = elected_edges .iter() - .fold(0 as ExtendedBalance, |s, e| s.saturating_add(e.2)); + .fold(0 as ExtendedBalance, |s, e| s.saturating_add(e.1)); let backed_stakes_iter = elected_edges .iter() - .filter_map(|e| expo_map.get(&e.0)) - .map(|e| to_votes(e.total)); + .filter_map(|e| support_map.get(&e.0)) + .map(|e| e.total); let backing_backed_stake = elected_edges .iter() - .filter(|e| e.2 > 0) - .filter_map(|e| expo_map.get(&e.0)) - .map(|e| to_votes(e.total)) + .filter(|e| e.1 > 0) + .filter_map(|e| support_map.get(&e.0)) + .map(|e| e.total) .collect::>(); let mut difference; @@ -346,24 +408,24 @@ fn do_equalize( difference = budget; } - // Undo updates to exposure + // Undo updates to support elected_edges.iter_mut().for_each(|e| { - if let Some(expo) = expo_map.get_mut(&e.0) { - expo.total = expo.total.saturating_sub(to_balance(e.2)); - expo.others.retain(|i_expo| i_expo.who != *nominator); + if let Some(support) = support_map.get_mut(&e.0) { + support.total = support.total.saturating_sub(e.1); + support.others.retain(|i_support| i_support.0 != *voter); } - e.2 = 0; + e.1 = 0; }); elected_edges.sort_unstable_by_key(|e| - if let Some(e) = expo_map.get(&e.0) { e.total } else { Zero::zero() } + if let Some(e) = support_map.get(&e.0) { e.total } else { Zero::zero() } ); let mut cumulative_stake: ExtendedBalance = 0; let mut last_index = elected_edges.len() - 1; elected_edges.iter_mut().enumerate().for_each(|(idx, e)| { - if let Some(expo) = expo_map.get_mut(&e.0) { - let stake: ExtendedBalance = to_votes(expo.total); + if let Some(support) = support_map.get_mut(&e.0) { + let stake: ExtendedBalance = support.total; let stake_mul = stake.saturating_mul(idx as ExtendedBalance); let stake_sub = stake_mul.saturating_sub(cumulative_stake); if stake_sub > budget { @@ -374,18 +436,18 @@ fn do_equalize( } }); - let last_stake = elected_edges[last_index].2; + let last_stake = elected_edges[last_index].1; let split_ways = last_index + 1; let excess = budget .saturating_add(cumulative_stake) .saturating_sub(last_stake.saturating_mul(split_ways as ExtendedBalance)); elected_edges.iter_mut().take(split_ways).for_each(|e| { - if let Some(expo) = expo_map.get_mut(&e.0) { - e.2 = (excess / split_ways as ExtendedBalance) + if let Some(support) = support_map.get_mut(&e.0) { + e.1 = (excess / split_ways as ExtendedBalance) .saturating_add(last_stake) - .saturating_sub(to_votes(expo.total)); - expo.total = expo.total.saturating_add(to_balance(e.2)); - expo.others.push(IndividualExposure { who: nominator.clone(), value: to_balance(e.2)}); + .saturating_sub(support.total); + support.total = support.total.saturating_add(e.1); + support.others.push((voter.clone(), e.1)); } }); diff --git a/srml/staking/src/benches.rs b/srml/staking/src/benches.rs index 6e79ee70a47a2..3d898e22ea57a 100644 --- a/srml/staking/src/benches.rs +++ b/srml/staking/src/benches.rs @@ -27,6 +27,7 @@ use test::Bencher; use runtime_io::with_externalities; use mock::*; use super::*; +use sr_primitives::phragmen; use rand::{self, Rng}; const VALIDATORS: u64 = 1000; @@ -71,67 +72,55 @@ fn do_phragmen( }); b.iter(|| { - let r = phragmen::elect::( + let r = phragmen::elect::<_, _, _, ::CurrencyToVote>( count, 1_usize, - >::enumerate(), - >::enumerate(), - Staking::slashable_balance_of + >::enumerate().map(|(who, _)| who).collect::>(), + >::enumerate().collect(), + Staking::slashable_balance_of, + true, ).unwrap(); // Do the benchmarking with equalize. if eq_iters > 0 { - let elected_stashes = r.0; - let assignments = r.1; + let elected_stashes = r.winners; + let mut assignments = r.assignments; - let to_balance = |b: ExtendedBalance| - <::CurrencyToVote as Convert>::convert(b); let to_votes = |b: Balance| - <::CurrencyToVote as Convert>::convert(b) as ExtendedBalance; - let ratio_of = |b, p| (p as ExtendedBalance).saturating_mul(to_votes(b)) / ACCURACY; - - let assignments_with_stakes = assignments.into_iter().map(|(n, a)|( - n, - Staking::slashable_balance_of(&n), - a.into_iter().map(|(acc, r)| ( - acc.clone(), - r, - to_balance(ratio_of(Staking::slashable_balance_of(&n), r)), - )) - .collect::>>() - )).collect::>)>>(); - - let mut exposures = >::new(); + <::CurrencyToVote as Convert>::convert(b) as ExtendedBalance; + let ratio_of = |b, r: ExtendedBalance| r.saturating_mul(to_votes(b)) / ACCURACY; + + // Initialize the support of each candidate. + let mut supports = >::new(); elected_stashes - .into_iter() - .map(|e| (e, Staking::slashable_balance_of(&e))) + .iter() + .map(|e| (e, to_votes(Staking::slashable_balance_of(e)))) .for_each(|(e, s)| { - let item = Exposure { own: s, total: s, ..Default::default() }; - exposures.insert(e, item); + let item = Support { own: s, total: s, ..Default::default() }; + supports.insert(e.clone(), item); }); - for (n, _, assignment) in &assignments_with_stakes { - for (c, _, s) in assignment { - if let Some(expo) = exposures.get_mut(c) { - expo.total = expo.total.saturating_add(*s); - expo.others.push( IndividualExposure { who: n.clone(), value: *s } ); + for (n, assignment) in assignments.iter_mut() { + for (c, r) in assignment.iter_mut() { + let nominator_stake = Staking::slashable_balance_of(n); + let other_stake = ratio_of(nominator_stake, *r); + if let Some(support) = supports.get_mut(c) { + support.total = support.total.saturating_add(other_stake); + support.others.push((n.clone(), other_stake)); } + *r = other_stake; } } - let mut assignments_with_votes = assignments_with_stakes.into_iter() - .map(|a| ( - a.0, a.1, - a.2.into_iter() - .map(|e| (e.0, e.1, to_votes(e.2))) - .collect::>() - )) - .collect:: - )>>(); - equalize::(&mut assignments_with_votes, &mut exposures, eq_tolerance, eq_iters); + let tolerance = 0_u128; + let iterations = 2_usize; + phragmen::equalize::<_, _, ::CurrencyToVote, _>( + assignments, + &mut supports, + tolerance, + iterations, + Staking::slashable_balance_of, + ); } }) }) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 2e46d6b2b4a49..afbc274239c78 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -254,7 +254,6 @@ mod mock; #[cfg(test)] mod tests; -mod phragmen; pub mod inflation; #[cfg(all(feature = "bench", test))] @@ -262,7 +261,7 @@ mod benches; #[cfg(feature = "std")] use runtime_io::with_storage; -use rstd::{prelude::*, result, collections::btree_map::BTreeMap}; +use rstd::{prelude::*, result}; use codec::{HasCompact, Encode, Decode}; use srml_support::{ StorageValue, StorageMap, EnumerableStorageMap, decl_module, decl_event, @@ -275,9 +274,10 @@ use session::{historical::OnSessionEnding, SelectInitialValidators}; use sr_primitives::Perbill; use sr_primitives::weights::SimpleDispatchInfo; use sr_primitives::traits::{ - Convert, Zero, One, StaticLookup, CheckedSub, Saturating, Bounded, - SimpleArithmetic, SaturatedConversion, + Convert, Zero, One, StaticLookup, CheckedSub, Saturating, Bounded, SimpleArithmetic, + SaturatedConversion, }; +use sr_primitives::phragmen::{elect, equalize, Support, SupportMap, ExtendedBalance, ACCURACY}; use sr_staking_primitives::{ SessionIndex, CurrentElectedSet, offence::{OnOffenceHandler, OffenceDetails, Offence, ReportOffence}, @@ -286,8 +286,6 @@ use sr_staking_primitives::{ use sr_primitives::{Serialize, Deserialize}; use system::{ensure_signed, ensure_root}; -use phragmen::{elect, ACCURACY, ExtendedBalance, equalize}; - const DEFAULT_MINIMUM_VALIDATOR_COUNT: u32 = 4; const MAX_NOMINATIONS: usize = 16; const MAX_UNLOCKING_CHUNKS: usize = 32; @@ -458,13 +456,6 @@ type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; type MomentOf= <::Time as Time>::Moment; -type RawAssignment = (::AccountId, ExtendedBalance); -type Assignment = (::AccountId, ExtendedBalance, BalanceOf); -type ExpoMap = BTreeMap< - ::AccountId, - Exposure<::AccountId, BalanceOf> ->; - /// Means for interacting with a specialized version of the `session` trait. /// /// This is needed because `Staking` sets the `ValidatorIdOf` of the `session::Trait` @@ -1254,17 +1245,18 @@ impl Module { /// /// Returns the new `SlotStake` value and a set of newly selected _stash_ IDs. fn select_validators() -> (BalanceOf, Option>) { - let maybe_elected_set = elect::( + let maybe_phragmen_result = elect::<_, _, _, T::CurrencyToVote>( Self::validator_count() as usize, Self::minimum_validator_count().max(1) as usize, - >::enumerate(), - >::enumerate(), + >::enumerate().map(|(who, _)| who).collect::>(), + >::enumerate().collect(), Self::slashable_balance_of, + true, ); - if let Some(elected_set) = maybe_elected_set { - let elected_stashes = elected_set.0; - let assignments = elected_set.1; + if let Some(phragmen_result) = maybe_phragmen_result { + let elected_stashes = phragmen_result.winners; + let mut assignments = phragmen_result.assignments; // helper closure. let to_balance = |b: ExtendedBalance| @@ -1277,59 +1269,45 @@ impl Module { // to be properly multiplied by a ratio, which will lead to another value // less than u64 for sure. The result can then be safely passed to `to_balance`. // For now the backward convert is used. A simple `TryFrom` is also safe. - let ratio_of = |b, p| (p as ExtendedBalance).saturating_mul(to_votes(b)) / ACCURACY; - - // Compute the actual stake from nominator's ratio. - let assignments_with_stakes = assignments.iter().map(|(n, a)|( - n.clone(), - Self::slashable_balance_of(n), - a.iter().map(|(acc, r)| ( - acc.clone(), - *r, - to_balance(ratio_of(Self::slashable_balance_of(n), *r)), - )) - .collect::>>() - )).collect::, Vec>)>>(); - - // update elected candidate exposures. - let mut exposures = >::new(); + let ratio_of = |b, r: ExtendedBalance| r.saturating_mul(to_votes(b)) / ACCURACY; + + // Initialize the support of each candidate. + let mut supports = >::new(); elected_stashes .iter() - .map(|e| (e, Self::slashable_balance_of(e))) + .map(|e| (e, to_votes(Self::slashable_balance_of(e)))) .for_each(|(e, s)| { - let item = Exposure { own: s, total: s, ..Default::default() }; - exposures.insert(e.clone(), item); + let item = Support { own: s, total: s, ..Default::default() }; + supports.insert(e.clone(), item); }); - for (n, _, assignment) in &assignments_with_stakes { - for (c, _, s) in assignment { - if let Some(expo) = exposures.get_mut(c) { - // NOTE: simple example where this saturates: - // candidate with max_value stake. 1 nominator with max_value stake. - // Nuked. Sadly there is not much that we can do about this. - // See this test: phragmen_should_not_overflow_xxx() - expo.total = expo.total.saturating_add(*s); - expo.others.push( IndividualExposure { who: n.clone(), value: *s } ); + // convert the ratio in-place (and replace) to the balance but still in the extended + // balance type. + for (n, assignment) in assignments.iter_mut() { + for (c, r) in assignment.iter_mut() { + let nominator_stake = Self::slashable_balance_of(n); + let other_stake = ratio_of(nominator_stake, *r); + if let Some(support) = supports.get_mut(c) { + // This for an astronomically rich validator with more astronomically rich + // set of nominators, this might saturate. + support.total = support.total.saturating_add(other_stake); + support.others.push((n.clone(), other_stake)); } + // convert the ratio to extended balance + *r = other_stake; } } if cfg!(feature = "equalize") { let tolerance = 0_u128; let iterations = 2_usize; - let mut assignments_with_votes = assignments_with_stakes.iter() - .map(|a| ( - a.0.clone(), a.1, - a.2.iter() - .map(|e| (e.0.clone(), e.1, to_votes(e.2))) - .collect::>() - )) - .collect::, - Vec<(T::AccountId, ExtendedBalance, ExtendedBalance)> - )>>(); - equalize::(&mut assignments_with_votes, &mut exposures, tolerance, iterations); + equalize::<_, _, T::CurrencyToVote, _>( + assignments, + &mut supports, + tolerance, + iterations, + Self::slashable_balance_of, + ); } // Clear Stakers. @@ -1339,11 +1317,24 @@ impl Module { // Populate Stakers and figure out the minimum stake behind a slot. let mut slot_stake = BalanceOf::::max_value(); - for (c, e) in exposures.iter() { - if e.total < slot_stake { - slot_stake = e.total; + for (c, s) in supports.into_iter() { + // build `struct exposure` from `support` + let exposure = Exposure { + own: to_balance(s.own), + // This might reasonably saturate and we cannot do much about it. The sum of + // someone's stake might exceed the balance type if they have the maximum amount + // of balance and receive some support. This is super unlikely to happen, yet + // we simulate it it in some tests. + total: to_balance(s.total), + others: s.others + .into_iter() + .map(|(who, value)| IndividualExposure { who, value: to_balance(value) }) + .collect::>>(), + }; + if exposure.total < slot_stake { + slot_stake = exposure.total; } - >::insert(c.clone(), e.clone()); + >::insert(c.clone(), exposure.clone()); } // Update slot stake. diff --git a/srml/staking/src/mock.rs b/srml/staking/src/mock.rs index aafe065118fa1..2f2e81196c137 100644 --- a/srml/staking/src/mock.rs +++ b/srml/staking/src/mock.rs @@ -18,7 +18,7 @@ use std::{collections::HashSet, cell::RefCell}; use sr_primitives::Perbill; -use sr_primitives::traits::{IdentityLookup, Convert, OpaqueKeys, OnInitialize}; +use sr_primitives::traits::{IdentityLookup, Convert, OpaqueKeys, OnInitialize, SaturatedConversion}; use sr_primitives::testing::{Header, UintAuthorityId}; use sr_staking_primitives::SessionIndex; use primitives::{H256, Blake2Hasher}; @@ -41,9 +41,7 @@ impl Convert for CurrencyToVoteHandler { fn convert(x: u64) -> u64 { x } } impl Convert for CurrencyToVoteHandler { - fn convert(x: u128) -> u64 { - x as u64 - } + fn convert(x: u128) -> u64 { x.saturated_into() } } thread_local! { diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index bbae3615c2b6e..ed727699c8bf4 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -18,7 +18,7 @@ use super::*; use runtime_io::with_externalities; -use phragmen; +use sr_primitives::phragmen; use sr_primitives::traits::OnInitialize; use sr_staking_primitives::offence::{OffenceDetails, OnOffenceHandler}; use srml_support::{assert_ok, assert_noop, assert_eq_uvec, EnumerableStorageMap}; @@ -1429,19 +1429,20 @@ fn phragmen_poc_2_works() { assert_ok!(Staking::bond(Origin::signed(3), 4, 1000, RewardDestination::default())); assert_ok!(Staking::nominate(Origin::signed(4), vec![11, 31])); - let winners = phragmen::elect::( + let results = phragmen::elect::<_, _, _, ::CurrencyToVote>( 2, Staking::minimum_validator_count() as usize, - >::enumerate(), - >::enumerate(), + >::enumerate().map(|(who, _)| who).collect::>(), + >::enumerate().collect(), Staking::slashable_balance_of, + true, ); - let (winners, assignment) = winners.unwrap(); + let phragmen::PhragmenResult { winners, assignments } = results.unwrap(); // 10 and 30 must be the winners assert_eq!(winners, vec![11, 31]); - assert_eq!(assignment, vec![ + assert_eq!(assignments, vec![ (3, vec![(11, 2816371998), (31, 1478595298)]), (1, vec![(11, 4294967296)]), ]); From c1e081171b440e0381de88706f8a3c9b455fadb2 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 28 Aug 2019 11:28:21 +0200 Subject: [PATCH 02/11] Improved docs --- core/sr-primitives/src/phragmen.rs | 312 ++++++++++++++++------------- srml/staking/src/lib.rs | 2 +- 2 files changed, 177 insertions(+), 137 deletions(-) diff --git a/core/sr-primitives/src/phragmen.rs b/core/sr-primitives/src/phragmen.rs index 70a74b30bb129..fcee4cab33324 100644 --- a/core/sr-primitives/src/phragmen.rs +++ b/core/sr-primitives/src/phragmen.rs @@ -14,7 +14,22 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -//! Rust implementation of the Phragmén election algorithm. +//! Rust implementation of the Phragmén election algorithm. This is used in several SRML modules to +//! optimally distribute the weight of a set of voters among an elected set of candidates. In the +//! context of staking this is mapped to validators and nominators. +//! +//! The algorithm has two phases: +//! - Sequential phragmen: performed in [`elect`] function which is first pass of the distribution +//! The results are not optimal but the execution time is less. +//! - Equalize post-processing: tries to further distribute the weight fairly among candidates. +//! Incurs more execution time. +//! +//! The main objective of the assignments done by phragmen is to maximize the minimum backed +//! candidate in the elected set. +//! +//! Reference implementation: https://github.com/w3f/consensus +//! Further details: +//! https://research.web3.foundation/en/latest/polkadot/NPoS/4.%20Sequential%20Phragm%C3%A9n%E2%80%99s%20method/ use rstd::{prelude::*, collections::btree_map::BTreeMap}; use crate::PerU128; @@ -22,67 +37,74 @@ use crate::traits::{Zero, Convert, Member, SimpleArithmetic}; /// Type used as the fraction. type Fraction = PerU128; -/// Wrapper around the type used as the _safe_ wrapper around a `balance`. + +/// A type in which performing operations on balances and stakes of candidates and voters are safe. +/// +/// This module's functions expect a `Convert` type to convert all balances to u64. Hence, u128 is +/// a safe type for arithmetic operations over them. +/// +/// Balance types converted to `ExtendedBalance` are referred to as `Votes`. pub type ExtendedBalance = u128; // this is only used while creating the candidate score. Due to reasons explained below // The more accurate this is, the less likely we choose a wrong candidate. +// TODO: can be removed with proper use of per-things #2908 const SCALE_FACTOR: ExtendedBalance = u32::max_value() as ExtendedBalance + 1; + /// These are used to expose a fixed accuracy to the caller function. The bigger they are, /// the more accurate we get, but the more likely it is for us to overflow. The case of overflow /// is handled but accuracy will be lost. 32 or 16 are reasonable values. +// TODO: can be removed with proper use of per-things #2908 pub const ACCURACY: ExtendedBalance = u32::max_value() as ExtendedBalance + 1; -/// Wrapper around validation candidates some metadata. +/// A candidate entity for phragmen election. #[derive(Clone, Default)] #[cfg_attr(feature = "std", derive(Debug))] pub struct Candidate { - /// The validator's account + /// Identifier. pub who: AccountId, /// Intermediary value used to sort candidates. pub score: Fraction, - /// Accumulator of the stake of this candidate based on received votes. + /// Sum of the stake of this candidate based on received votes. approval_stake: ExtendedBalance, /// Flag for being elected. elected: bool, } -/// Wrapper around the nomination info of a single voter for a group of validators. +/// A voter entity. #[derive(Clone, Default)] #[cfg_attr(feature = "std", derive(Debug))] pub struct Voter { - /// The voter's account. + /// Identifier. who: AccountId, - /// List of validators proposed by this voter. + /// List of candidates proposed by this voter. edges: Vec>, - /// the stake amount proposed by the voter as a part of the vote. + /// The stake of this voter. budget: ExtendedBalance, - /// Incremented each time a nominee that this voter voted for has been elected. + /// Incremented each time a candidate that this voter voted for has been elected. load: Fraction, } -/// Wrapper around a voter's vote and the load of that vote. +/// A candidate being backed by a voter. #[derive(Clone, Default)] #[cfg_attr(feature = "std", derive(Debug))] pub struct Edge { - /// Account being voted for + /// Identifier. who: AccountId, /// Load of this vote. load: Fraction, - /// Equal to `edge.load / nom.load`. Stored only to be used with post-processing. - ratio: ExtendedBalance, /// Index of the candidate stored in the 'candidates' vector. candidate_index: usize, } -/// A ratio of a voter's vote, indicated by `ExtendedBalance` / `ACCURACY`. +/// Means a particular `AccountId` was backed by a ratio of `ExtendedBalance / ACCURACY`. pub type PhragmenAssignment = (AccountId, ExtendedBalance); /// Final result of the phragmen election. pub struct PhragmenResult { /// Just winners. pub winners: Vec, - /// individual assignments. for each tuple, the first elements is a voter and the second + /// Individual assignments. for each tuple, the first elements is a voter and the second /// is the list of candidates that it supports. pub assignments: Vec<(AccountId, Vec>)> } @@ -110,18 +132,24 @@ pub type SupportMap = BTreeMap>; /// Perform election based on Phragmén algorithm. /// -/// Reference implementation: https://github.com/w3f/consensus -/// /// Returns an `Option` the set of winners and their detailed support ratio from each voter if /// enough candidates are provided. Returns `None` otherwise. /// -/// +/// * `candidate_count`: number of candidates to elect. +/// * `minimum_candidate_count`: minimum number of candidates to elect. If less candidates exist, +/// `None` is returned. +/// * `initial_candidates`: candidates list to be elected from. +/// * `initial_voters`: voters list. +/// * `stake_of`: something that can return the stake stake of a particular candidate or voter. +/// * `self_vote`. If true, then each candidate will automatically vote for themselves with the a +/// weight indicated by their stake. Note that when this is `true` candidates are filtered by +/// having at least some backed stake from themselves. pub fn elect( - validator_count: usize, - minimum_validator_count: usize, + candidate_count: usize, + minimum_candidate_count: usize, initial_candidates: Vec, initial_voters: Vec<(AccountId, Vec)>, - slashable_balance_of: FS, + stake_of: FS, self_vote: bool, ) -> Option> where AccountId: Default + Ord + Member, @@ -136,14 +164,18 @@ pub fn elect( let mut elected_candidates: Vec; let mut assigned: Vec<(AccountId, Vec>)>; + // used to cache and access candidates index. let mut c_idx_cache = BTreeMap::::new(); - // 1- Pre-process candidates and place them in a container, optimisation and add phantom votes. - // Candidates who have 0 stake => have no votes or all null-votes. Kick them out not. - let mut voters: Vec> = Vec::with_capacity(initial_candidates.len() + initial_voters.len()); + // voters list. + let num_voters = initial_candidates.len() + initial_voters.len(); + let mut voters: Vec> = Vec::with_capacity(num_voters); + + // collect candidates. self vote or filter might apply let mut candidates = if self_vote { + // self vote. filter. initial_candidates.into_iter().map(|who| { - let stake = slashable_balance_of(&who); + let stake = stake_of(&who); Candidate { who, approval_stake: to_votes(stake), ..Default::default() } }) .filter_map(|c| { @@ -166,6 +198,7 @@ pub fn elect( }) .collect::>>() } else { + // no self vote. just collect. initial_candidates.into_iter() .enumerate() .map(|(idx, who)| { @@ -175,17 +208,20 @@ pub fn elect( .collect::>>() }; - // 2- collect the voters with the associated votes. - // Also collect approval stake along the way. - voters.extend(initial_voters.into_iter().map(|(who, nominees)| { - let voter_stake = slashable_balance_of(&who); - let mut edges: Vec> = Vec::with_capacity(nominees.len()); - for n in nominees { - if let Some(idx) = c_idx_cache.get(&n) { + // early return if we don't have enough candidates + if candidates.len() < minimum_candidate_count { return None; } + + // collect voters. use `c_idx_cache` for fast access and aggregate `approval_stake` of + // candidates. + voters.extend(initial_voters.into_iter().map(|(who, votes)| { + let voter_stake = stake_of(&who); + let mut edges: Vec> = Vec::with_capacity(votes.len()); + for v in votes { + if let Some(idx) = c_idx_cache.get(&v) { // This candidate is valid + already cached. candidates[*idx].approval_stake = candidates[*idx].approval_stake .saturating_add(to_votes(voter_stake)); - edges.push(Edge { who: n.clone(), candidate_index: *idx, ..Default::default() }); + edges.push(Edge { who: v.clone(), candidate_index: *idx, ..Default::default() }); } // else {} would be wrong votes. We don't really care about it. } Voter { @@ -196,118 +232,114 @@ pub fn elect( } })); - // 4- If we have more candidates then needed, run Phragmén. - if candidates.len() >= minimum_validator_count { - let validator_count = validator_count.min(candidates.len()); - elected_candidates = Vec::with_capacity(validator_count); - assigned = Vec::with_capacity(validator_count); - - // Main election loop - for _round in 0..validator_count { - // Loop 1: initialize score - for c in &mut candidates { - if !c.elected { - c.score = Fraction::from_xth(c.approval_stake); - } + + // we have already checked that we have more candidates than minimum_candidate_count. + // run phragmen. + let to_elect = candidate_count.min(candidates.len()); + elected_candidates = Vec::with_capacity(candidate_count); + assigned = Vec::with_capacity(candidate_count); + + // main election loop + for _round in 0..to_elect { + // loop 1: initialize score + for c in &mut candidates { + if !c.elected { + c.score = Fraction::from_xth(c.approval_stake); } - // Loop 2: increment score. - for n in &voters { - for e in &n.edges { - let c = &mut candidates[e.candidate_index]; - if !c.elected && !c.approval_stake.is_zero() { - // Basic fixed-point shifting by 32. - // `n.budget.saturating_mul(SCALE_FACTOR)` will never saturate - // since n.budget cannot exceed u64,despite being stored in u128. yet, - // `*n.load / SCALE_FACTOR` might collapse to zero. Hence, 32 or 16 bits are better scale factors. - // Note that left-associativity in operators precedence is crucially important here. - let temp = - n.budget.saturating_mul(SCALE_FACTOR) / c.approval_stake - * (*n.load / SCALE_FACTOR); - c.score = Fraction::from_parts((*c.score).saturating_add(temp)); - } + } + // loop 2: increment score + for n in &voters { + for e in &n.edges { + let c = &mut candidates[e.candidate_index]; + if !c.elected && !c.approval_stake.is_zero() { + // Basic fixed-point shifting by 32. + // `n.budget.saturating_mul(SCALE_FACTOR)` will never saturate + // since n.budget cannot exceed u64,despite being stored in u128. yet, + // `*n.load / SCALE_FACTOR` might collapse to zero. Hence, 32 or 16 bits are + // better scale factors. Note that left-associativity in operators precedence is + // crucially important here. + let temp = + n.budget.saturating_mul(SCALE_FACTOR) / c.approval_stake + * (*n.load / SCALE_FACTOR); + c.score = Fraction::from_parts((*c.score).saturating_add(temp)); } } + } - // Find the best - if let Some(winner) = candidates - .iter_mut() - .filter(|c| !c.elected) - .min_by_key(|c| *c.score) - { - // loop 3: update voter and edge load - winner.elected = true; - for n in &mut voters { - for e in &mut n.edges { - if e.who == winner.who { - e.load = Fraction::from_parts(*winner.score - *n.load); - n.load = winner.score; - } + // loop 3: find the best + if let Some(winner) = candidates + .iter_mut() + .filter(|c| !c.elected) + .min_by_key(|c| *c.score) + { + // loop 3: update voter and edge load + winner.elected = true; + for n in &mut voters { + for e in &mut n.edges { + if e.who == winner.who { + e.load = Fraction::from_parts(*winner.score - *n.load); + n.load = winner.score; } } - - elected_candidates.push(winner.who.clone()); - } else { - break } - } // end of all rounds - - // 4.1- Update backing stake of candidates and voters - for n in &mut voters { - let mut assignment = (n.who.clone(), vec![]); - for e in &mut n.edges { - if let Some(c) = elected_candidates.iter().cloned().find(|c| *c == e.who) { - if c != n.who { - let ratio = { - // Full support. No need to calculate. - if *n.load == *e.load { ACCURACY } - else { - // This should not saturate. Safest is to just check - if let Some(r) = ACCURACY.checked_mul(*e.load) { - r / n.load.max(1) - } else { - // Just a simple trick. - *e.load / (n.load.max(1) / ACCURACY) - } + + elected_candidates.push(winner.who.clone()); + } else { + break + } + } // end of all rounds + + // update backing stake of candidates and voters + for n in &mut voters { + let mut assignment = (n.who.clone(), vec![]); + for e in &mut n.edges { + if let Some(c) = elected_candidates.iter().cloned().find(|c| *c == e.who) { + if c != n.who { + let ratio = { + // Full support. No need to calculate. + if *n.load == *e.load { ACCURACY } + else { + // This should not saturate. Safest is to just check + if let Some(r) = ACCURACY.checked_mul(*e.load) { + r / n.load.max(1) + } else { + // Just a simple trick. + *e.load / (n.load.max(1) / ACCURACY) } - }; - e.ratio = ratio; - assignment.1.push((e.who.clone(), ratio)); - } + } + }; + assignment.1.push((e.who.clone(), ratio)); } } + } - if assignment.1.len() > 0 { - // To ensure an assertion indicating: no stake from the voter going to waste, we add - // a minimal post-processing to equally assign all of the leftover stake ratios. - let vote_count = assignment.1.len() as ExtendedBalance; - let l = assignment.1.len(); - let sum = assignment.1.iter().map(|a| a.1).sum::(); - let diff = ACCURACY.checked_sub(sum).unwrap_or(0); - let diff_per_vote= diff / vote_count; - - if diff_per_vote > 0 { - for i in 0..l { - assignment.1[i%l].1 = - assignment.1[i%l].1 - .saturating_add(diff_per_vote); - } - } - - // `remainder` is set to be less than maximum votes of a voter (currently 16). - // safe to cast it to usize. - let remainder = diff - diff_per_vote * vote_count; - for i in 0..remainder as usize { + if assignment.1.len() > 0 { + // To ensure an assertion indicating: no stake from the voter going to waste, we add + // a minimal post-processing to equally assign all of the leftover stake ratios. + let vote_count = assignment.1.len() as ExtendedBalance; + let l = assignment.1.len(); + let sum = assignment.1.iter().map(|a| a.1).sum::(); + let diff = ACCURACY.checked_sub(sum).unwrap_or(0); + let diff_per_vote= diff / vote_count; + + if diff_per_vote > 0 { + for i in 0..l { assignment.1[i%l].1 = assignment.1[i%l].1 - .saturating_add(1); + .saturating_add(diff_per_vote); } - assigned.push(assignment); } - } - } else { - // if we have less than minimum, use the previous validator set. - return None + // `remainder` is set to be less than maximum votes of a voter (currently 16). + // safe to cast it to usize. + let remainder = diff - diff_per_vote * vote_count; + for i in 0..remainder as usize { + assignment.1[i%l].1 = + assignment.1[i%l].1 + .saturating_add(1); + } + assigned.push(assignment); + } } Some(PhragmenResult { @@ -316,17 +348,23 @@ pub fn elect( }) } -/// Performs equalize post-processing to the output of the election algorithm -/// This function mutates the input parameters, most noticeably it updates the exposure of -/// the elected candidates. +/// Performs equalize post-processing to the output of the election algorithm. This happens in +/// rounds. The number of rounds and the maximum diff-per-round tolerance can be tuned through input +/// parameters. +/// +/// No value is returned from the function and the `supports` parameter is updated. /// -/// No value is returned from the function and the `expo_map` parameter is updated. +/// * `assignments`: exactly the same is the output of phragmen. +/// * `supports`: mutable reference to s `SupportMap`. This parameter is updated. +/// * `tolerance`: maximum difference that can occur before an early quite happens. +/// * `iterations`: maximum number of iterations that will be processed. +/// * `stake_of`: something that can return the stake stake of a particular candidate or voter. pub fn equalize( mut assignments: Vec<(AccountId, Vec>)>, supports: &mut SupportMap, tolerance: ExtendedBalance, iterations: usize, - slashable_balance_of: FS, + stake_of: FS, ) where C: Convert + Convert, for <'r> FS: Fn(&'r AccountId) -> Balance, @@ -337,7 +375,7 @@ pub fn equalize( let mut max_diff = 0; for (voter, assignment) in assignments.iter_mut() { - let voter_budget = slashable_balance_of(&voter); + let voter_budget = stake_of(&voter); let diff = do_equalize::<_, _, C>( voter, @@ -355,6 +393,8 @@ pub fn equalize( } } +/// actually perform equalize. same interface is `equalize`. Just called in loops with a check for +/// maximum difference. fn do_equalize( voter: &AccountId, budget_balance: Balance, diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index afbc274239c78..c86b93286a86b 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -503,7 +503,7 @@ pub trait Trait: system::Trait { /// This must fit into a `u64` but is allowed to be sensibly lossy. /// TODO: #1377 /// The backward convert should be removed as the new Phragmen API returns ratio. - /// The post-processing needs it but will be moved to off-chain. + /// The post-processing needs it but will be moved to off-chain. TODO: #2908 type CurrencyToVote: Convert, u64> + Convert>; /// Some tokens minted. From 0434af5d56119298613b0d23206a20845d8b28ae Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 28 Aug 2019 12:40:31 +0200 Subject: [PATCH 03/11] New crate. --- Cargo.lock | 9 +++++++++ core/phragmen/Cargo.toml | 16 ++++++++++++++++ .../src/phragmen.rs => phragmen/src/lib.rs} | 10 +++++----- core/sr-primitives/src/lib.rs | 1 - srml/staking/Cargo.toml | 2 ++ srml/staking/src/benches.rs | 2 +- srml/staking/src/lib.rs | 2 +- srml/staking/src/tests.rs | 1 - 8 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 core/phragmen/Cargo.toml rename core/{sr-primitives/src/phragmen.rs => phragmen/src/lib.rs} (98%) diff --git a/Cargo.lock b/Cargo.lock index 2967d167da1f7..f237efc74a821 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2934,6 +2934,14 @@ name = "percent-encoding" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "phragmen" +version = "2.0.0" +dependencies = [ + "sr-primitives 2.0.0", + "sr-std 2.0.0", +] + [[package]] name = "pin-utils" version = "0.1.0-alpha.4" @@ -4154,6 +4162,7 @@ name = "srml-staking" version = "2.0.0" dependencies = [ "parity-scale-codec 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "phragmen 2.0.0", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/core/phragmen/Cargo.toml b/core/phragmen/Cargo.toml new file mode 100644 index 0000000000000..cc4080bcca4b5 --- /dev/null +++ b/core/phragmen/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "phragmen" +version = "2.0.0" +authors = ["Parity Technologies "] +edition = "2018" + +[dependencies] +sr-primitives = { path = "../../core/sr-primitives" } +rstd = { package = "sr-std", path = "../sr-std", default-features = false } + +[features] +default = ["std"] +std = [ + "rstd/std", + "sr-primitives/std", +] diff --git a/core/sr-primitives/src/phragmen.rs b/core/phragmen/src/lib.rs similarity index 98% rename from core/sr-primitives/src/phragmen.rs rename to core/phragmen/src/lib.rs index fcee4cab33324..bed42a9934951 100644 --- a/core/sr-primitives/src/phragmen.rs +++ b/core/phragmen/src/lib.rs @@ -32,8 +32,8 @@ //! https://research.web3.foundation/en/latest/polkadot/NPoS/4.%20Sequential%20Phragm%C3%A9n%E2%80%99s%20method/ use rstd::{prelude::*, collections::btree_map::BTreeMap}; -use crate::PerU128; -use crate::traits::{Zero, Convert, Member, SimpleArithmetic}; +use sr_primitives::PerU128; +use sr_primitives::traits::{Zero, Convert, Member, SimpleArithmetic}; /// Type used as the fraction. type Fraction = PerU128; @@ -186,14 +186,14 @@ pub fn elect( } }) .enumerate() - .map(|(idx, c)| { + .map(|(i, c)| { voters.push(Voter { who: c.who.clone(), - edges: vec![ Edge { who: c.who.clone(), candidate_index: idx, ..Default::default() }], + edges: vec![Edge { who: c.who.clone(), candidate_index: i, ..Default::default() }], budget: c.approval_stake, load: Fraction::zero(), }); - c_idx_cache.insert(c.who.clone(), idx); + c_idx_cache.insert(c.who.clone(), i); c }) .collect::>>() diff --git a/core/sr-primitives/src/lib.rs b/core/sr-primitives/src/lib.rs index 62c60eefd9708..41dac41f79b35 100644 --- a/core/sr-primitives/src/lib.rs +++ b/core/sr-primitives/src/lib.rs @@ -46,7 +46,6 @@ use traits::{SaturatedConversion, UniqueSaturatedInto, Saturating, Bounded, Chec pub mod testing; pub mod weights; -pub mod phragmen; pub mod traits; pub mod generic; diff --git a/srml/staking/Cargo.toml b/srml/staking/Cargo.toml index d985730912c56..fdfea32fb5dcc 100644 --- a/srml/staking/Cargo.toml +++ b/srml/staking/Cargo.toml @@ -10,6 +10,7 @@ safe-mix = { version = "1.0", default-features = false} codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } substrate-keyring = { path = "../../core/keyring", optional = true } rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false } +phragmen = { path = "../../core/phragmen", default-features = false } runtime_io = { package = "sr-io", path = "../../core/sr-io", default-features = false } sr-primitives = { path = "../../core/sr-primitives", default-features = false } sr-staking-primitives = { path = "../../core/sr-staking-primitives", default-features = false } @@ -34,6 +35,7 @@ std = [ "substrate-keyring", "codec/std", "rstd/std", + "phragmen/std", "runtime_io/std", "srml-support/std", "sr-primitives/std", diff --git a/srml/staking/src/benches.rs b/srml/staking/src/benches.rs index 3d898e22ea57a..aa76bab6409f0 100644 --- a/srml/staking/src/benches.rs +++ b/srml/staking/src/benches.rs @@ -27,7 +27,7 @@ use test::Bencher; use runtime_io::with_externalities; use mock::*; use super::*; -use sr_primitives::phragmen; +use phragmen; use rand::{self, Rng}; const VALIDATORS: u64 = 1000; diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index c86b93286a86b..4020e1f32fde4 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -277,7 +277,7 @@ use sr_primitives::traits::{ Convert, Zero, One, StaticLookup, CheckedSub, Saturating, Bounded, SimpleArithmetic, SaturatedConversion, }; -use sr_primitives::phragmen::{elect, equalize, Support, SupportMap, ExtendedBalance, ACCURACY}; +use phragmen::{elect, equalize, Support, SupportMap, ExtendedBalance, ACCURACY}; use sr_staking_primitives::{ SessionIndex, CurrentElectedSet, offence::{OnOffenceHandler, OffenceDetails, Offence, ReportOffence}, diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index ed727699c8bf4..6e0eaf3b508bf 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -18,7 +18,6 @@ use super::*; use runtime_io::with_externalities; -use sr_primitives::phragmen; use sr_primitives::traits::OnInitialize; use sr_staking_primitives::offence::{OffenceDetails, OnOffenceHandler}; use srml_support::{assert_ok, assert_noop, assert_eq_uvec, EnumerableStorageMap}; From f39f86ebb64e30e6c3752b31be12ecf735c159ef Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 28 Aug 2019 12:41:44 +0200 Subject: [PATCH 04/11] Update lock. --- Cargo.lock | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45d8bbf4b04ed..d0a9be5c83f2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2930,18 +2930,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -<<<<<<< HEAD +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "phragmen" version = "2.0.0" dependencies = [ "sr-primitives 2.0.0", "sr-std 2.0.0", ] -======= -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" ->>>>>>> e3f57ff9c86866a040e2c1f5dbe0b994b103e5cd [[package]] name = "pin-utils" @@ -4162,12 +4161,8 @@ dependencies = [ name = "srml-staking" version = "2.0.0" dependencies = [ -<<<<<<< HEAD - "parity-scale-codec 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "phragmen 2.0.0", -======= "parity-scale-codec 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ->>>>>>> e3f57ff9c86866a040e2c1f5dbe0b994b103e5cd + "phragmen 2.0.0", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", From 6f150cc402771d7e75f6bdc14264cc2b957f9cef Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 28 Aug 2019 14:41:46 +0200 Subject: [PATCH 05/11] Fix dependency. --- core/phragmen/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/phragmen/Cargo.toml b/core/phragmen/Cargo.toml index cc4080bcca4b5..53de35bd07fce 100644 --- a/core/phragmen/Cargo.toml +++ b/core/phragmen/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Parity Technologies "] edition = "2018" [dependencies] -sr-primitives = { path = "../../core/sr-primitives" } +sr-primitives = { path = "../sr-primitives", default-features = false } rstd = { package = "sr-std", path = "../sr-std", default-features = false } [features] From 68ea5c8e90b28f567a536966e3d230c6385d6ad1 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 28 Aug 2019 14:46:30 +0200 Subject: [PATCH 06/11] Fix build. --- core/phragmen/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/phragmen/src/lib.rs b/core/phragmen/src/lib.rs index bed42a9934951..f6951bf5fa66e 100644 --- a/core/phragmen/src/lib.rs +++ b/core/phragmen/src/lib.rs @@ -31,6 +31,8 @@ //! Further details: //! https://research.web3.foundation/en/latest/polkadot/NPoS/4.%20Sequential%20Phragm%C3%A9n%E2%80%99s%20method/ +#![cfg_attr(not(feature = "std"), no_std)] + use rstd::{prelude::*, collections::btree_map::BTreeMap}; use sr_primitives::PerU128; use sr_primitives::traits::{Zero, Convert, Member, SimpleArithmetic}; From 7d7b597be0a7af41a2cb1811a0e8fe0c180c77e1 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 28 Aug 2019 16:16:40 +0200 Subject: [PATCH 07/11] Add basic testing and truth-value implementation with float types --- Cargo.lock | 1 + core/phragmen/Cargo.toml | 3 + core/phragmen/src/lib.rs | 229 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 233 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index d0a9be5c83f2b..bb243f846cba4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2940,6 +2940,7 @@ version = "2.0.0" dependencies = [ "sr-primitives 2.0.0", "sr-std 2.0.0", + "srml-support 2.0.0", ] [[package]] diff --git a/core/phragmen/Cargo.toml b/core/phragmen/Cargo.toml index 53de35bd07fce..c4d1518452c72 100644 --- a/core/phragmen/Cargo.toml +++ b/core/phragmen/Cargo.toml @@ -8,6 +8,9 @@ edition = "2018" sr-primitives = { path = "../sr-primitives", default-features = false } rstd = { package = "sr-std", path = "../sr-std", default-features = false } +[dev-dependencies] +support = { package = "srml-support", path = "../../srml/support" } + [features] default = ["std"] std = [ diff --git a/core/phragmen/src/lib.rs b/core/phragmen/src/lib.rs index f6951bf5fa66e..432658881a333 100644 --- a/core/phragmen/src/lib.rs +++ b/core/phragmen/src/lib.rs @@ -495,3 +495,232 @@ fn do_equalize( difference } + +#[cfg(test)] +mod tests { + use super::{elect, ACCURACY, PhragmenResult}; + use sr_primitives::traits::{Convert, Member, SaturatedConversion}; + use rstd::collections::btree_map::BTreeMap; + use support::assert_eq_uvec; + + pub struct C; + impl Convert for C { + fn convert(x: u64) -> u64 { x } + } + impl Convert for C { + fn convert(x: u128) -> u64 { x.saturated_into() } + } + + #[derive(Default, Debug)] + struct _Candidate { + who: AccountId, + score: f64, + approval_stake: f64, + elected: bool, + } + + #[derive(Default, Debug)] + struct _Voter { + who: AccountId, + edges: Vec<_Edge>, + budget: f64, + load: f64, + } + + #[derive(Default, Debug)] + struct _Edge { + who: AccountId, + load: f64, + candidate_index: usize, + } + + type _PhragmenAssignment = (AccountId, f64); + + #[derive(Debug)] + pub struct _PhragmenResult { + pub winners: Vec, + pub assignments: Vec<(AccountId, Vec<_PhragmenAssignment>)> + } + + pub fn elect_poc( + candidate_count: usize, + minimum_candidate_count: usize, + initial_candidates: Vec, + initial_voters: Vec<(AccountId, Vec)>, + stake_of: FS, + self_vote: bool, + ) -> Option<_PhragmenResult> where + AccountId: Default + Ord + Member + Copy, + for <'r> FS: Fn(&'r AccountId) -> u64, + { + let mut elected_candidates: Vec; + let mut assigned: Vec<(AccountId, Vec<_PhragmenAssignment>)>; + let mut c_idx_cache = BTreeMap::::new(); + let num_voters = initial_candidates.len() + initial_voters.len(); + let mut voters: Vec<_Voter> = Vec::with_capacity(num_voters); + + let mut candidates = if self_vote { + initial_candidates.into_iter().map(|who| { + let stake = stake_of(&who) as f64; + _Candidate { who, approval_stake: stake, ..Default::default() } + }) + .filter_map(|c| { + if c.approval_stake == 0f64 { + None + } else { + Some(c) + } + }) + .enumerate() + .map(|(i, c)| { + let who = c.who; + voters.push(_Voter { + who: who.clone(), + edges: vec![ + _Edge { who: who.clone(), candidate_index: i, ..Default::default() } + ], + budget: c.approval_stake, + load: 0f64, + }); + c_idx_cache.insert(c.who.clone(), i); + c + }) + .collect::>>() + } else { + initial_candidates.into_iter() + .enumerate() + .map(|(idx, who)| { + c_idx_cache.insert(who.clone(), idx); + _Candidate { who, ..Default::default() } + }) + .collect::>>() + }; + + if candidates.len() < minimum_candidate_count { + return None; + } + + voters.extend(initial_voters.into_iter().map(|(who, votes)| { + let voter_stake = stake_of(&who) as f64; + let mut edges: Vec<_Edge> = Vec::with_capacity(votes.len()); + for v in votes { + if let Some(idx) = c_idx_cache.get(&v) { + candidates[*idx].approval_stake = candidates[*idx].approval_stake + voter_stake; + edges.push( + _Edge { who: v.clone(), candidate_index: *idx, ..Default::default() } + ); + } + } + _Voter { + who, + edges: edges, + budget: voter_stake, + load: 0f64, + } + })); + + let to_elect = candidate_count.min(candidates.len()); + elected_candidates = Vec::with_capacity(candidate_count); + assigned = Vec::with_capacity(candidate_count); + + for _round in 0..to_elect { + for c in &mut candidates { + if !c.elected { + c.score = 1.0 / c.approval_stake; + } + } + for n in &voters { + for e in &n.edges { + let c = &mut candidates[e.candidate_index]; + if !c.elected && !(c.approval_stake == 0f64) { + c.score += n.budget * n.load / c.approval_stake; + } + } + } + + if let Some(winner) = candidates + .iter_mut() + .filter(|c| !c.elected) + .min_by(|x, y| x.score.partial_cmp(&y.score).unwrap_or(rstd::cmp::Ordering::Equal)) + { + winner.elected = true; + for n in &mut voters { + for e in &mut n.edges { + if e.who == winner.who { + e.load = winner.score - n.load; + n.load = winner.score; + } + } + } + + elected_candidates.push(winner.who.clone()); + } else { + break + } + } + + for n in &mut voters { + let mut assignment = (n.who.clone(), vec![]); + for e in &mut n.edges { + if let Some(c) = elected_candidates.iter().cloned().find(|c| *c == e.who) { + if c != n.who { + let ratio = e.load / n.load; + assignment.1.push((e.who.clone(), ratio)); + } + } + } + assigned.push(assignment); + } + + Some(_PhragmenResult { + winners: elected_candidates, + assignments: assigned, + }) + } + + #[test] + fn float_poc_works() { + let candidates = vec![1, 2, 3]; + let voters = vec![ + (10, vec![1, 2]), + (20, vec![1, 3]), + (30, vec![2, 3]), + ]; + let stake_of = |x: &u64| { if *x >= 10 { *x } else { 0 }}; + let _PhragmenResult { winners, assignments } = + elect_poc(2, 2, candidates, voters, stake_of, false).unwrap(); + + assert_eq_uvec!(winners, vec![2, 3]); + assert_eq_uvec!( + assignments, + vec![ + (10, vec![(2, 1.0)]), + (20, vec![(3, 1.0)]), + (30, vec![(2, 0.5), (3, 0.5)]) + ] + ); + } + + #[test] + fn phragmen_works() { + let candidates = vec![1, 2, 3]; + let voters = vec![ + (10, vec![1, 2]), + (20, vec![1, 3]), + (30, vec![2, 3]), + ]; + let stake_of = |x: &u64| { if *x >= 10 { *x } else { 0 }}; + let PhragmenResult { winners, assignments } = + elect::<_, _, _, C>(2, 2, candidates, voters, stake_of, false).unwrap(); + + assert_eq_uvec!(winners, vec![2, 3]); + assert_eq_uvec!( + assignments, + vec![ + (10, vec![(2, ACCURACY)]), + (20, vec![(3, ACCURACY)]), + (30, vec![(2, ACCURACY/2), (3, ACCURACY/2)]) + ] + ); + } +} From da47039ed3eaf82fe3fbbb6802233ce7b9b0e2f4 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Wed, 28 Aug 2019 17:00:07 +0200 Subject: [PATCH 08/11] Update srml/staking/src/lib.rs --- srml/staking/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 4020e1f32fde4..d0c60bfa46eeb 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -1324,7 +1324,7 @@ impl Module { // This might reasonably saturate and we cannot do much about it. The sum of // someone's stake might exceed the balance type if they have the maximum amount // of balance and receive some support. This is super unlikely to happen, yet - // we simulate it it in some tests. + // we simulate it in some tests. total: to_balance(s.total), others: s.others .into_iter() From 97bb107dd837ecdc0a30f2bf94ef9ce1e21f8a97 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 29 Aug 2019 10:35:51 +0200 Subject: [PATCH 09/11] Nits. --- Cargo.lock | 20 ++++++++++---------- core/phragmen/Cargo.toml | 2 +- core/phragmen/src/lib.rs | 24 ++++++------------------ srml/staking/Cargo.toml | 2 +- srml/staking/src/benches.rs | 5 +++-- 5 files changed, 21 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb243f846cba4..5e025d634ca04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2934,15 +2934,6 @@ name = "percent-encoding" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "phragmen" -version = "2.0.0" -dependencies = [ - "sr-primitives 2.0.0", - "sr-std 2.0.0", - "srml-support 2.0.0", -] - [[package]] name = "pin-utils" version = "0.1.0-alpha.4" @@ -4163,7 +4154,6 @@ name = "srml-staking" version = "2.0.0" dependencies = [ "parity-scale-codec 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "phragmen 2.0.0", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", @@ -4178,6 +4168,7 @@ dependencies = [ "srml-system 2.0.0", "srml-timestamp 2.0.0", "substrate-keyring 2.0.0", + "substrate-phragmen 2.0.0", "substrate-primitives 2.0.0", ] @@ -4940,6 +4931,15 @@ dependencies = [ "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "substrate-phragmen" +version = "2.0.0" +dependencies = [ + "sr-primitives 2.0.0", + "sr-std 2.0.0", + "srml-support 2.0.0", +] + [[package]] name = "substrate-primitives" version = "2.0.0" diff --git a/core/phragmen/Cargo.toml b/core/phragmen/Cargo.toml index c4d1518452c72..335bddb401034 100644 --- a/core/phragmen/Cargo.toml +++ b/core/phragmen/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "phragmen" +name = "substrate-phragmen" version = "2.0.0" authors = ["Parity Technologies "] edition = "2018" diff --git a/core/phragmen/src/lib.rs b/core/phragmen/src/lib.rs index 432658881a333..459254370bc7f 100644 --- a/core/phragmen/src/lib.rs +++ b/core/phragmen/src/lib.rs @@ -156,7 +156,7 @@ pub fn elect( ) -> Option> where AccountId: Default + Ord + Member, Balance: Default + Copy + SimpleArithmetic, - for <'r> FS: Fn(&'r AccountId) -> Balance, + for<'r> FS: Fn(&'r AccountId) -> Balance, C: Convert + Convert, { let to_votes = |b: Balance| @@ -180,13 +180,7 @@ pub fn elect( let stake = stake_of(&who); Candidate { who, approval_stake: to_votes(stake), ..Default::default() } }) - .filter_map(|c| { - if c.approval_stake.is_zero() { - None - } else { - Some(c) - } - }) + .filter(|c| !c.approval_stake.is_zero()) .enumerate() .map(|(i, c)| { voters.push(Voter { @@ -369,7 +363,7 @@ pub fn equalize( stake_of: FS, ) where C: Convert + Convert, - for <'r> FS: Fn(&'r AccountId) -> Balance, + for<'r> FS: Fn(&'r AccountId) -> Balance, AccountId: Ord + Clone, { // prepare the data for equalise @@ -384,7 +378,7 @@ pub fn equalize( voter_budget, assignment, supports, - tolerance + tolerance, ); if diff > max_diff { max_diff = diff; } } @@ -551,7 +545,7 @@ mod tests { self_vote: bool, ) -> Option<_PhragmenResult> where AccountId: Default + Ord + Member + Copy, - for <'r> FS: Fn(&'r AccountId) -> u64, + for<'r> FS: Fn(&'r AccountId) -> u64, { let mut elected_candidates: Vec; let mut assigned: Vec<(AccountId, Vec<_PhragmenAssignment>)>; @@ -564,13 +558,7 @@ mod tests { let stake = stake_of(&who) as f64; _Candidate { who, approval_stake: stake, ..Default::default() } }) - .filter_map(|c| { - if c.approval_stake == 0f64 { - None - } else { - Some(c) - } - }) + .filter(|c| c.approval_stake != 0f64) .enumerate() .map(|(i, c)| { let who = c.who; diff --git a/srml/staking/Cargo.toml b/srml/staking/Cargo.toml index fdfea32fb5dcc..a29b4aa4b9657 100644 --- a/srml/staking/Cargo.toml +++ b/srml/staking/Cargo.toml @@ -10,7 +10,7 @@ safe-mix = { version = "1.0", default-features = false} codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } substrate-keyring = { path = "../../core/keyring", optional = true } rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false } -phragmen = { path = "../../core/phragmen", default-features = false } +phragmen = { package = "substrate-phragmen", path = "../../core/phragmen", default-features = false } runtime_io = { package = "sr-io", path = "../../core/sr-io", default-features = false } sr-primitives = { path = "../../core/sr-primitives", default-features = false } sr-staking-primitives = { path = "../../core/sr-staking-primitives", default-features = false } diff --git a/srml/staking/src/benches.rs b/srml/staking/src/benches.rs index aa76bab6409f0..2c9c62cb745a8 100644 --- a/srml/staking/src/benches.rs +++ b/srml/staking/src/benches.rs @@ -36,6 +36,8 @@ const EDGES: u64 = 2; const TO_ELECT: usize = 100; const STAKE: u64 = 1000; +type C = <::CurrencyToVote as Convert>; + fn do_phragmen( b: &mut Bencher, num_vals: u64, @@ -86,8 +88,7 @@ fn do_phragmen( let elected_stashes = r.winners; let mut assignments = r.assignments; - let to_votes = |b: Balance| - <::CurrencyToVote as Convert>::convert(b) as ExtendedBalance; + let to_votes = |b: Balance| C::convert(b) as ExtendedBalance; let ratio_of = |b, r: ExtendedBalance| r.saturating_mul(to_votes(b)) / ACCURACY; // Initialize the support of each candidate. From a7902f7e95b62a8027264022e8acd32b16a028c3 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 29 Aug 2019 10:36:18 +0200 Subject: [PATCH 10/11] Bump. --- node/runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index 0360827e9a6dd..a36801c44b85d 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -80,7 +80,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. spec_version: 154, - impl_version: 155, + impl_version: 156, apis: RUNTIME_API_VERSIONS, }; From fe23b4ae6bf14909cb0f90f78118f165de5cbd10 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 29 Aug 2019 10:48:35 +0200 Subject: [PATCH 11/11] Fix benchmarks. --- srml/staking/src/benches.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/srml/staking/src/benches.rs b/srml/staking/src/benches.rs index 2c9c62cb745a8..e9c3667984af1 100644 --- a/srml/staking/src/benches.rs +++ b/srml/staking/src/benches.rs @@ -36,7 +36,7 @@ const EDGES: u64 = 2; const TO_ELECT: usize = 100; const STAKE: u64 = 1000; -type C = <::CurrencyToVote as Convert>; +type C = ::CurrencyToVote; fn do_phragmen( b: &mut Bencher, @@ -45,7 +45,7 @@ fn do_phragmen( count: usize, votes_per: u64, eq_iters: usize, - eq_tolerance: u128, + _eq_tolerance: u128, ) { with_externalities(&mut ExtBuilder::default().nominate(false).build(), || { assert!(num_vals > votes_per); @@ -88,7 +88,8 @@ fn do_phragmen( let elected_stashes = r.winners; let mut assignments = r.assignments; - let to_votes = |b: Balance| C::convert(b) as ExtendedBalance; + let to_votes = |b: Balance| + as Convert>::convert(b) as ExtendedBalance; let ratio_of = |b, r: ExtendedBalance| r.saturating_mul(to_votes(b)) / ACCURACY; // Initialize the support of each candidate.