1use std::{
7 collections::{BTreeMap, BTreeSet, HashMap},
8 fmt::{Display, Formatter, Write},
9 hash::{Hash, Hasher},
10};
11
12use fastcrypto::traits::KeyPair;
13pub use iota_protocol_config::ProtocolVersion;
14use once_cell::sync::OnceCell;
15use rand::{
16 Rng, SeedableRng,
17 rngs::{StdRng, ThreadRng},
18 seq::SliceRandom,
19};
20use serde::{Deserialize, Serialize};
21
22use super::base_types::*;
23use crate::{
24 crypto::{
25 AuthorityKeyPair, AuthorityPublicKey, NetworkPublicKey, random_committee_key_pairs_of_size,
26 },
27 error::{IotaError, IotaResult},
28 multiaddr::Multiaddr,
29};
30
31pub type EpochId = u64;
32
33pub type StakeUnit = u64;
37
38pub type CommitteeDigest = [u8; 32];
39
40pub const TOTAL_VOTING_POWER: StakeUnit = 10_000;
50
51pub const QUORUM_THRESHOLD: StakeUnit = 6_667;
54
55pub const VALIDITY_THRESHOLD: StakeUnit = 3_334;
57
58#[derive(Clone, Debug, Serialize, Deserialize, Eq)]
59pub struct Committee {
60 pub epoch: EpochId,
61 pub voting_rights: Vec<(AuthorityName, StakeUnit)>,
62 expanded_keys: HashMap<AuthorityName, AuthorityPublicKey>,
63 index_map: HashMap<AuthorityName, usize>,
64}
65
66impl Committee {
67 pub fn new(epoch: EpochId, voting_rights: BTreeMap<AuthorityName, StakeUnit>) -> Self {
68 let mut voting_rights: Vec<(AuthorityName, StakeUnit)> =
69 voting_rights.iter().map(|(a, s)| (*a, *s)).collect();
70
71 assert!(!voting_rights.is_empty());
72 assert!(voting_rights.iter().any(|(_, s)| *s != 0));
73
74 voting_rights.sort_by_key(|(a, _)| *a);
75 let total_votes: StakeUnit = voting_rights.iter().map(|(_, votes)| *votes).sum();
76 assert_eq!(total_votes, TOTAL_VOTING_POWER);
77
78 let (expanded_keys, index_map) = Self::load_inner(&voting_rights);
79
80 Committee {
81 epoch,
82 voting_rights,
83 expanded_keys,
84 index_map,
85 }
86 }
87
88 pub fn new_for_testing_with_normalized_voting_power(
92 epoch: EpochId,
93 mut voting_weights: BTreeMap<AuthorityName, StakeUnit>,
94 ) -> Self {
95 let num_nodes = voting_weights.len();
96 let total_votes: StakeUnit = voting_weights.values().cloned().sum();
97
98 let normalization_coef = TOTAL_VOTING_POWER as f64 / total_votes as f64;
99 let mut total_sum = 0;
100 for (idx, (_auth, weight)) in voting_weights.iter_mut().enumerate() {
101 if idx < num_nodes - 1 {
102 *weight = (*weight as f64 * normalization_coef).floor() as u64; total_sum += *weight;
104 } else {
105 *weight = TOTAL_VOTING_POWER - total_sum;
107 }
108 }
109
110 Self::new(epoch, voting_weights)
111 }
112
113 pub fn load_inner(
115 voting_rights: &[(AuthorityName, StakeUnit)],
116 ) -> (
117 HashMap<AuthorityName, AuthorityPublicKey>,
118 HashMap<AuthorityName, usize>,
119 ) {
120 let expanded_keys: HashMap<AuthorityName, AuthorityPublicKey> = voting_rights
121 .iter()
122 .map(|(addr, _)| {
123 (
124 *addr,
125 (*addr)
126 .try_into()
127 .expect("Validator pubkey is always verified on-chain"),
128 )
129 })
130 .collect();
131
132 let index_map: HashMap<AuthorityName, usize> = voting_rights
133 .iter()
134 .enumerate()
135 .map(|(index, (addr, _))| (*addr, index))
136 .collect();
137 (expanded_keys, index_map)
138 }
139
140 pub fn authority_index(&self, author: &AuthorityName) -> Option<u32> {
141 self.index_map.get(author).map(|i| *i as u32)
142 }
143
144 pub fn authority_by_index(&self, index: u32) -> Option<&AuthorityName> {
145 self.voting_rights.get(index as usize).map(|(name, _)| name)
146 }
147
148 pub fn epoch(&self) -> EpochId {
149 self.epoch
150 }
151
152 pub fn public_key(&self, authority: &AuthorityName) -> IotaResult<&AuthorityPublicKey> {
153 debug_assert_eq!(self.expanded_keys.len(), self.voting_rights.len());
154 match self.expanded_keys.get(authority) {
155 Some(v) => Ok(v),
156 None => Err(IotaError::InvalidCommittee(format!(
157 "Authority #{} not found, committee size {}",
158 authority,
159 self.expanded_keys.len()
160 ))),
161 }
162 }
163
164 pub fn sample(&self) -> &AuthorityName {
166 Self::choose_multiple_weighted(&self.voting_rights[..], 1, &mut ThreadRng::default())
168 .next()
169 .unwrap()
170 }
171
172 fn choose_multiple_weighted<'a>(
173 slice: &'a [(AuthorityName, StakeUnit)],
174 count: usize,
175 rng: &mut impl Rng,
176 ) -> impl Iterator<Item = &'a AuthorityName> {
177 slice
181 .choose_multiple_weighted(rng, count, |(_, weight)| *weight as f64)
182 .unwrap()
183 .map(|(a, _)| a)
184 }
185
186 pub fn choose_multiple_weighted_iter(
187 &self,
188 count: usize,
189 ) -> impl Iterator<Item = &AuthorityName> {
190 self.voting_rights
191 .choose_multiple_weighted(&mut ThreadRng::default(), count, |(_, weight)| {
192 *weight as f64
193 })
194 .unwrap()
195 .map(|(a, _)| a)
196 }
197
198 pub fn total_votes(&self) -> StakeUnit {
199 TOTAL_VOTING_POWER
200 }
201
202 pub fn quorum_threshold(&self) -> StakeUnit {
203 QUORUM_THRESHOLD
204 }
205
206 pub fn validity_threshold(&self) -> StakeUnit {
207 VALIDITY_THRESHOLD
208 }
209
210 pub fn threshold<const STRENGTH: bool>(&self) -> StakeUnit {
211 if STRENGTH {
212 QUORUM_THRESHOLD
213 } else {
214 VALIDITY_THRESHOLD
215 }
216 }
217
218 pub fn effective_threshold(&self, mut buffer_stake_bps: u64) -> StakeUnit {
227 if buffer_stake_bps > 10000 {
228 buffer_stake_bps = 10000;
229 }
230 let quorum_threshold = self.quorum_threshold();
231 let f = self.total_votes() - quorum_threshold;
232 let buffer_stake = (f * buffer_stake_bps).div_ceil(10000);
233 quorum_threshold + buffer_stake
234 }
235
236 pub fn num_members(&self) -> usize {
237 self.voting_rights.len()
238 }
239
240 pub fn members(&self) -> impl Iterator<Item = &(AuthorityName, StakeUnit)> {
241 self.voting_rights.iter()
242 }
243
244 pub fn names(&self) -> impl Iterator<Item = &AuthorityName> {
245 self.voting_rights.iter().map(|(name, _)| name)
246 }
247
248 pub fn stakes(&self) -> impl Iterator<Item = StakeUnit> + '_ {
249 self.voting_rights.iter().map(|(_, stake)| *stake)
250 }
251
252 pub fn authority_exists(&self, name: &AuthorityName) -> bool {
253 self.voting_rights
254 .binary_search_by_key(name, |(a, _)| *a)
255 .is_ok()
256 }
257
258 pub fn shuffle_by_stake_from_tx_digest(
261 &self,
262 tx_digest: &TransactionDigest,
263 ) -> Vec<AuthorityName> {
264 let digest_bytes = tx_digest.into_inner();
266
267 let mut rng = StdRng::from_seed(digest_bytes);
269 self.shuffle_by_stake_with_rng(None, None, &mut rng)
270 }
271
272 pub fn new_simple_test_committee_of_size(size: usize) -> (Self, Vec<AuthorityKeyPair>) {
275 let key_pairs: Vec<_> = random_committee_key_pairs_of_size(size)
276 .into_iter()
277 .collect();
278 let committee = Self::new_for_testing_with_normalized_voting_power(
279 0,
280 key_pairs
281 .iter()
282 .map(|key| {
283 (AuthorityName::from(key.public()), 1)
284 })
285 .collect(),
286 );
287 (committee, key_pairs)
288 }
289
290 pub fn new_simple_test_committee() -> (Self, Vec<AuthorityKeyPair>) {
293 Self::new_simple_test_committee_of_size(4)
294 }
295}
296
297impl CommitteeTrait<AuthorityName> for Committee {
298 fn shuffle_by_stake_with_rng(
299 &self,
300 preferences: Option<&BTreeSet<AuthorityName>>,
302 restrict_to: Option<&BTreeSet<AuthorityName>>,
304 rng: &mut impl Rng,
305 ) -> Vec<AuthorityName> {
306 let restricted = self
307 .voting_rights
308 .iter()
309 .filter(|(name, _)| {
310 if let Some(restrict_to) = restrict_to {
311 restrict_to.contains(name)
312 } else {
313 true
314 }
315 })
316 .cloned();
317
318 let (preferred, rest): (Vec<_>, Vec<_>) = if let Some(preferences) = preferences {
319 restricted.partition(|(name, _)| preferences.contains(name))
320 } else {
321 (Vec::new(), restricted.collect())
322 };
323
324 Self::choose_multiple_weighted(&preferred, preferred.len(), rng)
325 .chain(Self::choose_multiple_weighted(&rest, rest.len(), rng))
326 .cloned()
327 .collect()
328 }
329
330 fn weight(&self, author: &AuthorityName) -> StakeUnit {
331 match self.voting_rights.binary_search_by_key(author, |(a, _)| *a) {
332 Err(_) => 0,
333 Ok(idx) => self.voting_rights[idx].1,
334 }
335 }
336}
337
338impl PartialEq for Committee {
339 fn eq(&self, other: &Self) -> bool {
340 self.epoch == other.epoch && self.voting_rights == other.voting_rights
341 }
342}
343
344impl Hash for Committee {
345 fn hash<H: Hasher>(&self, state: &mut H) {
346 self.epoch.hash(state);
347 self.voting_rights.hash(state);
348 }
349}
350
351impl Display for Committee {
352 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
353 let mut voting_rights = String::new();
354 for (name, vote) in &self.voting_rights {
355 write!(voting_rights, "{}: {}, ", name.concise(), vote)?;
356 }
357 write!(
358 f,
359 "Committee (epoch={:?}, voting_rights=[{}])",
360 self.epoch, voting_rights
361 )
362 }
363}
364
365pub trait CommitteeTrait<K: Ord> {
366 fn shuffle_by_stake_with_rng(
367 &self,
368 preferences: Option<&BTreeSet<K>>,
370 restrict_to: Option<&BTreeSet<K>>,
372 rng: &mut impl Rng,
373 ) -> Vec<K>;
374
375 fn shuffle_by_stake(
376 &self,
377 preferences: Option<&BTreeSet<K>>,
379 restrict_to: Option<&BTreeSet<K>>,
381 ) -> Vec<K> {
382 self.shuffle_by_stake_with_rng(preferences, restrict_to, &mut ThreadRng::default())
383 }
384
385 fn weight(&self, author: &K) -> StakeUnit;
386}
387
388#[derive(Clone, Debug, Serialize, Deserialize)]
389pub struct NetworkMetadata {
390 pub network_address: Multiaddr,
391 pub primary_address: Multiaddr,
392 pub network_public_key: Option<NetworkPublicKey>,
393}
394
395#[derive(Clone, Debug, Serialize, Deserialize)]
396pub struct CommitteeWithNetworkMetadata {
397 epoch_id: EpochId,
398 validators: BTreeMap<AuthorityName, (StakeUnit, NetworkMetadata)>,
399
400 #[serde(skip)]
401 committee: OnceCell<Committee>,
402}
403
404impl CommitteeWithNetworkMetadata {
405 pub fn new(
406 epoch_id: EpochId,
407 validators: BTreeMap<AuthorityName, (StakeUnit, NetworkMetadata)>,
408 ) -> Self {
409 Self {
410 epoch_id,
411 validators,
412 committee: OnceCell::new(),
413 }
414 }
415 pub fn epoch(&self) -> EpochId {
416 self.epoch_id
417 }
418
419 pub fn validators(&self) -> &BTreeMap<AuthorityName, (StakeUnit, NetworkMetadata)> {
420 &self.validators
421 }
422
423 pub fn committee(&self) -> &Committee {
424 self.committee.get_or_init(|| {
425 Committee::new(
426 self.epoch_id,
427 self.validators
428 .iter()
429 .map(|(name, (stake, _))| (*name, *stake))
430 .collect(),
431 )
432 })
433 }
434}
435
436impl Display for CommitteeWithNetworkMetadata {
437 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
438 write!(
439 f,
440 "CommitteeWithNetworkMetadata (epoch={}, validators={:?})",
441 self.epoch_id, self.validators
442 )
443 }
444}
445
446#[cfg(test)]
447mod test {
448 use fastcrypto::traits::KeyPair;
449
450 use super::*;
451 use crate::crypto::{AuthorityKeyPair, get_key_pair};
452
453 #[test]
454 fn test_shuffle_by_weight() {
455 let (_, sec1): (_, AuthorityKeyPair) = get_key_pair();
456 let (_, sec2): (_, AuthorityKeyPair) = get_key_pair();
457 let (_, sec3): (_, AuthorityKeyPair) = get_key_pair();
458 let a1: AuthorityName = sec1.public().into();
459 let a2: AuthorityName = sec2.public().into();
460 let a3: AuthorityName = sec3.public().into();
461
462 let mut authorities = BTreeMap::new();
463 authorities.insert(a1, 1);
464 authorities.insert(a2, 1);
465 authorities.insert(a3, 1);
466
467 let committee = Committee::new_for_testing_with_normalized_voting_power(0, authorities);
468
469 assert_eq!(committee.shuffle_by_stake(None, None).len(), 3);
470
471 let mut pref = BTreeSet::new();
472 pref.insert(a2);
473
474 for _ in 0..100 {
476 assert_eq!(
477 a2,
478 *committee
479 .shuffle_by_stake(Some(&pref), None)
480 .first()
481 .unwrap()
482 );
483 }
484
485 let mut restrict = BTreeSet::new();
486 restrict.insert(a2);
487
488 for _ in 0..100 {
489 let res = committee.shuffle_by_stake(None, Some(&restrict));
490 assert_eq!(1, res.len());
491 assert_eq!(a2, res[0]);
492 }
493
494 let res = committee.shuffle_by_stake(Some(&BTreeSet::new()), None);
496 assert_eq!(3, res.len());
497
498 let res = committee.shuffle_by_stake(None, Some(&BTreeSet::new()));
499 assert_eq!(0, res.len());
500 }
501}