From 2ae599ccce814cf61881ba30ccce845589caf323 Mon Sep 17 00:00:00 2001 From: Vladimir Komendantskiy Date: Mon, 12 Nov 2018 16:20:07 +0000 Subject: [PATCH 1/5] converted the BA test to net framework --- tests/binary_agreement.rs | 122 ++++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 58 deletions(-) diff --git a/tests/binary_agreement.rs b/tests/binary_agreement.rs index 44cccad5..e1f1a17f 100644 --- a/tests/binary_agreement.rs +++ b/tests/binary_agreement.rs @@ -1,74 +1,79 @@ #![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 +//! +//! 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. //! //! 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 integer_sqrt; extern crate log; +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; +mod net; use std::iter::once; use std::sync::Arc; +use std::time; use log::info; use rand::Rng; 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::TestRng; +use net::{NetBuilder, NewNodeInfo, VirtualNet}; -fn test_binary_agreement>>( - mut network: TestNetwork>, - input: Option, -) { - let ids: Vec = network.nodes.keys().cloned().collect(); - for id in ids { - network.input(id, input.unwrap_or_else(rand::random)); - } +type NodeId = usize; +type SessionId = u8; +type Algo = BinaryAgreement; - // Handle messages in random order until all nodes have output the proposed value. - while !network.nodes.values().all(TestNode::terminated) { - network.step(); - } - // 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]); +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().clone()).collect(); + for id in ids { + let _ = self.send_input(id, input.unwrap_or_else(|| rng.gen::())); + } + + // 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. } - 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(); - +fn test_binary_agreement_different_sizes() { + // FIXME: Seed the Rng. let mut rng = rand::thread_rng(); let sizes = (1..6) .chain(once(rng.gen_range(6, 20))) @@ -81,12 +86,18 @@ where "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); + let mut net: VirtualNet<_> = NetBuilder::new(0..size) + .num_faulty(num_faulty_nodes) + .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(input, rng.gen::()); info!( "Test success: {} good nodes and {} faulty nodes, input: {:?}", num_good_nodes, num_faulty_nodes, input @@ -95,14 +106,9 @@ where } } +/// Tests Binary Agreement with random inputs, all `false` inputs and all `true` inputs. #[test] -fn test_binary_agreement_random_silent() { - let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::Random); - test_binary_agreement_different_sizes(new_adversary); -} - -#[test] -fn test_binary_agreement_first_silent() { - let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::First); - test_binary_agreement_different_sizes(new_adversary); +fn binary_agreement() { + let _ = env_logger::try_init(); + test_binary_agreement_different_sizes(); } From 60d656cb652db8ce9a938e54105b120bed8ddea1 Mon Sep 17 00:00:00 2001 From: Vladimir Komendantskiy Date: Mon, 12 Nov 2018 23:29:48 +0000 Subject: [PATCH 2/5] fixed lints and corrected docs --- tests/binary_agreement.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/binary_agreement.rs b/tests/binary_agreement.rs index e1f1a17f..a41ad1ce 100644 --- a/tests/binary_agreement.rs +++ b/tests/binary_agreement.rs @@ -1,9 +1,10 @@ #![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. +//! Each of the nodes in the simulated network runs only one instance of Binary Agreement for node +//! `0`, the proposer. In real applications there would be multiple proposers in the +//! network. However, having only one proposer allows to test correctness of the protocol rather +//! than message dispatch between multiple proposers. //! //! There are three properties that are tested: //! @@ -23,7 +24,7 @@ extern crate proptest; extern crate rand; extern crate threshold_crypto; -mod net; +pub mod net; use std::iter::once; use std::sync::Arc; @@ -40,15 +41,13 @@ use net::proptest::TestRng; use net::{NetBuilder, NewNodeInfo, VirtualNet}; type NodeId = usize; -type SessionId = u8; -type Algo = BinaryAgreement; -impl VirtualNet { +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().clone()).collect(); + 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::())); } @@ -86,6 +85,7 @@ fn test_binary_agreement_different_sizes() { "Test start: {} good nodes and {} faulty nodes, input: {:?}", num_good_nodes, num_faulty_nodes, input ); + // Create a network with `size` validators and one observer. let mut net: VirtualNet<_> = NetBuilder::new(0..size) .num_faulty(num_faulty_nodes) .message_limit(10_000 * size as usize) From 6bce56e7a893239772ade4483abd7493e026d6ea Mon Sep 17 00:00:00 2001 From: Vladimir Komendantskiy Date: Tue, 13 Nov 2018 10:02:34 +0000 Subject: [PATCH 3/5] seeded the Rng and removed logging --- tests/binary_agreement.rs | 114 +++++++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 46 deletions(-) diff --git a/tests/binary_agreement.rs b/tests/binary_agreement.rs index a41ad1ce..8ed0baef 100644 --- a/tests/binary_agreement.rs +++ b/tests/binary_agreement.rs @@ -15,11 +15,9 @@ //! - 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 integer_sqrt; -extern crate log; extern crate proptest; extern crate rand; extern crate threshold_crypto; @@ -30,19 +28,53 @@ use std::iter::once; use std::sync::Arc; use std::time; -use log::info; -use rand::Rng; +use proptest::{prelude::ProptestConfig, prop_compose, proptest, proptest_helper}; +use rand::{Rng, SeedableRng}; use hbbft::binary_agreement::BinaryAgreement; use hbbft::DistAlgorithm; use net::adversary::ReorderingAdversary; -use net::proptest::TestRng; +use net::proptest::{gen_seed, NetworkDimension, TestRng, TestRngSeed}; use net::{NetBuilder, NewNodeInfo, VirtualNet}; -type NodeId = usize; +/// 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, +} + +prop_compose! { + /// Strategy to generate a test configuration. + fn arb_config() + ( + dimension in NetworkDimension::range(1, 50), + seed in gen_seed() + ) -> TestConfig + { + TestConfig { dimension, seed } + } +} + +/// Proptest wrapper for `binary_agreement`. +proptest!{ + #![proptest_config(ProptestConfig { + cases: 1, .. ProptestConfig::default() + })] + #[test] + #[cfg_attr(feature = "cargo-clippy", allow(unnecessary_operation))] + fn binary_agreement_wrapper(cfg in arb_config()) { + binary_agreement(cfg) + } +} + +type NodeId = u16; +type Algo = BinaryAgreement; -impl VirtualNet> { +impl VirtualNet { fn test_binary_agreement(&mut self, input: Option, mut rng: R) where R: Rng + 'static, @@ -71,44 +103,34 @@ impl VirtualNet> { } } -fn test_binary_agreement_different_sizes() { - // FIXME: Seed the Rng. - 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 - ); - // Create a network with `size` validators and one observer. - let mut net: VirtualNet<_> = NetBuilder::new(0..size) - .num_faulty(num_faulty_nodes) - .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(input, rng.gen::()); - info!( - "Test success: {} good nodes and {} faulty nodes, input: {:?}", - num_good_nodes, num_faulty_nodes, input - ); - } +/// Tests Binary Agreement on a given configuration with random inputs, with all `false` inputs and +/// with all `true` inputs. +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; + for &input in &[None, Some(false), Some(true)] { + println!( + "Test start: {} good nodes and {} faulty nodes, input: {:?}", + num_good_nodes, num_faulty_nodes, 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(input, rng.gen::()); + println!( + "Test success: {} good nodes and {} faulty nodes, input: {:?}", + num_good_nodes, num_faulty_nodes, input + ); } } - -/// Tests Binary Agreement with random inputs, all `false` inputs and all `true` inputs. -#[test] -fn binary_agreement() { - let _ = env_logger::try_init(); - test_binary_agreement_different_sizes(); -} From cb997737c9a0d5ffb011b34919eb33550f12d2c9 Mon Sep 17 00:00:00 2001 From: Vladimir Komendantskiy Date: Tue, 13 Nov 2018 10:08:57 +0000 Subject: [PATCH 4/5] allowed pass by value of binary_agreement argument --- tests/binary_agreement.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/binary_agreement.rs b/tests/binary_agreement.rs index 8ed0baef..f72b4754 100644 --- a/tests/binary_agreement.rs +++ b/tests/binary_agreement.rs @@ -105,6 +105,7 @@ impl VirtualNet { /// Tests Binary Agreement on a given configuration with random inputs, with all `false` inputs and /// with all `true` inputs. +#[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(); From 9c207e734a76e4ccd2720278af8fcf509d76d7b2 Mon Sep 17 00:00:00 2001 From: Vladimir Komendantskiy Date: Tue, 13 Nov 2018 11:50:37 +0000 Subject: [PATCH 5/5] handling of input via proptest and doc correction --- tests/binary_agreement.rs | 69 ++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/tests/binary_agreement.rs b/tests/binary_agreement.rs index f72b4754..313e19e8 100644 --- a/tests/binary_agreement.rs +++ b/tests/binary_agreement.rs @@ -1,10 +1,8 @@ #![deny(unused_must_use)] //! Tests of the Binary Agreement protocol //! -//! Each of the nodes in the simulated network runs only one instance of Binary Agreement for node -//! `0`, the proposer. In real applications there would be multiple proposers in the -//! network. However, having only one proposer allows to test correctness of the protocol rather -//! than message dispatch between multiple proposers. +//! 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: //! @@ -28,6 +26,7 @@ use std::iter::once; use std::sync::Arc; use std::time; +use proptest::arbitrary::any; use proptest::{prelude::ProptestConfig, prop_compose, proptest, proptest_helper}; use rand::{Rng, SeedableRng}; @@ -45,6 +44,12 @@ struct TestConfig { 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, } prop_compose! { @@ -52,21 +57,22 @@ prop_compose! { fn arb_config() ( dimension in NetworkDimension::range(1, 50), - seed in gen_seed() + seed in gen_seed(), + input in any::>(), ) -> TestConfig { - TestConfig { dimension, seed } + TestConfig { dimension, seed, input } } } -/// Proptest wrapper for `binary_agreement`. +/// 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 binary_agreement_wrapper(cfg in arb_config()) { + fn run_binary_agreement(cfg in arb_config()) { binary_agreement(cfg) } } @@ -103,35 +109,32 @@ impl VirtualNet { } } -/// Tests Binary Agreement on a given configuration with random inputs, with all `false` inputs and -/// with all `true` inputs. +/// 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; - for &input in &[None, Some(false), Some(true)] { - println!( - "Test start: {} good nodes and {} faulty nodes, input: {:?}", - num_good_nodes, num_faulty_nodes, 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(input, rng.gen::()); - println!( - "Test success: {} good nodes and {} faulty nodes, input: {:?}", - num_good_nodes, num_faulty_nodes, input - ); - } + 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 + ); }