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 messages_checkpoint::{CertifiedCheckpointSummary, VerifiedCheckpoint},
29 multiaddr::Multiaddr,
30};
31
32pub type EpochId = u64;
33
34pub type StakeUnit = u64;
38
39pub type CommitteeDigest = [u8; 32];
40
41pub const TOTAL_VOTING_POWER: StakeUnit = 10_000;
51
52pub const QUORUM_THRESHOLD: StakeUnit = 6_667;
55
56pub const VALIDITY_THRESHOLD: StakeUnit = 3_334;
58
59#[derive(Clone, Debug, Serialize, Deserialize, Eq)]
60pub struct Committee {
61 pub epoch: EpochId,
62 pub voting_rights: Vec<(AuthorityName, StakeUnit)>,
63 expanded_keys: HashMap<AuthorityName, AuthorityPublicKey>,
64 index_map: HashMap<AuthorityName, usize>,
65}
66
67impl Committee {
68 pub fn new(epoch: EpochId, voting_rights: BTreeMap<AuthorityName, StakeUnit>) -> Self {
69 let mut voting_rights: Vec<(AuthorityName, StakeUnit)> =
70 voting_rights.iter().map(|(a, s)| (*a, *s)).collect();
71
72 assert!(!voting_rights.is_empty());
73 assert!(voting_rights.iter().any(|(_, s)| *s != 0));
74
75 voting_rights.sort_by_key(|(a, _)| *a);
76 let total_votes: StakeUnit = voting_rights.iter().map(|(_, votes)| *votes).sum();
77 assert_eq!(total_votes, TOTAL_VOTING_POWER);
78
79 let (expanded_keys, index_map) = Self::load_inner(&voting_rights);
80
81 Committee {
82 epoch,
83 voting_rights,
84 expanded_keys,
85 index_map,
86 }
87 }
88
89 pub fn new_for_testing_with_normalized_voting_power(
93 epoch: EpochId,
94 mut voting_weights: BTreeMap<AuthorityName, StakeUnit>,
95 ) -> Self {
96 let num_nodes = voting_weights.len();
97 let total_votes: StakeUnit = voting_weights.values().cloned().sum();
98
99 let normalization_coef = TOTAL_VOTING_POWER as f64 / total_votes as f64;
100 let mut total_sum = 0;
101 for (idx, (_auth, weight)) in voting_weights.iter_mut().enumerate() {
102 if idx < num_nodes - 1 {
103 *weight = (*weight as f64 * normalization_coef).floor() as u64; total_sum += *weight;
105 } else {
106 *weight = TOTAL_VOTING_POWER - total_sum;
108 }
109 }
110
111 Self::new(epoch, voting_weights)
112 }
113
114 pub fn load_inner(
116 voting_rights: &[(AuthorityName, StakeUnit)],
117 ) -> (
118 HashMap<AuthorityName, AuthorityPublicKey>,
119 HashMap<AuthorityName, usize>,
120 ) {
121 let expanded_keys: HashMap<AuthorityName, AuthorityPublicKey> = voting_rights
122 .iter()
123 .map(|(addr, _)| {
124 (
125 *addr,
126 (*addr)
127 .try_into()
128 .expect("Validator pubkey is always verified on-chain"),
129 )
130 })
131 .collect();
132
133 let index_map: HashMap<AuthorityName, usize> = voting_rights
134 .iter()
135 .enumerate()
136 .map(|(index, (addr, _))| (*addr, index))
137 .collect();
138 (expanded_keys, index_map)
139 }
140
141 pub fn authority_index(&self, author: &AuthorityName) -> Option<u32> {
142 self.index_map.get(author).map(|i| *i as u32)
143 }
144
145 pub fn authority_by_index(&self, index: u32) -> Option<&AuthorityName> {
146 self.voting_rights.get(index as usize).map(|(name, _)| name)
147 }
148
149 pub fn epoch(&self) -> EpochId {
150 self.epoch
151 }
152
153 pub fn public_key(&self, authority: &AuthorityName) -> IotaResult<&AuthorityPublicKey> {
154 debug_assert_eq!(self.expanded_keys.len(), self.voting_rights.len());
155 match self.expanded_keys.get(authority) {
156 Some(v) => Ok(v),
157 None => Err(IotaError::InvalidCommittee(format!(
158 "Authority #{} not found, committee size {}",
159 authority,
160 self.expanded_keys.len()
161 ))),
162 }
163 }
164
165 pub fn sample(&self) -> &AuthorityName {
167 Self::choose_multiple_weighted(&self.voting_rights[..], 1, &mut ThreadRng::default())
169 .next()
170 .unwrap()
171 }
172
173 fn choose_multiple_weighted<'a>(
174 slice: &'a [(AuthorityName, StakeUnit)],
175 count: usize,
176 rng: &mut impl Rng,
177 ) -> impl Iterator<Item = &'a AuthorityName> {
178 slice
182 .choose_multiple_weighted(rng, count, |(_, weight)| *weight as f64)
183 .unwrap()
184 .map(|(a, _)| a)
185 }
186
187 pub fn choose_multiple_weighted_iter(
188 &self,
189 count: usize,
190 ) -> impl Iterator<Item = &AuthorityName> {
191 self.voting_rights
192 .choose_multiple_weighted(&mut ThreadRng::default(), count, |(_, weight)| {
193 *weight as f64
194 })
195 .unwrap()
196 .map(|(a, _)| a)
197 }
198
199 pub fn total_votes(&self) -> StakeUnit {
200 TOTAL_VOTING_POWER
201 }
202
203 pub fn quorum_threshold(&self) -> StakeUnit {
204 QUORUM_THRESHOLD
205 }
206
207 pub fn validity_threshold(&self) -> StakeUnit {
208 VALIDITY_THRESHOLD
209 }
210
211 pub fn threshold<const STRENGTH: bool>(&self) -> StakeUnit {
212 if STRENGTH {
213 QUORUM_THRESHOLD
214 } else {
215 VALIDITY_THRESHOLD
216 }
217 }
218
219 pub fn effective_threshold(&self, mut buffer_stake_bps: u64) -> StakeUnit {
228 if buffer_stake_bps > 10000 {
229 buffer_stake_bps = 10000;
230 }
231 let quorum_threshold = self.quorum_threshold();
232 let f = self.total_votes() - quorum_threshold;
233 let buffer_stake = (f * buffer_stake_bps).div_ceil(10000);
234 quorum_threshold + buffer_stake
235 }
236
237 pub fn num_members(&self) -> usize {
238 self.voting_rights.len()
239 }
240
241 pub fn members(&self) -> impl Iterator<Item = &(AuthorityName, StakeUnit)> {
242 self.voting_rights.iter()
243 }
244
245 pub fn names(&self) -> impl Iterator<Item = &AuthorityName> {
246 self.voting_rights.iter().map(|(name, _)| name)
247 }
248
249 pub fn stakes(&self) -> impl Iterator<Item = StakeUnit> + '_ {
250 self.voting_rights.iter().map(|(_, stake)| *stake)
251 }
252
253 pub fn stake_by_index(&self, index: u32) -> Option<StakeUnit> {
256 self.voting_rights
257 .get(index as usize)
258 .map(|(_, stake)| *stake)
259 }
260
261 pub fn authority_exists(&self, name: &AuthorityName) -> bool {
262 self.voting_rights
263 .binary_search_by_key(name, |(a, _)| *a)
264 .is_ok()
265 }
266
267 pub fn shuffle_by_stake_from_tx_digest(
270 &self,
271 tx_digest: &TransactionDigest,
272 ) -> Vec<AuthorityName> {
273 let digest_bytes = tx_digest.into_inner();
275
276 let mut rng = StdRng::from_seed(digest_bytes);
278 self.shuffle_by_stake_with_rng(None, None, &mut rng)
279 }
280
281 pub fn new_simple_test_committee_of_size(size: usize) -> (Self, Vec<AuthorityKeyPair>) {
284 let key_pairs: Vec<_> = random_committee_key_pairs_of_size(size)
285 .into_iter()
286 .collect();
287 let committee = Self::new_for_testing_with_normalized_voting_power(
288 0,
289 key_pairs
290 .iter()
291 .map(|key| {
292 (AuthorityName::from(key.public()), 1)
293 })
294 .collect(),
295 );
296 (committee, key_pairs)
297 }
298
299 pub fn new_simple_test_committee() -> (Self, Vec<AuthorityKeyPair>) {
302 Self::new_simple_test_committee_of_size(4)
303 }
304}
305
306impl CommitteeTrait<AuthorityName> for Committee {
307 fn shuffle_by_stake_with_rng(
308 &self,
309 preferences: Option<&BTreeSet<AuthorityName>>,
311 restrict_to: Option<&BTreeSet<AuthorityName>>,
313 rng: &mut impl Rng,
314 ) -> Vec<AuthorityName> {
315 let restricted = self
316 .voting_rights
317 .iter()
318 .filter(|(name, _)| {
319 if let Some(restrict_to) = restrict_to {
320 restrict_to.contains(name)
321 } else {
322 true
323 }
324 })
325 .cloned();
326
327 let (preferred, rest): (Vec<_>, Vec<_>) = if let Some(preferences) = preferences {
328 restricted.partition(|(name, _)| preferences.contains(name))
329 } else {
330 (Vec::new(), restricted.collect())
331 };
332
333 Self::choose_multiple_weighted(&preferred, preferred.len(), rng)
334 .chain(Self::choose_multiple_weighted(&rest, rest.len(), rng))
335 .cloned()
336 .collect()
337 }
338
339 fn weight(&self, author: &AuthorityName) -> StakeUnit {
340 match self.voting_rights.binary_search_by_key(author, |(a, _)| *a) {
341 Err(_) => 0,
342 Ok(idx) => self.voting_rights[idx].1,
343 }
344 }
345}
346
347impl PartialEq for Committee {
348 fn eq(&self, other: &Self) -> bool {
349 self.epoch == other.epoch && self.voting_rights == other.voting_rights
350 }
351}
352
353impl Hash for Committee {
354 fn hash<H: Hasher>(&self, state: &mut H) {
355 self.epoch.hash(state);
356 self.voting_rights.hash(state);
357 }
358}
359
360impl Display for Committee {
361 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
362 let mut voting_rights = String::new();
363 for (name, vote) in &self.voting_rights {
364 write!(voting_rights, "{}: {}, ", name.concise(), vote)?;
365 }
366 write!(
367 f,
368 "Committee (epoch={:?}, voting_rights=[{}])",
369 self.epoch, voting_rights
370 )
371 }
372}
373
374pub trait CommitteeTrait<K: Ord> {
375 fn shuffle_by_stake_with_rng(
376 &self,
377 preferences: Option<&BTreeSet<K>>,
379 restrict_to: Option<&BTreeSet<K>>,
381 rng: &mut impl Rng,
382 ) -> Vec<K>;
383
384 fn shuffle_by_stake(
385 &self,
386 preferences: Option<&BTreeSet<K>>,
388 restrict_to: Option<&BTreeSet<K>>,
390 ) -> Vec<K> {
391 self.shuffle_by_stake_with_rng(preferences, restrict_to, &mut ThreadRng::default())
392 }
393
394 fn weight(&self, author: &K) -> StakeUnit;
395}
396
397#[derive(Clone, Debug, Serialize, Deserialize)]
398pub struct NetworkMetadata {
399 pub network_address: Multiaddr,
400 pub primary_address: Multiaddr,
401 pub network_public_key: Option<NetworkPublicKey>,
402}
403
404#[derive(Clone, Debug, Serialize, Deserialize)]
405pub struct CommitteeWithNetworkMetadata {
406 epoch_id: EpochId,
407 validators: BTreeMap<AuthorityName, (StakeUnit, NetworkMetadata)>,
408
409 #[serde(skip)]
410 committee: OnceCell<Committee>,
411}
412
413impl CommitteeWithNetworkMetadata {
414 pub fn new(
415 epoch_id: EpochId,
416 validators: BTreeMap<AuthorityName, (StakeUnit, NetworkMetadata)>,
417 ) -> Self {
418 Self {
419 epoch_id,
420 validators,
421 committee: OnceCell::new(),
422 }
423 }
424 pub fn epoch(&self) -> EpochId {
425 self.epoch_id
426 }
427
428 pub fn validators(&self) -> &BTreeMap<AuthorityName, (StakeUnit, NetworkMetadata)> {
429 &self.validators
430 }
431
432 pub fn committee(&self) -> &Committee {
433 self.committee.get_or_init(|| {
434 Committee::new(
435 self.epoch_id,
436 self.validators
437 .iter()
438 .map(|(name, (stake, _))| (*name, *stake))
439 .collect(),
440 )
441 })
442 }
443}
444
445impl Display for CommitteeWithNetworkMetadata {
446 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
447 write!(
448 f,
449 "CommitteeWithNetworkMetadata (epoch={}, validators={:?})",
450 self.epoch_id, self.validators
451 )
452 }
453}
454
455#[derive(Debug)]
469pub struct CommitteeChainVerifier {
470 committee: Committee,
471}
472
473impl CommitteeChainVerifier {
474 pub fn new(trusted_committee: Committee) -> Self {
477 Self {
478 committee: trusted_committee,
479 }
480 }
481
482 pub fn epoch(&self) -> EpochId {
484 self.committee.epoch
485 }
486
487 pub fn committee(&self) -> &Committee {
489 &self.committee
490 }
491
492 pub fn verify_epoch_close(
501 &mut self,
502 summary: CertifiedCheckpointSummary,
503 ) -> IotaResult<VerifiedCheckpoint> {
504 if summary.data().epoch != self.committee.epoch {
508 return Err(IotaError::WrongEpoch {
509 expected_epoch: self.committee.epoch,
510 actual_epoch: summary.data().epoch,
511 });
512 }
513
514 if summary.data().end_of_epoch_data.is_none() {
515 return Err(IotaError::GenericAuthority {
516 error: format!(
517 "checkpoint {} is not the closing checkpoint of epoch {} (no \
518 end-of-epoch data)",
519 summary.data().sequence_number,
520 self.committee.epoch,
521 ),
522 });
523 }
524
525 let verified = summary.try_into_verified(&self.committee)?;
526 let end_of_epoch_data = verified
527 .end_of_epoch_data
528 .as_ref()
529 .expect("checked before verification");
530
531 self.committee = Committee::new(
532 self.committee
533 .epoch
534 .checked_add(1)
535 .ok_or(IotaError::AdvanceEpoch {
536 error: "epoch number overflow".to_string(),
537 })?,
538 end_of_epoch_data
539 .next_epoch_committee
540 .iter()
541 .cloned()
542 .collect(),
543 );
544 Ok(verified)
545 }
546}
547
548#[cfg(test)]
549mod test {
550 use fastcrypto::traits::KeyPair;
551
552 use super::*;
553 use crate::{
554 crypto::{AuthorityKeyPair, get_key_pair},
555 messages_checkpoint::{CheckpointSummary, EndOfEpochData, SignedCheckpointSummary},
556 utils::make_committee_key,
557 };
558
559 const RNG_SEED: [u8; 32] = [
560 21, 23, 199, 200, 234, 250, 252, 178, 94, 15, 202, 178, 62, 186, 88, 137, 233, 192, 130,
561 157, 179, 179, 65, 9, 31, 249, 221, 123, 225, 112, 199, 247,
562 ];
563
564 #[test]
565 fn test_shuffle_by_weight() {
566 let (_, sec1): (_, AuthorityKeyPair) = get_key_pair();
567 let (_, sec2): (_, AuthorityKeyPair) = get_key_pair();
568 let (_, sec3): (_, AuthorityKeyPair) = get_key_pair();
569 let a1: AuthorityName = sec1.public().into();
570 let a2: AuthorityName = sec2.public().into();
571 let a3: AuthorityName = sec3.public().into();
572
573 let mut authorities = BTreeMap::new();
574 authorities.insert(a1, 1);
575 authorities.insert(a2, 1);
576 authorities.insert(a3, 1);
577
578 let committee = Committee::new_for_testing_with_normalized_voting_power(0, authorities);
579
580 assert_eq!(committee.shuffle_by_stake(None, None).len(), 3);
581
582 let mut pref = BTreeSet::new();
583 pref.insert(a2);
584
585 for _ in 0..100 {
587 assert_eq!(
588 a2,
589 *committee
590 .shuffle_by_stake(Some(&pref), None)
591 .first()
592 .unwrap()
593 );
594 }
595
596 let mut restrict = BTreeSet::new();
597 restrict.insert(a2);
598
599 for _ in 0..100 {
600 let res = committee.shuffle_by_stake(None, Some(&restrict));
601 assert_eq!(1, res.len());
602 assert_eq!(a2, res[0]);
603 }
604
605 let res = committee.shuffle_by_stake(Some(&BTreeSet::new()), None);
607 assert_eq!(3, res.len());
608
609 let res = committee.shuffle_by_stake(None, Some(&BTreeSet::new()));
610 assert_eq!(0, res.len());
611 }
612
613 #[test]
618 fn committee_chain_verifier_walks_and_rejects() {
619 let mut rng = StdRng::from_seed(RNG_SEED);
620 let (keys, committee) = make_committee_key(&mut rng);
621 let (other_keys, other_committee) = make_committee_key(&mut rng);
622
623 let close_of_epoch = |epoch: EpochId, end_of_epoch_data: Option<EndOfEpochData>| {
624 let summary = CheckpointSummary {
625 epoch,
626 sequence_number: epoch,
627 network_total_transactions: 0,
628 content_digest: Default::default(),
629 previous_digest: None,
630 epoch_rolling_gas_cost_summary: Default::default(),
631 end_of_epoch_data,
632 timestamp_ms: 0,
633 version_specific_data: Vec::new(),
634 checkpoint_commitments: Vec::new(),
635 };
636 let signatures = keys
637 .iter()
638 .map(|k| SignedCheckpointSummary::sign(epoch, &summary, k, k.public().into()))
639 .collect();
640 let committee_at_epoch =
641 Committee::new(epoch, committee.voting_rights.iter().cloned().collect());
642 CertifiedCheckpointSummary::new(summary, signatures, &committee_at_epoch)
643 .expect("test summary must certify")
644 };
645 let handing_forward = Some(EndOfEpochData {
646 next_epoch_committee: committee.voting_rights.clone(),
647 next_epoch_protocol_version: 1.into(),
648 epoch_commitments: Vec::new(),
649 epoch_supply_change: 0,
650 });
651
652 let mut verifier = CommitteeChainVerifier::new(committee.clone());
653
654 assert!(matches!(
656 verifier.verify_epoch_close(close_of_epoch(1, handing_forward.clone())),
657 Err(IotaError::WrongEpoch { .. })
658 ));
659 assert_eq!(verifier.epoch(), 0, "a rejected summary must not advance");
660
661 verifier
663 .verify_epoch_close(close_of_epoch(0, None))
664 .expect_err("a non-closing checkpoint must be rejected");
665 assert_eq!(verifier.epoch(), 0);
666
667 let foreign_non_closing = {
671 let summary = CheckpointSummary {
672 epoch: 0,
673 sequence_number: 0,
674 network_total_transactions: 0,
675 content_digest: Default::default(),
676 previous_digest: None,
677 epoch_rolling_gas_cost_summary: Default::default(),
678 end_of_epoch_data: None,
679 timestamp_ms: 0,
680 version_specific_data: Vec::new(),
681 checkpoint_commitments: Vec::new(),
682 };
683 let signatures = other_keys
684 .iter()
685 .map(|k| SignedCheckpointSummary::sign(0, &summary, k, k.public().into()))
686 .collect();
687 CertifiedCheckpointSummary::new(summary, signatures, &other_committee)
688 .expect("certifies under the foreign committee")
689 };
690 assert!(matches!(
691 verifier.verify_epoch_close(foreign_non_closing),
692 Err(IotaError::GenericAuthority { .. })
693 ));
694 assert_eq!(verifier.epoch(), 0);
695
696 verifier
698 .verify_epoch_close(close_of_epoch(0, handing_forward.clone()))
699 .expect("epoch 0 close must verify");
700 assert_eq!(verifier.epoch(), 1);
701 verifier
702 .verify_epoch_close(close_of_epoch(1, handing_forward.clone()))
703 .expect("epoch 1 close must verify");
704 assert_eq!(verifier.epoch(), 2);
705
706 let mut wrong_root = CommitteeChainVerifier::new(other_committee);
708 wrong_root
709 .verify_epoch_close(close_of_epoch(0, handing_forward))
710 .expect_err("a chain signed by a different committee must be rejected");
711 assert_eq!(wrong_root.epoch(), 0);
712 }
713}