mirror of https://github.com/poanetwork/hbbft.git
207 lines
6.7 KiB
Rust
207 lines
6.7 KiB
Rust
//! Proptest helpers and strategies.
|
|
//!
|
|
//! This module houses strategies to generate (and reduce/expand) various `hbbft` and `net` related
|
|
//! structures.
|
|
|
|
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::XorShiftRng;
|
|
|
|
/// Seed type of the random number generator used in testing.
|
|
// Note: In `rand` 0.5, this is an associated type of the `SeedableRng` trait, but for 0.4 and below
|
|
// we still need to alias this type.
|
|
pub type TestRngSeed = [u32; 4];
|
|
|
|
/// 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)]
|
|
pub struct NetworkDimension {
|
|
/// Total number of nodes in network.
|
|
pub size: usize,
|
|
/// Number of faulty nodes in a network.
|
|
pub faulty: usize,
|
|
}
|
|
|
|
impl NetworkDimension {
|
|
/// Creates a new `NetworkDimension` with the supplied parameters.
|
|
///
|
|
/// Dimensions that do not satisfy BFT conditions (see `is_bft`) can be created using this
|
|
/// function.
|
|
pub fn new(size: usize, faulty: usize) -> Self {
|
|
NetworkDimension { size, faulty }
|
|
}
|
|
|
|
/// Checks whether the network dimension satisfies the `3 * faulty + 1 <= size` condition.
|
|
pub fn is_bft(&self) -> bool {
|
|
self.faulty * 3 < self.size
|
|
}
|
|
|
|
/// Creates a new dimension of average complexity.
|
|
///
|
|
/// The new dimension is approximately half way in the interval of `[self, high]` and will
|
|
/// conform to the constraint checked by `is_bft()`.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// `high` must be have a higher or equal size and faulty node count.
|
|
pub fn average_higher(&self, high: NetworkDimension) -> NetworkDimension {
|
|
assert!(high.size >= self.size);
|
|
assert!(high.faulty >= self.faulty);
|
|
|
|
// We try halving both values, rounding down. If `size` is at the minimum, `faulty` will
|
|
// shrink afterwards.
|
|
let mut half = NetworkDimension {
|
|
size: self.size + (high.size - self.size) / 2,
|
|
faulty: self.faulty + (high.faulty - self.faulty) / 2,
|
|
};
|
|
|
|
// Reduce the number of faulty nodes, if we are outside our limits.
|
|
if half.faulty * 3 > half.size {
|
|
half.faulty -= 1;
|
|
}
|
|
|
|
// This assert just checks for bugs.
|
|
assert!(half.is_bft());
|
|
|
|
half
|
|
}
|
|
|
|
/// Creates a proptest strategy to create network dimensions within a certain range.
|
|
pub fn range(min_size: usize, max_size: usize) -> NetworkDimensionStrategy {
|
|
NetworkDimensionStrategy { min_size, max_size }
|
|
}
|
|
}
|
|
|
|
/// 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: NetworkDimension,
|
|
/// The currently generated network dimension.
|
|
current: NetworkDimension,
|
|
/// The lower bound for any generated dimension value (changes during generation or shrinking).
|
|
low: NetworkDimension,
|
|
}
|
|
|
|
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: usize, max_size: usize) -> 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 {
|
|
size: total,
|
|
faulty,
|
|
};
|
|
assert!(high.is_bft());
|
|
|
|
let low = NetworkDimension {
|
|
size: min_size,
|
|
faulty: 0,
|
|
};
|
|
assert!(low.is_bft());
|
|
|
|
NetworkDimensionTree {
|
|
high,
|
|
current: high,
|
|
low,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ValueTree for NetworkDimensionTree {
|
|
type Value = NetworkDimension;
|
|
|
|
fn current(&self) -> Self::Value {
|
|
self.current
|
|
}
|
|
|
|
fn simplify(&mut self) -> bool {
|
|
// Shrinking is simply done through `average_higher`.
|
|
let prev = *self;
|
|
|
|
self.high = prev.current;
|
|
self.current = self.low.average_higher(prev.high);
|
|
|
|
(prev.high != self.high || prev.current != self.current)
|
|
}
|
|
|
|
fn complicate(&mut self) -> bool {
|
|
let prev = *self;
|
|
|
|
// Minimally increase the faulty-node ratio by adjusting the number of faulty nodes and the
|
|
// size slightly less. If we are at the maximum number of faulty nodes, we would end up
|
|
// increasing the network size instead (see branch below though).
|
|
let mut new_low = self.current;
|
|
new_low.faulty += 1;
|
|
new_low.size = (new_low.size + 2).max(new_low.faulty * 3 + 1);
|
|
assert!(new_low.is_bft());
|
|
|
|
// Instead of growing the network, return unchanged if the new network would be larger than
|
|
// the current high.
|
|
if new_low.size > self.high.size {
|
|
return false;
|
|
}
|
|
|
|
self.current = new_low.average_higher(self.high);
|
|
self.low = new_low;
|
|
|
|
(prev.current != self.current || prev.low != self.low)
|
|
}
|
|
}
|
|
|
|
/// Network dimension strategy for proptest.
|
|
#[derive(Debug)]
|
|
pub struct NetworkDimensionStrategy {
|
|
/// Minimum number of nodes for newly generated networks dimensions.
|
|
pub min_size: usize,
|
|
/// Maximum number of nodes for newly generated networks dimensions.
|
|
pub max_size: usize,
|
|
}
|
|
|
|
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,
|
|
))
|
|
}
|
|
}
|