1use num_traits::ToPrimitive;
32
33pub trait Faults {
43 fn max_faults(n: impl ToPrimitive) -> u32;
52
53 fn quorum(n: impl ToPrimitive) -> u32 {
62 let n = n
63 .to_u32()
64 .expect("n must be a non-negative integer that fits in u32");
65 assert!(n > 0, "n must not be zero");
66 n - Self::max_faults(n)
67 }
68
69 fn quorum_from_slice<T>(slice: &[T]) -> u32 {
77 let n: u32 = slice
78 .len()
79 .try_into()
80 .expect("slice length must be less than u32::MAX");
81 Self::quorum(n)
82 }
83}
84
85#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
101pub struct N3f1;
102
103impl Faults for N3f1 {
104 fn max_faults(n: impl ToPrimitive) -> u32 {
105 let n = n
106 .to_u32()
107 .expect("n must be a non-negative integer that fits in u32");
108 assert!(n > 0, "n must not be zero");
109 (n - 1) / 3
110 }
111}
112
113#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
129pub struct N5f1;
130
131impl Faults for N5f1 {
132 fn max_faults(n: impl ToPrimitive) -> u32 {
133 let n = n
134 .to_u32()
135 .expect("n must be a non-negative integer that fits in u32");
136 assert!(n > 0, "n must not be zero");
137 (n - 1) / 5
138 }
139}
140
141impl N5f1 {
142 pub fn m_quorum(n: impl ToPrimitive) -> u32 {
148 let n = n
149 .to_u32()
150 .expect("n must be a non-negative integer that fits in u32");
151 assert!(n > 0, "n must not be zero");
152 2 * Self::max_faults(n) + 1
153 }
154
155 pub fn l_quorum(n: impl ToPrimitive) -> u32 {
163 Self::quorum(n)
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use proptest::prelude::*;
171 use rstest::rstest;
172
173 #[test]
174 #[should_panic(expected = "n must not be zero")]
175 fn test_bft3f1_max_faults_zero_panics() {
176 N3f1::max_faults(0);
177 }
178
179 #[test]
180 #[should_panic(expected = "n must not be zero")]
181 fn test_bft3f1_quorum_zero_panics() {
182 N3f1::quorum(0);
183 }
184
185 #[rstest]
186 #[case(1, 0, 1)]
187 #[case(2, 0, 2)]
188 #[case(3, 0, 3)]
189 #[case(4, 1, 3)]
190 #[case(5, 1, 4)]
191 #[case(6, 1, 5)]
192 #[case(7, 2, 5)]
193 #[case(8, 2, 6)]
194 #[case(9, 2, 7)]
195 #[case(10, 3, 7)]
196 #[case(11, 3, 8)]
197 #[case(12, 3, 9)]
198 #[case(13, 4, 9)]
199 #[case(14, 4, 10)]
200 #[case(15, 4, 11)]
201 #[case(16, 5, 11)]
202 #[case(17, 5, 12)]
203 #[case(18, 5, 13)]
204 #[case(19, 6, 13)]
205 #[case(20, 6, 14)]
206 #[case(21, 6, 15)]
207 fn test_bft3f1_quorum_and_max_faults(
208 #[case] n: u32,
209 #[case] expected_f: u32,
210 #[case] expected_q: u32,
211 ) {
212 assert_eq!(N3f1::max_faults(n), expected_f);
213 assert_eq!(N3f1::quorum(n), expected_q);
214 assert_eq!(n, expected_f + expected_q);
216 }
217
218 #[test]
219 #[should_panic(expected = "n must not be zero")]
220 fn test_bft5f1_max_faults_zero_panics() {
221 N5f1::max_faults(0);
222 }
223
224 #[test]
225 #[should_panic(expected = "n must not be zero")]
226 fn test_bft5f1_quorum_zero_panics() {
227 N5f1::quorum(0);
228 }
229
230 #[test]
231 #[should_panic(expected = "n must not be zero")]
232 fn test_bft5f1_m_quorum_zero_panics() {
233 N5f1::m_quorum(0);
234 }
235
236 #[test]
237 #[should_panic(expected = "n must not be zero")]
238 fn test_bft5f1_l_quorum_zero_panics() {
239 N5f1::l_quorum(0);
240 }
241
242 #[rstest]
243 #[case(1, 0, 1, 1)]
245 #[case(2, 0, 2, 1)]
246 #[case(3, 0, 3, 1)]
247 #[case(4, 0, 4, 1)]
248 #[case(5, 0, 5, 1)]
249 #[case(6, 1, 5, 3)]
251 #[case(7, 1, 6, 3)]
252 #[case(8, 1, 7, 3)]
253 #[case(9, 1, 8, 3)]
254 #[case(10, 1, 9, 3)]
255 #[case(11, 2, 9, 5)]
257 #[case(12, 2, 10, 5)]
258 #[case(13, 2, 11, 5)]
259 #[case(14, 2, 12, 5)]
260 #[case(15, 2, 13, 5)]
261 #[case(16, 3, 13, 7)]
263 #[case(17, 3, 14, 7)]
264 #[case(18, 3, 15, 7)]
265 #[case(19, 3, 16, 7)]
266 #[case(20, 3, 17, 7)]
267 #[case(21, 4, 17, 9)]
269 fn test_bft5f1_quorums(
270 #[case] n: u32,
271 #[case] expected_f: u32,
272 #[case] expected_l_quorum: u32,
273 #[case] expected_m_quorum: u32,
274 ) {
275 assert_eq!(N5f1::max_faults(n), expected_f);
276 assert_eq!(N5f1::quorum(n), expected_l_quorum);
277 assert_eq!(N5f1::l_quorum(n), expected_l_quorum);
278 assert_eq!(N5f1::m_quorum(n), expected_m_quorum);
279
280 assert_eq!(n, expected_f + expected_l_quorum); assert_eq!(expected_m_quorum, 2 * expected_f + 1); }
284
285 #[test]
286 fn test_quorum_from_slice() {
287 let items = vec![1, 2, 3, 4, 5, 6, 7];
288 assert_eq!(N3f1::quorum_from_slice(&items), 5); assert_eq!(N5f1::quorum_from_slice(&items), 6); }
291
292 #[test]
293 #[should_panic(expected = "n must not be zero")]
294 fn test_quorum_from_empty_slice_panics() {
295 let items: Vec<u8> = vec![];
296 N3f1::quorum_from_slice(&items);
297 }
298
299 #[test]
300 fn test_generic_integer_types() {
301 assert_eq!(N3f1::max_faults(10u8), 3);
303 assert_eq!(N3f1::max_faults(10u16), 3);
304 assert_eq!(N3f1::max_faults(10u32), 3);
305 assert_eq!(N3f1::max_faults(10u64), 3);
306 assert_eq!(N3f1::max_faults(10usize), 3);
307 assert_eq!(N3f1::max_faults(10i32), 3);
308 assert_eq!(N3f1::max_faults(10i64), 3);
309
310 assert_eq!(N3f1::quorum(10u8), 7);
311 assert_eq!(N3f1::quorum(10u16), 7);
312 assert_eq!(N3f1::quorum(10u64), 7);
313 assert_eq!(N3f1::quorum(10usize), 7);
314 assert_eq!(N3f1::quorum(10i32), 7);
315 assert_eq!(N3f1::quorum(10i64), 7);
316
317 assert_eq!(N5f1::max_faults(10u64), 1);
318 assert_eq!(N5f1::quorum(10usize), 9);
319 assert_eq!(N5f1::m_quorum(10i32), 3);
320 assert_eq!(N5f1::l_quorum(10i64), 9);
321 }
322
323 #[test]
324 #[should_panic(expected = "n must be a non-negative integer that fits in u32")]
325 fn test_max_faults_negative_panics() {
326 N3f1::max_faults(-1i32);
327 }
328
329 #[test]
330 #[should_panic(expected = "n must be a non-negative integer that fits in u32")]
331 fn test_max_faults_overflow_panics() {
332 N3f1::max_faults(u64::MAX);
333 }
334
335 #[test]
336 #[should_panic(expected = "n must be a non-negative integer that fits in u32")]
337 fn test_quorum_negative_panics() {
338 N3f1::quorum(-1i32);
339 }
340
341 #[test]
342 #[should_panic(expected = "n must be a non-negative integer that fits in u32")]
343 fn test_quorum_overflow_panics() {
344 N3f1::quorum(u64::MAX);
345 }
346
347 proptest! {
348 #[test]
354 fn test_n5f1_quorum_relationships(n in 6u32..10_000) {
355 let m = N5f1::m_quorum(n);
356 let l = N5f1::l_quorum(n);
357
358 prop_assert!(
360 m < l,
361 "M-quorum ({}) should be less than L-quorum ({}) for n={}",
362 m, l, n
363 );
364
365 prop_assert!(m <= n, "M-quorum ({}) should be <= n ({})", m, n);
367 prop_assert!(l <= n, "L-quorum ({}) should be <= n ({})", l, n);
368 }
369
370 #[test]
377 fn test_bft_model_safety_property(n in 1u32..10_000) {
378 let f_3f1 = N3f1::max_faults(n);
380 let q_3f1 = N3f1::quorum(n);
381 prop_assert!(
382 2 * q_3f1 > n + f_3f1,
383 "N3f1 safety violated for n={}: 2*{} <= {} + {}",
384 n, q_3f1, n, f_3f1
385 );
386
387 let f_5f1 = N5f1::max_faults(n);
389 let q_5f1 = N5f1::quorum(n);
390 prop_assert!(
391 2 * q_5f1 > n + f_5f1,
392 "N5f1 safety violated for n={}: 2*{} <= {} + {}",
393 n, q_5f1, n, f_5f1
394 );
395 }
396 }
397}