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 #[cfg(test)]
117 pub fn validator_sig_count(&self) -> usize {
118 self.data.len()
119 }
120}
121
122impl<const STRENGTH: bool> StakeAggregator<AuthoritySignInfo, STRENGTH> {
123 pub fn insert<T: Message + Serialize>(
127 &mut self,
128 envelope: Envelope<T, AuthoritySignInfo>,
129 ) -> InsertResult<AuthorityQuorumSignInfo<STRENGTH>> {
130 let (data, sig) = envelope.into_data_and_sig();
131 if self.committee.epoch != sig.epoch {
132 return InsertResult::Failed {
133 error: SuiErrorKind::WrongEpoch {
134 expected_epoch: self.committee.epoch,
135 actual_epoch: sig.epoch,
136 }
137 .into(),
138 };
139 }
140 match self.insert_generic(sig.authority, sig) {
141 InsertResult::QuorumReached(_) => {
142 match AuthorityQuorumSignInfo::<STRENGTH>::new_from_auth_sign_infos(
143 self.data.values().cloned().collect(),
144 self.committee(),
145 ) {
146 Ok(aggregated) => {
147 match aggregated.verify_secure(
148 &data,
149 Intent::sui_app(T::SCOPE),
150 self.committee(),
151 ) {
152 Ok(_) => InsertResult::QuorumReached(aggregated),
155 Err(_) => {
156 let mut bad_votes = 0;
165 let mut bad_authorities = vec![];
166 for (name, sig) in &self.data.clone() {
167 if let Err(err) = sig.verify_secure(
168 &data,
169 Intent::sui_app(T::SCOPE),
170 self.committee(),
171 ) {
172 warn!(name=?name.concise(), "Bad stake from validator: {:?}", err);
176 self.data.remove(name);
177 let votes = self.committee.weight(name);
178 self.total_votes -= votes;
179 bad_votes += votes;
180 bad_authorities.push(*name);
181 }
182 }
183 InsertResult::NotEnoughVotes {
184 bad_votes,
185 bad_authorities,
186 }
187 }
188 }
189 }
190 Err(error) => InsertResult::Failed { error },
191 }
192 }
193 InsertResult::Failed { error } => InsertResult::Failed { error },
195 InsertResult::NotEnoughVotes {
196 bad_votes,
197 bad_authorities,
198 } => InsertResult::NotEnoughVotes {
199 bad_votes,
200 bad_authorities,
201 },
202 }
203 }
204}
205
206pub enum InsertResult<CertT> {
207 QuorumReached(CertT),
208 Failed {
209 error: SuiError,
210 },
211 NotEnoughVotes {
212 bad_votes: u64,
213 bad_authorities: Vec<AuthorityName>,
214 },
215}
216
217impl<CertT> InsertResult<CertT> {
218 pub fn is_quorum_reached(&self) -> bool {
219 matches!(self, Self::QuorumReached(..))
220 }
221}
222
223#[derive(Debug)]
228pub struct MultiStakeAggregator<K, V, const STRENGTH: bool> {
229 committee: Arc<Committee>,
230 stake_maps: HashMap<K, (V, StakeAggregator<AuthoritySignInfo, STRENGTH>)>,
231}
232
233impl<K, V, const STRENGTH: bool> MultiStakeAggregator<K, V, STRENGTH> {
234 pub fn new(committee: Arc<Committee>) -> Self {
235 Self {
236 committee,
237 stake_maps: Default::default(),
238 }
239 }
240
241 pub fn total_votes(&self) -> StakeUnit {
242 let mut voted_authorities = HashSet::new();
243 self.stake_maps.values().for_each(|(_, stake_aggregator)| {
244 stake_aggregator.keys().for_each(|k| {
245 voted_authorities.insert(k);
246 })
247 });
248 voted_authorities
249 .iter()
250 .map(|k| self.committee.weight(k))
251 .sum()
252 }
253
254 #[cfg(test)]
255 pub fn unique_key_count(&self) -> usize {
256 self.stake_maps.len()
257 }
258}
259
260impl<K, V, const STRENGTH: bool> MultiStakeAggregator<K, V, STRENGTH>
261where
262 K: Hash + Eq,
263 V: Message + Serialize + Clone,
264{
265 pub fn insert(
266 &mut self,
267 k: K,
268 envelope: Envelope<V, AuthoritySignInfo>,
269 ) -> InsertResult<AuthorityQuorumSignInfo<STRENGTH>> {
270 if let Some(entry) = self.stake_maps.get_mut(&k) {
271 entry.1.insert(envelope)
272 } else {
273 let mut new_entry = StakeAggregator::new(self.committee.clone());
274 let result = new_entry.insert(envelope.clone());
275 if !matches!(result, InsertResult::Failed { .. }) {
276 self.stake_maps.insert(k, (envelope.into_data(), new_entry));
279 }
280 result
281 }
282 }
283}
284
285impl<K, V, const STRENGTH: bool> MultiStakeAggregator<K, V, STRENGTH>
286where
287 K: Clone + Ord,
288{
289 pub fn get_all_unique_values(&self) -> BTreeMap<K, (Vec<AuthorityName>, StakeUnit)> {
290 self.stake_maps
291 .iter()
292 .map(|(k, (_, s))| (k.clone(), (s.data.keys().copied().collect(), s.total_votes)))
293 .collect()
294 }
295}
296
297impl<K, V, const STRENGTH: bool> MultiStakeAggregator<K, V, STRENGTH>
298where
299 K: Hash + Eq,
300{
301 #[allow(dead_code)]
302 pub fn authorities_for_key(&self, k: &K) -> Option<impl Iterator<Item = &AuthorityName>> {
303 self.stake_maps.get(k).map(|(_, agg)| agg.keys())
304 }
305
306 pub fn uncommitted_stake(&self) -> StakeUnit {
309 self.committee.total_votes() - self.total_votes()
310 }
311
312 pub fn plurality_stake(&self) -> StakeUnit {
314 self.stake_maps
315 .values()
316 .map(|(_, agg)| agg.total_votes())
317 .max()
318 .unwrap_or_default()
319 }
320
321 pub fn quorum_unreachable(&self) -> bool {
323 self.uncommitted_stake() + self.plurality_stake() < self.committee.threshold::<STRENGTH>()
324 }
325}
326
327pub struct GenericMultiStakeAggregator<K, const STRENGTH: bool> {
330 committee: Arc<Committee>,
331 stake_maps: HashMap<K, StakeAggregator<(), STRENGTH>>,
332 votes_per_authority: HashMap<AuthorityName, u64>,
333}
334
335impl<K, const STRENGTH: bool> GenericMultiStakeAggregator<K, STRENGTH>
336where
337 K: Hash + Eq,
338{
339 pub fn new(committee: Arc<Committee>) -> Self {
340 Self {
341 committee,
342 stake_maps: Default::default(),
343 votes_per_authority: Default::default(),
344 }
345 }
346
347 pub fn insert(
348 &mut self,
349 authority: AuthorityName,
350 k: K,
351 ) -> InsertResult<&HashMap<AuthorityName, ()>> {
352 let agg = self
353 .stake_maps
354 .entry(k)
355 .or_insert_with(|| StakeAggregator::new(self.committee.clone()));
356
357 if !agg.contains_key(&authority) {
358 *self.votes_per_authority.entry(authority).or_default() += 1;
359 }
360
361 agg.insert_generic(authority, ())
362 }
363
364 pub fn has_quorum_for_key(&self, k: &K) -> bool {
365 if let Some(entry) = self.stake_maps.get(k) {
366 entry.has_quorum()
367 } else {
368 false
369 }
370 }
371
372 pub fn votes_for_authority(&self, authority: AuthorityName) -> u64 {
373 self.votes_per_authority
374 .get(&authority)
375 .copied()
376 .unwrap_or_default()
377 }
378}
379
380#[test]
381fn test_votes_per_authority() {
382 let (committee, _) = Committee::new_simple_test_committee();
383 let authorities: Vec<_> = committee.names().copied().collect();
384
385 let mut agg: GenericMultiStakeAggregator<&str, true> =
386 GenericMultiStakeAggregator::new(Arc::new(committee));
387
388 let key1: &str = "key1";
390 let authority1 = authorities[0];
391 agg.insert(authority1, key1);
392 assert_eq!(agg.votes_for_authority(authority1), 1);
393
394 agg.insert(authority1, key1);
396 agg.insert(authority1, key1);
397 assert_eq!(agg.votes_for_authority(authority1), 1);
398
399 let authority2 = authorities[1];
401 assert_eq!(agg.votes_for_authority(authority2), 0);
402
403 let key2: &str = "key2";
405 agg.insert(authority2, key2);
406 assert_eq!(agg.votes_for_authority(authority2), 1);
407 assert_eq!(agg.votes_for_authority(authority1), 1);
408
409 let key3: &str = "key3";
411 agg.insert(authority1, key3);
412 assert_eq!(agg.votes_for_authority(authority1), 2);
413}
414
415#[cfg(test)]
416mod multi_stake_aggregator_tests {
417 use super::*;
418 use fastcrypto::hash::{HashFunction, Sha3_256};
419 use shared_crypto::intent::IntentScope;
420
421 #[derive(Clone, Debug, Serialize, PartialEq, Eq, Hash)]
422 struct TestMessage {
423 value: String,
424 }
425
426 impl Message for TestMessage {
427 type DigestType = [u8; 32];
428 const SCOPE: IntentScope = IntentScope::SenderSignedTransaction;
429
430 fn digest(&self) -> Self::DigestType {
431 let mut hasher = Sha3_256::default();
432 hasher.update(self.value.as_bytes());
433 hasher.finalize().digest
434 }
435 }
436
437 #[test]
438 fn test_equivocation_stake_not_double_counted() {
439 let (committee, key_pairs) = Committee::new_simple_test_committee();
440 let committee = Arc::new(committee);
441 let authorities: Vec<_> = committee.names().copied().collect();
442
443 let mut agg: MultiStakeAggregator<String, TestMessage, true> =
444 MultiStakeAggregator::new(committee.clone());
445
446 let total_stake = committee.total_votes();
448 let num_authorities = authorities.len();
449 let stake_per_authority = total_stake / num_authorities as u64;
450
451 let authority0 = authorities[0];
453 let key0 = &key_pairs[0];
454
455 let msg1 = TestMessage {
457 value: "value1".to_string(),
458 };
459 let envelope1 =
460 <Envelope<TestMessage, AuthoritySignInfo>>::new(0, msg1.clone(), key0, authority0);
461 agg.insert("key1".to_string(), envelope1);
462
463 let msg2 = TestMessage {
465 value: "value2".to_string(),
466 };
467 let envelope2 =
468 <Envelope<TestMessage, AuthoritySignInfo>>::new(0, msg2.clone(), key0, authority0);
469 agg.insert("key2".to_string(), envelope2);
470
471 let msg3 = TestMessage {
473 value: "value3".to_string(),
474 };
475 let envelope3 =
476 <Envelope<TestMessage, AuthoritySignInfo>>::new(0, msg3.clone(), key0, authority0);
477 agg.insert("key3".to_string(), envelope3);
478
479 let aggregated_votes = agg.total_votes();
481 assert_eq!(aggregated_votes, stake_per_authority);
482
483 let authority1 = authorities[1];
485 let key1 = &key_pairs[1];
486 let envelope4 =
487 <Envelope<TestMessage, AuthoritySignInfo>>::new(0, msg1.clone(), key1, authority1);
488 agg.insert("key1".to_string(), envelope4);
489
490 let authority2 = authorities[2];
491 let key2 = &key_pairs[2];
492 let envelope5 = <Envelope<TestMessage, AuthoritySignInfo>>::new(0, msg2, key2, authority2);
493 agg.insert("key2".to_string(), envelope5);
494
495 let aggregated_votes = agg.total_votes();
498 assert_eq!(aggregated_votes, stake_per_authority * 3);
499 assert!(aggregated_votes <= total_stake);
500
501 let uncommitted = agg.uncommitted_stake();
503 assert_eq!(uncommitted, stake_per_authority); assert_eq!(agg.unique_key_count(), 3);
507 }
508
509 #[test]
510 fn test_multistake_uncommitted_and_plurality() {
511 let (committee, key_pairs) = Committee::new_simple_test_committee();
512 let committee = Arc::new(committee);
513 let authorities: Vec<_> = committee.names().copied().collect();
514
515 let mut agg: MultiStakeAggregator<String, TestMessage, true> =
516 MultiStakeAggregator::new(committee.clone());
517
518 let total_stake = committee.total_votes();
519 let num_authorities = authorities.len();
520 let stake_per_authority = total_stake / num_authorities as u64;
521
522 assert_eq!(agg.uncommitted_stake(), total_stake);
524 assert_eq!(agg.plurality_stake(), 0);
525 assert!(!agg.quorum_unreachable());
526
527 let msg1 = TestMessage {
529 value: "value1".to_string(),
530 };
531 let envelope1 = <Envelope<TestMessage, AuthoritySignInfo>>::new(
532 0,
533 msg1.clone(),
534 &key_pairs[0],
535 authorities[0],
536 );
537 agg.insert("key1".to_string(), envelope1);
538
539 assert_eq!(agg.uncommitted_stake(), total_stake - stake_per_authority);
540 assert_eq!(agg.plurality_stake(), stake_per_authority);
541
542 let msg2 = TestMessage {
544 value: "value2".to_string(),
545 };
546 let envelope2 = <Envelope<TestMessage, AuthoritySignInfo>>::new(
547 0,
548 msg2.clone(),
549 &key_pairs[1],
550 authorities[1],
551 );
552 agg.insert("key2".to_string(), envelope2);
553
554 assert_eq!(
555 agg.uncommitted_stake(),
556 total_stake - 2 * stake_per_authority
557 );
558 assert_eq!(agg.plurality_stake(), stake_per_authority);
559
560 let envelope3 = <Envelope<TestMessage, AuthoritySignInfo>>::new(
562 0,
563 msg1.clone(),
564 &key_pairs[2],
565 authorities[2],
566 );
567 agg.insert("key1".to_string(), envelope3);
568
569 assert_eq!(
570 agg.uncommitted_stake(),
571 total_stake - 3 * stake_per_authority
572 );
573 assert_eq!(agg.plurality_stake(), 2 * stake_per_authority);
574 }
575
576 #[test]
577 fn test_multistake_quorum_unreachable() {
578 let (committee, key_pairs) = Committee::new_simple_test_committee();
579 let committee = Arc::new(committee);
580 let authorities: Vec<_> = committee.names().copied().collect();
581
582 let mut agg: MultiStakeAggregator<String, TestMessage, true> =
583 MultiStakeAggregator::new(committee.clone());
584
585 let msg1 = TestMessage {
588 value: "value1".to_string(),
589 };
590 let msg2 = TestMessage {
591 value: "value2".to_string(),
592 };
593
594 let envelope1 = <Envelope<TestMessage, AuthoritySignInfo>>::new(
596 0,
597 msg1.clone(),
598 &key_pairs[0],
599 authorities[0],
600 );
601 agg.insert("key1".to_string(), envelope1);
602
603 let envelope2 = <Envelope<TestMessage, AuthoritySignInfo>>::new(
604 0,
605 msg1.clone(),
606 &key_pairs[1],
607 authorities[1],
608 );
609 agg.insert("key1".to_string(), envelope2);
610
611 let envelope3 = <Envelope<TestMessage, AuthoritySignInfo>>::new(
613 0,
614 msg2.clone(),
615 &key_pairs[2],
616 authorities[2],
617 );
618 agg.insert("key2".to_string(), envelope3);
619
620 let envelope4 = <Envelope<TestMessage, AuthoritySignInfo>>::new(
621 0,
622 msg2.clone(),
623 &key_pairs[3],
624 authorities[3],
625 );
626 agg.insert("key2".to_string(), envelope4);
627
628 assert!(agg.quorum_unreachable());
630 }
631}
632
633#[cfg(test)]
634mod stake_aggregator_tests {
635 use super::*;
636
637 #[test]
638 fn test_stake_aggregator_strong_quorum() {
639 let (committee, _) = Committee::new_simple_test_committee();
640 let committee = Arc::new(committee);
641 let authorities: Vec<_> = committee.names().copied().collect();
642
643 let mut agg: StakeAggregator<(), true> = StakeAggregator::new(committee.clone());
644
645 let total_stake = committee.total_votes();
646 let num_authorities = authorities.len();
647 let stake_per_authority = total_stake / num_authorities as u64;
648
649 assert_eq!(agg.total_votes(), 0);
650 assert!(!agg.has_quorum());
651 assert_eq!(agg.validator_sig_count(), 0);
652
653 let result = agg.insert_generic(authorities[0], ());
655 assert!(matches!(result, InsertResult::NotEnoughVotes { .. }));
656 assert_eq!(agg.total_votes(), stake_per_authority);
657 assert!(!agg.has_quorum());
658 assert_eq!(agg.validator_sig_count(), 1);
659
660 let result = agg.insert_generic(authorities[1], ());
662 assert!(matches!(result, InsertResult::NotEnoughVotes { .. }));
663 assert_eq!(agg.total_votes(), 2 * stake_per_authority);
664 assert!(!agg.has_quorum());
665
666 let result = agg.insert_generic(authorities[2], ());
668 assert!(result.is_quorum_reached());
669 assert!(agg.has_quorum());
670 assert_eq!(agg.validator_sig_count(), 3);
671 }
672
673 #[test]
674 fn test_stake_aggregator_weak_quorum() {
675 let (committee, _) = Committee::new_simple_test_committee();
676 let committee = Arc::new(committee);
677 let authorities: Vec<_> = committee.names().copied().collect();
678
679 let mut agg: StakeAggregator<(), false> = StakeAggregator::new(committee.clone());
680
681 let result = agg.insert_generic(authorities[0], ());
683 assert!(matches!(result, InsertResult::NotEnoughVotes { .. }));
684 assert!(!agg.has_quorum());
685
686 let result = agg.insert_generic(authorities[1], ());
688 assert!(result.is_quorum_reached());
689 assert!(agg.has_quorum());
690 }
691
692 #[test]
693 fn test_stake_aggregator_repeated_signer() {
694 let (committee, _) = Committee::new_simple_test_committee();
695 let committee = Arc::new(committee);
696 let authorities: Vec<_> = committee.names().copied().collect();
697
698 let mut agg: StakeAggregator<u32, true> = StakeAggregator::new(committee.clone());
699
700 let result = agg.insert_generic(authorities[0], 1);
702 assert!(matches!(result, InsertResult::NotEnoughVotes { .. }));
703
704 let result = agg.insert_generic(authorities[0], 1);
706 assert!(matches!(
707 result,
708 InsertResult::Failed {
709 error
710 } if matches!(error.as_inner(), SuiErrorKind::StakeAggregatorRepeatedSigner { .. } )
711 ));
712
713 let result = agg.insert_generic(authorities[0], 2);
715 let InsertResult::Failed { error } = result else {
716 panic!("Expected StakeAggregatorRepeatedSigner error");
717 };
718 let SuiErrorKind::StakeAggregatorRepeatedSigner {
719 signer,
720 conflicting_sig,
721 } = error.into_inner()
722 else {
723 panic!("Expected StakeAggregatorRepeatedSigner error");
724 };
725 assert_eq!(signer, authorities[0]);
726 assert!(conflicting_sig);
727 }
728
729 #[test]
730 fn test_stake_aggregator_from_iter() {
731 let (committee, _) = Committee::new_simple_test_committee();
732 let committee = Arc::new(committee);
733 let authorities: Vec<_> = committee.names().copied().collect();
734
735 let data = vec![
736 Ok((authorities[0], ())),
737 Ok((authorities[1], ())),
738 Ok((authorities[2], ())),
739 ];
740
741 let agg: StakeAggregator<(), true> =
742 StakeAggregator::from_iter(committee.clone(), data.into_iter()).unwrap();
743
744 assert_eq!(agg.validator_sig_count(), 3);
745 assert!(agg.has_quorum());
746 assert!(agg.contains_key(&authorities[0]));
747 assert!(agg.contains_key(&authorities[1]));
748 assert!(agg.contains_key(&authorities[2]));
749 }
750
751 #[test]
752 fn test_stake_aggregator_from_iter_with_error() {
753 let (committee, _) = Committee::new_simple_test_committee();
754 let committee = Arc::new(committee);
755 let authorities: Vec<_> = committee.names().copied().collect();
756
757 let data: Vec<Result<(AuthorityName, ()), TypedStoreError>> = vec![
758 Ok((authorities[0], ())),
759 Err(TypedStoreError::RocksDBError("test error".to_string())),
760 ];
761
762 let result: SuiResult<StakeAggregator<(), true>> =
763 StakeAggregator::from_iter(committee.clone(), data.into_iter());
764
765 assert!(result.is_err());
766 }
767}
768
769#[cfg(test)]
770mod generic_multi_stake_aggregator_tests {
771 use super::*;
772
773 #[test]
774 fn test_has_quorum_for_key() {
775 let (committee, _) = Committee::new_simple_test_committee();
776 let committee = Arc::new(committee);
777 let authorities: Vec<_> = committee.names().copied().collect();
778
779 let mut agg: GenericMultiStakeAggregator<&str, true> =
780 GenericMultiStakeAggregator::new(committee.clone());
781
782 let key1 = "key1";
783 let key2 = "key2";
784
785 assert!(!agg.has_quorum_for_key(&key1));
787 assert!(!agg.has_quorum_for_key(&key2));
788
789 agg.insert(authorities[0], key1);
791 assert!(!agg.has_quorum_for_key(&key1));
792
793 agg.insert(authorities[1], key1);
794 assert!(!agg.has_quorum_for_key(&key1));
795
796 agg.insert(authorities[2], key1);
797 assert!(agg.has_quorum_for_key(&key1));
798 assert!(!agg.has_quorum_for_key(&key2));
799
800 agg.insert(authorities[3], key2);
802 assert!(agg.has_quorum_for_key(&key1));
803 assert!(!agg.has_quorum_for_key(&key2));
804 }
805}