Skip to main content

quorum_set/quorum/
explicit_quorum_set.rs

1use std::collections::BTreeSet;
2use std::collections::btree_set;
3
4use crate::quorum::QuorumIntersection;
5use crate::quorum::QuorumSet;
6
7/// A quorum rule that accepts an ID set holding every ID of one of its listed quorums.
8///
9/// The rule keeps only the minimal quorums. [`new()`](Self::new) sorts the list, drops duplicate
10/// quorums, and drops each quorum that holds another listed quorum, because every set that holds
11/// the larger quorum also holds the smaller one. Two rules that accept the same sets therefore
12/// hold the same list.
13///
14/// Every accepted set holds a listed quorum, so two rules have quorum intersection exactly when
15/// every listed quorum of one meets every listed quorum of the other. The check compares each
16/// pair of listed quorums. A rule with no listed quorum accepts no set.
17///
18/// Listing quorums costs memory that grows with the policy. Some policies need exponentially many
19/// minimal quorums: a majority of `n` IDs has `C(n, n/2 + 1)` of them, 126 for 9 IDs and
20/// 6435 for 15.
21///
22/// # Examples
23///
24/// ```
25/// use std::collections::BTreeSet;
26///
27/// use quorum_set::{ExplicitQuorumSet, QuorumIntersection, QuorumSet};
28///
29/// // {1,2,3} holds {1,2}, so the rule keeps only {1,2} and {3,4}.
30/// let quorums = [BTreeSet::from([1, 2, 3]), BTreeSet::from([3, 4]), BTreeSet::from([1, 2])];
31/// let rule = ExplicitQuorumSet::new(quorums).unwrap();
32/// assert_eq!(&[BTreeSet::from([1, 2]), BTreeSet::from([3, 4])], rule.quorums());
33///
34/// let accepts = rule.is_quorum([4, 3, 9].iter());
35/// assert!(accepts);
36/// let accepts = rule.is_quorum([1, 3].iter());
37/// assert!(!accepts);
38///
39/// // {3,4} misses {1,2}.
40/// let intersects = rule.intersects_with(&rule);
41/// assert_eq!(Some(false), intersects);
42/// ```
43#[derive(Clone, Debug, PartialEq, Eq)]
44pub struct ExplicitQuorumSet<ID> {
45    /// The minimal quorums, sorted.
46    quorums: Vec<BTreeSet<ID>>,
47}
48
49impl<ID> ExplicitQuorumSet<ID>
50where ID: Ord + Clone
51{
52    /// Builds a rule that accepts an ID set holding every ID of one of `quorums`.
53    ///
54    /// Returns `None` when a listed quorum is empty: every set holds the empty set, so every set,
55    /// the empty one included, would be a quorum.
56    pub fn new<I>(quorums: I) -> Option<Self>
57    where I: IntoIterator<Item = BTreeSet<ID>> {
58        let mut quorums: Vec<BTreeSet<ID>> = quorums.into_iter().collect();
59        let has_empty_quorum = quorums.iter().any(|quorum| quorum.is_empty());
60        if has_empty_quorum {
61            return None;
62        }
63
64        quorums.sort();
65        quorums.dedup();
66
67        let mut minimal = Vec::new();
68        for quorum in &quorums {
69            let holds_other =
70                quorums.iter().any(|other| other != quorum && other.is_subset(quorum));
71            if holds_other {
72                continue;
73            }
74            minimal.push(quorum.clone());
75        }
76
77        let rule = Self { quorums: minimal };
78        Some(rule)
79    }
80
81    /// Returns the minimal quorums, sorted.
82    pub fn quorums(&self) -> &[BTreeSet<ID>] {
83        &self.quorums
84    }
85
86    /// Returns the first pair of a listed quorum of `self` and a listed quorum of `other` that
87    /// share no ID, in the order of the two lists, or `None` when every pair meets.
88    ///
89    /// Such a pair shows two disjoint quorums, so `None` means the two rules have quorum
90    /// intersection.
91    pub fn disjoint_quorums<'a>(
92        &'a self,
93        other: &'a Self,
94    ) -> Option<(&'a BTreeSet<ID>, &'a BTreeSet<ID>)> {
95        for quorum in &self.quorums {
96            for other_quorum in &other.quorums {
97                if quorum.is_disjoint(other_quorum) {
98                    return Some((quorum, other_quorum));
99                }
100            }
101        }
102        None
103    }
104}
105
106impl<ID> QuorumSet for ExplicitQuorumSet<ID>
107where ID: Ord + Clone
108{
109    type Id = ID;
110    type Iter = btree_set::IntoIter<ID>;
111
112    fn is_quorum<'a, I>(&self, ids: I) -> bool
113    where
114        ID: 'a,
115        I: Iterator<Item = &'a ID> + Clone,
116    {
117        let candidates: BTreeSet<&ID> = ids.collect();
118        for quorum in &self.quorums {
119            let holds_quorum = quorum.iter().all(|id| candidates.contains(id));
120            if holds_quorum {
121                return true;
122            }
123        }
124        false
125    }
126
127    fn ids(&self) -> Self::Iter {
128        let mut ids = BTreeSet::new();
129        for quorum in &self.quorums {
130            ids.extend(quorum.iter().cloned());
131        }
132        ids.into_iter()
133    }
134}
135
136/// Exact quorum intersection for two lists of minimal quorums.
137///
138/// Every quorum of a rule holds one of its listed quorums, and every listed quorum is a quorum.
139/// Two quorums are therefore disjoint only when two listed quorums are, so the check is exact.
140/// It runs in `O(a * b * n)` for `a` and `b` listed quorums of at most `n` IDs.
141impl<ID> QuorumIntersection<ExplicitQuorumSet<ID>> for ExplicitQuorumSet<ID>
142where ID: Ord + Clone
143{
144    /// Return `Some(false)` when a listed quorum of `self` and one of `other` are disjoint, and
145    /// `Some(true)` otherwise. This check never returns `None`.
146    ///
147    /// A rule with no listed quorum has no quorum, so it has quorum intersection with every rule.
148    fn intersects_with(&self, other: &ExplicitQuorumSet<ID>) -> Option<bool> {
149        let disjoint = self.disjoint_quorums(other);
150        Some(disjoint.is_none())
151    }
152}