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