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