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
70#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
86pub struct N3f1;
87
88impl Faults for N3f1 {
89 fn max_faults(n: impl ToPrimitive) -> u32 {
90 let n = n
91 .to_u32()
92 .expect("n must be a non-negative integer that fits in u32");
93 assert!(n > 0, "n must not be zero");
94 (n - 1) / 3
95 }
96}
97
98#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
114pub struct N5f1;
115
116impl Faults for N5f1 {
117 fn max_faults(n: impl ToPrimitive) -> u32 {
118 let n = n
119 .to_u32()
120 .expect("n must be a non-negative integer that fits in u32");
121 assert!(n > 0, "n must not be zero");
122 (n - 1) / 5
123 }
124}
125
126impl N5f1 {
127 pub fn m_quorum(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 2 * Self::max_faults(n) + 1
138 }
139
140 pub fn l_quorum(n: impl ToPrimitive) -> u32 {
148 Self::quorum(n)
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use proptest::prelude::*;
156 use rstest::rstest;
157
158 #[test]
159 #[should_panic(expected = "n must not be zero")]
160 fn test_bft3f1_max_faults_zero_panics() {
161 N3f1::max_faults(0);
162 }
163
164 #[test]
165 #[should_panic(expected = "n must not be zero")]
166 fn test_bft3f1_quorum_zero_panics() {
167 N3f1::quorum(0);
168 }
169
170 #[rstest]
171 #[case(1, 0, 1)]
172 #[case(2, 0, 2)]
173 #[case(3, 0, 3)]
174 #[case(4, 1, 3)]
175 #[case(5, 1, 4)]
176 #[case(6, 1, 5)]
177 #[case(7, 2, 5)]
178 #[case(8, 2, 6)]
179 #[case(9, 2, 7)]
180 #[case(10, 3, 7)]
181 #[case(11, 3, 8)]
182 #[case(12, 3, 9)]
183 #[case(13, 4, 9)]
184 #[case(14, 4, 10)]
185 #[case(15, 4, 11)]
186 #[case(16, 5, 11)]
187 #[case(17, 5, 12)]
188 #[case(18, 5, 13)]
189 #[case(19, 6, 13)]
190 #[case(20, 6, 14)]
191 #[case(21, 6, 15)]
192 fn test_bft3f1_quorum_and_max_faults(
193 #[case] n: u32,
194 #[case] expected_f: u32,
195 #[case] expected_q: u32,
196 ) {
197 assert_eq!(N3f1::max_faults(n), expected_f);
198 assert_eq!(N3f1::quorum(n), expected_q);
199 assert_eq!(n, expected_f + expected_q);
201 }
202
203 #[test]
204 #[should_panic(expected = "n must not be zero")]
205 fn test_bft5f1_max_faults_zero_panics() {
206 N5f1::max_faults(0);
207 }
208
209 #[test]
210 #[should_panic(expected = "n must not be zero")]
211 fn test_bft5f1_quorum_zero_panics() {
212 N5f1::quorum(0);
213 }
214
215 #[test]
216 #[should_panic(expected = "n must not be zero")]
217 fn test_bft5f1_m_quorum_zero_panics() {
218 N5f1::m_quorum(0);
219 }
220
221 #[test]
222 #[should_panic(expected = "n must not be zero")]
223 fn test_bft5f1_l_quorum_zero_panics() {
224 N5f1::l_quorum(0);
225 }
226
227 #[rstest]
228 #[case(1, 0, 1, 1)]
230 #[case(2, 0, 2, 1)]
231 #[case(3, 0, 3, 1)]
232 #[case(4, 0, 4, 1)]
233 #[case(5, 0, 5, 1)]
234 #[case(6, 1, 5, 3)]
236 #[case(7, 1, 6, 3)]
237 #[case(8, 1, 7, 3)]
238 #[case(9, 1, 8, 3)]
239 #[case(10, 1, 9, 3)]
240 #[case(11, 2, 9, 5)]
242 #[case(12, 2, 10, 5)]
243 #[case(13, 2, 11, 5)]
244 #[case(14, 2, 12, 5)]
245 #[case(15, 2, 13, 5)]
246 #[case(16, 3, 13, 7)]
248 #[case(17, 3, 14, 7)]
249 #[case(18, 3, 15, 7)]
250 #[case(19, 3, 16, 7)]
251 #[case(20, 3, 17, 7)]
252 #[case(21, 4, 17, 9)]
254 fn test_bft5f1_quorums(
255 #[case] n: u32,
256 #[case] expected_f: u32,
257 #[case] expected_l_quorum: u32,
258 #[case] expected_m_quorum: u32,
259 ) {
260 assert_eq!(N5f1::max_faults(n), expected_f);
261 assert_eq!(N5f1::quorum(n), expected_l_quorum);
262 assert_eq!(N5f1::l_quorum(n), expected_l_quorum);
263 assert_eq!(N5f1::m_quorum(n), expected_m_quorum);
264
265 assert_eq!(n, expected_f + expected_l_quorum); assert_eq!(expected_m_quorum, 2 * expected_f + 1); }
269
270 #[test]
271 fn test_generic_integer_types() {
272 assert_eq!(N3f1::max_faults(10u8), 3);
274 assert_eq!(N3f1::max_faults(10u16), 3);
275 assert_eq!(N3f1::max_faults(10u32), 3);
276 assert_eq!(N3f1::max_faults(10u64), 3);
277 assert_eq!(N3f1::max_faults(10usize), 3);
278 assert_eq!(N3f1::max_faults(10i32), 3);
279 assert_eq!(N3f1::max_faults(10i64), 3);
280
281 assert_eq!(N3f1::quorum(10u8), 7);
282 assert_eq!(N3f1::quorum(10u16), 7);
283 assert_eq!(N3f1::quorum(10u64), 7);
284 assert_eq!(N3f1::quorum(10usize), 7);
285 assert_eq!(N3f1::quorum(10i32), 7);
286 assert_eq!(N3f1::quorum(10i64), 7);
287
288 assert_eq!(N5f1::max_faults(10u64), 1);
289 assert_eq!(N5f1::quorum(10usize), 9);
290 assert_eq!(N5f1::m_quorum(10i32), 3);
291 assert_eq!(N5f1::l_quorum(10i64), 9);
292 }
293
294 #[test]
295 #[should_panic(expected = "n must be a non-negative integer that fits in u32")]
296 fn test_max_faults_negative_panics() {
297 N3f1::max_faults(-1i32);
298 }
299
300 #[test]
301 #[should_panic(expected = "n must be a non-negative integer that fits in u32")]
302 fn test_max_faults_overflow_panics() {
303 N3f1::max_faults(u64::MAX);
304 }
305
306 #[test]
307 #[should_panic(expected = "n must be a non-negative integer that fits in u32")]
308 fn test_quorum_negative_panics() {
309 N3f1::quorum(-1i32);
310 }
311
312 #[test]
313 #[should_panic(expected = "n must be a non-negative integer that fits in u32")]
314 fn test_quorum_overflow_panics() {
315 N3f1::quorum(u64::MAX);
316 }
317
318 proptest! {
319 #[test]
325 fn test_n5f1_quorum_relationships(n in 6u32..10_000) {
326 let m = N5f1::m_quorum(n);
327 let l = N5f1::l_quorum(n);
328
329 prop_assert!(
331 m < l,
332 "M-quorum ({}) should be less than L-quorum ({}) for n={}",
333 m, l, n
334 );
335
336 prop_assert!(m <= n, "M-quorum ({}) should be <= n ({})", m, n);
338 prop_assert!(l <= n, "L-quorum ({}) should be <= n ({})", l, n);
339 }
340
341 #[test]
348 fn test_bft_model_safety_property(n in 1u32..10_000) {
349 let f_3f1 = N3f1::max_faults(n);
351 let q_3f1 = N3f1::quorum(n);
352 prop_assert!(
353 2 * q_3f1 > n + f_3f1,
354 "N3f1 safety violated for n={}: 2*{} <= {} + {}",
355 n, q_3f1, n, f_3f1
356 );
357
358 let f_5f1 = N5f1::max_faults(n);
360 let q_5f1 = N5f1::quorum(n);
361 prop_assert!(
362 2 * q_5f1 > n + f_5f1,
363 "N5f1 safety violated for n={}: 2*{} <= {} + {}",
364 n, q_5f1, n, f_5f1
365 );
366 }
367 }
368}