Skip to main content

quorum_set/quorum/
joint.rs

1use std::collections::BTreeSet;
2use std::collections::btree_set;
3
4use crate::quorum::QuorumBridge;
5use crate::quorum::QuorumIntersection;
6use crate::quorum::QuorumSet;
7
8/// A quorum rule that accepts an ID set only when every one of its rules accepts it.
9///
10/// A membership change can move through a joint of the old and the new rule, whose every quorum
11/// is a quorum of both. An empty `Joint` accepts no set, like an empty `Vec<BTreeSet<ID>>`.
12///
13/// [`ids()`](QuorumSet::ids) returns the union of the IDs of every rule, each ID once.
14///
15/// # Examples
16///
17/// ```
18/// use std::collections::BTreeSet;
19/// use std::num::NonZeroU64;
20///
21/// use quorum_set::{Joint, QuorumSet, ThresholdQuorumSet};
22///
23/// let two = NonZeroU64::new(2).unwrap();
24/// let old = ThresholdQuorumSet::new(two, BTreeSet::from([1, 2, 3]));
25/// let new = ThresholdQuorumSet::new(two, BTreeSet::from([2, 3, 4]));
26/// let joint = Joint::new([old, new]);
27///
28/// // {1,2} holds 2 of the old IDs but only 1 of the new IDs.
29/// let accepts_one_two = joint.is_quorum([1, 2].iter());
30/// assert!(!accepts_one_two);
31/// let accepts_one_two_four = joint.is_quorum([1, 2, 4].iter());
32/// assert!(accepts_one_two_four);
33/// ```
34#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct Joint<Q> {
36    rules: Vec<Q>,
37}
38
39impl<Q> Joint<Q> {
40    /// Builds a rule that accepts an ID set only when every one of `rules` accepts it.
41    pub fn new(rules: impl IntoIterator<Item = Q>) -> Self {
42        let rules = rules.into_iter().collect();
43        Self { rules }
44    }
45
46    /// Returns the rules, in the order they were given.
47    pub fn rules(&self) -> &[Q] {
48        &self.rules
49    }
50}
51
52/// Written by hand: `derive(Default)` would require `Q: Default`.
53impl<Q> Default for Joint<Q> {
54    fn default() -> Self {
55        Self { rules: vec![] }
56    }
57}
58
59impl<Q> QuorumSet for Joint<Q>
60where
61    Q: QuorumSet,
62    Q::Id: Ord,
63{
64    type Id = Q::Id;
65    type Iter = btree_set::IntoIter<Q::Id>;
66
67    fn is_quorum<'a, I>(&self, ids: I) -> bool
68    where
69        Self::Id: 'a,
70        I: Iterator<Item = &'a Self::Id> + Clone,
71    {
72        if self.rules.is_empty() {
73            return false;
74        }
75
76        for rule in &self.rules {
77            if !rule.is_quorum(ids.clone()) {
78                return false;
79            }
80        }
81        true
82    }
83
84    fn ids(&self) -> Self::Iter {
85        let mut ids = BTreeSet::new();
86        for rule in &self.rules {
87            ids.extend(rule.ids());
88        }
89        ids.into_iter()
90    }
91}
92
93/// Two joints are treated as having quorum intersection when some rule of one intersects some
94/// rule of the other: every quorum of a joint is also a quorum of each of its rules, so every
95/// quorum of one joint then intersects every quorum of the other. This is sufficient but not
96/// necessary for that property, so this check answers `None` for some pairs of joints whose
97/// quorums do in fact all intersect.
98impl<Q> QuorumIntersection<Joint<Q>> for Joint<Q>
99where
100    Q: QuorumIntersection<Q>,
101    Q::Id: Ord,
102{
103    /// Return the answer of the rule check when each joint holds one rule. For other joints,
104    /// return `Some(true)` when some rule of `self` intersects some rule of `other`.
105    ///
106    /// When either joint is empty, return `Some(true)`: an empty joint has no quorum, so no pair
107    /// of quorums can be disjoint.
108    ///
109    /// Return `None` for every other pair: two rules that do not intersect prove nothing about
110    /// two joints that contain them.
111    fn intersects_with(&self, other: &Joint<Q>) -> Option<bool> {
112        let either_is_empty = self.rules.is_empty() || other.rules.is_empty();
113        if either_is_empty {
114            return Some(true);
115        }
116
117        let both_uniform = self.rules.len() == 1 && other.rules.len() == 1;
118        if both_uniform {
119            return self.rules[0].intersects_with(&other.rules[0]);
120        }
121
122        for a in &self.rules {
123            for b in &other.rules {
124                let intersects = a.intersects_with(b);
125                if intersects == Some(true) {
126                    return Some(true);
127                }
128            }
129        }
130        None
131    }
132}
133
134/// Builds the joint of the source and target rules.
135impl<Q> QuorumBridge<Joint<Q>> for Joint<Q>
136where
137    Q: QuorumSet + Clone,
138    Q::Id: Ord,
139{
140    /// Return the rules of `self` followed by the rules of `other`. Return an empty joint when
141    /// `self` or `other` is empty: an empty joint has no quorum, so a joint with it has no quorum
142    /// either.
143    fn joint_with(&self, other: Joint<Q>) -> Self {
144        if self.rules.is_empty() || other.rules.is_empty() {
145            return Self::default();
146        }
147        let mut rules = self.rules.clone();
148        rules.extend(other.rules);
149        Self { rules }
150    }
151}