mirror of https://github.com/poanetwork/hbbft.git
236 lines
9.2 KiB
Rust
236 lines
9.2 KiB
Rust
#![deny(unused_must_use)]
|
|
//! Network tests for Queueing Honey Badger.
|
|
|
|
mod network;
|
|
|
|
use std::collections::{BTreeMap, BTreeSet};
|
|
use std::iter;
|
|
use std::sync::Arc;
|
|
|
|
use log::info;
|
|
use rand::{Rng, SeedableRng};
|
|
use rand_xorshift::XorShiftRng;
|
|
|
|
use hbbft::dynamic_honey_badger::{DynamicHoneyBadger, JoinPlan};
|
|
use hbbft::queueing_honey_badger::{Change, ChangeState, Input, QueueingHoneyBadger};
|
|
use hbbft::sender_queue::{Message, SenderQueue, Step};
|
|
use hbbft::{util, NetworkInfo};
|
|
|
|
use crate::network::{Adversary, MessageScheduler, NodeId, SilentAdversary, TestNetwork, TestNode};
|
|
|
|
type QHB = SenderQueue<QueueingHoneyBadger<usize, NodeId, Vec<usize>>>;
|
|
|
|
/// Proposes `num_txs` values and expects nodes to output and order them.
|
|
fn test_queueing_honey_badger<A>(mut network: TestNetwork<A, QHB>, num_txs: usize)
|
|
where
|
|
A: Adversary<QHB>,
|
|
{
|
|
let netinfo = network.observer.instance().algo().netinfo().clone();
|
|
let pub_keys_add = netinfo.public_key_map().clone();
|
|
let mut pub_keys_rm = pub_keys_add.clone();
|
|
pub_keys_rm.remove(&NodeId(0));
|
|
network.input_all(Input::Change(Change::NodeChange(pub_keys_rm.clone())));
|
|
|
|
// The second half of the transactions will be input only after a node has been removed and
|
|
// readded.
|
|
for tx in 0..(num_txs / 2) {
|
|
network.input_all(Input::User(tx));
|
|
}
|
|
|
|
let input_second_half = |network: &mut TestNetwork<_, _>, id: NodeId| {
|
|
for tx in (num_txs / 2)..num_txs {
|
|
network.input(id, Input::User(tx));
|
|
}
|
|
};
|
|
|
|
let has_remove = |node: &TestNode<QHB>| {
|
|
node.outputs().iter().any(|batch| match batch.change() {
|
|
ChangeState::Complete(Change::NodeChange(pub_keys)) => pub_keys == &pub_keys_rm,
|
|
_ => false,
|
|
})
|
|
};
|
|
|
|
let has_add = |node: &TestNode<QHB>| {
|
|
node.outputs().iter().any(|batch| match batch.change() {
|
|
ChangeState::Complete(Change::NodeChange(pub_keys)) => pub_keys == &pub_keys_add,
|
|
_ => false,
|
|
})
|
|
};
|
|
|
|
// Returns `true` if the node has not output all changes or transactions yet.
|
|
let node_busy = |node: &TestNode<QHB>| {
|
|
!has_remove(node) || !has_add(node) || !node.instance().algo().queue().is_empty()
|
|
};
|
|
|
|
let mut awaiting_removal: BTreeSet<_> = network.nodes.iter().map(|(id, _)| *id).collect();
|
|
let mut awaiting_addition: BTreeSet<_> = network
|
|
.nodes
|
|
.iter()
|
|
.map(|(id, _)| *id)
|
|
.filter(|id| *id != NodeId(0))
|
|
.collect();
|
|
// The set of nodes awaiting the second half of user transactions.
|
|
let mut awaiting_second_half: BTreeSet<_> = awaiting_removal.clone();
|
|
// Whether node 0 was rejoined as a validator.
|
|
let mut rejoined_node0 = false;
|
|
// The removed node 0 which is to be restarted as soon as all remaining validators agree to add
|
|
// it back.
|
|
let mut saved_node0: Option<TestNode<QHB>> = None;
|
|
|
|
// Handle messages in random order until all nodes have output all transactions.
|
|
while network.nodes.values().any(node_busy) || !has_add(&network.observer) {
|
|
let stepped_id = network.step();
|
|
if awaiting_removal.contains(&stepped_id) && has_remove(&network.nodes[&stepped_id]) {
|
|
awaiting_removal.remove(&stepped_id);
|
|
info!(
|
|
"{:?} has finished waiting for node removal; still waiting: {:?}",
|
|
stepped_id, awaiting_removal
|
|
);
|
|
if awaiting_removal.is_empty() {
|
|
info!("Removing node 0 from the test network");
|
|
saved_node0 = network.nodes.remove(&NodeId(0));
|
|
}
|
|
// Vote to add node 0 back.
|
|
if stepped_id != NodeId(0) {
|
|
network.input(
|
|
stepped_id,
|
|
Input::Change(Change::NodeChange(pub_keys_add.clone())),
|
|
);
|
|
info!(
|
|
"Input the vote to add node 0 into {:?} with netinfo {:?}",
|
|
stepped_id,
|
|
network.nodes[&stepped_id].instance().algo().netinfo()
|
|
);
|
|
}
|
|
}
|
|
if awaiting_removal.is_empty() && awaiting_addition.contains(&stepped_id) {
|
|
// If the stepped node started voting to add node 0 back, take a note of that and rejoin
|
|
// node 0.
|
|
if let Some(join_plan) = network.nodes[&stepped_id]
|
|
.outputs()
|
|
.iter()
|
|
.find_map(|batch| match batch.change() {
|
|
ChangeState::InProgress(Change::NodeChange(pub_keys))
|
|
if pub_keys == &pub_keys_add =>
|
|
{
|
|
batch.join_plan()
|
|
}
|
|
_ => None,
|
|
})
|
|
{
|
|
awaiting_addition.remove(&stepped_id);
|
|
info!(
|
|
"{:?} has finished waiting for node addition; still waiting: {:?}",
|
|
stepped_id, awaiting_addition
|
|
);
|
|
if awaiting_addition.is_empty() && !rejoined_node0 {
|
|
let node = saved_node0.take().expect("node 0 wasn't saved");
|
|
let step = restart_node_for_add(&mut network, node, join_plan);
|
|
network.dispatch_messages(NodeId(0), step.messages);
|
|
rejoined_node0 = true;
|
|
}
|
|
}
|
|
}
|
|
if rejoined_node0 && awaiting_second_half.contains(&stepped_id) {
|
|
// Input the second half of user transactions into the stepped node.
|
|
input_second_half(&mut network, stepped_id);
|
|
awaiting_second_half.remove(&stepped_id);
|
|
}
|
|
}
|
|
let node1 = network.nodes.get(&NodeId(1)).expect("node 1 is missing");
|
|
network.verify_batches(&node1);
|
|
}
|
|
|
|
/// Restarts a stopped and removed node with a given join plan and adds the node back on the test
|
|
/// network.
|
|
fn restart_node_for_add<A>(
|
|
network: &mut TestNetwork<A, QHB>,
|
|
mut node: TestNode<QHB>,
|
|
join_plan: JoinPlan<NodeId>,
|
|
) -> Step<QueueingHoneyBadger<usize, NodeId, Vec<usize>>>
|
|
where
|
|
A: Adversary<QHB>,
|
|
{
|
|
let our_id = node.id;
|
|
info!("Restarting {:?} with {:?}", our_id, join_plan);
|
|
let observer = network.observer.id;
|
|
let peer_ids: Vec<NodeId> = network
|
|
.nodes
|
|
.keys()
|
|
.cloned()
|
|
.filter(|id| *id != our_id)
|
|
.chain(iter::once(observer))
|
|
.collect();
|
|
let secret_key = node.instance().algo().netinfo().secret_key().clone();
|
|
let mut rng = XorShiftRng::from_seed(rand::thread_rng().gen::<[u8; 16]>());
|
|
let (qhb, qhb_step) =
|
|
QueueingHoneyBadger::builder_joining(our_id, secret_key, join_plan, &mut rng)
|
|
.and_then(|builder| builder.batch_size(3).build(&mut rng))
|
|
.expect("failed to rebuild the node with a join plan");
|
|
let (sq, mut sq_step) = SenderQueue::builder(qhb, peer_ids.into_iter()).build(our_id);
|
|
*node.instance_mut() = sq;
|
|
sq_step.extend(qhb_step.map(|output| output, Message::from));
|
|
network.nodes.insert(our_id, node);
|
|
sq_step
|
|
}
|
|
|
|
// Allow passing `netinfo` by value. `TestNetwork` expects this function signature.
|
|
#[allow(clippy::needless_pass_by_value)]
|
|
fn new_queueing_hb(
|
|
netinfo: Arc<NetworkInfo<NodeId>>,
|
|
) -> (QHB, Step<QueueingHoneyBadger<usize, NodeId, Vec<usize>>>) {
|
|
let observer = NodeId(netinfo.num_nodes());
|
|
let our_id = *netinfo.our_id();
|
|
let peer_ids = netinfo
|
|
.all_ids()
|
|
.filter(|&&them| them != our_id)
|
|
.cloned()
|
|
.chain(iter::once(observer));
|
|
let dhb = DynamicHoneyBadger::builder().build((*netinfo).clone());
|
|
let mut rng = XorShiftRng::from_seed(rand::thread_rng().gen::<[u8; 16]>());
|
|
let (qhb, qhb_step) = QueueingHoneyBadger::builder(dhb)
|
|
.batch_size(3)
|
|
.build(&mut rng)
|
|
.expect("failed to build QueueingHoneyBadger");
|
|
let (sq, mut step) = SenderQueue::builder(qhb, peer_ids).build(our_id);
|
|
assert!(step.extend_with(qhb_step, Message::from).is_empty());
|
|
(sq, step)
|
|
}
|
|
|
|
fn test_queueing_honey_badger_different_sizes<A, F>(new_adversary: F, num_txs: usize)
|
|
where
|
|
A: Adversary<QHB>,
|
|
F: Fn(usize, usize, BTreeMap<NodeId, Arc<NetworkInfo<NodeId>>>) -> A,
|
|
{
|
|
// This returns an error in all but the first test.
|
|
let _ = env_logger::try_init();
|
|
|
|
let mut rng = rand::thread_rng();
|
|
let sizes = vec![3, 5, rng.gen_range(6, 10)];
|
|
for size in sizes {
|
|
// The test is removing one correct node, so we allow fewer faulty ones.
|
|
let num_adv_nodes = util::max_faulty(size - 1);
|
|
let num_good_nodes = size - num_adv_nodes;
|
|
info!(
|
|
"Network size: {} good nodes, {} faulty nodes",
|
|
num_good_nodes, num_adv_nodes
|
|
);
|
|
let adversary = |adv_nodes| new_adversary(num_good_nodes, num_adv_nodes, adv_nodes);
|
|
let network =
|
|
TestNetwork::new_with_step(num_good_nodes, num_adv_nodes, adversary, new_queueing_hb);
|
|
test_queueing_honey_badger(network, num_txs);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_queueing_honey_badger_random_delivery_silent() {
|
|
let new_adversary = |_: usize, _: usize, _| SilentAdversary::new(MessageScheduler::Random);
|
|
test_queueing_honey_badger_different_sizes(new_adversary, 30);
|
|
}
|
|
|
|
#[test]
|
|
fn test_queueing_honey_badger_first_delivery_silent() {
|
|
let new_adversary = |_: usize, _: usize, _| SilentAdversary::new(MessageScheduler::First);
|
|
test_queueing_honey_badger_different_sizes(new_adversary, 30);
|
|
}
|