2018-07-16 06:31:21 -07:00
|
|
|
#![deny(unused_must_use)]
|
2018-07-09 05:29:01 -07:00
|
|
|
//! Network tests for Queueing Honey Badger.
|
|
|
|
|
2019-03-19 09:55:56 -07:00
|
|
|
use std::collections::BTreeSet;
|
2018-07-11 12:15:08 -07:00
|
|
|
use std::sync::Arc;
|
2018-07-09 05:29:01 -07:00
|
|
|
|
2018-12-17 05:27:46 -08:00
|
|
|
use hbbft::dynamic_honey_badger::{DynamicHoneyBadger, JoinPlan};
|
|
|
|
use hbbft::queueing_honey_badger::{Change, ChangeState, Input, QueueingHoneyBadger};
|
2018-10-25 08:07:52 -07:00
|
|
|
use hbbft::sender_queue::{Message, SenderQueue, Step};
|
2018-11-22 04:00:02 -08:00
|
|
|
use hbbft::{util, NetworkInfo};
|
2019-04-26 07:54:12 -07:00
|
|
|
use hbbft_testing::adversary::{Adversary, NodeOrderAdversary, ReorderingAdversary};
|
|
|
|
use hbbft_testing::proptest::{gen_seed, TestRng, TestRngSeed};
|
|
|
|
use hbbft_testing::{NetBuilder, NewNodeInfo, Node, VirtualNet};
|
|
|
|
use log::info;
|
|
|
|
use proptest::{prelude::ProptestConfig, proptest};
|
|
|
|
use rand::{Rng, SeedableRng};
|
2018-07-09 05:29:01 -07:00
|
|
|
|
2019-03-19 09:55:56 -07:00
|
|
|
type NodeId = u16;
|
2018-10-25 08:07:52 -07:00
|
|
|
type QHB = SenderQueue<QueueingHoneyBadger<usize, NodeId, Vec<usize>>>;
|
2018-10-11 06:33:03 -07:00
|
|
|
|
2019-03-19 09:55:56 -07:00
|
|
|
// Send the second half of the transactions to the specified node.
|
|
|
|
fn input_second_half<A>(
|
|
|
|
net: &mut VirtualNet<QHB, A>,
|
|
|
|
id: NodeId,
|
|
|
|
num_txs: usize,
|
|
|
|
mut rng: &mut TestRng,
|
|
|
|
) where
|
|
|
|
A: Adversary<QHB>,
|
|
|
|
{
|
|
|
|
for tx in (num_txs / 2)..num_txs {
|
|
|
|
let _ = net.send_input(id, Input::User(tx), &mut rng);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-09 05:29:01 -07:00
|
|
|
/// Proposes `num_txs` values and expects nodes to output and order them.
|
2019-03-19 09:55:56 -07:00
|
|
|
fn test_queueing_honey_badger<A>(mut net: VirtualNet<QHB, A>, num_txs: usize, mut rng: &mut TestRng)
|
2018-10-11 06:33:03 -07:00
|
|
|
where
|
|
|
|
A: Adversary<QHB>,
|
2018-07-09 05:29:01 -07:00
|
|
|
{
|
2019-03-19 09:55:56 -07:00
|
|
|
let netinfo = net
|
|
|
|
.correct_nodes()
|
|
|
|
.nth(0)
|
|
|
|
.expect("At least one correct node needs to exist")
|
|
|
|
.algorithm()
|
|
|
|
.algo()
|
|
|
|
.netinfo()
|
|
|
|
.clone();
|
|
|
|
|
|
|
|
// Make two copies of all public keys.
|
2018-11-18 01:17:33 -08:00
|
|
|
let pub_keys_add = netinfo.public_key_map().clone();
|
|
|
|
let mut pub_keys_rm = pub_keys_add.clone();
|
|
|
|
|
2019-03-19 09:55:56 -07:00
|
|
|
// Get the first correct node id as candidate for removal/re-adding.
|
|
|
|
let first_correct_node = *net.correct_nodes().nth(0).unwrap().id();
|
|
|
|
|
|
|
|
// Remove the first correct node, which is to be removed.
|
|
|
|
pub_keys_rm.remove(&first_correct_node);
|
|
|
|
|
|
|
|
// Broadcast public keys of all nodes except for the node to be removed.
|
|
|
|
let _ = net.broadcast_input(
|
|
|
|
&Input::Change(Change::NodeChange(pub_keys_rm.clone())),
|
|
|
|
&mut rng,
|
|
|
|
);
|
|
|
|
|
|
|
|
// Broadcast the first half of the transactions.
|
2018-07-09 05:29:01 -07:00
|
|
|
for tx in 0..(num_txs / 2) {
|
2019-03-19 09:55:56 -07:00
|
|
|
let _ = net.broadcast_input(&Input::User(tx), &mut rng);
|
2018-07-09 05:29:01 -07:00
|
|
|
}
|
|
|
|
|
2019-03-19 09:55:56 -07:00
|
|
|
// Closure for checking the output of a node for ChangeSet completion containing
|
|
|
|
// all nodes but the removed node.
|
|
|
|
let has_remove = |node: &Node<QHB>| {
|
2018-11-18 01:17:33 -08:00
|
|
|
node.outputs().iter().any(|batch| match batch.change() {
|
|
|
|
ChangeState::Complete(Change::NodeChange(pub_keys)) => pub_keys == &pub_keys_rm,
|
|
|
|
_ => false,
|
2018-10-23 22:18:18 -07:00
|
|
|
})
|
2018-11-18 01:17:33 -08:00
|
|
|
};
|
2018-07-09 05:29:01 -07:00
|
|
|
|
2019-03-19 09:55:56 -07:00
|
|
|
// Closure for checking the output of a node for ChangeSet completion containing
|
|
|
|
// all nodes, including the previously removed node.
|
|
|
|
let has_add = |node: &Node<QHB>| {
|
2018-11-18 01:17:33 -08:00
|
|
|
node.outputs().iter().any(|batch| match batch.change() {
|
|
|
|
ChangeState::Complete(Change::NodeChange(pub_keys)) => pub_keys == &pub_keys_add,
|
2018-07-09 05:29:01 -07:00
|
|
|
_ => false,
|
|
|
|
})
|
2018-11-18 01:17:33 -08:00
|
|
|
};
|
2018-07-09 05:29:01 -07:00
|
|
|
|
2018-12-17 05:27:46 -08:00
|
|
|
// Returns `true` if the node has not output all changes or transactions yet.
|
2019-03-19 09:55:56 -07:00
|
|
|
let node_busy = |node: &Node<QHB>| {
|
|
|
|
!has_remove(node) || !has_add(node) || !node.algorithm().algo().queue().is_empty()
|
2018-07-09 05:29:01 -07:00
|
|
|
};
|
|
|
|
|
2019-03-19 09:55:56 -07:00
|
|
|
// All nodes await removal.
|
|
|
|
let mut awaiting_removal: BTreeSet<_> = net.correct_nodes().map(|node| *node.id()).collect();
|
|
|
|
|
|
|
|
// All nodes but the removed node await addition.
|
|
|
|
let mut awaiting_addition: BTreeSet<_> = net
|
|
|
|
.correct_nodes()
|
|
|
|
.map(|node| *node.id())
|
|
|
|
.filter(|id| *id != first_correct_node)
|
2018-12-17 05:27:46 -08:00
|
|
|
.collect();
|
2019-03-19 09:55:56 -07:00
|
|
|
|
|
|
|
// All, including the previously removed node, await the second half of transactions.
|
2018-12-17 05:27:46 -08:00
|
|
|
let mut awaiting_second_half: BTreeSet<_> = awaiting_removal.clone();
|
2019-03-19 09:55:56 -07:00
|
|
|
// Whether the first correct node was rejoined as a validator.
|
|
|
|
let mut rejoined_first_correct = false;
|
|
|
|
// The removed first correct node which is to be restarted as soon as all remaining
|
|
|
|
// validators agreed to add it back.
|
|
|
|
let mut saved_first_correct: Option<Node<QHB>> = None;
|
2018-12-17 05:27:46 -08:00
|
|
|
|
2018-07-09 05:29:01 -07:00
|
|
|
// Handle messages in random order until all nodes have output all transactions.
|
2019-03-19 09:55:56 -07:00
|
|
|
while net.correct_nodes().any(node_busy) {
|
|
|
|
let stepped_id = net.crank_expect(&mut rng).0;
|
|
|
|
if awaiting_removal.contains(&stepped_id) && has_remove(&net.get(stepped_id).unwrap()) {
|
2018-12-17 05:27:46 -08:00
|
|
|
awaiting_removal.remove(&stepped_id);
|
|
|
|
info!(
|
|
|
|
"{:?} has finished waiting for node removal; still waiting: {:?}",
|
|
|
|
stepped_id, awaiting_removal
|
|
|
|
);
|
2019-03-19 09:55:56 -07:00
|
|
|
|
2018-12-17 05:27:46 -08:00
|
|
|
if awaiting_removal.is_empty() {
|
2019-03-19 09:55:56 -07:00
|
|
|
info!("Removing first correct node from the test network");
|
|
|
|
saved_first_correct = net.remove_node(&first_correct_node);
|
2018-12-17 05:27:46 -08:00
|
|
|
}
|
2019-03-19 09:55:56 -07:00
|
|
|
// Vote to add the first correct node back.
|
|
|
|
if stepped_id != first_correct_node {
|
|
|
|
let _ = net.send_input(
|
2018-12-17 05:27:46 -08:00
|
|
|
stepped_id,
|
|
|
|
Input::Change(Change::NodeChange(pub_keys_add.clone())),
|
2019-03-19 09:55:56 -07:00
|
|
|
rng,
|
2018-12-17 05:27:46 -08:00
|
|
|
);
|
|
|
|
info!(
|
2019-03-19 09:55:56 -07:00
|
|
|
"Input the vote to add the first correct node into {:?} with netinfo {:?}",
|
2018-12-17 05:27:46 -08:00
|
|
|
stepped_id,
|
2019-03-19 09:55:56 -07:00
|
|
|
net.get(stepped_id).unwrap().algorithm().algo().netinfo()
|
2018-12-17 05:27:46 -08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2019-03-19 09:55:56 -07:00
|
|
|
|
2018-12-17 05:27:46 -08:00
|
|
|
if awaiting_removal.is_empty() && awaiting_addition.contains(&stepped_id) {
|
2019-03-19 09:55:56 -07:00
|
|
|
// If the stepped node started voting to add the first correct node back,
|
|
|
|
// take a note of that and rejoin it.
|
|
|
|
if let Some(join_plan) =
|
|
|
|
net.get(stepped_id)
|
|
|
|
.unwrap()
|
|
|
|
.outputs()
|
|
|
|
.iter()
|
|
|
|
.find_map(|batch| match batch.change() {
|
|
|
|
ChangeState::InProgress(Change::NodeChange(pub_keys))
|
|
|
|
if pub_keys == &pub_keys_add =>
|
|
|
|
{
|
|
|
|
batch.join_plan()
|
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
})
|
2018-12-17 05:27:46 -08:00
|
|
|
{
|
|
|
|
awaiting_addition.remove(&stepped_id);
|
|
|
|
info!(
|
|
|
|
"{:?} has finished waiting for node addition; still waiting: {:?}",
|
|
|
|
stepped_id, awaiting_addition
|
|
|
|
);
|
2019-03-19 09:55:56 -07:00
|
|
|
if awaiting_addition.is_empty() && !rejoined_first_correct {
|
|
|
|
let node = saved_first_correct
|
|
|
|
.take()
|
|
|
|
.expect("first correct node wasn't saved");
|
|
|
|
let step = restart_node_for_add(&mut net, node, join_plan, &mut rng);
|
|
|
|
net.process_step(first_correct_node, &step)
|
|
|
|
.expect("processing a step failed");
|
|
|
|
rejoined_first_correct = true;
|
2018-12-17 05:27:46 -08:00
|
|
|
}
|
2018-07-09 05:29:01 -07:00
|
|
|
}
|
2018-12-17 05:27:46 -08:00
|
|
|
}
|
2019-03-19 09:55:56 -07:00
|
|
|
|
|
|
|
if rejoined_first_correct && awaiting_second_half.contains(&stepped_id) {
|
2018-12-17 05:27:46 -08:00
|
|
|
// Input the second half of user transactions into the stepped node.
|
2019-03-19 09:55:56 -07:00
|
|
|
input_second_half(&mut net, stepped_id, num_txs, &mut rng);
|
2018-12-17 05:27:46 -08:00
|
|
|
awaiting_second_half.remove(&stepped_id);
|
2018-07-09 05:29:01 -07:00
|
|
|
}
|
|
|
|
}
|
2019-03-19 09:55:56 -07:00
|
|
|
let node_1 = net
|
|
|
|
.correct_nodes()
|
|
|
|
.nth(1)
|
|
|
|
.expect("second correct node is missing");
|
|
|
|
net.verify_batches(node_1);
|
2018-12-17 05:27:46 -08:00
|
|
|
}
|
|
|
|
|
2019-03-19 09:55:56 -07:00
|
|
|
/// Restarts specified node on the test network for adding it back as a validator.
|
|
|
|
fn restart_node_for_add<R, A>(
|
|
|
|
net: &mut VirtualNet<QHB, A>,
|
|
|
|
mut node: Node<QHB>,
|
2018-12-17 05:27:46 -08:00
|
|
|
join_plan: JoinPlan<NodeId>,
|
2019-03-19 09:55:56 -07:00
|
|
|
mut rng: &mut R,
|
2018-12-17 05:27:46 -08:00
|
|
|
) -> Step<QueueingHoneyBadger<usize, NodeId, Vec<usize>>>
|
|
|
|
where
|
2019-03-19 09:55:56 -07:00
|
|
|
R: rand::Rng,
|
2018-12-17 05:27:46 -08:00
|
|
|
A: Adversary<QHB>,
|
|
|
|
{
|
2019-03-19 09:55:56 -07:00
|
|
|
let our_id = *node.id();
|
|
|
|
println!("Restarting node {} with {:?}", node.id(), join_plan);
|
|
|
|
|
|
|
|
// TODO: When an observer node is added to the network, it should also be added to peer_ids.
|
|
|
|
let peer_ids: Vec<_> = net
|
|
|
|
.nodes()
|
2019-04-26 07:54:12 -07:00
|
|
|
.map(Node::id)
|
2019-03-19 09:55:56 -07:00
|
|
|
.filter(|id| *id != node.id())
|
2018-12-17 05:27:46 -08:00
|
|
|
.cloned()
|
|
|
|
.collect();
|
2019-03-19 09:55:56 -07:00
|
|
|
|
|
|
|
let secret_key = node.algorithm().algo().netinfo().secret_key().clone();
|
2018-12-17 05:27:46 -08:00
|
|
|
let (qhb, qhb_step) =
|
|
|
|
QueueingHoneyBadger::builder_joining(our_id, secret_key, join_plan, &mut rng)
|
2018-12-11 03:59:54 -08:00
|
|
|
.and_then(|builder| builder.batch_size(3).build(&mut rng))
|
|
|
|
.expect("failed to rebuild the node with a join plan");
|
2018-12-17 05:27:46 -08:00
|
|
|
let (sq, mut sq_step) = SenderQueue::builder(qhb, peer_ids.into_iter()).build(our_id);
|
2019-03-19 09:55:56 -07:00
|
|
|
*node.algorithm_mut() = sq;
|
2018-12-18 07:17:46 -08:00
|
|
|
sq_step.extend(qhb_step.map(|output| output, |fault| fault, Message::from));
|
2019-03-19 09:55:56 -07:00
|
|
|
net.insert_node(node);
|
2018-12-17 05:27:46 -08:00
|
|
|
sq_step
|
2018-07-09 05:29:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Allow passing `netinfo` by value. `TestNetwork` expects this function signature.
|
2018-12-11 05:44:36 -08:00
|
|
|
#[allow(clippy::needless_pass_by_value)]
|
2018-10-25 08:07:52 -07:00
|
|
|
fn new_queueing_hb(
|
|
|
|
netinfo: Arc<NetworkInfo<NodeId>>,
|
2019-03-19 09:55:56 -07:00
|
|
|
seed: TestRngSeed,
|
2018-10-25 08:07:52 -07:00
|
|
|
) -> (QHB, Step<QueueingHoneyBadger<usize, NodeId, Vec<usize>>>) {
|
2019-03-19 09:55:56 -07:00
|
|
|
let mut rng: TestRng = TestRng::from_seed(seed);
|
2018-10-25 08:07:52 -07:00
|
|
|
let our_id = *netinfo.our_id();
|
2019-03-19 09:55:56 -07:00
|
|
|
let peer_ids = netinfo.all_ids().filter(|&&them| them != our_id).cloned();
|
2018-10-25 08:07:52 -07:00
|
|
|
let dhb = DynamicHoneyBadger::builder().build((*netinfo).clone());
|
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 (qhb, qhb_step) = QueueingHoneyBadger::builder(dhb)
|
|
|
|
.batch_size(3)
|
2018-12-10 08:08:01 -08:00
|
|
|
.build(&mut rng)
|
|
|
|
.expect("failed to build QueueingHoneyBadger");
|
2018-10-25 08:07:52 -07:00
|
|
|
let (sq, mut step) = SenderQueue::builder(qhb, peer_ids).build(our_id);
|
2018-12-18 07:17:46 -08:00
|
|
|
let output = step.extend_with(qhb_step, |fault| fault, Message::from);
|
|
|
|
assert!(output.is_empty());
|
2018-10-25 08:07:52 -07:00
|
|
|
(sq, step)
|
2018-07-09 05:29:01 -07:00
|
|
|
}
|
|
|
|
|
2019-03-19 09:55:56 -07:00
|
|
|
fn test_queueing_honey_badger_different_sizes<A, F>(
|
|
|
|
new_adversary: F,
|
|
|
|
num_txs: usize,
|
|
|
|
seed: TestRngSeed,
|
|
|
|
) where
|
2018-10-11 06:33:03 -07:00
|
|
|
A: Adversary<QHB>,
|
2019-03-19 09:55:56 -07:00
|
|
|
F: Fn() -> A,
|
2018-07-09 05:29:01 -07:00
|
|
|
{
|
|
|
|
// This returns an error in all but the first test.
|
|
|
|
let _ = env_logger::try_init();
|
|
|
|
|
2019-03-19 09:55:56 -07:00
|
|
|
let mut rng: TestRng = TestRng::from_seed(seed);
|
|
|
|
|
2018-07-12 08:53:12 -07:00
|
|
|
let sizes = vec![3, 5, rng.gen_range(6, 10)];
|
2018-07-09 05:29:01 -07:00
|
|
|
for size in sizes {
|
|
|
|
// The test is removing one correct node, so we allow fewer faulty ones.
|
2018-11-22 04:00:02 -08:00
|
|
|
let num_adv_nodes = util::max_faulty(size - 1);
|
2018-07-09 05:29:01 -07:00
|
|
|
let num_good_nodes = size - num_adv_nodes;
|
|
|
|
info!(
|
|
|
|
"Network size: {} good nodes, {} faulty nodes",
|
|
|
|
num_good_nodes, num_adv_nodes
|
|
|
|
);
|
2019-03-19 09:55:56 -07:00
|
|
|
|
|
|
|
let (net, _) = NetBuilder::new(0..size as u16)
|
|
|
|
.num_faulty(num_adv_nodes)
|
|
|
|
.message_limit(20_000 * size)
|
|
|
|
.no_time_limit()
|
|
|
|
.adversary(new_adversary())
|
|
|
|
.using_step(move |node_info: NewNodeInfo<_>| {
|
|
|
|
// Note: The "seed" variable is implicitly copied by the move closure.
|
|
|
|
// The "Copy" trait is *not* implemented for TestRng, which additionally
|
|
|
|
// needs to be mutable, while we are in a function which captures immutably.
|
|
|
|
// To avoid convoluted clone/borrow constructs we pass a TestRngSeed
|
|
|
|
// rather than a TestRng instance.
|
|
|
|
new_queueing_hb(Arc::new(node_info.netinfo), seed)
|
|
|
|
})
|
|
|
|
.build(&mut rng)
|
|
|
|
.expect("Could not construct test network.");
|
|
|
|
|
|
|
|
test_queueing_honey_badger(net, num_txs, &mut rng);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
proptest! {
|
|
|
|
#![proptest_config(ProptestConfig {
|
|
|
|
cases: 1, .. ProptestConfig::default()
|
|
|
|
})]
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[allow(clippy::unnecessary_operation)]
|
|
|
|
fn test_queueing_honey_badger_random_delivery_silent(seed in gen_seed()) {
|
|
|
|
do_test_queueing_honey_badger_random_delivery_silent(seed)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[allow(clippy::unnecessary_operation)]
|
|
|
|
fn test_queueing_honey_badger_first_delivery_silent(seed in gen_seed()) {
|
|
|
|
do_test_queueing_honey_badger_first_delivery_silent(seed)
|
2018-07-09 05:29:01 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-19 09:55:56 -07:00
|
|
|
fn do_test_queueing_honey_badger_random_delivery_silent(seed: TestRngSeed) {
|
|
|
|
test_queueing_honey_badger_different_sizes(ReorderingAdversary::new, 30, seed);
|
2018-07-09 05:29:01 -07:00
|
|
|
}
|
|
|
|
|
2019-03-19 09:55:56 -07:00
|
|
|
fn do_test_queueing_honey_badger_first_delivery_silent(seed: TestRngSeed) {
|
|
|
|
test_queueing_honey_badger_different_sizes(NodeOrderAdversary::new, 30, seed);
|
2018-07-09 05:29:01 -07:00
|
|
|
}
|