referrerpolicy=no-referrer-when-downgrade

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 Debug;
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(
114	Eq,
115	PartialEq,
116	Debug,
117	Clone,
118	codec::Encode,
119	codec::Decode,
120	codec::DecodeWithMemTracking,
121	scale_info::TypeInfo,
122)]
123pub enum Error {
124	/// While going from solution indices to ratio, the weight of all the edges has gone above the
125	/// total.
126	SolutionWeightOverflow,
127	/// The solution type has a voter who's number of targets is out of bound.
128	SolutionTargetOverflow,
129	/// One of the index functions returned none.
130	SolutionInvalidIndex,
131	/// One of the page indices was invalid.
132	SolutionInvalidPageIndex,
133	/// An error occurred in some arithmetic operation.
134	ArithmeticError,
135	/// The data provided to create support map was invalid.
136	InvalidSupportEdge,
137	/// The number of voters is bigger than the `MaxVoters` bound.
138	TooManyVoters,
139	/// Some bounds were exceeded when converting election types.
140	BoundsExceeded,
141	/// A duplicate voter was detected.
142	DuplicateVoter,
143	/// A duplicate target was detected.
144	DuplicateTarget,
145}
146
147/// A type which is used in the API of this crate as a numeric weight of a vote, most often the
148/// stake of the voter. It is always converted to [`ExtendedBalance`] for computation.
149pub type VoteWeight = u64;
150
151/// A type in which performing operations on vote weights are safe.
152pub type ExtendedBalance = u128;
153
154/// The score of an election. This is the main measure of an election's quality.
155///
156/// By definition, the order of significance in [`ElectionScore`] is:
157///
158/// 1. `minimal_stake`.
159/// 2. `sum_stake`.
160/// 3. `sum_stake_squared`.
161#[derive(
162	Clone,
163	Copy,
164	PartialEq,
165	Eq,
166	Encode,
167	Decode,
168	DecodeWithMemTracking,
169	MaxEncodedLen,
170	TypeInfo,
171	Debug,
172	Default,
173)]
174#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
175pub struct ElectionScore {
176	/// The minimal winner, in terms of total backing stake.
177	///
178	/// This parameter should be maximized.
179	pub minimal_stake: ExtendedBalance,
180	/// The sum of the total backing of all winners.
181	///
182	/// This parameter should maximized
183	pub sum_stake: ExtendedBalance,
184	/// The sum squared of the total backing of all winners, aka. the variance.
185	///
186	/// This parameter should be minimized.
187	pub sum_stake_squared: ExtendedBalance,
188}
189
190#[cfg(feature = "std")]
191impl ElectionScore {
192	/// format the election score in a pretty way with the given `token` symbol and `decimals`.
193	pub fn pretty(&self, token: &str, decimals: u32) -> String {
194		format!(
195			"ElectionScore (minimal_stake: {}, sum_stake: {}, sum_stake_squared: {})",
196			pretty_balance(self.minimal_stake, token, decimals),
197			pretty_balance(self.sum_stake, token, decimals),
198			pretty_balance(self.sum_stake_squared, token, decimals),
199		)
200	}
201}
202
203/// Format a single [`ExtendedBalance`] into a pretty string with the given `token` symbol and
204/// `decimals`.
205#[cfg(feature = "std")]
206pub fn pretty_balance<B: Into<u128>>(b: B, token: &str, decimals: u32) -> String {
207	let b: u128 = b.into();
208	format!("{} {}", b / 10u128.pow(decimals), token)
209}
210
211impl ElectionScore {
212	/// Iterate over the inner items, first visiting the most significant one.
213	fn iter_by_significance(self) -> impl Iterator<Item = ExtendedBalance> {
214		[self.minimal_stake, self.sum_stake, self.sum_stake_squared].into_iter()
215	}
216
217	/// Compares two sets of election scores based on desirability, returning true if `self` is
218	/// strictly `threshold` better than `other`. In other words, each element of `self` must be
219	/// better than `other` relative to the given `threshold`.
220	///
221	/// Evaluation is done based on the order of significance of the fields of [`ElectionScore`].
222	pub fn strict_threshold_better(self, other: Self, threshold: impl PerThing) -> bool {
223		match self
224			.iter_by_significance()
225			.zip(other.iter_by_significance())
226			.map(|(this, that)| (this.ge(&that), this.tcmp(&that, threshold.mul_ceil(that))))
227			.collect::<Vec<(bool, Ordering)>>()
228			.as_slice()
229		{
230			// threshold better in the `score.minimal_stake`, accept.
231			[(x, Ordering::Greater), _, _] => {
232				debug_assert!(x);
233				true
234			},
235
236			// less than threshold better in `score.minimal_stake`, but more than threshold better
237			// in `score.sum_stake`.
238			[(true, Ordering::Equal), (_, Ordering::Greater), _] => true,
239
240			// less than threshold better in `score.minimal_stake` and `score.sum_stake`, but more
241			// than threshold better in `score.sum_stake_squared`.
242			[(true, Ordering::Equal), (true, Ordering::Equal), (_, Ordering::Less)] => true,
243
244			// anything else is not a good score.
245			_ => false,
246		}
247	}
248
249	/// Compares two sets of election scores based on desirability, returning true if `self` is
250	/// strictly better than `other`.
251	pub fn strict_better(self, other: Self) -> bool {
252		self.strict_threshold_better(other, sp_runtime::Perbill::zero())
253	}
254}
255
256impl core::cmp::Ord for ElectionScore {
257	fn cmp(&self, other: &Self) -> Ordering {
258		// we delegate this to the lexicographic cmp of slices`, and to incorporate that we want the
259		// third element to be minimized, we swap them.
260		[self.minimal_stake, self.sum_stake, other.sum_stake_squared].cmp(&[
261			other.minimal_stake,
262			other.sum_stake,
263			self.sum_stake_squared,
264		])
265	}
266}
267
268impl core::cmp::PartialOrd for ElectionScore {
269	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
270		Some(self.cmp(other))
271	}
272}
273
274/// Utility struct to group parameters for the balancing algorithm.
275#[derive(Clone, Copy)]
276pub struct BalancingConfig {
277	pub iterations: usize,
278	pub tolerance: ExtendedBalance,
279}
280
281/// A pointer to a candidate struct with interior mutability.
282pub type CandidatePtr<A> = Rc<RefCell<Candidate<A>>>;
283
284/// A candidate entity for the election.
285#[derive(Debug, Clone, Default)]
286pub struct Candidate<AccountId> {
287	/// Identifier.
288	who: AccountId,
289	/// Score of the candidate.
290	///
291	/// Used differently in seq-phragmen and max-score.
292	score: Rational128,
293	/// Approval stake of the candidate. Merely the sum of all the voter's stake who approve this
294	/// candidate.
295	approval_stake: ExtendedBalance,
296	/// The final stake of this candidate. Will be equal to a subset of approval stake.
297	backed_stake: ExtendedBalance,
298	/// True if this candidate is already elected in the current election.
299	elected: bool,
300	/// The round index at which this candidate was elected.
301	round: usize,
302}
303
304impl<AccountId> Candidate<AccountId> {
305	pub fn to_ptr(self) -> CandidatePtr<AccountId> {
306		Rc::new(RefCell::new(self))
307	}
308}
309
310/// A vote being casted by a [`Voter`] to a [`Candidate`] is an `Edge`.
311#[derive(Clone)]
312pub struct Edge<AccountId> {
313	/// Identifier of the target.
314	///
315	/// This is equivalent of `self.candidate.borrow().who`, yet it helps to avoid double borrow
316	/// errors of the candidate pointer.
317	who: AccountId,
318	/// Load of this edge.
319	load: Rational128,
320	/// Pointer to the candidate.
321	candidate: CandidatePtr<AccountId>,
322	/// The weight (i.e. stake given to `who`) of this edge.
323	weight: ExtendedBalance,
324}
325
326#[cfg(test)]
327impl<AccountId: Clone> Edge<AccountId> {
328	fn new(candidate: Candidate<AccountId>, weight: ExtendedBalance) -> Self {
329		let who = candidate.who.clone();
330		let candidate = Rc::new(RefCell::new(candidate));
331		Self { weight, who, candidate, load: Default::default() }
332	}
333}
334
335#[cfg(feature = "std")]
336impl<A: IdentifierT> core::fmt::Debug for Edge<A> {
337	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
338		write!(f, "Edge({:?}, weight = {:?})", self.who, self.weight)
339	}
340}
341
342/// A voter entity.
343#[derive(Clone, Default)]
344pub struct Voter<AccountId> {
345	/// Identifier.
346	who: AccountId,
347	/// List of candidates approved by this voter.
348	edges: Vec<Edge<AccountId>>,
349	/// The stake of this voter.
350	budget: ExtendedBalance,
351	/// Load of the voter.
352	load: Rational128,
353}
354
355#[cfg(feature = "std")]
356impl<A: IdentifierT> std::fmt::Debug for Voter<A> {
357	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358		write!(f, "Voter({:?}, budget = {}, edges = {:?})", self.who, self.budget, self.edges)
359	}
360}
361
362impl<AccountId: IdentifierT> Voter<AccountId> {
363	/// Create a new `Voter`.
364	pub fn new(who: AccountId) -> Self {
365		Self {
366			who,
367			edges: Default::default(),
368			budget: Default::default(),
369			load: Default::default(),
370		}
371	}
372
373	/// Returns `true` if `self` votes for `target`.
374	///
375	/// Note that this does not take into account if `target` is elected (i.e. is *active*) or not.
376	pub fn votes_for(&self, target: &AccountId) -> bool {
377		self.edges.iter().any(|e| &e.who == target)
378	}
379
380	/// Returns none if this voter does not have any non-zero distributions.
381	///
382	/// Note that this might create _un-normalized_ assignments, due to accuracy loss of `P`. Call
383	/// site might compensate by calling `normalize()` on the returned `Assignment` as a
384	/// post-processing.
385	pub fn into_assignment<P: PerThing>(self) -> Option<Assignment<AccountId, P>> {
386		let who = self.who;
387		let budget = self.budget;
388		let distribution = self
389			.edges
390			.into_iter()
391			.filter_map(|e| {
392				let per_thing = P::from_rational(e.weight, budget);
393				// trim zero edges.
394				if per_thing.is_zero() {
395					None
396				} else {
397					Some((e.who, per_thing))
398				}
399			})
400			.collect::<Vec<_>>();
401
402		if distribution.len() > 0 {
403			Some(Assignment { who, distribution })
404		} else {
405			None
406		}
407	}
408
409	/// Try and normalize the votes of self.
410	///
411	/// If the normalization is successful then `Ok(())` is returned.
412	///
413	/// Note that this will not distinguish between elected and unelected edges. Thus, it should
414	/// only be called on a voter who has already been reduced to only elected edges.
415	///
416	/// ### Errors
417	///
418	/// This will return only if the internal `normalize` fails. This can happen if the sum of the
419	/// weights exceeds `ExtendedBalance::max_value()`.
420	pub fn try_normalize(&mut self) -> Result<(), &'static str> {
421		let edge_weights = self.edges.iter().map(|e| e.weight).collect::<Vec<_>>();
422		edge_weights.normalize(self.budget).map(|normalized| {
423			// here we count on the fact that normalize does not change the order.
424			for (edge, corrected) in self.edges.iter_mut().zip(normalized.into_iter()) {
425				let mut candidate = edge.candidate.borrow_mut();
426				// first, subtract the incorrect weight
427				candidate.backed_stake = candidate.backed_stake.saturating_sub(edge.weight);
428				edge.weight = corrected;
429				// Then add the correct one again.
430				candidate.backed_stake = candidate.backed_stake.saturating_add(edge.weight);
431			}
432		})
433	}
434
435	/// Same as [`Self::try_normalize`] but the normalization is only limited between elected edges.
436	pub fn try_normalize_elected(&mut self) -> Result<(), &'static str> {
437		let elected_edge_weights = self
438			.edges
439			.iter()
440			.filter_map(|e| if e.candidate.borrow().elected { Some(e.weight) } else { None })
441			.collect::<Vec<_>>();
442		elected_edge_weights.normalize(self.budget).map(|normalized| {
443			// here we count on the fact that normalize does not change the order, and that vector
444			// iteration is deterministic.
445			for (edge, corrected) in self
446				.edges
447				.iter_mut()
448				.filter(|e| e.candidate.borrow().elected)
449				.zip(normalized.into_iter())
450			{
451				let mut candidate = edge.candidate.borrow_mut();
452				// first, subtract the incorrect weight
453				candidate.backed_stake = candidate.backed_stake.saturating_sub(edge.weight);
454				edge.weight = corrected;
455				// Then add the correct one again.
456				candidate.backed_stake = candidate.backed_stake.saturating_add(edge.weight);
457			}
458		})
459	}
460
461	/// This voter's budget.
462	#[inline]
463	pub fn budget(&self) -> ExtendedBalance {
464		self.budget
465	}
466}
467
468/// Final result of the election.
469#[derive(Debug)]
470pub struct ElectionResult<AccountId, P: PerThing> {
471	/// Just winners zipped with their approval stake. Note that the approval stake is merely the
472	/// sub of their received stake and could be used for very basic sorting and approval voting.
473	pub winners: Vec<(AccountId, ExtendedBalance)>,
474	/// Individual assignments. for each tuple, the first elements is a voter and the second is the
475	/// list of candidates that it supports.
476	pub assignments: Vec<Assignment<AccountId, P>>,
477}
478
479/// A structure to demonstrate the election result from the perspective of the candidate, i.e. how
480/// much support each candidate is receiving.
481///
482/// This complements the [`ElectionResult`] and is needed to run the balancing post-processing.
483///
484/// This, at the current version, resembles the `Exposure` defined in the Staking pallet, yet they
485/// do not necessarily have to be the same.
486#[derive(Debug, Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)]
487#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
488pub struct Support<AccountId> {
489	/// Total support.
490	pub total: ExtendedBalance,
491	/// Support from voters.
492	pub voters: Vec<(AccountId, ExtendedBalance)>,
493}
494
495impl<AccountId> Default for Support<AccountId> {
496	fn default() -> Self {
497		Self { total: Default::default(), voters: vec![] }
498	}
499}
500
501impl<AccountId> Backings for &Support<AccountId> {
502	fn total(&self) -> ExtendedBalance {
503		self.total
504	}
505}
506
507/// A target-major representation of the the election outcome.
508///
509/// Essentially a flat variant of [`SupportMap`].
510///
511/// The main advantage of this is that it is encodable.
512pub type Supports<A> = Vec<(A, Support<A>)>;
513
514/// Linkage from a winner to their [`Support`].
515///
516/// This is more helpful than a normal [`Supports`] as it allows faster error checking.
517pub type SupportMap<A> = BTreeMap<A, Support<A>>;
518
519/// Build the support map from the assignments.
520pub fn to_support_map<AccountId: IdentifierT>(
521	assignments: &[StakedAssignment<AccountId>],
522) -> SupportMap<AccountId> {
523	let mut supports = <BTreeMap<AccountId, Support<AccountId>>>::new();
524
525	// build support struct.
526	for StakedAssignment { who, distribution } in assignments.iter() {
527		for (c, weight_extended) in distribution.iter() {
528			let support = supports.entry(c.clone()).or_default();
529			support.total = support.total.saturating_add(*weight_extended);
530			support.voters.push((who.clone(), *weight_extended));
531		}
532	}
533
534	supports
535}
536
537/// Same as [`to_support_map`] except it returns a flat vector.
538pub fn to_supports<AccountId: IdentifierT>(
539	assignments: &[StakedAssignment<AccountId>],
540) -> Supports<AccountId> {
541	to_support_map(assignments).into_iter().collect()
542}
543
544/// Extension trait for evaluating a support map or vector.
545pub trait EvaluateSupport {
546	/// Evaluate a support map. The returned tuple contains:
547	///
548	/// - Minimum support. This value must be **maximized**.
549	/// - Sum of all supports. This value must be **maximized**.
550	/// - Sum of all supports squared. This value must be **minimized**.
551	fn evaluate(&self) -> ElectionScore;
552}
553
554impl<AccountId: IdentifierT> EvaluateSupport for Supports<AccountId> {
555	fn evaluate(&self) -> ElectionScore {
556		evaluate_support(self.iter().map(|(_, s)| s))
557	}
558}
559
560/// Generic representation of a support.
561pub trait Backings {
562	/// The total backing of an individual target.
563	fn total(&self) -> ExtendedBalance;
564}
565
566/// General evaluation of a list of backings that returns an election score.
567pub fn evaluate_support(backings: impl Iterator<Item = impl Backings>) -> ElectionScore {
568	let mut minimal_stake = ExtendedBalance::max_value();
569	let mut sum_stake: ExtendedBalance = Zero::zero();
570	// NOTE: The third element might saturate but fine for now since this will run on-chain and
571	// need to be fast.
572	let mut sum_stake_squared: ExtendedBalance = Zero::zero();
573
574	for support in backings {
575		sum_stake = sum_stake.saturating_add(support.total());
576		let squared = support.total().saturating_mul(support.total());
577		sum_stake_squared = sum_stake_squared.saturating_add(squared);
578		if support.total() < minimal_stake {
579			minimal_stake = support.total();
580		}
581	}
582
583	ElectionScore { minimal_stake, sum_stake, sum_stake_squared }
584}
585
586/// Converts raw inputs to types used in this crate.
587///
588/// This will perform some cleanup that are most often important:
589/// - It drops any votes that are pointing to non-candidates.
590/// - It drops duplicate targets within a voter.
591pub fn setup_inputs<AccountId: IdentifierT>(
592	initial_candidates: Vec<AccountId>,
593	initial_voters: Vec<(AccountId, VoteWeight, impl IntoIterator<Item = AccountId>)>,
594) -> (Vec<CandidatePtr<AccountId>>, Vec<Voter<AccountId>>) {
595	// used to cache and access candidates index.
596	let mut c_idx_cache = BTreeMap::<AccountId, usize>::new();
597
598	let candidates = initial_candidates
599		.into_iter()
600		.enumerate()
601		.map(|(idx, who)| {
602			c_idx_cache.insert(who.clone(), idx);
603			Candidate {
604				who,
605				score: Default::default(),
606				approval_stake: Default::default(),
607				backed_stake: Default::default(),
608				elected: Default::default(),
609				round: Default::default(),
610			}
611			.to_ptr()
612		})
613		.collect::<Vec<CandidatePtr<AccountId>>>();
614
615	let voters = initial_voters
616		.into_iter()
617		.filter_map(|(who, voter_stake, votes)| {
618			let mut edges: Vec<Edge<AccountId>> = Vec::new();
619			for v in votes {
620				if edges.iter().any(|e| e.who == v) {
621					// duplicate edge.
622					continue
623				}
624				if let Some(idx) = c_idx_cache.get(&v) {
625					// This candidate is valid + already cached.
626					let mut candidate = candidates[*idx].borrow_mut();
627					candidate.approval_stake =
628						candidate.approval_stake.saturating_add(voter_stake.into());
629					edges.push(Edge {
630						who: v.clone(),
631						candidate: Rc::clone(&candidates[*idx]),
632						load: Default::default(),
633						weight: Default::default(),
634					});
635				} // else {} would be wrong votes. We don't really care about it.
636			}
637			if edges.is_empty() {
638				None
639			} else {
640				Some(Voter { who, edges, budget: voter_stake.into(), load: Rational128::zero() })
641			}
642		})
643		.collect::<Vec<_>>();
644
645	(candidates, voters)
646}