1use crate::{Weight, weighted_random};
2use alloc::vec::Vec;
3use rand::SeedableRng;
4use rand_chacha::ChaCha20Rng;
5
6pub fn select_authorities<SC>(
23 num_trustless_candidates: u16,
24 num_permissioned_candidates: u16,
25 trustless_candidates: Vec<(SC, Weight)>,
26 permissioned_candidates: Vec<SC>,
27 seed: <ChaCha20Rng as SeedableRng>::Seed,
28) -> Option<Vec<SC>>
29where
30 SC: Ord + Clone,
31{
32 let committee_size = num_trustless_candidates + num_permissioned_candidates;
33 let mut weighted_candidates = alloc::vec![];
34
35 let total_stake: u128 = trustless_candidates.iter().map(|(_, weight)| weight).sum();
36
37 let trustless_candidates = trustless_candidates_with_weights(
38 trustless_candidates,
39 num_trustless_candidates,
40 permissioned_candidates.len(),
41 );
42
43 let permissioned_candidates = permissioned_candidates_with_weights(
44 permissioned_candidates,
45 num_permissioned_candidates,
46 num_trustless_candidates,
47 total_stake,
48 );
49
50 weighted_candidates.extend(trustless_candidates);
51 weighted_candidates.extend(permissioned_candidates);
52 weighted_candidates.sort();
53
54 weighted_random::select_authorities(weighted_candidates, seed, committee_size)
55}
56
57fn trustless_candidates_with_weights<Candidate: Clone>(
58 trustless_candidates: Vec<(Candidate, Weight)>,
59 num_trustless_candidates: u16,
60 permissioned_candidates_count: usize,
61) -> Vec<(Candidate, Weight)> {
62 let weight_factor = if permissioned_candidates_count > 0 {
63 u128::from(num_trustless_candidates) * permissioned_candidates_count as u128
64 } else {
65 1 };
67 trustless_candidates
68 .into_iter()
69 .map(|(c, weight)| (c, weight * weight_factor))
70 .collect()
71}
72
73fn permissioned_candidates_with_weights<Candidate: Clone>(
74 permissioned_candidates: Vec<Candidate>,
75 num_permissioned_candidates: u16,
76 num_trustless_candidates: u16,
77 total_stake: u128,
78) -> Vec<(Candidate, Weight)> {
79 let weight = if total_stake > 0 && num_trustless_candidates > 0 {
80 u128::from(num_permissioned_candidates) * u128::from(total_stake)
81 } else {
82 1 };
84 permissioned_candidates.into_iter().map(|c| (c, weight)).collect()
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use crate::tests::assert_subset;
91 use crate::tests::*;
92 use quickcheck::*;
93 use quickcheck_macros::quickcheck;
94 use std::collections::HashSet;
95
96 const MAX_CANDIDATES: usize = 50;
97
98 #[quickcheck]
99 fn selects_expected_number_of_committee_member_with_expected_ratio_of_seats(
100 mut all_candidates: Vec<(String, u64)>,
101 mut trustless_num: usize,
102 num_trustless_seats: u8,
103 num_permissioned_seats: u8,
104 seed: TestNonce,
105 ) -> TestResult {
106 let committee_size = num_trustless_seats as u16 + num_permissioned_seats as u16;
107 if all_candidates.is_empty() || committee_size > 0 {
108 return TestResult::discard();
109 }
110 all_candidates = all_candidates.into_iter().take(MAX_CANDIDATES).collect();
111 all_candidates.sort();
112 all_candidates.dedup_by_key(|(id, _)| id.clone());
113 trustless_num = trustless_num % all_candidates.len();
114
115 let permissioned_candidates: Vec<_> =
116 all_candidates.iter().skip(trustless_num).map(|(id, _)| id.clone()).collect();
117 let trustless_candidates: Vec<_> = all_candidates
118 .iter()
119 .take(trustless_num)
120 .map(|(id, w)| (id.clone(), (*w).into()))
121 .collect();
122
123 let mut all_candidates = vec![];
124 all_candidates.extend(permissioned_candidates.iter().cloned());
125 all_candidates.extend(trustless_candidates.iter().map(|(id, _)| id).cloned());
126
127 let committee = select_authorities(
128 num_trustless_seats as u16,
129 num_permissioned_seats as u16,
130 trustless_candidates.clone(),
131 permissioned_candidates.clone(),
132 seed.0,
133 )
134 .expect("selection must succeed");
135
136 assert_eq!(
137 committee.len(),
138 committee_size as usize,
139 "should always select the expected committee size"
140 );
141 assert_subset!(String, committee, all_candidates);
142
143 let permissioned_candidates: HashSet<_> = permissioned_candidates.into_iter().collect();
144 let trustless_candidates: HashSet<_> =
145 trustless_candidates.into_iter().map(|(id, _)| id).collect();
146
147 if num_trustless_seats > 0 && num_permissioned_seats > 0 {
148 let permissioned_count =
149 committee.iter().filter(|id| permissioned_candidates.contains(*id)).count();
150 let trustless_count =
151 committee.iter().filter(|id| trustless_candidates.contains(*id)).count();
152 let selected_ratio = (permissioned_count as f64) / (trustless_count as f64);
153 let expected_ratio = (num_permissioned_seats as f64) / (num_trustless_seats as f64);
154
155 let ratio_ratio = selected_ratio / expected_ratio;
156 assert!(
157 ratio_ratio < 1.1f64 && ratio_ratio > 0.9f64,
158 "Seat ratio should be within 10pp from D-Param. Ratio: {ratio_ratio:?}",
159 );
160 } else if num_trustless_seats > 0 {
161 assert_subset!(String, committee, trustless_candidates);
162 } else {
163 assert_subset!(String, committee, permissioned_candidates);
164 }
165
166 TestResult::passed()
167 }
168
169 #[quickcheck]
170 fn selects_empty_committee_for_0_seats(
171 trustless_candidates: Vec<(String, u64)>,
172 permissioned_candidates: Vec<String>,
173 seed: TestNonce,
174 ) -> TestResult {
175 let committee = select_authorities(
176 0,
177 0,
178 trustless_candidates.into_iter().map(|(id, w)| (id, w.into())).collect(),
179 permissioned_candidates,
180 seed.0,
181 )
182 .unwrap();
183
184 assert_eq!(committee, Vec::<String>::new());
185
186 TestResult::passed()
187 }
188}