1use serde::Serialize;
5use shared_crypto::intent::Intent;
6use std::collections::hash_map::Entry;
7use std::collections::{BTreeMap, HashMap, HashSet};
8use std::hash::Hash;
9use std::sync::Arc;
10use sui_types::base_types::AuthorityName;
11use sui_types::base_types::ConciseableName;
12use sui_types::committee::{Committee, CommitteeTrait, StakeUnit};
13use sui_types::crypto::{AuthorityQuorumSignInfo, AuthoritySignInfo, AuthoritySignInfoTrait};
14use sui_types::error::{SuiError, SuiErrorKind, SuiResult};
15use sui_types::message_envelope::{Envelope, Message};
16use tracing::warn;
17use typed_store::TypedStoreError;
18
19#[derive(Debug)]
22pub struct StakeAggregator<S, const STRENGTH: bool> {
23 data: HashMap<AuthorityName, S>,
24 total_votes: StakeUnit,
25 committee: Arc<Committee>,
26}
27
28impl<S: Clone + Eq, const STRENGTH: bool> StakeAggregator<S, STRENGTH> {
34 pub fn new(committee: Arc<Committee>) -> Self {
35 Self {
36 data: Default::default(),
37 total_votes: Default::default(),
38 committee,
39 }
40 }
41
42 pub fn from_iter<I: Iterator<Item = Result<(AuthorityName, S), TypedStoreError>>>(
43 committee: Arc<Committee>,
44 data: I,
45 ) -> SuiResult<Self> {
46 let mut this = Self::new(committee);
47 for item in data {
48 let (authority, s) = item?;
49 this.insert_generic(authority, s);
50 }
51 Ok(this)
52 }
53
54 pub fn insert_generic(
60 &mut self,
61 authority: AuthorityName,
62 s: S,
63 ) -> InsertResult<&HashMap<AuthorityName, S>> {
64 match self.data.entry(authority) {
65 Entry::Occupied(oc) => {
66 return InsertResult::Failed {
67 error: SuiErrorKind::StakeAggregatorRepeatedSigner {
68 signer: authority,
69 conflicting_sig: oc.get() != &s,
70 }
71 .into(),
72 };
73 }
74 Entry::Vacant(va) => {
75 va.insert(s);
76 }
77 }
78 let votes = self.committee.weight(&authority);
79 if votes > 0 {
80 self.total_votes += votes;
81 if self.total_votes >= self.committee.threshold::<STRENGTH>() {
82 InsertResult::QuorumReached(&self.data)
83 } else {
84 InsertResult::NotEnoughVotes {
85 bad_votes: 0,
86 bad_authorities: vec![],
87 }
88 }
89 } else {
90 InsertResult::Failed {
91 error: SuiErrorKind::InvalidAuthenticator.into(),
92 }
93 }
94 }
95
96 pub fn contains_key(&self, authority: &AuthorityName) -> bool {
97 self.data.contains_key(authority)
98 }
99
100 pub fn keys(&self) -> impl Iterator<Item = &AuthorityName> {
101 self.data.keys()
102 }
103
104 pub fn committee(&self) -> &Committee {
105 &self.committee
106 }
107
108 pub fn total_votes(&self) -> StakeUnit {
109 self.total_votes
110 }
111
112 pub fn has_quorum(&self) -> bool {
113 self.total_votes >= self.committee.threshold::<STRENGTH>()
114 }
115
116 pub fn validator_sig_count(&self) -> usize {
117 self.data.len()
118 }
119}
120
121impl<const STRENGTH: bool> StakeAggregator<AuthoritySignInfo, STRENGTH> {
122 pub fn insert<T: Message + Serialize>(
126 &mut self,
127 envelope: Envelope<T, AuthoritySignInfo>,
128 ) -> InsertResult<AuthorityQuorumSignInfo<STRENGTH>> {
129 let (data, sig) = envelope.into_data_and_sig();
130 if self.committee.epoch != sig.epoch {
131 return InsertResult::Failed {
132 error: SuiErrorKind::WrongEpoch {
133 expected_epoch: self.committee.epoch,
134 actual_epoch: sig.epoch,
135 }
136 .into(),
137 };
138 }
139 match self.insert_generic(sig.authority, sig) {
140 InsertResult::QuorumReached(_) => {
141 match AuthorityQuorumSignInfo::<STRENGTH>::new_from_auth_sign_infos(
142 self.data.values().cloned().collect(),
143 self.committee(),
144 ) {
145 Ok(aggregated) => {
146 match aggregated.verify_secure(
147 &data,
148 Intent::sui_app(T::SCOPE),
149 self.committee(),
150 ) {
151 Ok(_) => InsertResult::QuorumReached(aggregated),
154 Err(_) => {
155 let mut bad_votes = 0;
164 let mut bad_authorities = vec![];
165 for (name, sig) in &self.data.clone() {
166 if let Err(err) = sig.verify_secure(
167 &data,
168 Intent::sui_app(T::SCOPE),
169 self.committee(),
170 ) {
171 warn!(name=?name.concise(), "Bad stake from validator: {:?}", err);
175 self.data.remove(name);
176 let votes = self.committee.weight(name);
177 self.total_votes -= votes;
178 bad_votes += votes;
179 bad_authorities.push(*name);
180 }
181 }
182 InsertResult::NotEnoughVotes {
183 bad_votes,
184 bad_authorities,
185 }
186 }
187 }
188 }
189 Err(error) => InsertResult::Failed { error },
190 }
191 }
192 InsertResult::Failed { error } => InsertResult::Failed { error },
194 InsertResult::NotEnoughVotes {
195 bad_votes,
196 bad_authorities,
197 } => InsertResult::NotEnoughVotes {
198 bad_votes,
199 bad_authorities,
200 },
201 }
202 }
203}
204
205pub enum InsertResult<CertT> {
206 QuorumReached(CertT),
207 Failed {
208 error: SuiError,
209 },
210 NotEnoughVotes {
211 bad_votes: u64,
212 bad_authorities: Vec<AuthorityName>,
213 },
214}
215
216impl<CertT> InsertResult<CertT> {
217 pub fn is_quorum_reached(&self) -> bool {
218 matches!(self, Self::QuorumReached(..))
219 }
220}
221
222#[derive(Debug)]
227pub struct MultiStakeAggregator<K, V, const STRENGTH: bool> {
228 committee: Arc<Committee>,
229 stake_maps: HashMap<K, (V, StakeAggregator<AuthoritySignInfo, STRENGTH>)>,
230}
231
232impl<K, V, const STRENGTH: bool> MultiStakeAggregator<K, V, STRENGTH> {
233 pub fn new(committee: Arc<Committee>) -> Self {
234 Self {
235 committee,
236 stake_maps: Default::default(),
237 }
238 }
239
240 pub fn unique_key_count(&self) -> usize {
241 self.stake_maps.len()
242 }
243
244 pub fn total_votes(&self) -> StakeUnit {
245 let mut voted_authorities = HashSet::new();
246 self.stake_maps.values().for_each(|(_, stake_aggregator)| {
247 stake_aggregator.keys().for_each(|k| {
248 voted_authorities.insert(k);
249 })
250 });
251 voted_authorities
252 .iter()
253 .map(|k| self.committee.weight(k))
254 .sum()
255 }
256}
257
258impl<K, V, const STRENGTH: bool> MultiStakeAggregator<K, V, STRENGTH>
259where
260 K: Hash + Eq,
261 V: Message + Serialize + Clone,
262{
263 pub fn insert(
264 &mut self,
265 k: K,
266 envelope: Envelope<V, AuthoritySignInfo>,
267 ) -> InsertResult<AuthorityQuorumSignInfo<STRENGTH>> {
268 if let Some(entry) = self.stake_maps.get_mut(&k) {
269 entry.1.insert(envelope)
270 } else {
271 let mut new_entry = StakeAggregator::new(self.committee.clone());
272 let result = new_entry.insert(envelope.clone());
273 if !matches!(result, InsertResult::Failed { .. }) {
274 self.stake_maps.insert(k, (envelope.into_data(), new_entry));
277 }
278 result
279 }
280 }
281}
282
283impl<K, V, const STRENGTH: bool> MultiStakeAggregator<K, V, STRENGTH>
284where
285 K: Clone + Ord,
286{
287 pub fn get_all_unique_values(&self) -> BTreeMap<K, (Vec<AuthorityName>, StakeUnit)> {
288 self.stake_maps
289 .iter()
290 .map(|(k, (_, s))| (k.clone(), (s.data.keys().copied().collect(), s.total_votes)))
291 .collect()
292 }
293}
294
295impl<K, V, const STRENGTH: bool> MultiStakeAggregator<K, V, STRENGTH>
296where
297 K: Hash + Eq,
298{
299 #[allow(dead_code)]
300 pub fn authorities_for_key(&self, k: &K) -> Option<impl Iterator<Item = &AuthorityName>> {
301 self.stake_maps.get(k).map(|(_, agg)| agg.keys())
302 }
303
304 pub fn uncommitted_stake(&self) -> StakeUnit {
307 self.committee.total_votes() - self.total_votes()
308 }
309
310 pub fn plurality_stake(&self) -> StakeUnit {
312 self.stake_maps
313 .values()
314 .map(|(_, agg)| agg.total_votes())
315 .max()
316 .unwrap_or_default()
317 }
318
319 pub fn quorum_unreachable(&self) -> bool {
321 self.uncommitted_stake() + self.plurality_stake() < self.committee.threshold::<STRENGTH>()
322 }
323}
324
325pub struct GenericMultiStakeAggregator<K, const STRENGTH: bool> {
328 committee: Arc<Committee>,
329 stake_maps: HashMap<K, StakeAggregator<(), STRENGTH>>,
330 votes_per_authority: HashMap<AuthorityName, u64>,
331}
332
333impl<K, const STRENGTH: bool> GenericMultiStakeAggregator<K, STRENGTH>
334where
335 K: Hash + Eq,
336{
337 pub fn new(committee: Arc<Committee>) -> Self {
338 Self {
339 committee,
340 stake_maps: Default::default(),
341 votes_per_authority: Default::default(),
342 }
343 }
344
345 pub fn insert(
346 &mut self,
347 authority: AuthorityName,
348 k: K,
349 ) -> InsertResult<&HashMap<AuthorityName, ()>> {
350 let agg = self
351 .stake_maps
352 .entry(k)
353 .or_insert_with(|| StakeAggregator::new(self.committee.clone()));
354
355 if !agg.contains_key(&authority) {
356 *self.votes_per_authority.entry(authority).or_default() += 1;
357 }
358
359 agg.insert_generic(authority, ())
360 }
361
362 pub fn has_quorum_for_key(&self, k: &K) -> bool {
363 if let Some(entry) = self.stake_maps.get(k) {
364 entry.has_quorum()
365 } else {
366 false
367 }
368 }
369
370 pub fn votes_for_authority(&self, authority: AuthorityName) -> u64 {
371 self.votes_per_authority
372 .get(&authority)
373 .copied()
374 .unwrap_or_default()
375 }
376}
377
378#[test]
379fn test_votes_per_authority() {
380 let (committee, _) = Committee::new_simple_test_committee();
381 let authorities: Vec<_> = committee.names().copied().collect();
382
383 let mut agg: GenericMultiStakeAggregator<&str, true> =
384 GenericMultiStakeAggregator::new(Arc::new(committee));
385
386 let key1: &str = "key1";
388 let authority1 = authorities[0];
389 agg.insert(authority1, key1);
390 assert_eq!(agg.votes_for_authority(authority1), 1);
391
392 agg.insert(authority1, key1);
394 agg.insert(authority1, key1);
395 assert_eq!(agg.votes_for_authority(authority1), 1);
396
397 let authority2 = authorities[1];
399 assert_eq!(agg.votes_for_authority(authority2), 0);
400
401 let key2: &str = "key2";
403 agg.insert(authority2, key2);
404 assert_eq!(agg.votes_for_authority(authority2), 1);
405 assert_eq!(agg.votes_for_authority(authority1), 1);
406
407 let key3: &str = "key3";
409 agg.insert(authority1, key3);
410 assert_eq!(agg.votes_for_authority(authority1), 2);
411}
412
413#[cfg(test)]
414mod multi_stake_aggregator_tests {
415 use super::*;
416 use fastcrypto::hash::{HashFunction, Sha3_256};
417 use shared_crypto::intent::IntentScope;
418
419 #[derive(Clone, Debug, Serialize, PartialEq, Eq, Hash)]
420 struct TestMessage {
421 value: String,
422 }
423
424 impl Message for TestMessage {
425 type DigestType = [u8; 32];
426 const SCOPE: IntentScope = IntentScope::SenderSignedTransaction;
427
428 fn digest(&self) -> Self::DigestType {
429 let mut hasher = Sha3_256::default();
430 hasher.update(self.value.as_bytes());
431 hasher.finalize().digest
432 }
433 }
434
435 #[test]
436 fn test_equivocation_stake_not_double_counted() {
437 let (committee, key_pairs) = Committee::new_simple_test_committee();
438 let committee = Arc::new(committee);
439 let authorities: Vec<_> = committee.names().copied().collect();
440
441 let mut agg: MultiStakeAggregator<String, TestMessage, true> =
442 MultiStakeAggregator::new(committee.clone());
443
444 let total_stake = committee.total_votes();
446 let num_authorities = authorities.len();
447 let stake_per_authority = total_stake / num_authorities as u64;
448
449 let authority0 = authorities[0];
451 let key0 = &key_pairs[0];
452
453 let msg1 = TestMessage {
455 value: "value1".to_string(),
456 };
457 let envelope1 =
458 <Envelope<TestMessage, AuthoritySignInfo>>::new(0, msg1.clone(), key0, authority0);
459 agg.insert("key1".to_string(), envelope1);
460
461 let msg2 = TestMessage {
463 value: "value2".to_string(),
464 };
465 let envelope2 =
466 <Envelope<TestMessage, AuthoritySignInfo>>::new(0, msg2.clone(), key0, authority0);
467 agg.insert("key2".to_string(), envelope2);
468
469 let msg3 = TestMessage {
471 value: "value3".to_string(),
472 };
473 let envelope3 =
474 <Envelope<TestMessage, AuthoritySignInfo>>::new(0, msg3.clone(), key0, authority0);
475 agg.insert("key3".to_string(), envelope3);
476
477 let aggregated_votes = agg.total_votes();
479 assert_eq!(aggregated_votes, stake_per_authority);
480
481 let authority1 = authorities[1];
483 let key1 = &key_pairs[1];
484 let envelope4 =
485 <Envelope<TestMessage, AuthoritySignInfo>>::new(0, msg1.clone(), key1, authority1);
486 agg.insert("key1".to_string(), envelope4);
487
488 let authority2 = authorities[2];
489 let key2 = &key_pairs[2];
490 let envelope5 = <Envelope<TestMessage, AuthoritySignInfo>>::new(0, msg2, key2, authority2);
491 agg.insert("key2".to_string(), envelope5);
492
493 let aggregated_votes = agg.total_votes();
496 assert_eq!(aggregated_votes, stake_per_authority * 3);
497 assert!(aggregated_votes <= total_stake);
498
499 let uncommitted = agg.uncommitted_stake();
501 assert_eq!(uncommitted, stake_per_authority); assert_eq!(agg.unique_key_count(), 3);
505 }
506
507 #[test]
508 fn test_multistake_uncommitted_and_plurality() {
509 let (committee, key_pairs) = Committee::new_simple_test_committee();
510 let committee = Arc::new(committee);
511 let authorities: Vec<_> = committee.names().copied().collect();
512
513 let mut agg: MultiStakeAggregator<String, TestMessage, true> =
514 MultiStakeAggregator::new(committee.clone());
515
516 let total_stake = committee.total_votes();
517 let num_authorities = authorities.len();
518 let stake_per_authority = total_stake / num_authorities as u64;
519
520 assert_eq!(agg.uncommitted_stake(), total_stake);
522 assert_eq!(agg.plurality_stake(), 0);
523 assert!(!agg.quorum_unreachable());
524
525 let msg1 = TestMessage {
527 value: "value1".to_string(),
528 };
529 let envelope1 = <Envelope<TestMessage, AuthoritySignInfo>>::new(
530 0,
531 msg1.clone(),
532 &key_pairs[0],
533 authorities[0],
534 );
535 agg.insert("key1".to_string(), envelope1);
536
537 assert_eq!(agg.uncommitted_stake(), total_stake - stake_per_authority);
538 assert_eq!(agg.plurality_stake(), stake_per_authority);
539
540 let msg2 = TestMessage {
542 value: "value2".to_string(),
543 };
544 let envelope2 = <Envelope<TestMessage, AuthoritySignInfo>>::new(
545 0,
546 msg2.clone(),
547 &key_pairs[1],
548 authorities[1],
549 );
550 agg.insert("key2".to_string(), envelope2);
551
552 assert_eq!(
553 agg.uncommitted_stake(),
554 total_stake - 2 * stake_per_authority
555 );
556 assert_eq!(agg.plurality_stake(), stake_per_authority);
557
558 let envelope3 = <Envelope<TestMessage, AuthoritySignInfo>>::new(
560 0,
561 msg1.clone(),
562 &key_pairs[2],
563 authorities[2],
564 );
565 agg.insert("key1".to_string(), envelope3);
566
567 assert_eq!(
568 agg.uncommitted_stake(),
569 total_stake - 3 * stake_per_authority
570 );
571 assert_eq!(agg.plurality_stake(), 2 * stake_per_authority);
572 }
573
574 #[test]
575 fn test_multistake_quorum_unreachable() {
576 let (committee, key_pairs) = Committee::new_simple_test_committee();
577 let committee = Arc::new(committee);
578 let authorities: Vec<_> = committee.names().copied().collect();
579
580 let mut agg: MultiStakeAggregator<String, TestMessage, true> =
581 MultiStakeAggregator::new(committee.clone());
582
583 let msg1 = TestMessage {
586 value: "value1".to_string(),
587 };
588 let msg2 = TestMessage {
589 value: "value2".to_string(),
590 };
591
592 let envelope1 = <Envelope<TestMessage, AuthoritySignInfo>>::new(
594 0,
595 msg1.clone(),
596 &key_pairs[0],
597 authorities[0],
598 );
599 agg.insert("key1".to_string(), envelope1);
600
601 let envelope2 = <Envelope<TestMessage, AuthoritySignInfo>>::new(
602 0,
603 msg1.clone(),
604 &key_pairs[1],
605 authorities[1],
606 );
607 agg.insert("key1".to_string(), envelope2);
608
609 let envelope3 = <Envelope<TestMessage, AuthoritySignInfo>>::new(
611 0,
612 msg2.clone(),
613 &key_pairs[2],
614 authorities[2],
615 );
616 agg.insert("key2".to_string(), envelope3);
617
618 let envelope4 = <Envelope<TestMessage, AuthoritySignInfo>>::new(
619 0,
620 msg2.clone(),
621 &key_pairs[3],
622 authorities[3],
623 );
624 agg.insert("key2".to_string(), envelope4);
625
626 assert!(agg.quorum_unreachable());
628 }
629}
630
631#[cfg(test)]
632mod stake_aggregator_tests {
633 use super::*;
634
635 #[test]
636 fn test_stake_aggregator_strong_quorum() {
637 let (committee, _) = Committee::new_simple_test_committee();
638 let committee = Arc::new(committee);
639 let authorities: Vec<_> = committee.names().copied().collect();
640
641 let mut agg: StakeAggregator<(), true> = StakeAggregator::new(committee.clone());
642
643 let total_stake = committee.total_votes();
644 let num_authorities = authorities.len();
645 let stake_per_authority = total_stake / num_authorities as u64;
646
647 assert_eq!(agg.total_votes(), 0);
648 assert!(!agg.has_quorum());
649 assert_eq!(agg.validator_sig_count(), 0);
650
651 let result = agg.insert_generic(authorities[0], ());
653 assert!(matches!(result, InsertResult::NotEnoughVotes { .. }));
654 assert_eq!(agg.total_votes(), stake_per_authority);
655 assert!(!agg.has_quorum());
656 assert_eq!(agg.validator_sig_count(), 1);
657
658 let result = agg.insert_generic(authorities[1], ());
660 assert!(matches!(result, InsertResult::NotEnoughVotes { .. }));
661 assert_eq!(agg.total_votes(), 2 * stake_per_authority);
662 assert!(!agg.has_quorum());
663
664 let result = agg.insert_generic(authorities[2], ());
666 assert!(result.is_quorum_reached());
667 assert!(agg.has_quorum());
668 assert_eq!(agg.validator_sig_count(), 3);
669 }
670
671 #[test]
672 fn test_stake_aggregator_weak_quorum() {
673 let (committee, _) = Committee::new_simple_test_committee();
674 let committee = Arc::new(committee);
675 let authorities: Vec<_> = committee.names().copied().collect();
676
677 let mut agg: StakeAggregator<(), false> = StakeAggregator::new(committee.clone());
678
679 let result = agg.insert_generic(authorities[0], ());
681 assert!(matches!(result, InsertResult::NotEnoughVotes { .. }));
682 assert!(!agg.has_quorum());
683
684 let result = agg.insert_generic(authorities[1], ());
686 assert!(result.is_quorum_reached());
687 assert!(agg.has_quorum());
688 }
689
690 #[test]
691 fn test_stake_aggregator_repeated_signer() {
692 let (committee, _) = Committee::new_simple_test_committee();
693 let committee = Arc::new(committee);
694 let authorities: Vec<_> = committee.names().copied().collect();
695
696 let mut agg: StakeAggregator<u32, true> = StakeAggregator::new(committee.clone());
697
698 let result = agg.insert_generic(authorities[0], 1);
700 assert!(matches!(result, InsertResult::NotEnoughVotes { .. }));
701
702 let result = agg.insert_generic(authorities[0], 1);
704 assert!(matches!(
705 result,
706 InsertResult::Failed {
707 error
708 } if matches!(error.as_inner(), SuiErrorKind::StakeAggregatorRepeatedSigner { .. } )
709 ));
710
711 let result = agg.insert_generic(authorities[0], 2);
713 let InsertResult::Failed { error } = result else {
714 panic!("Expected StakeAggregatorRepeatedSigner error");
715 };
716 let SuiErrorKind::StakeAggregatorRepeatedSigner {
717 signer,
718 conflicting_sig,
719 } = error.into_inner()
720 else {
721 panic!("Expected StakeAggregatorRepeatedSigner error");
722 };
723 assert_eq!(signer, authorities[0]);
724 assert!(conflicting_sig);
725 }
726
727 #[test]
728 fn test_stake_aggregator_from_iter() {
729 let (committee, _) = Committee::new_simple_test_committee();
730 let committee = Arc::new(committee);
731 let authorities: Vec<_> = committee.names().copied().collect();
732
733 let data = vec![
734 Ok((authorities[0], ())),
735 Ok((authorities[1], ())),
736 Ok((authorities[2], ())),
737 ];
738
739 let agg: StakeAggregator<(), true> =
740 StakeAggregator::from_iter(committee.clone(), data.into_iter()).unwrap();
741
742 assert_eq!(agg.validator_sig_count(), 3);
743 assert!(agg.has_quorum());
744 assert!(agg.contains_key(&authorities[0]));
745 assert!(agg.contains_key(&authorities[1]));
746 assert!(agg.contains_key(&authorities[2]));
747 }
748
749 #[test]
750 fn test_stake_aggregator_from_iter_with_error() {
751 let (committee, _) = Committee::new_simple_test_committee();
752 let committee = Arc::new(committee);
753 let authorities: Vec<_> = committee.names().copied().collect();
754
755 let data: Vec<Result<(AuthorityName, ()), TypedStoreError>> = vec![
756 Ok((authorities[0], ())),
757 Err(TypedStoreError::RocksDBError("test error".to_string())),
758 ];
759
760 let result: SuiResult<StakeAggregator<(), true>> =
761 StakeAggregator::from_iter(committee.clone(), data.into_iter());
762
763 assert!(result.is_err());
764 }
765}
766
767#[cfg(test)]
768mod generic_multi_stake_aggregator_tests {
769 use super::*;
770
771 #[test]
772 fn test_has_quorum_for_key() {
773 let (committee, _) = Committee::new_simple_test_committee();
774 let committee = Arc::new(committee);
775 let authorities: Vec<_> = committee.names().copied().collect();
776
777 let mut agg: GenericMultiStakeAggregator<&str, true> =
778 GenericMultiStakeAggregator::new(committee.clone());
779
780 let key1 = "key1";
781 let key2 = "key2";
782
783 assert!(!agg.has_quorum_for_key(&key1));
785 assert!(!agg.has_quorum_for_key(&key2));
786
787 agg.insert(authorities[0], key1);
789 assert!(!agg.has_quorum_for_key(&key1));
790
791 agg.insert(authorities[1], key1);
792 assert!(!agg.has_quorum_for_key(&key1));
793
794 agg.insert(authorities[2], key1);
795 assert!(agg.has_quorum_for_key(&key1));
796 assert!(!agg.has_quorum_for_key(&key2));
797
798 agg.insert(authorities[3], key2);
800 assert!(agg.has_quorum_for_key(&key1));
801 assert!(!agg.has_quorum_for_key(&key2));
802 }
803}