sp_npos_elections/lib.rs
1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7// in compliance with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software distributed under the License
12// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13// or implied. See the License for the specific language governing permissions and limitations under
14// the License.
15
16//! A set of election algorithms to be used with a substrate runtime, typically within the staking
17//! sub-system. Notable implementation include:
18//!
19//! - [`seq_phragmen`]: Implements the Phragmén Sequential Method. An un-ranked, relatively fast
20//! election method that ensures PJR, but does not provide a constant factor approximation of the
21//! maximin problem.
22//! - [`ghragmms`](phragmms::phragmms()): Implements a hybrid approach inspired by Phragmén which is
23//! executed faster but it can achieve a constant factor approximation of the maximin problem,
24//! similar to that of the MMS algorithm.
25//! - [`balance`]: Implements the star balancing algorithm. This iterative process can push a
26//! solution toward being more "balanced", which in turn can increase its score.
27//!
28//! ### Terminology
29//!
30//! This crate uses context-independent words, not to be confused with staking. This is because the
31//! election algorithms of this crate, while designed for staking, can be used in other contexts as
32//! well.
33//!
34//! `Voter`: The entity casting some votes to a number of `Targets`. This is the same as `Nominator`
35//! in the context of staking. `Target`: The entities eligible to be voted upon. This is the same as
36//! `Validator` in the context of staking. `Edge`: A mapping from a `Voter` to a `Target`.
37//!
38//! The goal of an election algorithm is to provide an `ElectionResult`. A data composed of:
39//! - `winners`: A flat list of identifiers belonging to those who have won the election, usually
40//! ordered in some meaningful way. They are zipped with their total backing stake.
41//! - `assignment`: A mapping from each voter to their winner-only targets, zipped with a ration
42//! denoting the amount of support given to that particular target.
43//!
44//! ```rust
45//! # use sp_npos_elections::*;
46//! # use sp_runtime::Perbill;
47//! // the winners.
48//! let winners = vec![(1, 100), (2, 50)];
49//! let assignments = vec![
50//! // A voter, giving equal backing to both 1 and 2.
51//! Assignment {
52//! who: 10,
53//! distribution: vec![(1, Perbill::from_percent(50)), (2, Perbill::from_percent(50))],
54//! },
55//! // A voter, Only backing 1.
56//! Assignment { who: 20, distribution: vec![(1, Perbill::from_percent(100))] },
57//! ];
58//!
59//! // the combination of the two makes the election result.
60//! let election_result = ElectionResult { winners, assignments };
61//! ```
62//!
63//! The `Assignment` field of the election result is voter-major, i.e. it is from the perspective of
64//! the voter. The struct that represents the opposite is called a `Support`. This struct is usually
65//! accessed in a map-like manner, i.e. keyed by voters, therefore it is stored as a mapping called
66//! `SupportMap`.
67//!
68//! Moreover, the support is built from absolute backing values, not ratios like the example above.
69//! A struct similar to `Assignment` that has stake value instead of ratios is called an
70//! `StakedAssignment`.
71//!
72//!
73//! More information can be found at: <https://arxiv.org/abs/2004.12990>
74
75#![cfg_attr(not(feature = "std"), no_std)]
76
77extern crate alloc;
78
79use alloc::{collections::btree_map::BTreeMap, rc::Rc, vec, vec::Vec};
80use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
81use core::{cell::RefCell, cmp::Ordering};
82use scale_info::TypeInfo;
83#[cfg(feature = "serde")]
84use serde::{Deserialize, Serialize};
85use sp_arithmetic::{traits::Zero, Normalizable, PerThing, Rational128, ThresholdOrd};
86use sp_core::{bounded::BoundedVec, RuntimeDebug};
87
88#[cfg(test)]
89mod mock;
90#[cfg(test)]
91mod tests;
92
93mod assignments;
94pub mod balancing;
95pub mod helpers;
96pub mod node;
97pub mod phragmen;
98pub mod phragmms;
99pub mod pjr;
100pub mod reduce;
101pub mod traits;
102
103pub use assignments::{Assignment, StakedAssignment};
104pub use balancing::*;
105pub use helpers::*;
106pub use phragmen::*;
107pub use phragmms::*;
108pub use pjr::*;
109pub use reduce::reduce;
110pub use traits::{IdentifierT, PerThing128};
111
112/// The errors that might occur in this crate and `frame-election-provider-solution-type`.
113#[derive(Eq, PartialEq, RuntimeDebug)]
114pub enum Error {
115 /// While going from solution indices to ratio, the weight of all the edges has gone above the
116 /// total.
117 SolutionWeightOverflow,
118 /// The solution type has a voter who's number of targets is out of bound.
119 SolutionTargetOverflow,
120 /// One of the index functions returned none.
121 SolutionInvalidIndex,
122 /// One of the page indices was invalid.
123 SolutionInvalidPageIndex,
124 /// An error occurred in some arithmetic operation.
125 ArithmeticError(&'static str),
126 /// The data provided to create support map was invalid.
127 InvalidSupportEdge,
128 /// The number of voters is bigger than the `MaxVoters` bound.
129 TooManyVoters,
130}
131
132/// A type which is used in the API of this crate as a numeric weight of a vote, most often the
133/// stake of the voter. It is always converted to [`ExtendedBalance`] for computation.
134pub type VoteWeight = u64;
135
136/// A type in which performing operations on vote weights are safe.
137pub type ExtendedBalance = u128;
138
139/// The score of an election. This is the main measure of an election's quality.
140///
141/// By definition, the order of significance in [`ElectionScore`] is:
142///
143/// 1. `minimal_stake`.
144/// 2. `sum_stake`.
145/// 3. `sum_stake_squared`.
146#[derive(
147 Clone,
148 Copy,
149 PartialEq,
150 Eq,
151 Encode,
152 Decode,
153 DecodeWithMemTracking,
154 MaxEncodedLen,
155 TypeInfo,
156 Debug,
157 Default,
158)]
159#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
160pub struct ElectionScore {
161 /// The minimal winner, in terms of total backing stake.
162 ///
163 /// This parameter should be maximized.
164 pub minimal_stake: ExtendedBalance,
165 /// The sum of the total backing of all winners.
166 ///
167 /// This parameter should maximized
168 pub sum_stake: ExtendedBalance,
169 /// The sum squared of the total backing of all winners, aka. the variance.
170 ///
171 /// Ths parameter should be minimized.
172 pub sum_stake_squared: ExtendedBalance,
173}
174
175impl ElectionScore {
176 /// Iterate over the inner items, first visiting the most significant one.
177 fn iter_by_significance(self) -> impl Iterator<Item = ExtendedBalance> {
178 [self.minimal_stake, self.sum_stake, self.sum_stake_squared].into_iter()
179 }
180
181 /// Compares two sets of election scores based on desirability, returning true if `self` is
182 /// strictly `threshold` better than `other`. In other words, each element of `self` must be
183 /// `self * threshold` better than `other`.
184 ///
185 /// Evaluation is done based on the order of significance of the fields of [`ElectionScore`].
186 pub fn strict_threshold_better(self, other: Self, threshold: impl PerThing) -> bool {
187 match self
188 .iter_by_significance()
189 .zip(other.iter_by_significance())
190 .map(|(this, that)| (this.ge(&that), this.tcmp(&that, threshold.mul_ceil(that))))
191 .collect::<Vec<(bool, Ordering)>>()
192 .as_slice()
193 {
194 // threshold better in the `score.minimal_stake`, accept.
195 [(x, Ordering::Greater), _, _] => {
196 debug_assert!(x);
197 true
198 },
199
200 // less than threshold better in `score.minimal_stake`, but more than threshold better
201 // in `score.sum_stake`.
202 [(true, Ordering::Equal), (_, Ordering::Greater), _] => true,
203
204 // less than threshold better in `score.minimal_stake` and `score.sum_stake`, but more
205 // than threshold better in `score.sum_stake_squared`.
206 [(true, Ordering::Equal), (true, Ordering::Equal), (_, Ordering::Less)] => true,
207
208 // anything else is not a good score.
209 _ => false,
210 }
211 }
212}
213
214impl core::cmp::Ord for ElectionScore {
215 fn cmp(&self, other: &Self) -> Ordering {
216 // we delegate this to the lexicographic cmp of slices`, and to incorporate that we want the
217 // third element to be minimized, we swap them.
218 [self.minimal_stake, self.sum_stake, other.sum_stake_squared].cmp(&[
219 other.minimal_stake,
220 other.sum_stake,
221 self.sum_stake_squared,
222 ])
223 }
224}
225
226impl core::cmp::PartialOrd for ElectionScore {
227 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
228 Some(self.cmp(other))
229 }
230}
231
232/// Utility struct to group parameters for the balancing algorithm.
233#[derive(Clone, Copy)]
234pub struct BalancingConfig {
235 pub iterations: usize,
236 pub tolerance: ExtendedBalance,
237}
238
239/// A pointer to a candidate struct with interior mutability.
240pub type CandidatePtr<A> = Rc<RefCell<Candidate<A>>>;
241
242/// A candidate entity for the election.
243#[derive(RuntimeDebug, Clone, Default)]
244pub struct Candidate<AccountId> {
245 /// Identifier.
246 who: AccountId,
247 /// Score of the candidate.
248 ///
249 /// Used differently in seq-phragmen and max-score.
250 score: Rational128,
251 /// Approval stake of the candidate. Merely the sum of all the voter's stake who approve this
252 /// candidate.
253 approval_stake: ExtendedBalance,
254 /// The final stake of this candidate. Will be equal to a subset of approval stake.
255 backed_stake: ExtendedBalance,
256 /// True if this candidate is already elected in the current election.
257 elected: bool,
258 /// The round index at which this candidate was elected.
259 round: usize,
260}
261
262impl<AccountId> Candidate<AccountId> {
263 pub fn to_ptr(self) -> CandidatePtr<AccountId> {
264 Rc::new(RefCell::new(self))
265 }
266}
267
268/// A vote being casted by a [`Voter`] to a [`Candidate`] is an `Edge`.
269#[derive(Clone)]
270pub struct Edge<AccountId> {
271 /// Identifier of the target.
272 ///
273 /// This is equivalent of `self.candidate.borrow().who`, yet it helps to avoid double borrow
274 /// errors of the candidate pointer.
275 who: AccountId,
276 /// Load of this edge.
277 load: Rational128,
278 /// Pointer to the candidate.
279 candidate: CandidatePtr<AccountId>,
280 /// The weight (i.e. stake given to `who`) of this edge.
281 weight: ExtendedBalance,
282}
283
284#[cfg(test)]
285impl<AccountId: Clone> Edge<AccountId> {
286 fn new(candidate: Candidate<AccountId>, weight: ExtendedBalance) -> Self {
287 let who = candidate.who.clone();
288 let candidate = Rc::new(RefCell::new(candidate));
289 Self { weight, who, candidate, load: Default::default() }
290 }
291}
292
293#[cfg(feature = "std")]
294impl<A: IdentifierT> core::fmt::Debug for Edge<A> {
295 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
296 write!(f, "Edge({:?}, weight = {:?})", self.who, self.weight)
297 }
298}
299
300/// A voter entity.
301#[derive(Clone, Default)]
302pub struct Voter<AccountId> {
303 /// Identifier.
304 who: AccountId,
305 /// List of candidates approved by this voter.
306 edges: Vec<Edge<AccountId>>,
307 /// The stake of this voter.
308 budget: ExtendedBalance,
309 /// Load of the voter.
310 load: Rational128,
311}
312
313#[cfg(feature = "std")]
314impl<A: IdentifierT> std::fmt::Debug for Voter<A> {
315 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316 write!(f, "Voter({:?}, budget = {}, edges = {:?})", self.who, self.budget, self.edges)
317 }
318}
319
320impl<AccountId: IdentifierT> Voter<AccountId> {
321 /// Create a new `Voter`.
322 pub fn new(who: AccountId) -> Self {
323 Self {
324 who,
325 edges: Default::default(),
326 budget: Default::default(),
327 load: Default::default(),
328 }
329 }
330
331 /// Returns `true` if `self` votes for `target`.
332 ///
333 /// Note that this does not take into account if `target` is elected (i.e. is *active*) or not.
334 pub fn votes_for(&self, target: &AccountId) -> bool {
335 self.edges.iter().any(|e| &e.who == target)
336 }
337
338 /// Returns none if this voter does not have any non-zero distributions.
339 ///
340 /// Note that this might create _un-normalized_ assignments, due to accuracy loss of `P`. Call
341 /// site might compensate by calling `normalize()` on the returned `Assignment` as a
342 /// post-processing.
343 pub fn into_assignment<P: PerThing>(self) -> Option<Assignment<AccountId, P>> {
344 let who = self.who;
345 let budget = self.budget;
346 let distribution = self
347 .edges
348 .into_iter()
349 .filter_map(|e| {
350 let per_thing = P::from_rational(e.weight, budget);
351 // trim zero edges.
352 if per_thing.is_zero() {
353 None
354 } else {
355 Some((e.who, per_thing))
356 }
357 })
358 .collect::<Vec<_>>();
359
360 if distribution.len() > 0 {
361 Some(Assignment { who, distribution })
362 } else {
363 None
364 }
365 }
366
367 /// Try and normalize the votes of self.
368 ///
369 /// If the normalization is successful then `Ok(())` is returned.
370 ///
371 /// Note that this will not distinguish between elected and unelected edges. Thus, it should
372 /// only be called on a voter who has already been reduced to only elected edges.
373 ///
374 /// ### Errors
375 ///
376 /// This will return only if the internal `normalize` fails. This can happen if the sum of the
377 /// weights exceeds `ExtendedBalance::max_value()`.
378 pub fn try_normalize(&mut self) -> Result<(), &'static str> {
379 let edge_weights = self.edges.iter().map(|e| e.weight).collect::<Vec<_>>();
380 edge_weights.normalize(self.budget).map(|normalized| {
381 // here we count on the fact that normalize does not change the order.
382 for (edge, corrected) in self.edges.iter_mut().zip(normalized.into_iter()) {
383 let mut candidate = edge.candidate.borrow_mut();
384 // first, subtract the incorrect weight
385 candidate.backed_stake = candidate.backed_stake.saturating_sub(edge.weight);
386 edge.weight = corrected;
387 // Then add the correct one again.
388 candidate.backed_stake = candidate.backed_stake.saturating_add(edge.weight);
389 }
390 })
391 }
392
393 /// Same as [`Self::try_normalize`] but the normalization is only limited between elected edges.
394 pub fn try_normalize_elected(&mut self) -> Result<(), &'static str> {
395 let elected_edge_weights = self
396 .edges
397 .iter()
398 .filter_map(|e| if e.candidate.borrow().elected { Some(e.weight) } else { None })
399 .collect::<Vec<_>>();
400 elected_edge_weights.normalize(self.budget).map(|normalized| {
401 // here we count on the fact that normalize does not change the order, and that vector
402 // iteration is deterministic.
403 for (edge, corrected) in self
404 .edges
405 .iter_mut()
406 .filter(|e| e.candidate.borrow().elected)
407 .zip(normalized.into_iter())
408 {
409 let mut candidate = edge.candidate.borrow_mut();
410 // first, subtract the incorrect weight
411 candidate.backed_stake = candidate.backed_stake.saturating_sub(edge.weight);
412 edge.weight = corrected;
413 // Then add the correct one again.
414 candidate.backed_stake = candidate.backed_stake.saturating_add(edge.weight);
415 }
416 })
417 }
418
419 /// This voter's budget.
420 #[inline]
421 pub fn budget(&self) -> ExtendedBalance {
422 self.budget
423 }
424}
425
426/// Final result of the election.
427#[derive(RuntimeDebug)]
428pub struct ElectionResult<AccountId, P: PerThing> {
429 /// Just winners zipped with their approval stake. Note that the approval stake is merely the
430 /// sub of their received stake and could be used for very basic sorting and approval voting.
431 pub winners: Vec<(AccountId, ExtendedBalance)>,
432 /// Individual assignments. for each tuple, the first elements is a voter and the second is the
433 /// list of candidates that it supports.
434 pub assignments: Vec<Assignment<AccountId, P>>,
435}
436
437/// A structure to demonstrate the election result from the perspective of the candidate, i.e. how
438/// much support each candidate is receiving.
439///
440/// This complements the [`ElectionResult`] and is needed to run the balancing post-processing.
441///
442/// This, at the current version, resembles the `Exposure` defined in the Staking pallet, yet they
443/// do not necessarily have to be the same.
444#[derive(RuntimeDebug, Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)]
445#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
446pub struct Support<AccountId> {
447 /// Total support.
448 pub total: ExtendedBalance,
449 /// Support from voters.
450 pub voters: Vec<(AccountId, ExtendedBalance)>,
451}
452
453impl<AccountId> Default for Support<AccountId> {
454 fn default() -> Self {
455 Self { total: Default::default(), voters: vec![] }
456 }
457}
458
459/// A target-major representation of the the election outcome.
460///
461/// Essentially a flat variant of [`SupportMap`].
462///
463/// The main advantage of this is that it is encodable.
464pub type Supports<A> = Vec<(A, Support<A>)>;
465
466/// Same as `Supports` but bounded by `B`.
467///
468/// To note, the inner `Support` is still unbounded.
469pub type BoundedSupports<A, B> = BoundedVec<(A, Support<A>), B>;
470
471/// Linkage from a winner to their [`Support`].
472///
473/// This is more helpful than a normal [`Supports`] as it allows faster error checking.
474pub type SupportMap<A> = BTreeMap<A, Support<A>>;
475
476/// Build the support map from the assignments.
477pub fn to_support_map<AccountId: IdentifierT>(
478 assignments: &[StakedAssignment<AccountId>],
479) -> SupportMap<AccountId> {
480 let mut supports = <BTreeMap<AccountId, Support<AccountId>>>::new();
481
482 // build support struct.
483 for StakedAssignment { who, distribution } in assignments.iter() {
484 for (c, weight_extended) in distribution.iter() {
485 let support = supports.entry(c.clone()).or_default();
486 support.total = support.total.saturating_add(*weight_extended);
487 support.voters.push((who.clone(), *weight_extended));
488 }
489 }
490
491 supports
492}
493
494/// Same as [`to_support_map`] except it returns a
495/// flat vector.
496pub fn to_supports<AccountId: IdentifierT>(
497 assignments: &[StakedAssignment<AccountId>],
498) -> Supports<AccountId> {
499 to_support_map(assignments).into_iter().collect()
500}
501
502/// Extension trait for evaluating a support map or vector.
503pub trait EvaluateSupport {
504 /// Evaluate a support map. The returned tuple contains:
505 ///
506 /// - Minimum support. This value must be **maximized**.
507 /// - Sum of all supports. This value must be **maximized**.
508 /// - Sum of all supports squared. This value must be **minimized**.
509 fn evaluate(&self) -> ElectionScore;
510}
511
512impl<AccountId: IdentifierT> EvaluateSupport for Supports<AccountId> {
513 fn evaluate(&self) -> ElectionScore {
514 let mut minimal_stake = ExtendedBalance::max_value();
515 let mut sum_stake: ExtendedBalance = Zero::zero();
516 // NOTE: The third element might saturate but fine for now since this will run on-chain and
517 // need to be fast.
518 let mut sum_stake_squared: ExtendedBalance = Zero::zero();
519
520 for (_, support) in self {
521 sum_stake = sum_stake.saturating_add(support.total);
522 let squared = support.total.saturating_mul(support.total);
523 sum_stake_squared = sum_stake_squared.saturating_add(squared);
524 if support.total < minimal_stake {
525 minimal_stake = support.total;
526 }
527 }
528
529 ElectionScore { minimal_stake, sum_stake, sum_stake_squared }
530 }
531}
532
533/// Converts raw inputs to types used in this crate.
534///
535/// This will perform some cleanup that are most often important:
536/// - It drops any votes that are pointing to non-candidates.
537/// - It drops duplicate targets within a voter.
538pub fn setup_inputs<AccountId: IdentifierT>(
539 initial_candidates: Vec<AccountId>,
540 initial_voters: Vec<(AccountId, VoteWeight, impl IntoIterator<Item = AccountId>)>,
541) -> (Vec<CandidatePtr<AccountId>>, Vec<Voter<AccountId>>) {
542 // used to cache and access candidates index.
543 let mut c_idx_cache = BTreeMap::<AccountId, usize>::new();
544
545 let candidates = initial_candidates
546 .into_iter()
547 .enumerate()
548 .map(|(idx, who)| {
549 c_idx_cache.insert(who.clone(), idx);
550 Candidate {
551 who,
552 score: Default::default(),
553 approval_stake: Default::default(),
554 backed_stake: Default::default(),
555 elected: Default::default(),
556 round: Default::default(),
557 }
558 .to_ptr()
559 })
560 .collect::<Vec<CandidatePtr<AccountId>>>();
561
562 let voters = initial_voters
563 .into_iter()
564 .filter_map(|(who, voter_stake, votes)| {
565 let mut edges: Vec<Edge<AccountId>> = Vec::new();
566 for v in votes {
567 if edges.iter().any(|e| e.who == v) {
568 // duplicate edge.
569 continue
570 }
571 if let Some(idx) = c_idx_cache.get(&v) {
572 // This candidate is valid + already cached.
573 let mut candidate = candidates[*idx].borrow_mut();
574 candidate.approval_stake =
575 candidate.approval_stake.saturating_add(voter_stake.into());
576 edges.push(Edge {
577 who: v.clone(),
578 candidate: Rc::clone(&candidates[*idx]),
579 load: Default::default(),
580 weight: Default::default(),
581 });
582 } // else {} would be wrong votes. We don't really care about it.
583 }
584 if edges.is_empty() {
585 None
586 } else {
587 Some(Voter { who, edges, budget: voter_stake.into(), load: Rational128::zero() })
588 }
589 })
590 .collect::<Vec<_>>();
591
592 (candidates, voters)
593}