finality_grandpa/
voter_set.rs

1// Copyright 2018-2019 Parity Technologies (UK) Ltd
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Implementation of a `VoterSet`, representing the complete set
16//! of voters and their weights in the context of a round of the
17//! protocol.
18
19use crate::{
20	std::{
21		collections::{btree_map::Entry, BTreeMap},
22		num::{NonZeroU64, NonZeroUsize},
23		vec::Vec,
24	},
25	weights::VoterWeight,
26};
27
28/// A (non-empty) set of voters and associated weights.
29///
30/// A `VoterSet` identifies all voters that are permitted to vote in a round
31/// of the protocol and their associated weights. A `VoterSet` is furthermore
32/// equipped with a total order, given by the ordering of the voter's IDs.
33#[derive(Clone, PartialEq, Eq, Debug)]
34pub struct VoterSet<Id: Eq + Ord> {
35	/// The voters in the voter set, this vec is always sorted by the voter ID.
36	voters: Vec<(Id, VoterInfo)>,
37	/// The required weight threshold for supermajority w.r.t. this set.
38	threshold: VoterWeight,
39	/// The total weight of all voters.
40	total_weight: VoterWeight,
41}
42
43impl<Id: Eq + Ord> VoterSet<Id> {
44	/// Create a voter set from a weight distribution produced by the given iterator.
45	///
46	/// If the distribution contains multiple weights for the same voter ID, they are
47	/// understood to be partial weights and are accumulated. As a result, the
48	/// order in which the iterator produces the weights is irrelevant.
49	///
50	/// Returns `None` if the iterator does not yield a valid voter set, which is
51	/// the case if it either produced no non-zero weights or, i.e. the voter set
52	/// would be empty, or if the total voter weight exceeds `u64::MAX`.
53	pub fn new<I>(weights: I) -> Option<Self>
54	where
55		Id: Ord + Clone,
56		I: IntoIterator<Item = (Id, u64)>,
57	{
58		// Populate the voter set, thereby calculating the total weight.
59		let mut voters = BTreeMap::new();
60		let mut total_weight = 0u64;
61		for (id, weight) in weights {
62			if let Some(w) = NonZeroU64::new(weight) {
63				// Prevent construction of inconsistent voter sets by checking
64				// for weight overflow (not just in debug mode). The protocol
65				// should never run with such voter sets.
66				total_weight = total_weight.checked_add(weight)?;
67				match voters.entry(id) {
68					Entry::Vacant(e) => {
69						e.insert(VoterInfo {
70							position: 0, // The total order is determined afterwards.
71							weight: VoterWeight(w),
72						});
73					},
74					Entry::Occupied(mut e) => {
75						let v = e.get_mut();
76						let n = v.weight.get() + weight;
77						let w = NonZeroU64::new(n).expect("nonzero + nonzero is nonzero");
78						v.weight = VoterWeight(w);
79					},
80				}
81			}
82		}
83
84		if voters.is_empty() {
85			// No non-zero weights; the set would be empty.
86			return None
87		}
88
89		let voters = voters
90			.into_iter()
91			.enumerate()
92			.map(|(position, (id, mut info))| {
93				info.position = position;
94				(id, info)
95			})
96			.collect();
97
98		let total_weight = VoterWeight::new(total_weight).expect("voters nonempty; qed");
99
100		Some(VoterSet { voters, total_weight, threshold: threshold(total_weight) })
101	}
102
103	/// Get the voter info for the voter with the given ID, if any.
104	pub fn get(&self, id: &Id) -> Option<&VoterInfo> {
105		self.voters
106			.binary_search_by_key(&id, |(id, _)| id)
107			.ok()
108			.map(|idx| &self.voters[idx].1)
109	}
110
111	/// Get the size of the set.
112	pub fn len(&self) -> NonZeroUsize {
113		unsafe {
114			// SAFETY: By VoterSet::new()
115			NonZeroUsize::new_unchecked(self.voters.len())
116		}
117	}
118
119	/// Whether the set contains a voter with the given ID.
120	pub fn contains(&self, id: &Id) -> bool {
121		self.voters.binary_search_by_key(&id, |(id, _)| id).is_ok()
122	}
123
124	/// Get the nth voter in the set, modulo the size of the set,
125	/// as per the associated total order.
126	pub fn nth_mod(&self, n: usize) -> (&Id, &VoterInfo) {
127		self.nth(n % self.voters.len()).expect("set is nonempty and n % len < len; qed")
128	}
129
130	/// Get the nth voter in the set, if any.
131	///
132	/// Returns `None` if `n >= len`.
133	pub fn nth(&self, n: usize) -> Option<(&Id, &VoterInfo)> {
134		self.voters.get(n).map(|(id, info)| (id, info))
135	}
136
137	/// Get the threshold vote weight required for supermajority
138	/// w.r.t. this set of voters.
139	pub fn threshold(&self) -> VoterWeight {
140		self.threshold
141	}
142
143	/// Get the total weight of all voters.
144	pub fn total_weight(&self) -> VoterWeight {
145		self.total_weight
146	}
147
148	/// Get an iterator over the voters in the set, as given by
149	/// the associated total order.
150	pub fn iter(&self) -> impl Iterator<Item = (&Id, &VoterInfo)> {
151		self.voters.iter().map(|(id, info)| (id, info))
152	}
153}
154
155/// Information about a voter in a `VoterSet`.
156#[derive(Clone, PartialEq, Eq, Debug)]
157pub struct VoterInfo {
158	position: usize,
159	weight: VoterWeight,
160}
161
162impl VoterInfo {
163	/// Get the position of the voter in the total order associated
164	/// with the `VoterSet` from which the `VoterInfo` was obtained.
165	pub fn position(&self) -> usize {
166		self.position
167	}
168
169	/// Get the weight of the voter.
170	pub fn weight(&self) -> VoterWeight {
171		self.weight
172	}
173}
174
175/// Compute the threshold weight given the total voting weight.
176fn threshold(total_weight: VoterWeight) -> VoterWeight {
177	let faulty = total_weight.get().saturating_sub(1) / 3;
178	VoterWeight::new(total_weight.get() - faulty).expect("subtrahend > minuend; qed")
179}
180
181#[cfg(test)]
182mod tests {
183	use super::*;
184	use crate::std::iter;
185	use quickcheck::*;
186	use rand::{seq::SliceRandom, thread_rng};
187
188	impl<Id: Arbitrary + Eq + Ord> Arbitrary for VoterSet<Id> {
189		fn arbitrary(g: &mut Gen) -> VoterSet<Id> {
190			loop {
191				let mut ids = Vec::<Id>::arbitrary(g);
192				if ids.is_empty() {
193					ids.push(Id::arbitrary(g))
194				}
195
196				let weights = iter::from_fn(|| Some(u32::arbitrary(g) as u64));
197
198				// we might generate an invalid voter set above if:
199				// - all validators have 0 weight
200				// - the total weight is higher than `u64::max_value()`
201				//
202				// the easiest thing to do is to just retry generating another instance.
203				if let Some(set) = VoterSet::new(ids.into_iter().zip(weights)) {
204					break set
205				}
206			}
207		}
208	}
209
210	#[test]
211	fn equality() {
212		fn prop(mut v: Vec<(usize, u64)>) {
213			if let Some(v1) = VoterSet::new(v.clone()) {
214				v.shuffle(&mut thread_rng());
215				let v2 = VoterSet::new(v).expect("nonempty");
216				assert_eq!(v1, v2)
217			} else {
218				assert!(
219					// either no authority has a valid weight
220					v.iter().all(|(_, w)| w == &0) ||
221					// or the total weight overflows a u64
222					v.iter().map(|(_, w)| *w as u128).sum::<u128>() > u64::max_value() as u128
223				);
224			}
225		}
226
227		quickcheck(prop as fn(_))
228	}
229
230	#[test]
231	fn total_weight() {
232		fn prop(v: Vec<(usize, u64)>) {
233			let total_weight = v.iter().map(|(_, weight)| *weight as u128).sum::<u128>();
234
235			// this validator set is invalid
236			if total_weight > u64::max_value() as u128 {
237				return
238			}
239
240			let expected = VoterWeight::new(total_weight as u64);
241
242			if let Some(v1) = VoterSet::new(v) {
243				assert_eq!(Some(v1.total_weight()), expected)
244			} else {
245				assert_eq!(expected, None)
246			}
247		}
248
249		quickcheck(prop as fn(_))
250	}
251
252	#[test]
253	fn min_threshold() {
254		fn prop(v: VoterSet<usize>) -> bool {
255			let t = v.threshold.get();
256			let w = v.total_weight.get();
257			t >= 2 * (w / 3) + (w % 3)
258		}
259
260		quickcheck(prop as fn(_) -> _);
261	}
262}