hbbft/tests/threshold_sign.rs

128 lines
4.4 KiB
Rust
Raw Normal View History

#![deny(unused_must_use)]
//! Threshold signing tests
2018-06-11 09:00:23 -07:00
mod network;
use std::iter::once;
2018-10-29 07:36:56 -07:00
use log::info;
2018-06-11 09:00:23 -07:00
use rand::Rng;
use hbbft::{crypto::Signature, threshold_sign::ThresholdSign, util};
2018-06-11 09:00:23 -07:00
use crate::network::{Adversary, MessageScheduler, NodeId, SilentAdversary, TestNetwork, TestNode};
2018-06-11 09:00:23 -07:00
/// Tests a network of threshold signing instances with an optional expected value. Outputs the
/// computed signature if the test is successful.
fn test_threshold_sign<A>(mut network: TestNetwork<A, ThresholdSign<NodeId>>) -> Signature
2018-06-11 09:00:23 -07:00
where
A: Adversary<ThresholdSign<NodeId>>,
2018-06-11 09:00:23 -07: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
let mut rng = rand::thread_rng();
network.input_all(());
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
network.observer.handle_input((), &mut rng); // Observer will only return after `input` was called.
2018-06-11 09:00:23 -07:00
// Handle messages until all good nodes have terminated.
while !network.nodes.values().all(TestNode::terminated) {
network.step();
}
let mut expected = None;
2018-06-11 09:00:23 -07:00
// Verify that all instances output the same value.
for node in network.nodes.values() {
if let Some(ref b) = expected {
assert!(once(b).eq(node.outputs()));
2018-06-11 09:00:23 -07:00
} else {
assert_eq!(1, node.outputs().len());
expected = Some(node.outputs()[0].clone());
2018-06-11 09:00:23 -07:00
}
}
// Now `expected` is the unique output of all good nodes.
assert!(expected.iter().eq(network.observer.outputs()));
2018-06-11 09:00:23 -07:00
expected.unwrap()
}
const GOOD_SAMPLE_SET: f64 = 400.0;
/// The count of throws of each side of the coin should be approaching 50% with a sufficiently large
/// sample set. This check assumes logarithmic growth of the expected number of throws of one coin
/// size.
fn check_coin_distribution(num_samples: usize, count_true: usize, count_false: usize) {
2018-06-14 04:28:38 -07:00
// Maximum 40% expectation in case of 400 samples or more.
const EXPECTED_SHARE: f64 = 0.4;
2018-06-11 09:00:23 -07:00
let max_gain = GOOD_SAMPLE_SET.log2();
let num_samples_f64 = num_samples as f64;
let gain = num_samples_f64.log2().min(max_gain);
2018-06-11 09:00:23 -07:00
let step = EXPECTED_SHARE / max_gain;
let min_throws = (num_samples_f64 * gain * step) as usize;
2018-06-11 09:00:23 -07:00
info!(
"Expecting a minimum of {} throws for each coin side. Throws of true: {}. Throws of false: {}.",
min_throws, count_true, count_false
);
assert!(count_true > min_throws);
assert!(count_false > min_throws);
}
fn test_threshold_sign_different_sizes<A, F>(new_adversary: F, num_samples: usize)
2018-06-11 09:00:23 -07:00
where
A: Adversary<ThresholdSign<NodeId>>,
2018-06-11 09:00:23 -07:00
F: Fn(usize, usize) -> A,
{
assert!(num_samples > 0);
// This returns an error in all but the first test.
let _ = env_logger::try_init();
let mut rng = rand::thread_rng();
let mut last_size = 1;
let mut sizes = vec![last_size];
let num_sizes = (GOOD_SAMPLE_SET.log2() - (num_samples as f64).log2()) as usize;
for _ in 0..num_sizes {
last_size += rng.gen_range(3, 7);
sizes.push(last_size);
}
for size in sizes {
let num_faulty_nodes = util::max_faulty(size);
2018-06-11 09:00:23 -07:00
let num_good_nodes = size - num_faulty_nodes;
info!(
"Network size: {} good nodes, {} faulty nodes",
num_good_nodes, num_faulty_nodes
);
let unique_id: u64 = rng.gen();
let mut count_true = 0;
let mut count_false = 0;
for i in 0..num_samples {
let adversary = |_| new_adversary(num_good_nodes, num_faulty_nodes);
2018-06-11 09:00:23 -07:00
let nonce = format!("My very unique nonce {:x}:{}", unique_id, i);
info!("Nonce: {}", nonce);
let new_coin = |netinfo: _| {
ThresholdSign::new_with_document(netinfo, nonce.clone())
.expect("Failed to set the new coin's ID")
};
2018-08-14 05:56:32 -07:00
let network = TestNetwork::new(num_good_nodes, num_faulty_nodes, adversary, new_coin);
let coin = test_threshold_sign(network).parity();
2018-06-11 09:00:23 -07:00
if coin {
count_true += 1;
} else {
count_false += 1;
}
}
check_coin_distribution(num_samples, count_true, count_false);
}
}
#[test]
fn test_threshold_sign_random_silent_200_samples() {
2018-06-11 09:00:23 -07:00
let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::Random);
test_threshold_sign_different_sizes(new_adversary, 200);
2018-06-11 09:00:23 -07:00
}
#[test]
fn test_threshold_sign_first_silent_50_samples() {
2018-06-11 09:00:23 -07:00
let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::First);
test_threshold_sign_different_sizes(new_adversary, 50);
2018-06-11 09:00:23 -07:00
}