// 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 . //! Rust implementation of the Phragmén election algorithm. use rstd::prelude::*; use primitives::Perquintill; use primitives::traits::{Zero, As, Bounded, CheckedMul, Saturating}; use parity_codec::{HasCompact, Encode, Decode}; use crate::{Exposure, BalanceOf, Trait, ValidatorPrefs, IndividualExposure}; /// Configure the behavior of the Phragmen election. /// Might be deprecated. pub struct ElectionConfig { /// Perform equalise?. pub equalise: bool, /// Number of equalise iterations. pub iterations: usize, /// Tolerance of max change per equalise iteration. pub tolerance: Balance, } /// Wrapper around validation candidates some metadata. #[derive(Clone, Encode, Decode, Default)] #[cfg_attr(feature = "std", derive(Debug))] pub struct Candidate { /// The validator's account pub who: AccountId, /// Exposure struct, holding info about the value that the validator has in stake. pub exposure: Exposure, /// Intermediary value used to sort candidates. pub score: Perquintill, /// Accumulator of the stake of this candidate based on received votes. approval_stake: Balance, /// Flag for being elected. elected: bool, /// This is most often equal to `Exposure.total` but not always. Needed for [`equalise`] backing_stake: Balance } /// Wrapper around the nomination info of a single nominator for a group of validators. #[derive(Clone, Encode, Decode, Default)] #[cfg_attr(feature = "std", derive(Debug))] pub struct Nominator { /// The nominator's account. who: AccountId, /// List of validators proposed by this nominator. edges: Vec>, /// the stake amount proposed by the nominator as a part of the vote. budget: Balance, /// Incremented each time a nominee that this nominator voted for has been elected. load: Perquintill, } /// Wrapper around a nominator vote and the load of that vote. #[derive(Clone, Encode, Decode, Default)] #[cfg_attr(feature = "std", derive(Debug))] pub struct Edge { /// Account being voted for who: AccountId, /// Load of this vote. load: Perquintill, /// Final backing stake of this vote. backing_stake: Balance, /// Index of the candidate stored in the 'candidates' vector candidate_index: usize, /// Index of the candidate stored in the 'elected_candidates' vector. Used only with equalise. elected_idx: usize, /// Indicates if this edge is a vote for an elected candidate. Used only with equalise. elected: bool, } /// 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. pub fn elect( get_rounds: FR, get_validators: FV, get_nominators: FN, stash_of: FS, minimum_validator_count: usize, config: ElectionConfig>, ) -> Option>>> where FR: Fn() -> usize, FV: Fn() -> Box>) >>, FN: Fn() -> Box) >>, for <'r> FS: Fn(&'r T::AccountId) -> BalanceOf, { let rounds = get_rounds(); let mut elected_candidates; // 1- Pre-process candidates and place them in a container let mut candidates = get_validators().map(|(who, _)| { let stash_balance = stash_of(&who); Candidate { who, exposure: Exposure { total: stash_balance, own: stash_balance, others: vec![] }, ..Default::default() } }).collect::>>>(); // 1.1- Add phantom votes. let mut nominators: Vec>> = Vec::with_capacity(candidates.len()); candidates.iter_mut().enumerate().for_each(|(idx, c)| { c.approval_stake += c.exposure.total; nominators.push(Nominator { who: c.who.clone(), edges: vec![ Edge { who: c.who.clone(), candidate_index: idx, ..Default::default() }], budget: c.exposure.total, load: Perquintill::zero(), }) }); // 2- Collect the nominators with the associated votes. // Also collect approval stake along the way. nominators.extend(get_nominators().map(|(who, nominees)| { let nominator_stake = stash_of(&who); let mut edges: Vec>> = Vec::with_capacity(nominees.len()); for n in &nominees { if let Some(idx) = candidates.iter_mut().position(|i| i.who == *n) { candidates[idx].approval_stake = candidates[idx].approval_stake.saturating_add(nominator_stake); edges.push(Edge { who: n.clone(), candidate_index: idx, ..Default::default() }); } } Nominator { who, edges: edges, budget: nominator_stake, load: Perquintill::zero(), } })); // 3- optimization: // Candidates who have 0 stake => have no votes or all null-votes. Kick them out not. let mut candidates = candidates.into_iter().filter(|c| c.approval_stake > BalanceOf::::zero()) .collect::>>>(); // 4- If we have more candidates then needed, run Phragmén. if candidates.len() >= rounds { elected_candidates = Vec::with_capacity(rounds); // Main election loop for _round in 0..rounds { // Loop 1: initialize score for c in &mut candidates { if !c.elected { c.score = Perquintill::from_xth(c.approval_stake.as_()); } } // Loop 2: increment score. for n in &nominators { for e in &n.edges { let c = &mut candidates[e.candidate_index]; if !c.elected { // Note: This seems to never overflow, ok to be safe though let temp = n.budget.as_().saturating_mul(*n.load) / c.approval_stake.as_(); // Note: This seems to never overflow, ok to be safe though c.score = Perquintill::from_quintillionths((*c.score).saturating_add(temp)); } } } // Find the best let winner = candidates .iter_mut() .filter(|c| !c.elected) .min_by_key(|c| *c.score) .expect("candidates length is checked to be >0; qed"); // 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 = Perquintill::from_quintillionths(*winner.score - *n.load); n.load = winner.score; } } } elected_candidates.push(winner.clone()); } // end of all rounds // 4.1- Update backing stake of candidates and nominators for n in &mut nominators { for e in &mut n.edges { // if the target of this vote is among the winners, otherwise let go. if let Some(c) = elected_candidates.iter_mut().find(|c| c.who == e.who) { e.elected = true; // NOTE: always divide last to avoid collapse to zero. e.backing_stake = >::sa(n.budget.as_().saturating_mul(*e.load) / *n.load); c.backing_stake = c.backing_stake.saturating_add(e.backing_stake); if c.who != n.who { // Only update the exposure if this vote is from some other account. c.exposure.total = c.exposure.total.saturating_add(e.backing_stake); c.exposure.others.push( IndividualExposure { who: n.who.clone(), value: e.backing_stake } ); } } } } // Optionally perform equalise post-processing. if config.equalise { let tolerance = config.tolerance; let equalise_iterations = config.iterations; // Fix indexes nominators.iter_mut().for_each(|n| { n.edges.iter_mut().for_each(|e| { if let Some(idx) = elected_candidates.iter().position(|c| c.who == e.who) { e.elected_idx = idx; } }); }); for _i in 0..equalise_iterations { let mut max_diff = >::zero(); nominators.iter_mut().for_each(|mut n| { let diff = equalise::(&mut n, &mut elected_candidates, tolerance); if diff > max_diff { max_diff = diff; } }); if max_diff < tolerance { break; } } } } else { if candidates.len() > minimum_validator_count { // if we don't have enough candidates, just choose all that have some vote. elected_candidates = candidates; for n in &mut nominators { let nominator = n.who.clone(); for e in &mut n.edges { if let Some(c) = elected_candidates.iter_mut().find(|c| c.who == e.who && c.who != nominator) { c.exposure.total = c.exposure.total.saturating_add(n.budget); c.exposure.others.push( IndividualExposure { who: n.who.clone(), value: n.budget } ); } } } } else { // if we have less than minimum, use the previous validator set. return None } } Some(elected_candidates) } pub fn equalise( nominator: &mut Nominator>, elected_candidates: &mut Vec>>, tolerance: BalanceOf ) -> BalanceOf { let mut elected_edges = nominator.edges .iter_mut() .filter(|e| e.elected) .collect::>>>(); if elected_edges.len() == 0 { return >::zero(); } let stake_used = elected_edges .iter() .fold(>::zero(), |s, e| s.saturating_add(e.backing_stake)); let backed_stakes = elected_edges .iter() .map(|e| elected_candidates[e.elected_idx].backing_stake) .collect::>>(); let backing_backed_stake = elected_edges .iter() .filter(|e| e.backing_stake > >::zero()) .map(|e| elected_candidates[e.elected_idx].backing_stake) .collect::>>(); 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("vector with positive length will have a min; qed"); difference = max_stake.saturating_sub(min_stake); difference = difference.saturating_add(nominator.budget.saturating_sub(stake_used)); if difference < tolerance { return difference; } } else { difference = nominator.budget; } // Undo updates to exposure elected_edges.iter_mut().for_each(|e| { // NOTE: no assertions in the runtime, but this should nonetheless be indicative. //assert_eq!(elected_candidates[e.elected_idx].who, e.who); elected_candidates[e.elected_idx].backing_stake -= e.backing_stake; elected_candidates[e.elected_idx].exposure.total -= e.backing_stake; e.backing_stake = >::zero(); }); elected_edges.sort_unstable_by_key(|e| elected_candidates[e.elected_idx].backing_stake); let mut cumulative_stake = >::zero(); let mut last_index = elected_edges.len() - 1; let budget = nominator.budget; elected_edges.iter_mut().enumerate().for_each(|(idx, e)| { let stake = elected_candidates[e.elected_idx].backing_stake; let stake_mul = stake.checked_mul(&>::sa(idx as u64)).unwrap_or(>::max_value()); 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_candidates[elected_edges[last_index].elected_idx].backing_stake; let split_ways = last_index + 1; let excess = nominator.budget .saturating_add(cumulative_stake) .saturating_sub( last_stake.checked_mul(&>::sa(split_ways as u64)) .unwrap_or(>::max_value()) ); let nominator_address = nominator.who.clone(); elected_edges.iter_mut().take(split_ways).for_each(|e| { let c = &mut elected_candidates[e.elected_idx]; e.backing_stake = (excess / >::sa(split_ways as u64)).saturating_add(last_stake) - c.backing_stake; c.exposure.total = c.exposure.total.saturating_add(e.backing_stake); c.backing_stake = c.backing_stake.saturating_add(e.backing_stake); if let Some(i_expo) = c.exposure.others.iter_mut().find(|i| i.who == nominator_address) { i_expo.value = e.backing_stake; } }); difference }