finality_grandpa/
voter_set.rs1use crate::{
20 std::{
21 collections::{btree_map::Entry, BTreeMap},
22 num::{NonZeroU64, NonZeroUsize},
23 vec::Vec,
24 },
25 weights::VoterWeight,
26};
27
28#[derive(Clone, PartialEq, Eq, Debug)]
34pub struct VoterSet<Id: Eq + Ord> {
35 voters: Vec<(Id, VoterInfo)>,
37 threshold: VoterWeight,
39 total_weight: VoterWeight,
41}
42
43impl<Id: Eq + Ord> VoterSet<Id> {
44 pub fn new<I>(weights: I) -> Option<Self>
54 where
55 Id: Ord + Clone,
56 I: IntoIterator<Item = (Id, u64)>,
57 {
58 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 total_weight = total_weight.checked_add(weight)?;
67 match voters.entry(id) {
68 Entry::Vacant(e) => {
69 e.insert(VoterInfo {
70 position: 0, 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 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 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 pub fn len(&self) -> NonZeroUsize {
113 unsafe {
114 NonZeroUsize::new_unchecked(self.voters.len())
116 }
117 }
118
119 pub fn contains(&self, id: &Id) -> bool {
121 self.voters.binary_search_by_key(&id, |(id, _)| id).is_ok()
122 }
123
124 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 pub fn nth(&self, n: usize) -> Option<(&Id, &VoterInfo)> {
134 self.voters.get(n).map(|(id, info)| (id, info))
135 }
136
137 pub fn threshold(&self) -> VoterWeight {
140 self.threshold
141 }
142
143 pub fn total_weight(&self) -> VoterWeight {
145 self.total_weight
146 }
147
148 pub fn iter(&self) -> impl Iterator<Item = (&Id, &VoterInfo)> {
151 self.voters.iter().map(|(id, info)| (id, info))
152 }
153}
154
155#[derive(Clone, PartialEq, Eq, Debug)]
157pub struct VoterInfo {
158 position: usize,
159 weight: VoterWeight,
160}
161
162impl VoterInfo {
163 pub fn position(&self) -> usize {
166 self.position
167 }
168
169 pub fn weight(&self) -> VoterWeight {
171 self.weight
172 }
173}
174
175fn 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 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 v.iter().all(|(_, w)| w == &0) ||
221 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 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}