use num_traits::ToPrimitive;
pub trait Faults {
fn max_faults(n: impl ToPrimitive) -> u32;
fn quorum(n: impl ToPrimitive) -> u32 {
let n = n
.to_u32()
.expect("n must be a non-negative integer that fits in u32");
assert!(n > 0, "n must not be zero");
n - Self::max_faults(n)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct N3f1;
impl Faults for N3f1 {
fn max_faults(n: impl ToPrimitive) -> u32 {
let n = n
.to_u32()
.expect("n must be a non-negative integer that fits in u32");
assert!(n > 0, "n must not be zero");
(n - 1) / 3
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct N5f1;
impl Faults for N5f1 {
fn max_faults(n: impl ToPrimitive) -> u32 {
let n = n
.to_u32()
.expect("n must be a non-negative integer that fits in u32");
assert!(n > 0, "n must not be zero");
(n - 1) / 5
}
}
impl N5f1 {
pub fn m_quorum(n: impl ToPrimitive) -> u32 {
let n = n
.to_u32()
.expect("n must be a non-negative integer that fits in u32");
assert!(n > 0, "n must not be zero");
2 * Self::max_faults(n) + 1
}
pub fn l_quorum(n: impl ToPrimitive) -> u32 {
Self::quorum(n)
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
use rstest::rstest;
#[test]
#[should_panic(expected = "n must not be zero")]
fn test_bft3f1_max_faults_zero_panics() {
N3f1::max_faults(0);
}
#[test]
#[should_panic(expected = "n must not be zero")]
fn test_bft3f1_quorum_zero_panics() {
N3f1::quorum(0);
}
#[rstest]
#[case(1, 0, 1)]
#[case(2, 0, 2)]
#[case(3, 0, 3)]
#[case(4, 1, 3)]
#[case(5, 1, 4)]
#[case(6, 1, 5)]
#[case(7, 2, 5)]
#[case(8, 2, 6)]
#[case(9, 2, 7)]
#[case(10, 3, 7)]
#[case(11, 3, 8)]
#[case(12, 3, 9)]
#[case(13, 4, 9)]
#[case(14, 4, 10)]
#[case(15, 4, 11)]
#[case(16, 5, 11)]
#[case(17, 5, 12)]
#[case(18, 5, 13)]
#[case(19, 6, 13)]
#[case(20, 6, 14)]
#[case(21, 6, 15)]
fn test_bft3f1_quorum_and_max_faults(
#[case] n: u32,
#[case] expected_f: u32,
#[case] expected_q: u32,
) {
assert_eq!(N3f1::max_faults(n), expected_f);
assert_eq!(N3f1::quorum(n), expected_q);
assert_eq!(n, expected_f + expected_q);
}
#[test]
#[should_panic(expected = "n must not be zero")]
fn test_bft5f1_max_faults_zero_panics() {
N5f1::max_faults(0);
}
#[test]
#[should_panic(expected = "n must not be zero")]
fn test_bft5f1_quorum_zero_panics() {
N5f1::quorum(0);
}
#[test]
#[should_panic(expected = "n must not be zero")]
fn test_bft5f1_m_quorum_zero_panics() {
N5f1::m_quorum(0);
}
#[test]
#[should_panic(expected = "n must not be zero")]
fn test_bft5f1_l_quorum_zero_panics() {
N5f1::l_quorum(0);
}
#[rstest]
#[case(1, 0, 1, 1)]
#[case(2, 0, 2, 1)]
#[case(3, 0, 3, 1)]
#[case(4, 0, 4, 1)]
#[case(5, 0, 5, 1)]
#[case(6, 1, 5, 3)]
#[case(7, 1, 6, 3)]
#[case(8, 1, 7, 3)]
#[case(9, 1, 8, 3)]
#[case(10, 1, 9, 3)]
#[case(11, 2, 9, 5)]
#[case(12, 2, 10, 5)]
#[case(13, 2, 11, 5)]
#[case(14, 2, 12, 5)]
#[case(15, 2, 13, 5)]
#[case(16, 3, 13, 7)]
#[case(17, 3, 14, 7)]
#[case(18, 3, 15, 7)]
#[case(19, 3, 16, 7)]
#[case(20, 3, 17, 7)]
#[case(21, 4, 17, 9)]
fn test_bft5f1_quorums(
#[case] n: u32,
#[case] expected_f: u32,
#[case] expected_l_quorum: u32,
#[case] expected_m_quorum: u32,
) {
assert_eq!(N5f1::max_faults(n), expected_f);
assert_eq!(N5f1::quorum(n), expected_l_quorum);
assert_eq!(N5f1::l_quorum(n), expected_l_quorum);
assert_eq!(N5f1::m_quorum(n), expected_m_quorum);
assert_eq!(n, expected_f + expected_l_quorum); assert_eq!(expected_m_quorum, 2 * expected_f + 1); }
#[test]
fn test_generic_integer_types() {
assert_eq!(N3f1::max_faults(10u8), 3);
assert_eq!(N3f1::max_faults(10u16), 3);
assert_eq!(N3f1::max_faults(10u32), 3);
assert_eq!(N3f1::max_faults(10u64), 3);
assert_eq!(N3f1::max_faults(10usize), 3);
assert_eq!(N3f1::max_faults(10i32), 3);
assert_eq!(N3f1::max_faults(10i64), 3);
assert_eq!(N3f1::quorum(10u8), 7);
assert_eq!(N3f1::quorum(10u16), 7);
assert_eq!(N3f1::quorum(10u64), 7);
assert_eq!(N3f1::quorum(10usize), 7);
assert_eq!(N3f1::quorum(10i32), 7);
assert_eq!(N3f1::quorum(10i64), 7);
assert_eq!(N5f1::max_faults(10u64), 1);
assert_eq!(N5f1::quorum(10usize), 9);
assert_eq!(N5f1::m_quorum(10i32), 3);
assert_eq!(N5f1::l_quorum(10i64), 9);
}
#[test]
#[should_panic(expected = "n must be a non-negative integer that fits in u32")]
fn test_max_faults_negative_panics() {
N3f1::max_faults(-1i32);
}
#[test]
#[should_panic(expected = "n must be a non-negative integer that fits in u32")]
fn test_max_faults_overflow_panics() {
N3f1::max_faults(u64::MAX);
}
#[test]
#[should_panic(expected = "n must be a non-negative integer that fits in u32")]
fn test_quorum_negative_panics() {
N3f1::quorum(-1i32);
}
#[test]
#[should_panic(expected = "n must be a non-negative integer that fits in u32")]
fn test_quorum_overflow_panics() {
N3f1::quorum(u64::MAX);
}
proptest! {
#[test]
fn test_n5f1_quorum_relationships(n in 6u32..10_000) {
let m = N5f1::m_quorum(n);
let l = N5f1::l_quorum(n);
prop_assert!(
m < l,
"M-quorum ({}) should be less than L-quorum ({}) for n={}",
m, l, n
);
prop_assert!(m <= n, "M-quorum ({}) should be <= n ({})", m, n);
prop_assert!(l <= n, "L-quorum ({}) should be <= n ({})", l, n);
}
#[test]
fn test_bft_model_safety_property(n in 1u32..10_000) {
let f_3f1 = N3f1::max_faults(n);
let q_3f1 = N3f1::quorum(n);
prop_assert!(
2 * q_3f1 > n + f_3f1,
"N3f1 safety violated for n={}: 2*{} <= {} + {}",
n, q_3f1, n, f_3f1
);
let f_5f1 = N5f1::max_faults(n);
let q_5f1 = N5f1::quorum(n);
prop_assert!(
2 * q_5f1 > n + f_5f1,
"N5f1 safety violated for n={}: 2*{} <= {} + {}",
n, q_5f1, n, f_5f1
);
}
}
}