hbbft 0.1.1

The Honey Badger of Byzantine Fault Tolerant Consensus Protocols
Documentation
//! Proptest helpers and strategies.
//!
//! This module houses strategies to generate (and reduce/expand) various `hbbft` and `net` related
//! structures.

use integer_sqrt::IntegerSquareRoot;
use proptest::arbitrary::any;
use proptest::prelude::Rng;
use proptest::strategy::{Strategy, ValueTree};
use proptest::test_runner::{Reason, TestRunner};
use rand::{self, SeedableRng};

/// Random number generator type used in testing.
pub type TestRng = rand_xorshift::XorShiftRng;

/// Seed type of the random number generator used in testing.
pub type TestRngSeed = [u8; 16];

/// Generates a random instance of a random number generator.
pub fn gen_rng() -> impl Strategy<Value = TestRng> {
    gen_seed().prop_map(TestRng::from_seed)
}

/// Generates a random seed to instantiate a `TestRng`.
///
/// The random seed is non-shrinkable, to avoid meaningless shrinking in case of failed tests.
pub fn gen_seed() -> impl Strategy<Value = TestRngSeed> {
    any::<TestRngSeed>().no_shrink()
}

/// Node network dimension.
///
/// A `NetworkDimension` describes the number of correct and faulty nodes in a network. It can also
/// be checked, "averaged" (using the `average_higher` function) and generated using
/// `NetworkDimensionTree`.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct NetworkDimension {
    /// Total number of nodes in network.
    size: u16,
    /// Number of faulty nodes in a network.
    faulty: u16,
}

impl NetworkDimension {
    /// Creates a new `NetworkDimension` with the supplied parameters.
    ///
    /// # Panics
    ///

    #[inline]
    pub fn new(size: u16, faulty: u16) -> Self {
        let dim = NetworkDimension { size, faulty };
        assert!(
            dim.is_bft(),
            "Tried to create network dimension that violates BFT-property."
        );
        dim
    }

    #[inline]
    pub fn faulty(self) -> usize {
        self.faulty.into()
    }

    #[inline]
    pub fn size(self) -> usize {
        self.size.into()
    }

    /// Checks whether the network dimension satisfies the `3 * faulty + 1 <= size` condition.
    #[inline]
    fn is_bft(self) -> bool {
        self.faulty * 3 < self.size
    }

    /// Creates a proptest strategy to create network dimensions within a certain range.
    #[inline]
    pub fn range(min_size: u16, max_size: u16) -> NetworkDimensionStrategy {
        NetworkDimensionStrategy { min_size, max_size }
    }

    /// Returns next-larger network dimension.
    ///
    /// The order on `NetworkDimension` is canonically defined by `(size, faulty)`. The `succ`
    /// function returns the next-higher valid instance by first trying to increase `faulty`, then
    /// `size`.
    #[inline]
    pub fn succ(self) -> NetworkDimension {
        let mut n = self.size;
        let mut f = self.faulty + 1;

        if 3 * f >= n {
            f = 0;
            n += 1;
        }

        NetworkDimension::new(n, f)
    }
}

/// Network dimension tree for proptest generation.
///
/// See `proptest::strategy::ValueTree` for a more thorough description.
#[derive(Copy, Clone, Debug)]
pub struct NetworkDimensionTree {
    /// The upper bound for any generated dimension.
    high: u32,
    /// The currently generated network dimension.
    current: u32,
    /// The lower bound for any generated dimension value (changes during generation or shrinking).
    low: u32,
}

impl NetworkDimensionTree {
    /// Generate a random network dimension tree.
    ///
    /// The resulting initial `NetworkDimension` will have a number of nodes within
    /// [`min_size`, `max_size`] and a valid number of faulty nodes.
    ///
    /// # Panics
    ///
    /// The minimum `min_size` is 1 and `min_size` must be less than or equal `max_size`.
    pub fn gen<R: Rng>(mut rng: R, min_size: u16, max_size: u16) -> Self {
        // A common mistake, add an extra assert for a more helpful error message.
        assert!(min_size > 0, "minimum network size is 1");

        let total = rng.gen_range(min_size, max_size + 1);
        let max_faulty = (total - 1) / 3;
        let faulty = rng.gen_range(0, max_faulty + 1);

        let high = NetworkDimension::new(total, faulty);

        NetworkDimensionTree {
            high: high.into(),
            current: high.into(),
            low: u32::from(min_size),
        }
    }
}

impl ValueTree for NetworkDimensionTree {
    type Value = NetworkDimension;

    fn current(&self) -> Self::Value {
        self.current.into()
    }

    fn simplify(&mut self) -> bool {
        let prev = *self;

        self.high = self.current;
        self.current = (self.low + self.high) / 2;

        (prev.high != self.high || prev.current != self.current)
    }

    fn complicate(&mut self) -> bool {
        let prev = *self;

        if self.high == self.current {
            return false;
        }

        self.low = self.current + 1;
        self.current = (self.low + self.high) / 2;

        (prev.current != self.current || prev.low != self.low)
    }
}

impl From<NetworkDimension> for u32 {
    fn from(dim: NetworkDimension) -> u32 {
        // `b` is the "Block index" here. Counting through `NetworkDimensions` a pattern shows:
        //
        //  n   f
        //  1   0  \
        //  2   0   |- Block 0
        //  3   0  /
        //  4   0 \
        //  4   1  \
        //  5   0   >  Block 1
        //  5   1   |
        //  6   0  /
        //  6   1 /
        //  7   0 ...
        //
        // We observe that each block starts at index `3 * (b(b+1)/2)`. Along with the offset,
        // we can calculate a mapping onto the natural numbers using this:

        let b = (u32::from(dim.size) - 1) / 3;
        let start = 3 * b * (b + 1) / 2;
        let offset = (u32::from(dim.size) - 3 * b - 1) * (b + 1) + u32::from(dim.faulty);

        start + offset
    }
}

impl From<u32> for NetworkDimension {
    fn from(n: u32) -> NetworkDimension {
        // Inverse of `u32 as From<NetworkDimension>`:

        // Find the block number first:
        let b = max_sum(n / 3);

        // Calculate the block start and the resulting offset of `n`:
        let start = 3 * b * (b + 1) / 2;
        let offset = n - start;

        let faulty = offset % (b + 1);
        let size = 3 * b + 1 + offset / (b + 1);

        NetworkDimension::new(size as u16, faulty as u16)
    }
}

/// Finds the largest consecutive summand less or equal than `n`.
///
/// The return value `k` will satisfy `SUM 1..k <= n`.
pub fn max_sum(n: u32) -> u32 {
    // Derived by quadratically solving `n(n+1)/2`; we only want the "positive" result.
    // `integer_sqrt` functions as a `floor` function here.
    ((1 + 8 * n).integer_sqrt() - 1) / 2
}

/// Network dimension strategy for proptest.
#[derive(Debug)]
pub struct NetworkDimensionStrategy {
    /// Minimum number of nodes for newly generated networks dimensions.
    pub min_size: u16,
    /// Maximum number of nodes for newly generated networks dimensions.
    pub max_size: u16,
}

impl Strategy for NetworkDimensionStrategy {
    type Value = NetworkDimension;
    type Tree = NetworkDimensionTree;

    fn new_tree(&self, runner: &mut TestRunner) -> Result<Self::Tree, Reason> {
        Ok(NetworkDimensionTree::gen(
            runner.rng(),
            self.min_size,
            self.max_size,
        ))
    }
}