2018-07-16 06:31:21 -07:00
|
|
|
#![deny(unused_must_use)]
|
2018-11-20 07:16:15 -08:00
|
|
|
//! Tests of the Binary Agreement protocol
|
|
|
|
//!
|
|
|
|
//! Each of the nodes in the simulated network runs one instance of Binary Agreement. This suffices
|
|
|
|
//! to test correctness of the protocol.
|
2018-05-14 01:20:16 -07:00
|
|
|
//!
|
|
|
|
//! There are three properties that are tested:
|
|
|
|
//!
|
2018-11-20 07:16:15 -08:00
|
|
|
//! - Agreement: If any correct node outputs the bit `b`, then every correct node outputs `b`.
|
2018-05-14 01:20:16 -07:00
|
|
|
//!
|
|
|
|
//! - Termination: If all correct nodes receive input, then every correct node outputs a bit.
|
|
|
|
//!
|
2018-11-20 07:16:15 -08:00
|
|
|
//! - Validity: If any correct node outputs `b`, then at least one correct node received `b` as
|
|
|
|
//! input.
|
2018-05-14 01:20:16 -07:00
|
|
|
|
2018-05-29 05:17:30 -07:00
|
|
|
use std::iter::once;
|
2018-07-11 12:15:08 -07:00
|
|
|
use std::sync::Arc;
|
2018-11-20 07:16:15 -08:00
|
|
|
use std::time;
|
2018-05-14 01:20:16 -07:00
|
|
|
|
2018-08-30 01:22:56 -07:00
|
|
|
use hbbft::binary_agreement::BinaryAgreement;
|
2019-01-08 00:30:37 -08:00
|
|
|
use hbbft::ConsensusProtocol;
|
2019-04-26 07:54:12 -07:00
|
|
|
use hbbft_testing::adversary::{Adversary, ReorderingAdversary};
|
|
|
|
use hbbft_testing::proptest::{gen_seed, NetworkDimension, TestRng, TestRngSeed};
|
|
|
|
use hbbft_testing::{NetBuilder, NewNodeInfo, VirtualNet};
|
|
|
|
use proptest::arbitrary::any;
|
|
|
|
use proptest::{prelude::ProptestConfig, prop_compose, proptest};
|
|
|
|
use rand::{Rng, SeedableRng};
|
2018-05-14 01:20:16 -07:00
|
|
|
|
2018-11-20 07:16:15 -08:00
|
|
|
/// Test configuration for Binary Agreement tests.
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct TestConfig {
|
|
|
|
/// The desired network dimension.
|
|
|
|
dimension: NetworkDimension,
|
|
|
|
/// Random number generator to be passed to subsystems.
|
|
|
|
seed: TestRngSeed,
|
|
|
|
/// Input to Binary Agreement instances that has the following meaning:
|
|
|
|
///
|
|
|
|
/// - `Some(b)`: all instances receive `b` as input.
|
|
|
|
///
|
|
|
|
/// - `None`: each instance receives a random `bool` as input.
|
2018-05-17 08:38:45 -07:00
|
|
|
input: Option<bool>,
|
2018-11-20 07:16:15 -08:00
|
|
|
}
|
2018-05-14 01:20:16 -07:00
|
|
|
|
2018-11-20 07:16:15 -08:00
|
|
|
prop_compose! {
|
|
|
|
/// Strategy to generate a test configuration.
|
|
|
|
fn arb_config()
|
|
|
|
(
|
|
|
|
dimension in NetworkDimension::range(1, 50),
|
|
|
|
seed in gen_seed(),
|
|
|
|
input in any::<Option<bool>>(),
|
|
|
|
) -> TestConfig
|
|
|
|
{
|
|
|
|
TestConfig { dimension, seed, input }
|
2018-05-19 05:29:31 -07:00
|
|
|
}
|
2018-11-20 07:16:15 -08:00
|
|
|
}
|
|
|
|
|
2019-06-11 00:57:31 -07:00
|
|
|
// Proptest wrapper for `binary_agreement` that runs the test function on generated configurations.
|
2018-12-11 05:44:36 -08:00
|
|
|
proptest! {
|
2018-11-20 07:16:15 -08:00
|
|
|
#![proptest_config(ProptestConfig {
|
|
|
|
cases: 1, .. ProptestConfig::default()
|
|
|
|
})]
|
|
|
|
#[test]
|
2018-12-11 05:44:36 -08:00
|
|
|
#[allow(clippy::unnecessary_operation)]
|
2018-11-20 07:16:15 -08:00
|
|
|
fn run_binary_agreement(cfg in arb_config()) {
|
|
|
|
binary_agreement(cfg)
|
2018-05-14 01:20:16 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-20 07:16:15 -08:00
|
|
|
type NodeId = u16;
|
2018-05-14 01:20:16 -07:00
|
|
|
|
2019-04-26 07:54:12 -07:00
|
|
|
fn test_binary_agreement<A, R>(
|
|
|
|
net: &mut VirtualNet<BinaryAgreement<NodeId, u8>, A>,
|
|
|
|
input: Option<bool>,
|
|
|
|
mut rng: R,
|
|
|
|
) where
|
|
|
|
R: Rng + 'static,
|
OsRng / external RNG Refactoring (#357)
* Use `OsRng` in place of `thread_rng`.
This changes the defaults of any builder by instantiating an `OsRng` instead of
a `thread_rng`, the former being much more secure than the latter.
Additionally, all the unit tests that still instantiate RNGs manually used `OsRng`s
as well; while there is no actual need for this level of security in tests, the performance overhead is very small and random number generation complexity has such a small impact on these tests that the convenience of being able to ban `thread_rng` from the codebase altogether, setting a good example and avoid issues when refactoring later greatly outweigh the negatives.
* Instead of storing random number generators in the various consensus algorithm instances, pass them in from the outside whenever they are needed.
This changes a large amount of interfaces (and in this commit is only partially done, since `DistAlgorithm` needs to be fundamentally altered as well.
It also obsoletes parts of the `util` module.
* Added an `R: Rng` type parameter to both methods of `DistAlgorithm`, forcing callers to pass in their own Rngs.
* Fixed documentation grammar and spelling in some of the altered interfaces due to RNG refactoring.
* Move `rng` argument to the end of the argument for most functions.
Also includes a reformatting due to Rust 1.30.
* Updated tests, accomodate `rng`-API changes.
* Fixed remaining compilation issues with new RNG code.
* Fix illegal `self` import outside curly braces.
* Cleaned up comments and fixed broken definition of `broadcast_input`.
* Updated existing test cases to properly work with static dispatch randomness.
* Do not use boxed `Rng`s for key generation in test networks.
* Use the passed-in `Rng` in `ReorderingAdversary`, instead of storing a boxed one.
* Fixed clippy lints after refactoring.
* Removed some no-longer necessary manual `fmt::Debug` implementations in test framework.
* Use `OsRng` even in tests in `binary_agreement_mitm`.
* Use a proper deterministic RNG in tests `binary_agreement_mitm`.
* Refactor `examples/simulation.rs` by not using `ThreadRng`, passing generic `Rng` parameters throughout and using a type alias instead of a newtype as the `Transaction`.
* Remove `thread_rng` use from `examples/node.rs`.
* Explicitly construct `InternalContrib` in `DynamicHoneyBadger::propose`.
* Fixed typo in description of `DistAlgorithm` trait.
2018-12-14 04:51:09 -08:00
|
|
|
A: Adversary<BinaryAgreement<NodeId, u8>>,
|
|
|
|
{
|
2019-04-26 07:54:12 -07:00
|
|
|
let ids: Vec<NodeId> = net.nodes().map(|n| *n.id()).collect();
|
|
|
|
for id in ids {
|
|
|
|
let _ = net.send_input(id, input.unwrap_or_else(|| rng.gen::<bool>()), &mut rng);
|
|
|
|
}
|
2018-05-14 01:20:16 -07:00
|
|
|
|
2019-04-26 07:54:12 -07:00
|
|
|
// Handle messages in random order until all nodes have output the proposed value.
|
|
|
|
while !net.nodes().all(|node| node.algorithm().terminated()) {
|
|
|
|
let _ = net.crank_expect(&mut rng);
|
|
|
|
}
|
|
|
|
// Verify that all instances output the same value.
|
|
|
|
let mut expected = input;
|
|
|
|
for node in net.nodes() {
|
|
|
|
if let Some(b) = expected {
|
|
|
|
assert!(once(&b).eq(node.outputs()));
|
|
|
|
} else {
|
|
|
|
assert_eq!(1, node.outputs().len());
|
|
|
|
expected = Some(node.outputs()[0]);
|
2018-11-20 07:16:15 -08:00
|
|
|
}
|
|
|
|
}
|
2019-04-26 07:54:12 -07:00
|
|
|
// TODO: As soon as observers are added to the test framework, compare the expected output
|
|
|
|
// against the output of observers.
|
2018-06-12 11:36:50 -07:00
|
|
|
}
|
2018-05-14 01:20:16 -07:00
|
|
|
|
2018-11-20 07:16:15 -08:00
|
|
|
/// Tests Binary Agreement on a given configuration.
|
2018-12-11 05:44:36 -08:00
|
|
|
#[allow(clippy::needless_pass_by_value)]
|
2018-11-20 07:16:15 -08:00
|
|
|
fn binary_agreement(cfg: TestConfig) {
|
|
|
|
let mut rng: TestRng = TestRng::from_seed(cfg.seed);
|
|
|
|
let size = cfg.dimension.size();
|
|
|
|
let num_faulty_nodes = cfg.dimension.faulty();
|
|
|
|
let num_good_nodes = size - num_faulty_nodes;
|
|
|
|
println!(
|
|
|
|
"Test start: {} good nodes and {} faulty nodes, input: {:?}",
|
|
|
|
num_good_nodes, num_faulty_nodes, cfg.input
|
|
|
|
);
|
|
|
|
// Create a network with `size` validators and one observer.
|
2018-12-11 00:12:38 -08:00
|
|
|
let (mut net, _) = NetBuilder::new(0..size as u16)
|
2018-11-20 07:16:15 -08:00
|
|
|
.num_faulty(num_faulty_nodes as usize)
|
|
|
|
.message_limit(10_000 * size as usize)
|
|
|
|
.time_limit(time::Duration::from_secs(30 * size as u64))
|
OsRng / external RNG Refactoring (#357)
* Use `OsRng` in place of `thread_rng`.
This changes the defaults of any builder by instantiating an `OsRng` instead of
a `thread_rng`, the former being much more secure than the latter.
Additionally, all the unit tests that still instantiate RNGs manually used `OsRng`s
as well; while there is no actual need for this level of security in tests, the performance overhead is very small and random number generation complexity has such a small impact on these tests that the convenience of being able to ban `thread_rng` from the codebase altogether, setting a good example and avoid issues when refactoring later greatly outweigh the negatives.
* Instead of storing random number generators in the various consensus algorithm instances, pass them in from the outside whenever they are needed.
This changes a large amount of interfaces (and in this commit is only partially done, since `DistAlgorithm` needs to be fundamentally altered as well.
It also obsoletes parts of the `util` module.
* Added an `R: Rng` type parameter to both methods of `DistAlgorithm`, forcing callers to pass in their own Rngs.
* Fixed documentation grammar and spelling in some of the altered interfaces due to RNG refactoring.
* Move `rng` argument to the end of the argument for most functions.
Also includes a reformatting due to Rust 1.30.
* Updated tests, accomodate `rng`-API changes.
* Fixed remaining compilation issues with new RNG code.
* Fix illegal `self` import outside curly braces.
* Cleaned up comments and fixed broken definition of `broadcast_input`.
* Updated existing test cases to properly work with static dispatch randomness.
* Do not use boxed `Rng`s for key generation in test networks.
* Use the passed-in `Rng` in `ReorderingAdversary`, instead of storing a boxed one.
* Fixed clippy lints after refactoring.
* Removed some no-longer necessary manual `fmt::Debug` implementations in test framework.
* Use `OsRng` even in tests in `binary_agreement_mitm`.
* Use a proper deterministic RNG in tests `binary_agreement_mitm`.
* Refactor `examples/simulation.rs` by not using `ThreadRng`, passing generic `Rng` parameters throughout and using a type alias instead of a newtype as the `Transaction`.
* Remove `thread_rng` use from `examples/node.rs`.
* Explicitly construct `InternalContrib` in `DynamicHoneyBadger::propose`.
* Fixed typo in description of `DistAlgorithm` trait.
2018-12-14 04:51:09 -08:00
|
|
|
.adversary(ReorderingAdversary::new())
|
2018-11-20 07:16:15 -08:00
|
|
|
.using(move |node_info: NewNodeInfo<_>| {
|
|
|
|
BinaryAgreement::new(Arc::new(node_info.netinfo), 0)
|
|
|
|
.expect("Failed to create a BinaryAgreement instance.")
|
2018-12-11 05:44:36 -08:00
|
|
|
})
|
OsRng / external RNG Refactoring (#357)
* Use `OsRng` in place of `thread_rng`.
This changes the defaults of any builder by instantiating an `OsRng` instead of
a `thread_rng`, the former being much more secure than the latter.
Additionally, all the unit tests that still instantiate RNGs manually used `OsRng`s
as well; while there is no actual need for this level of security in tests, the performance overhead is very small and random number generation complexity has such a small impact on these tests that the convenience of being able to ban `thread_rng` from the codebase altogether, setting a good example and avoid issues when refactoring later greatly outweigh the negatives.
* Instead of storing random number generators in the various consensus algorithm instances, pass them in from the outside whenever they are needed.
This changes a large amount of interfaces (and in this commit is only partially done, since `DistAlgorithm` needs to be fundamentally altered as well.
It also obsoletes parts of the `util` module.
* Added an `R: Rng` type parameter to both methods of `DistAlgorithm`, forcing callers to pass in their own Rngs.
* Fixed documentation grammar and spelling in some of the altered interfaces due to RNG refactoring.
* Move `rng` argument to the end of the argument for most functions.
Also includes a reformatting due to Rust 1.30.
* Updated tests, accomodate `rng`-API changes.
* Fixed remaining compilation issues with new RNG code.
* Fix illegal `self` import outside curly braces.
* Cleaned up comments and fixed broken definition of `broadcast_input`.
* Updated existing test cases to properly work with static dispatch randomness.
* Do not use boxed `Rng`s for key generation in test networks.
* Use the passed-in `Rng` in `ReorderingAdversary`, instead of storing a boxed one.
* Fixed clippy lints after refactoring.
* Removed some no-longer necessary manual `fmt::Debug` implementations in test framework.
* Use `OsRng` even in tests in `binary_agreement_mitm`.
* Use a proper deterministic RNG in tests `binary_agreement_mitm`.
* Refactor `examples/simulation.rs` by not using `ThreadRng`, passing generic `Rng` parameters throughout and using a type alias instead of a newtype as the `Transaction`.
* Remove `thread_rng` use from `examples/node.rs`.
* Explicitly construct `InternalContrib` in `DynamicHoneyBadger::propose`.
* Fixed typo in description of `DistAlgorithm` trait.
2018-12-14 04:51:09 -08:00
|
|
|
.build(&mut rng)
|
2018-11-20 07:16:15 -08:00
|
|
|
.expect("Could not construct test network.");
|
2019-04-26 07:54:12 -07:00
|
|
|
test_binary_agreement(
|
|
|
|
&mut net,
|
|
|
|
cfg.input,
|
|
|
|
TestRng::from_seed(rng.gen::<TestRngSeed>()),
|
|
|
|
);
|
2018-11-20 07:16:15 -08:00
|
|
|
println!(
|
|
|
|
"Test success: {} good nodes and {} faulty nodes, input: {:?}",
|
|
|
|
num_good_nodes, num_faulty_nodes, cfg.input
|
|
|
|
);
|
2018-05-14 01:20:16 -07:00
|
|
|
}
|