Decouple Phragmen from Staking. (#3498)

* Move phragmen to primitives

* Improved docs

* New crate.

* Update lock.

* Fix dependency.

* Fix build.

* Add basic testing and truth-value implementation with float types

* Update srml/staking/src/lib.rs

* Nits.

* Bump.

* Fix benchmarks.
This commit is contained in:
Kian Paimani
2019-08-29 11:07:49 +02:00
committed by GitHub
parent 98f64b6b93
commit f830db9642
11 changed files with 846 additions and 514 deletions
+10
View File
@@ -4169,6 +4169,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",
]
@@ -4931,6 +4932,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"
+19
View File
@@ -0,0 +1,19 @@
[package]
name = "substrate-phragmen"
version = "2.0.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
[dependencies]
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 = [
"rstd/std",
"sr-primitives/std",
]
+714
View File
@@ -0,0 +1,714 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! 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/
#![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};
/// Type used as the fraction.
type Fraction = PerU128;
/// 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;
/// A candidate entity for phragmen election.
#[derive(Clone, Default)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct Candidate<AccountId> {
/// Identifier.
pub who: AccountId,
/// Intermediary value used to sort candidates.
pub score: Fraction,
/// Sum of the stake of this candidate based on received votes.
approval_stake: ExtendedBalance,
/// Flag for being elected.
elected: bool,
}
/// A voter entity.
#[derive(Clone, Default)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct Voter<AccountId> {
/// Identifier.
who: AccountId,
/// List of candidates proposed by this voter.
edges: Vec<Edge<AccountId>>,
/// The stake of this voter.
budget: ExtendedBalance,
/// Incremented each time a candidate that this voter voted for has been elected.
load: Fraction,
}
/// A candidate being backed by a voter.
#[derive(Clone, Default)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct Edge<AccountId> {
/// Identifier.
who: AccountId,
/// Load of this vote.
load: Fraction,
/// Index of the candidate stored in the 'candidates' vector.
candidate_index: usize,
}
/// Means a particular `AccountId` was backed by a ratio of `ExtendedBalance / ACCURACY`.
pub type PhragmenAssignment<AccountId> = (AccountId, ExtendedBalance);
/// Final result of the phragmen election.
pub struct PhragmenResult<AccountId> {
/// Just winners.
pub winners: Vec<AccountId>,
/// 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<PhragmenAssignment<AccountId>>)>
}
/// 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<AccountId> {
/// The amount of support as the effect of self-vote.
pub own: ExtendedBalance,
/// Total support.
pub total: ExtendedBalance,
/// Support from voters.
pub others: Vec<PhragmenAssignment<AccountId>>,
}
/// A linkage from a candidate and its [`Support`].
pub type SupportMap<A> = BTreeMap<A, Support<A>>;
/// Perform election based on Phragmén algorithm.
///
/// 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<AccountId, Balance, FS, C>(
candidate_count: usize,
minimum_candidate_count: usize,
initial_candidates: Vec<AccountId>,
initial_voters: Vec<(AccountId, Vec<AccountId>)>,
stake_of: FS,
self_vote: bool,
) -> Option<PhragmenResult<AccountId>> where
AccountId: Default + Ord + Member,
Balance: Default + Copy + SimpleArithmetic,
for<'r> FS: Fn(&'r AccountId) -> Balance,
C: Convert<Balance, u64> + Convert<u128, Balance>,
{
let to_votes = |b: Balance|
<C as Convert<Balance, u64>>::convert(b) as ExtendedBalance;
// return structures
let mut elected_candidates: Vec<AccountId>;
let mut assigned: Vec<(AccountId, Vec<PhragmenAssignment<AccountId>>)>;
// used to cache and access candidates index.
let mut c_idx_cache = BTreeMap::<AccountId, usize>::new();
// voters list.
let num_voters = initial_candidates.len() + initial_voters.len();
let mut voters: Vec<Voter<AccountId>> = 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 = stake_of(&who);
Candidate { who, approval_stake: to_votes(stake), ..Default::default() }
})
.filter(|c| !c.approval_stake.is_zero())
.enumerate()
.map(|(i, c)| {
voters.push(Voter {
who: c.who.clone(),
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(), i);
c
})
.collect::<Vec<Candidate<AccountId>>>()
} else {
// no self vote. just collect.
initial_candidates.into_iter()
.enumerate()
.map(|(idx, who)| {
c_idx_cache.insert(who.clone(), idx);
Candidate { who, ..Default::default() }
})
.collect::<Vec<Candidate<AccountId>>>()
};
// 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<Edge<AccountId>> = 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: v.clone(), candidate_index: *idx, ..Default::default() });
} // else {} would be wrong votes. We don't really care about it.
}
Voter {
who,
edges: edges,
budget: to_votes(voter_stake),
load: Fraction::zero(),
}
}));
// 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 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
// 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)
}
}
};
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::<ExtendedBalance>();
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 {
assignment.1[i%l].1 =
assignment.1[i%l].1
.saturating_add(1);
}
assigned.push(assignment);
}
}
Some(PhragmenResult {
winners: elected_candidates,
assignments: assigned,
})
}
/// 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.
///
/// * `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<Balance, AccountId, C, FS>(
mut assignments: Vec<(AccountId, Vec<PhragmenAssignment<AccountId>>)>,
supports: &mut SupportMap<AccountId>,
tolerance: ExtendedBalance,
iterations: usize,
stake_of: FS,
) where
C: Convert<Balance, u64> + Convert<u128, Balance>,
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;
for (voter, assignment) in assignments.iter_mut() {
let voter_budget = stake_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;
}
}
}
/// actually perform equalize. same interface is `equalize`. Just called in loops with a check for
/// maximum difference.
fn do_equalize<Balance, AccountId, C>(
voter: &AccountId,
budget_balance: Balance,
elected_edges: &mut Vec<(AccountId, ExtendedBalance)>,
support_map: &mut SupportMap<AccountId>,
tolerance: ExtendedBalance
) -> ExtendedBalance where
C: Convert<Balance, u64> + Convert<u128, Balance>,
AccountId: Ord + Clone,
{
let to_votes = |b: Balance|
<C as Convert<Balance, u64>>::convert(b) as ExtendedBalance;
let budget = to_votes(budget_balance);
// 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.1));
let backed_stakes_iter = elected_edges
.iter()
.filter_map(|e| support_map.get(&e.0))
.map(|e| e.total);
let backing_backed_stake = elected_edges
.iter()
.filter(|e| e.1 > 0)
.filter_map(|e| support_map.get(&e.0))
.map(|e| e.total)
.collect::<Vec<ExtendedBalance>>();
let mut difference;
if backing_backed_stake.len() > 0 {
let max_stake = backing_backed_stake
.iter()
.max()
.expect("vector with positive length will have a max; qed");
let min_stake = backed_stakes_iter
.min()
.expect("iterator with positive length will have a min; qed");
difference = max_stake.saturating_sub(min_stake);
difference = difference.saturating_add(budget.saturating_sub(stake_used));
if difference < tolerance {
return difference;
}
} else {
difference = budget;
}
// Undo updates to support
elected_edges.iter_mut().for_each(|e| {
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.1 = 0;
});
elected_edges.sort_unstable_by_key(|e|
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(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 {
last_index = idx.checked_sub(1).unwrap_or(0);
return
}
cumulative_stake = cumulative_stake.saturating_add(stake);
}
});
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(support) = support_map.get_mut(&e.0) {
e.1 = (excess / split_ways as ExtendedBalance)
.saturating_add(last_stake)
.saturating_sub(support.total);
support.total = support.total.saturating_add(e.1);
support.others.push((voter.clone(), e.1));
}
});
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<u64, u64> for C {
fn convert(x: u64) -> u64 { x }
}
impl Convert<u128, u64> for C {
fn convert(x: u128) -> u64 { x.saturated_into() }
}
#[derive(Default, Debug)]
struct _Candidate<AccountId> {
who: AccountId,
score: f64,
approval_stake: f64,
elected: bool,
}
#[derive(Default, Debug)]
struct _Voter<AccountId> {
who: AccountId,
edges: Vec<_Edge<AccountId>>,
budget: f64,
load: f64,
}
#[derive(Default, Debug)]
struct _Edge<AccountId> {
who: AccountId,
load: f64,
candidate_index: usize,
}
type _PhragmenAssignment<AccountId> = (AccountId, f64);
#[derive(Debug)]
pub struct _PhragmenResult<AccountId> {
pub winners: Vec<AccountId>,
pub assignments: Vec<(AccountId, Vec<_PhragmenAssignment<AccountId>>)>
}
pub fn elect_poc<AccountId, FS>(
candidate_count: usize,
minimum_candidate_count: usize,
initial_candidates: Vec<AccountId>,
initial_voters: Vec<(AccountId, Vec<AccountId>)>,
stake_of: FS,
self_vote: bool,
) -> Option<_PhragmenResult<AccountId>> where
AccountId: Default + Ord + Member + Copy,
for<'r> FS: Fn(&'r AccountId) -> u64,
{
let mut elected_candidates: Vec<AccountId>;
let mut assigned: Vec<(AccountId, Vec<_PhragmenAssignment<AccountId>>)>;
let mut c_idx_cache = BTreeMap::<AccountId, usize>::new();
let num_voters = initial_candidates.len() + initial_voters.len();
let mut voters: Vec<_Voter<AccountId>> = 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(|c| c.approval_stake != 0f64)
.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::<Vec<_Candidate<AccountId>>>()
} else {
initial_candidates.into_iter()
.enumerate()
.map(|(idx, who)| {
c_idx_cache.insert(who.clone(), idx);
_Candidate { who, ..Default::default() }
})
.collect::<Vec<_Candidate<AccountId>>>()
};
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<AccountId>> = 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)])
]
);
}
}
+1 -1
View File
@@ -40,13 +40,13 @@ 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 traits;
use traits::{SaturatedConversion, UniqueSaturatedInto, Saturating, Bounded, CheckedSub, CheckedAdd};
pub mod generic;
pub mod transaction_validity;
+1 -1
View File
@@ -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,
};
+2
View File
@@ -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 = { 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 }
@@ -34,6 +35,7 @@ std = [
"substrate-keyring",
"codec/std",
"rstd/std",
"phragmen/std",
"runtime_io/std",
"srml-support/std",
"sr-primitives/std",
+36 -45
View File
@@ -27,6 +27,7 @@ use test::Bencher;
use runtime_io::with_externalities;
use mock::*;
use super::*;
use phragmen;
use rand::{self, Rng};
const VALIDATORS: u64 = 1000;
@@ -35,6 +36,8 @@ const EDGES: u64 = 2;
const TO_ELECT: usize = 100;
const STAKE: u64 = 1000;
type C<T> = <T as Trait>::CurrencyToVote;
fn do_phragmen(
b: &mut Bencher,
num_vals: u64,
@@ -42,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);
@@ -71,67 +74,55 @@ fn do_phragmen(
});
b.iter(|| {
let r = phragmen::elect::<Test, _, _, _>(
let r = phragmen::elect::<_, _, _, <Test as Trait>::CurrencyToVote>(
count,
1_usize,
<Validators<Test>>::enumerate(),
<Nominators<Test>>::enumerate(),
Staking::slashable_balance_of
<Validators<Test>>::enumerate().map(|(who, _)| who).collect::<Vec<u64>>(),
<Nominators<Test>>::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|
<<mock::Test as Trait>::CurrencyToVote as Convert<ExtendedBalance, Balance>>::convert(b);
let to_votes = |b: Balance|
<<mock::Test as Trait>::CurrencyToVote as Convert<Balance, u64>>::convert(b) as ExtendedBalance;
let ratio_of = |b, p| (p as ExtendedBalance).saturating_mul(to_votes(b)) / ACCURACY;
<C<Test> as Convert<Balance, AccountId>>::convert(b) as ExtendedBalance;
let ratio_of = |b, r: ExtendedBalance| r.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::<Vec<Assignment<Test>>>()
)).collect::<Vec<(AccountId, Balance, Vec<Assignment<Test>>)>>();
let mut exposures = <ExpoMap<Test>>::new();
// Initialize the support of each candidate.
let mut supports = <SupportMap<u64>>::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::<Vec<(AccountId, ExtendedBalance, ExtendedBalance)>>()
))
.collect::<Vec<(
AccountId,
Balance,
Vec<(AccountId, ExtendedBalance, ExtendedBalance)>
)>>();
equalize::<Test>(&mut assignments_with_votes, &mut exposures, eq_tolerance, eq_iters);
let tolerance = 0_u128;
let iterations = 2_usize;
phragmen::equalize::<_, _, <Test as Trait>::CurrencyToVote, _>(
assignments,
&mut supports,
tolerance,
iterations,
Staking::slashable_balance_of,
);
}
})
})
+55 -64
View File
@@ -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 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<T> =
<<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::NegativeImbalance;
type MomentOf<T>= <<T as Trait>::Time as Time>::Moment;
type RawAssignment<T> = (<T as system::Trait>::AccountId, ExtendedBalance);
type Assignment<T> = (<T as system::Trait>::AccountId, ExtendedBalance, BalanceOf<T>);
type ExpoMap<T> = BTreeMap<
<T as system::Trait>::AccountId,
Exposure<<T as system::Trait>::AccountId, BalanceOf<T>>
>;
/// Means for interacting with a specialized version of the `session` trait.
///
/// This is needed because `Staking` sets the `ValidatorIdOf` of the `session::Trait`
@@ -512,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<BalanceOf<Self>, u64> + Convert<u128, BalanceOf<Self>>;
/// Some tokens minted.
@@ -1254,17 +1245,18 @@ impl<T: Trait> Module<T> {
///
/// Returns the new `SlotStake` value and a set of newly selected _stash_ IDs.
fn select_validators() -> (BalanceOf<T>, Option<Vec<T::AccountId>>) {
let maybe_elected_set = elect::<T, _, _, _>(
let maybe_phragmen_result = elect::<_, _, _, T::CurrencyToVote>(
Self::validator_count() as usize,
Self::minimum_validator_count().max(1) as usize,
<Validators<T>>::enumerate(),
<Nominators<T>>::enumerate(),
<Validators<T>>::enumerate().map(|(who, _)| who).collect::<Vec<T::AccountId>>(),
<Nominators<T>>::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<T: Trait> Module<T> {
// 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<u64>` is also safe.
let ratio_of = |b, p| (p as ExtendedBalance).saturating_mul(to_votes(b)) / ACCURACY;
let ratio_of = |b, r: ExtendedBalance| r.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::<Vec<Assignment<T>>>()
)).collect::<Vec<(T::AccountId, BalanceOf<T>, Vec<Assignment<T>>)>>();
// update elected candidate exposures.
let mut exposures = <ExpoMap<T>>::new();
// Initialize the support of each candidate.
let mut supports = <SupportMap<T::AccountId>>::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::<Vec<(T::AccountId, ExtendedBalance, ExtendedBalance)>>()
))
.collect::<Vec<(
T::AccountId,
BalanceOf<T>,
Vec<(T::AccountId, ExtendedBalance, ExtendedBalance)>
)>>();
equalize::<T>(&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<T: Trait> Module<T> {
// Populate Stakers and figure out the minimum stake behind a slot.
let mut slot_stake = BalanceOf::<T>::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 in some tests.
total: to_balance(s.total),
others: s.others
.into_iter()
.map(|(who, value)| IndividualExposure { who, value: to_balance(value) })
.collect::<Vec<IndividualExposure<_, _>>>(),
};
if exposure.total < slot_stake {
slot_stake = exposure.total;
}
<Stakers<T>>::insert(c.clone(), e.clone());
<Stakers<T>>::insert(c.clone(), exposure.clone());
}
// Update slot stake.
+2 -4
View File
@@ -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<u64, u64> for CurrencyToVoteHandler {
fn convert(x: u64) -> u64 { x }
}
impl Convert<u128, u64> for CurrencyToVoteHandler {
fn convert(x: u128) -> u64 {
x as u64
}
fn convert(x: u128) -> u64 { x.saturated_into() }
}
thread_local! {
-393
View File
@@ -1,393 +0,0 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! 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};
type Fraction = PerU128;
/// Wrapper around the type used as the _safe_ wrapper around a `balance`.
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.
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.
pub const ACCURACY: ExtendedBalance = u32::max_value() as ExtendedBalance + 1;
/// Wrapper around validation candidates some metadata.
#[derive(Clone, Default)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct Candidate<AccountId> {
/// The validator's account
pub who: AccountId,
/// Intermediary value used to sort candidates.
pub score: Fraction,
/// Accumulator 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 nominator for a group of validators.
#[derive(Clone, Default)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct Nominator<AccountId> {
/// The nominator's account.
who: AccountId,
/// List of validators proposed by this nominator.
edges: Vec<Edge<AccountId>>,
/// the stake amount proposed by the nominator as a part of the vote.
budget: ExtendedBalance,
/// Incremented each time a nominee that this nominator voted for has been elected.
load: Fraction,
}
/// Wrapper around a nominator vote and the load of that vote.
#[derive(Clone, Default)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct Edge<AccountId> {
/// Account being voted for
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,
}
/// 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.
///
/// 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<T: Trait + 'static, FV, FN, FS>(
validator_count: usize,
minimum_validator_count: usize,
validator_iter: FV,
nominator_iter: FN,
slashable_balance_of: FS,
) -> Option<(Vec<T::AccountId>, Vec<(T::AccountId, Vec<RawAssignment<T>>)>)> where
FV: Iterator<Item=(T::AccountId, ValidatorPrefs<BalanceOf<T>>)>,
FN: Iterator<Item=(T::AccountId, Vec<T::AccountId>)>,
for <'r> FS: Fn(&'r T::AccountId) -> BalanceOf<T>,
{
let to_votes = |b: BalanceOf<T>| <T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(b) as ExtendedBalance;
// return structures
let mut elected_candidates: Vec<T::AccountId>;
let mut assigned: Vec<(T::AccountId, Vec<RawAssignment<T>>)>;
let mut c_idx_cache = BTreeMap::<T::AccountId, usize>::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<Nominator<T::AccountId>> =
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::<Vec<Candidate<T::AccountId>>>();
// 2- Collect the nominators 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<Edge<T::AccountId>> = 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));
edges.push(Edge { who: n.clone(), candidate_index: *idx, ..Default::default() });
} // else {} would be wrong votes. We don't really care about it.
}
Nominator {
who,
edges: edges,
budget: to_votes(nominator_stake),
load: Fraction::zero(),
}
}));
// 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);
}
}
// Loop 2: increment score.
for n in &nominators {
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 nominator and edge load
winner.elected = true;
for n in &mut nominators {
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 nominators
for n in &mut nominators {
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 {
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));
}
}
}
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.
let vote_count = assignment.1.len() as ExtendedBalance;
let l = assignment.1.len();
let sum = assignment.1.iter().map(|a| a.1).sum::<ExtendedBalance>();
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 nominator (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);
}
}
} else {
// if we have less than minimum, use the previous validator set.
return None
}
Some((elected_candidates, assigned))
}
/// 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.
///
/// No value is returned from the function and the `expo_map` parameter is updated.
pub fn equalize<T: Trait + 'static>(
assignments: &mut Vec<(T::AccountId, BalanceOf<T>, Vec<(T::AccountId, ExtendedBalance, ExtendedBalance)>)>,
expo_map: &mut ExpoMap<T>,
tolerance: ExtendedBalance,
iterations: usize,
) {
for _i in 0..iterations {
let mut max_diff = 0;
assignments.iter_mut().for_each(|(n, budget, assignment)| {
let diff = do_equalize::<T>(&n, *budget, assignment, expo_map, tolerance);
if diff > max_diff {
max_diff = diff;
}
});
if max_diff < tolerance {
break;
}
}
}
fn do_equalize<T: Trait + 'static>(
nominator: &T::AccountId,
budget_balance: BalanceOf<T>,
elected_edges: &mut Vec<(T::AccountId, ExtendedBalance, ExtendedBalance)>,
expo_map: &mut ExpoMap<T>,
tolerance: ExtendedBalance
) -> ExtendedBalance {
let to_votes = |b: BalanceOf<T>|
<T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(b) as ExtendedBalance;
let to_balance = |v: ExtendedBalance|
<T::CurrencyToVote as Convert<ExtendedBalance, BalanceOf<T>>>::convert(v);
let budget = to_votes(budget_balance);
// Nothing to do. This nominator 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));
let backed_stakes_iter = elected_edges
.iter()
.filter_map(|e| expo_map.get(&e.0))
.map(|e| to_votes(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))
.collect::<Vec<ExtendedBalance>>();
let mut difference;
if backing_backed_stake.len() > 0 {
let max_stake = backing_backed_stake
.iter()
.max()
.expect("vector with positive length will have a max; qed");
let min_stake = backed_stakes_iter
.min()
.expect("iterator with positive length will have a min; qed");
difference = max_stake.saturating_sub(min_stake);
difference = difference.saturating_add(budget.saturating_sub(stake_used));
if difference < tolerance {
return difference;
}
} else {
difference = budget;
}
// Undo updates to exposure
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);
}
e.2 = 0;
});
elected_edges.sort_unstable_by_key(|e|
if let Some(e) = expo_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);
let stake_mul = stake.saturating_mul(idx as ExtendedBalance);
let stake_sub = stake_mul.saturating_sub(cumulative_stake);
if stake_sub > budget {
last_index = idx.checked_sub(1).unwrap_or(0);
return
}
cumulative_stake = cumulative_stake.saturating_add(stake);
}
});
let last_stake = elected_edges[last_index].2;
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)
.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)});
}
});
difference
}
+6 -6
View File
@@ -18,7 +18,6 @@
use super::*;
use runtime_io::with_externalities;
use 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 +1428,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::<Test, _, _, _>(
let results = phragmen::elect::<_, _, _, <Test as Trait>::CurrencyToVote>(
2,
Staking::minimum_validator_count() as usize,
<Validators<Test>>::enumerate(),
<Nominators<Test>>::enumerate(),
<Validators<Test>>::enumerate().map(|(who, _)| who).collect::<Vec<u64>>(),
<Nominators<Test>>::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)]),
]);