diff --git a/tests/binary_agreement.rs b/tests/binary_agreement.rs index 44cccad5..313e19e8 100644 --- a/tests/binary_agreement.rs +++ b/tests/binary_agreement.rs @@ -1,108 +1,140 @@ #![deny(unused_must_use)] -//! Tests of the Binary Agreement protocol. Only one proposer instance -//! is tested. Each of the nodes in the simulated network run only one instance -//! of Binary Agreement. This way we only test correctness of the protocol and not -//! message dispatch between multiple proposers. +//! 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. //! //! There are three properties that are tested: //! -//! - Agreement: If any correct node outputs the bit b, then every correct node outputs b. +//! - Agreement: If any correct node outputs the bit `b`, then every correct node outputs `b`. //! //! - Termination: If all correct nodes receive input, then every correct node outputs a bit. //! -//! - Validity: If any correct node outputs b, then at least one correct node received b as input. -//! -//! TODO: Implement adversaries and send BVAL messages at different times. +//! - Validity: If any correct node outputs `b`, then at least one correct node received `b` as +//! input. -extern crate env_logger; +extern crate failure; extern crate hbbft; -extern crate log; +extern crate integer_sqrt; +extern crate proptest; extern crate rand; -extern crate rand_derive; -extern crate serde_derive; -extern crate threshold_crypto as crypto; +extern crate threshold_crypto; -mod network; +pub mod net; use std::iter::once; use std::sync::Arc; +use std::time; -use log::info; -use rand::Rng; +use proptest::arbitrary::any; +use proptest::{prelude::ProptestConfig, prop_compose, proptest, proptest_helper}; +use rand::{Rng, SeedableRng}; use hbbft::binary_agreement::BinaryAgreement; -use hbbft::NetworkInfo; +use hbbft::DistAlgorithm; -use network::{Adversary, MessageScheduler, NodeId, SilentAdversary, TestNetwork, TestNode}; +use net::adversary::ReorderingAdversary; +use net::proptest::{gen_seed, NetworkDimension, TestRng, TestRngSeed}; +use net::{NetBuilder, NewNodeInfo, VirtualNet}; -fn test_binary_agreement>>( - mut network: TestNetwork>, +/// 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. input: Option, -) { - let ids: Vec = network.nodes.keys().cloned().collect(); - for id in ids { - network.input(id, input.unwrap_or_else(rand::random)); - } +} - // Handle messages in random order until all nodes have output the proposed value. - while !network.nodes.values().all(TestNode::terminated) { - network.step(); +prop_compose! { + /// Strategy to generate a test configuration. + fn arb_config() + ( + dimension in NetworkDimension::range(1, 50), + seed in gen_seed(), + input in any::>(), + ) -> TestConfig + { + TestConfig { dimension, seed, input } } - // Verify that all instances output the same value. - let mut expected = input; - for node in network.nodes.values() { - if let Some(b) = expected { - assert!(once(&b).eq(node.outputs())); - } else { - assert_eq!(1, node.outputs().len()); - expected = Some(node.outputs()[0]); - } +} + +/// Proptest wrapper for `binary_agreement` that runs the test function on generated configurations. +proptest!{ + #![proptest_config(ProptestConfig { + cases: 1, .. ProptestConfig::default() + })] + #[test] + #[cfg_attr(feature = "cargo-clippy", allow(unnecessary_operation))] + fn run_binary_agreement(cfg in arb_config()) { + binary_agreement(cfg) } - assert!(expected.iter().eq(network.observer.outputs())); } -fn test_binary_agreement_different_sizes(new_adversary: F) -where - A: Adversary>, - F: Fn(usize, usize) -> A, -{ - // This returns an error in all but the first test. - let _ = env_logger::try_init(); +type NodeId = u16; +type Algo = BinaryAgreement; - let mut rng = rand::thread_rng(); - let sizes = (1..6) - .chain(once(rng.gen_range(6, 20))) - .chain(once(rng.gen_range(30, 50))); - for size in sizes { - let num_faulty_nodes = (size - 1) / 3; - let num_good_nodes = size - num_faulty_nodes; - for &input in &[None, Some(false), Some(true)] { - info!( - "Test start: {} good nodes and {} faulty nodes, input: {:?}", - num_good_nodes, num_faulty_nodes, input - ); - let adversary = |_| new_adversary(num_good_nodes, num_faulty_nodes); - let new_ba = |netinfo: Arc>| { - BinaryAgreement::new(netinfo, 0).expect("Binary Agreement instance") - }; - let network = TestNetwork::new(num_good_nodes, num_faulty_nodes, adversary, new_ba); - test_binary_agreement(network, input); - info!( - "Test success: {} good nodes and {} faulty nodes, input: {:?}", - num_good_nodes, num_faulty_nodes, input - ); +impl VirtualNet { + fn test_binary_agreement(&mut self, input: Option, mut rng: R) + where + R: Rng + 'static, + { + let ids: Vec = self.nodes().map(|n| *n.id()).collect(); + for id in ids { + let _ = self.send_input(id, input.unwrap_or_else(|| rng.gen::())); } - } -} -#[test] -fn test_binary_agreement_random_silent() { - let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::Random); - test_binary_agreement_different_sizes(new_adversary); + // Handle messages in random order until all nodes have output the proposed value. + while !self.nodes().all(|node| node.algorithm().terminated()) { + let _ = self.crank_expect(); + } + // Verify that all instances output the same value. + let mut expected = input; + for node in self.nodes() { + if let Some(b) = expected { + assert!(once(&b).eq(node.outputs())); + } else { + assert_eq!(1, node.outputs().len()); + expected = Some(node.outputs()[0]); + } + } + // TODO: As soon as observers are added to the test framework, compare the expected output + // against the output of observers. + } } -#[test] -fn test_binary_agreement_first_silent() { - let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::First); - test_binary_agreement_different_sizes(new_adversary); +/// Tests Binary Agreement on a given configuration. +#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] +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. + let mut net: VirtualNet = NetBuilder::new(0..size as u16) + .num_faulty(num_faulty_nodes as usize) + .message_limit(10_000 * size as usize) + .time_limit(time::Duration::from_secs(30 * size as u64)) + .rng(rng.gen::()) + .adversary(ReorderingAdversary::new(rng.gen::())) + .using(move |node_info: NewNodeInfo<_>| { + BinaryAgreement::new(Arc::new(node_info.netinfo), 0) + .expect("Failed to create a BinaryAgreement instance.") + }).build() + .expect("Could not construct test network."); + net.test_binary_agreement(cfg.input, rng.gen::()); + println!( + "Test success: {} good nodes and {} faulty nodes, input: {:?}", + num_good_nodes, num_faulty_nodes, cfg.input + ); }