quorum_set/tree/
quorum_tree.rs1use std::cmp::Ordering;
2use std::fmt;
3use std::hash::Hash;
4use std::hash::Hasher;
5
6use super::tree_spec::QuorumTreeSpec;
7use crate::CanonicalId;
8use crate::Node;
9use crate::QuorumTreeError;
10
11mod impl_display;
12
13#[derive(Clone, Debug)]
33pub struct QuorumTree<ID>
34where ID: Ord
35{
36 pub(crate) spec: QuorumTreeSpec<ID>,
37
38 canonical_id: String,
39}
40
41impl<ID> QuorumTree<ID>
42where ID: Ord
43{
44 pub fn new(
79 quorum_size: u64,
80 nodes: impl IntoIterator<Item = Node<ID>>,
81 ) -> Result<Self, QuorumTreeError>
82 where
83 ID: CanonicalId,
84 {
85 let spec = QuorumTreeSpec::new(quorum_size, nodes)?;
86 let canonical_id = spec.canonical_id();
87 Ok(Self { spec, canonical_id })
88 }
89
90 pub fn quorum_size(&self) -> u64 {
93 self.spec.quorum_size()
94 }
95
96 pub fn children(&self) -> impl Iterator<Item = &Node<ID>> {
100 self.spec.children()
101 }
102
103 pub fn canonical_id(&self) -> &str {
108 &self.canonical_id
109 }
110}
111
112impl<ID> PartialEq for QuorumTree<ID>
118where ID: Ord
119{
120 fn eq(&self, other: &Self) -> bool {
121 self.canonical_id == other.canonical_id
122 }
123}
124
125impl<ID> Eq for QuorumTree<ID> where ID: Ord {}
126
127impl<ID> PartialOrd for QuorumTree<ID>
128where ID: Ord
129{
130 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
131 Some(self.cmp(other))
132 }
133}
134
135impl<ID> Ord for QuorumTree<ID>
136where ID: Ord
137{
138 fn cmp(&self, other: &Self) -> Ordering {
139 self.canonical_id.cmp(&other.canonical_id)
140 }
141}
142
143impl<ID> Hash for QuorumTree<ID>
145where ID: Ord
146{
147 fn hash<H>(&self, state: &mut H)
148 where H: Hasher {
149 self.canonical_id.hash(state);
150 }
151}
152
153impl<ID> CanonicalId for QuorumTree<ID>
154where ID: Ord + CanonicalId
155{
156 fn fmt_canonical_id<W>(&self, f: &mut W) -> fmt::Result
157 where W: fmt::Write + ?Sized {
158 write!(f, "{}", self.canonical_id)
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use std::cmp::Ordering;
165 use std::collections::HashSet;
166
167 use crate::Node;
168 use crate::QuorumTree;
169 use crate::QuorumTreeError;
170
171 fn id(i: u64) -> Node<u64> {
172 Node::Id(i)
173 }
174
175 #[test]
176 fn test_eq_ignores_construction_order() {
177 let a = QuorumTree::new(2, [id(1), id(2), id(3)]).unwrap();
178 let b = QuorumTree::new(2, [id(3), id(1), id(2)]).unwrap();
179 let c = QuorumTree::new(3, [id(1), id(2), id(3)]).unwrap();
180
181 assert_eq!(a, b);
182 assert_eq!(a.canonical_id(), b.canonical_id());
183 assert_ne!(a, c);
184 }
185
186 #[test]
187 fn test_hash_is_consistent_with_eq() {
188 let a = QuorumTree::new(2, [id(1), id(2), id(3)]).unwrap();
189 let b = QuorumTree::new(2, [id(3), id(2), id(1)]).unwrap();
190 let c = QuorumTree::new(3, [id(1), id(2), id(3)]).unwrap();
191
192 let set: HashSet<QuorumTree<u64>> = [a.clone(), b.clone(), c.clone()].into_iter().collect();
193
194 assert_eq!(HashSet::from([a, c]), set);
195 }
196
197 #[test]
198 fn test_canonical_id_accessor() {
199 let tree = QuorumTree::new(2, [id(3), id(1), id(2)]).unwrap();
200
201 assert_eq!("2/(Id=1,Id=2,Id=3)", tree.canonical_id());
202 }
203
204 #[test]
205 fn test_quorum_size_accessor() {
206 let tree = QuorumTree::new(2, [id(1), id(2), id(3)]).unwrap();
207
208 assert_eq!(2, tree.quorum_size());
209 }
210
211 #[test]
212 fn test_ord_is_consistent_with_canonical_id() {
213 let a = QuorumTree::new(1, [id(1), id(2)]).unwrap();
214 let b = QuorumTree::new(2, [id(1), id(2)]).unwrap();
215
216 assert_eq!(Ordering::Less, a.cmp(&b));
217 assert_eq!(Some(Ordering::Less), a.partial_cmp(&b));
218 assert_eq!(
219 a.canonical_id().cmp(b.canonical_id()),
220 a.cmp(&b),
221 "ordering follows the canonical ID"
222 );
223 assert_eq!(Ordering::Equal, a.cmp(&a.clone()));
224 }
225
226 #[test]
227 fn test_children_are_sorted() {
228 let tree = QuorumTree::new(2, [id(3), id(1), id(2)]).unwrap();
229
230 assert_eq!(
231 vec![id(1), id(2), id(3)],
232 tree.children().cloned().collect::<Vec<_>>()
233 );
234 }
235
236 #[test]
237 fn test_new_rejects_duplicate_child() {
238 let err = QuorumTree::new(2, [id(1), id(2), id(2)]).unwrap_err();
239
240 assert_eq!(
241 QuorumTreeError::DuplicateChild {
242 canonical_id: "Id=2".to_string()
243 },
244 err
245 );
246
247 let sub = QuorumTree::new(1, [id(1), id(2)]).unwrap();
248 let err = QuorumTree::new(2, [Node::Subtree(sub.clone()), Node::Subtree(sub)]).unwrap_err();
249
250 assert_eq!(
251 QuorumTreeError::DuplicateChild {
252 canonical_id: "Subtree=1/(Id=1,Id=2)".to_string(),
253 },
254 err
255 );
256 }
257
258 #[test]
259 fn test_new_rejects_unsatisfiable_quorum() {
260 let err = QuorumTree::new(3, [id(1), id(2)]).unwrap_err();
261
262 assert_eq!(
263 QuorumTreeError::UnsatisfiableQuorum {
264 quorum_size: 3,
265 num_children: 2
266 },
267 err
268 );
269 }
270
271 #[test]
272 fn test_new_rejects_zero_quorum_size() {
273 let err = QuorumTree::new(0, [id(1), id(2)]).unwrap_err();
274
275 assert_eq!(QuorumTreeError::ZeroQuorumSize, err);
276 }
277}