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