1use crate::Weight;
2use alloc::vec::Vec;
3use core::iter::repeat_n;
4use rand::SeedableRng;
5use rand::seq::SliceRandom;
6use rand_chacha::ChaCha20Rng;
7
8pub fn select_authorities<SC>(
24 registered_seats: u16,
25 permissioned_seats: u16,
26 registered_candidates: Vec<(SC, Weight)>,
27 permissioned_candidates: Vec<SC>,
28 seed: <ChaCha20Rng as SeedableRng>::Seed,
29) -> Option<Vec<SC>>
30where
31 SC: Ord + Clone,
32{
33 let seats_total = permissioned_seats + registered_seats;
34 let mut rng = ChaCha20Rng::from_seed(seed);
35 let permissioned_candidates: Vec<(SC, Weight)> =
36 permissioned_candidates.into_iter().map(|c| (c, 1)).collect();
37
38 let mut selected = if !registered_candidates.is_empty() && !permissioned_candidates.is_empty() {
39 let registered_selected =
40 weighted_with_guaranteed_assignment(registered_candidates, registered_seats, &mut rng);
41 let permissioned_selected = weighted_with_guaranteed_assignment(
42 permissioned_candidates,
43 permissioned_seats,
44 &mut rng,
45 );
46 let mut selected = registered_selected;
47 selected.extend(permissioned_selected);
48 selected
49 } else if registered_candidates.is_empty() {
50 weighted_with_guaranteed_assignment(permissioned_candidates, seats_total, &mut rng)
52 } else {
53 weighted_with_guaranteed_assignment(registered_candidates, seats_total, &mut rng)
55 };
56 selected.shuffle(&mut rng);
57 if selected.is_empty() && seats_total > 0 { None } else { Some(selected) }
58}
59
60struct SelectGuaranteedResult<T> {
61 selected: Vec<T>,
62 remaining: Vec<(T, Weight)>,
63}
64
65pub fn weighted_with_guaranteed_assignment<T: Clone + Ord>(
69 mut candidates: Vec<(T, Weight)>,
70 n: u16,
71 rng: &mut ChaCha20Rng,
72) -> Vec<T> {
73 if candidates.is_empty() || n == 0 {
74 return Vec::with_capacity(0);
75 }
76 candidates.sort();
77 let SelectGuaranteedResult { mut selected, remaining } = select_guaranteed(candidates, n);
78 let selected_count: u16 = selected.len().try_into().expect("selected count can not exceed u16");
79 selected.extend(select_remaining(remaining, n - selected_count, rng));
80 selected
81}
82
83fn select_guaranteed<T: Clone + Ord>(
84 weighted_candidates: Vec<(T, Weight)>,
85 n: u16,
86) -> SelectGuaranteedResult<T> {
87 let threshold: u128 = weighted_candidates.iter().map(|(_, weight)| weight).sum();
88 let scale: u128 = u128::from(n);
89 let scaled_candidates = weighted_candidates
90 .into_iter()
91 .filter(|(_, weight)| *weight > 0)
92 .map(|(c, weight)| (c, weight * scale));
93 let mut selected = Vec::with_capacity(n.into());
94 let mut remaining = Vec::with_capacity(n.into());
95 for (c, weight) in scaled_candidates {
96 let guaranteed: usize = (weight / threshold).try_into().expect("fits u16");
97 let remaining_weight = weight % threshold;
98 selected.extend(repeat_n(c.clone(), guaranteed));
99 if remaining_weight > 0 {
100 remaining.push((c.clone(), remaining_weight))
101 }
102 }
103 SelectGuaranteedResult { selected, remaining }
104}
105
106fn select_remaining<T: Clone>(
107 weighted_candidates: Vec<(T, Weight)>,
108 n: u16,
109 rng: &mut ChaCha20Rng,
110) -> Vec<T> {
111 crate::weighted_random::select_authorities(weighted_candidates, rng.get_seed(), n)
112 .unwrap_or_else(|| Vec::with_capacity(0))
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use crate::tests::*;
119 use quickcheck_macros::quickcheck;
120
121 #[quickcheck]
122 fn guaranteed_places_are_given_no_empty(seed: TestNonce) {
123 let big_stake = 9223372036854775807u128;
124 let committee = select_authorities(
127 5,
128 3,
129 vec![("R1", big_stake * 2), ("R2", big_stake), ("R3", big_stake)],
130 vec!["P1", "P2"],
131 seed.0,
132 )
133 .unwrap();
134 let p1 = committee.iter().filter(|c| **c == "P1").count();
135 let r1 = committee.iter().filter(|c| **c == "R1").count();
136 let r2 = committee.iter().filter(|c| **c == "R2").count();
137 assert!(1 <= p1 && p1 <= 2);
138 assert!(2 <= r1 && r1 <= 3);
139 assert!(1 <= r2 && r2 <= 2);
140 assert_eq!(committee.len(), 8);
141 }
142
143 #[quickcheck]
144 fn permissioned_get_p_seats_registered_get_r_seats(p: u8, r: u8, seed: TestNonce) {
145 let p: u16 = p.into();
149 let r: u16 = r.into();
150 let registered_candidates: Vec<_> =
151 (0..2 * r).into_iter().map(|i| (format!("R{i}"), 1)).collect();
152 let permissioned_candidates: Vec<_> =
153 (0..2 * r).into_iter().map(|i| format!("P{i}")).collect();
154 let result = select_authorities(
155 r.into(),
156 p.into(),
157 registered_candidates.clone(),
158 permissioned_candidates.clone(),
159 seed.0,
160 );
161 match result {
162 None => assert!(
163 (p > 0 || r > 0)
164 && registered_candidates.is_empty()
165 && permissioned_candidates.is_empty()
166 ),
167 Some(committee) => {
168 let permissioned = committee.iter().filter(|c| c.starts_with("P")).count();
169 let registered = committee.iter().filter(|c| c.starts_with("R")).count();
170 assert_eq!(permissioned, p.into());
171 assert_eq!(registered, r.into());
172 },
173 }
174 }
175
176 #[quickcheck]
177 fn guaranteed_places_are_given_only_registered(seed: TestNonce) {
178 let committee =
181 select_authorities(5, 6, vec![("R1", 100), ("R2", 100), ("R3", 100)], vec![], seed.0)
182 .unwrap();
183 for key in vec!["R1", "R2", "R3"] {
184 let candidate_count = committee.iter().filter(|c| **c == key).count();
185 assert!(3 <= candidate_count && candidate_count <= 5)
186 }
187 }
188
189 #[quickcheck]
190 fn guaranteed_places_are_given_only_permissioned(seed: TestNonce) {
191 let permissioned = vec!["P1", "P2", "P3"];
192 let committee = select_authorities(5, 5, vec![], permissioned, seed.0).unwrap();
195 for key in vec!["P1", "P2", "P3"] {
196 let candidate_count = committee.iter().filter(|c| **c == key).count();
197 assert!(3 <= candidate_count && candidate_count <= 4)
198 }
199 }
200
201 #[test]
202 fn remaining_seats_are_given_according_to_weights() {
203 let permissioned = vec!["P1", "P2", "P3"];
205 let registered = vec![("R1", 40), ("R2", 60)];
207
208 let mut p1_count = 0;
209 let mut r1_count = 0;
210 let mut r2_count = 0;
211 for i in 0u32..10000 {
212 let mut seed = [0u8; 32];
213 seed[0..4].copy_from_slice(&i.to_le_bytes());
214
215 let committee =
216 select_authorities(3, 4, registered.clone(), permissioned.clone(), seed).unwrap();
217 p1_count += committee.iter().filter(|c| **c == "P1").count();
218 r1_count += committee.iter().filter(|c| **c == "R1").count();
219 r2_count += committee.iter().filter(|c| **c == "R2").count();
220 }
221 let tolerance = 50;
222 assert!(13330 - tolerance <= p1_count && p1_count <= 13330 + tolerance);
223 assert!(12000 - tolerance <= r1_count && r1_count <= 12000 + tolerance);
224 assert!(18000 - tolerance <= r2_count && r2_count <= 18000 + tolerance);
225 }
226
227 #[test]
228 fn use_registered_candidates_when_r_is_0_and_there_are_no_permissioned_candidates() {
229 let committee =
230 select_authorities(0, 3, vec![("R1", 100), ("R2", 100)], vec![], [0u8; 32]).unwrap();
231 assert_eq!(committee.len(), 3)
232 }
233
234 #[test]
235 fn use_permissioned_candidates_when_p_is_0_and_there_are_no_registered_candidates() {
236 let committee = select_authorities(3, 0, vec![], vec!["P1", "P2"], [0u8; 32]).unwrap();
237 assert_eq!(committee.len(), 3)
238 }
239
240 #[quickcheck]
241 fn selects_empty_committee_for_0_seats(
242 trustless_candidates: Vec<(String, u128)>,
243 permissioned_candidates: Vec<String>,
244 seed: TestNonce,
245 ) {
246 let committee =
247 select_authorities(0, 0, trustless_candidates, permissioned_candidates, seed.0)
248 .unwrap();
249 assert_eq!(committee, Vec::<String>::new());
250 }
251
252 #[quickcheck]
253 fn registered_candidates_order_does_not_matter(seed: TestNonce) {
254 let registered_candidates = vec![("a", 1), ("b", 1), ("c", 1), ("d", 1), ("e", 1)];
255 let mut reversed_candidates = registered_candidates.clone();
256 reversed_candidates.reverse();
257 let committee_1 = select_authorities(4, 0, registered_candidates, vec![], seed.0).unwrap();
258 let committee_2 = select_authorities(4, 0, reversed_candidates, vec![], seed.0).unwrap();
259 assert_eq!(committee_1, committee_2)
260 }
261
262 #[quickcheck]
263 fn permissioned_candidates_order_does_not_matter(seed: TestNonce) {
264 let candidates = vec![("a", 1), ("b", 1), ("c", 1), ("d", 1), ("e", 1)];
265 let mut reversed_candidates = candidates.clone();
266 reversed_candidates.reverse();
267 let committee_1 = select_authorities(0, 4, vec![], candidates, seed.0).unwrap();
268 let committee_2 = select_authorities(0, 4, vec![], reversed_candidates, seed.0).unwrap();
269 assert_eq!(committee_1, committee_2)
270 }
271}