Fix broadcast for size < 4, and test more network sizes.

This commit is contained in:
Andreas Fackler 2018-05-14 16:16:57 +02:00
parent 10d9aa73e3
commit 3895949cf6
2 changed files with 119 additions and 42 deletions

View File

@ -94,7 +94,7 @@ pub struct Broadcast<N> {
num_nodes: usize,
num_faulty_nodes: usize,
data_shard_num: usize,
coding: ReedSolomon,
coding: Coding,
/// Whether we have already multicast `Echo`.
echo_sent: bool,
/// Whether we have already multicast `Ready`.
@ -170,7 +170,7 @@ impl<N: Eq + Debug + Clone + Ord> Broadcast<N> {
let num_faulty_nodes = (num_nodes - 1) / 3;
let parity_shard_num = 2 * num_faulty_nodes;
let data_shard_num = num_nodes - parity_shard_num;
let coding = ReedSolomon::new(data_shard_num, parity_shard_num)?;
let coding = Coding::new(data_shard_num, parity_shard_num)?;
Ok(Broadcast {
our_id,
@ -427,6 +427,64 @@ impl<N: Eq + Debug + Clone + Ord> Broadcast<N> {
}
}
/// A wrapper for `ReedSolomon` that doesn't panic if there are no parity shards.
enum Coding {
/// A `ReedSolomon` instance with at least one parity shard.
ReedSolomon(Box<ReedSolomon>),
/// A no-op replacement that doesn't encode or decode anything.
Trivial(usize),
}
impl Coding {
/// Creates a new `Coding` instance with the given number of shards.
fn new(data_shard_num: usize, parity_shard_num: usize) -> Result<Self, Error> {
Ok(if parity_shard_num > 0 {
let rs = ReedSolomon::new(data_shard_num, parity_shard_num)?;
Coding::ReedSolomon(Box::new(rs))
} else {
Coding::Trivial(data_shard_num)
})
}
/// Returns the number of data shards.
fn data_shard_count(&self) -> usize {
match *self {
Coding::ReedSolomon(ref rs) => rs.data_shard_count(),
Coding::Trivial(dsc) => dsc,
}
}
/// Returns the number of parity shards.
fn parity_shard_count(&self) -> usize {
match *self {
Coding::ReedSolomon(ref rs) => rs.parity_shard_count(),
Coding::Trivial(_) => 0,
}
}
/// Constructs (and overwrites) the parity shards.
fn encode(&self, slices: &mut [&mut [u8]]) -> Result<(), Error> {
match *self {
Coding::ReedSolomon(ref rs) => rs.encode(slices)?,
Coding::Trivial(_) => (),
}
Ok(())
}
/// If enough shards are present, reconstructs the missing ones.
fn reconstruct_shards(&self, shards: &mut [Option<Box<[u8]>>]) -> Result<(), Error> {
match *self {
Coding::ReedSolomon(ref rs) => rs.reconstruct_shards(shards)?,
Coding::Trivial(_) => {
if shards.iter().any(Option::is_none) {
return Err(rse::Error::TooFewShardsPresent.into());
}
}
}
Ok(())
}
}
/// Errors returned by the broadcast instance.
#[derive(Debug, Clone)]
pub enum Error {
@ -447,7 +505,7 @@ impl From<rse::Error> for Error {
fn decode_from_shards<T>(
leaf_values: &mut [Option<Box<[u8]>>],
coding: &ReedSolomon,
coding: &Coding,
data_shard_num: usize,
root_hash: &[u8],
) -> Result<T, Error>

View File

@ -9,7 +9,7 @@ extern crate rand;
use rand::Rng;
use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::fmt;
use std::{fmt, iter};
use hbbft::broadcast::{Broadcast, BroadcastMessage};
use hbbft::messaging::{DistAlgorithm, Target, TargetedMessage};
@ -51,6 +51,12 @@ impl<D: DistAlgorithm> TestNode<D> {
.expect("handling message");
self.outputs.extend(self.algo.next_output());
}
/// Inputs a value into the instance.
fn input(&mut self, input: D::Input) {
self.algo.input(input).expect("input");
self.outputs.extend(self.algo.next_output());
}
}
/// A strategy for picking the next good node to handle a message.
@ -171,7 +177,10 @@ impl Adversary<Broadcast<NodeUid>> for ProposeAdversary {
.chain(self.good_nodes.iter())
.cloned()
.collect();
let id = *self.adv_nodes.iter().next().unwrap();
let id = match self.adv_nodes.iter().next() {
Some(id) => *id,
None => return vec![],
};
let mut bc = Broadcast::new(id, id, node_ids).expect("broadcast instance");
bc.input(b"Fake news".to_vec()).expect("propose");
bc.message_iter().map(|msg| (id, msg)).collect()
@ -264,7 +273,7 @@ impl<A: Adversary<Broadcast<NodeUid>>> TestNetwork<A, Broadcast<NodeUid>> {
fn input(&mut self, proposer_id: NodeUid, value: ProposedValue) {
let msgs: Vec<_> = {
let node = self.nodes.get_mut(&proposer_id).expect("proposer instance");
node.algo.input(value).expect("propose");
node.input(value);
node.algo.message_iter().collect()
};
self.dispatch_messages(proposer_id, msgs);
@ -292,6 +301,28 @@ fn test_broadcast<A: Adversary<Broadcast<NodeUid>>>(
}
}
fn test_broadcast_different_sizes<A, F>(new_adversary: F, proposed_value: &[u8])
where
A: Adversary<Broadcast<NodeUid>>,
F: Fn(usize, usize) -> A,
{
let mut rng = rand::thread_rng();
let sizes = (1..6)
.chain(iter::once(rng.gen_range(6, 20)))
.chain(iter::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;
println!(
"Network size: {} good nodes, {} faulty nodes",
num_good_nodes, num_faulty_nodes
);
let adversary = new_adversary(num_good_nodes, num_faulty_nodes);
let network = TestNetwork::new(num_good_nodes, num_faulty_nodes, adversary);
test_broadcast(network, proposed_value);
}
}
#[test]
fn test_8_broadcast_equal_leaves_silent() {
let adversary = SilentAdversary::new(MessageScheduler::Random);
@ -301,49 +332,37 @@ fn test_8_broadcast_equal_leaves_silent() {
}
#[test]
fn test_13_broadcast_nodes_random_delivery_silent() {
let adversary = SilentAdversary::new(MessageScheduler::Random);
test_broadcast(TestNetwork::new(13, 0, adversary), b"Foo");
fn test_broadcast_random_delivery_silent() {
let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::Random);
test_broadcast_different_sizes(new_adversary, b"Foo");
}
#[test]
fn test_4_broadcast_nodes_random_delivery_silent() {
let adversary = SilentAdversary::new(MessageScheduler::Random);
test_broadcast(TestNetwork::new(4, 0, adversary), b"Foo");
fn test_broadcast_nodes_first_delivery_silent() {
let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::First);
test_broadcast_different_sizes(new_adversary, b"Foo");
}
#[test]
fn test_11_5_broadcast_nodes_random_delivery_silent() {
let adversary = SilentAdversary::new(MessageScheduler::Random);
test_broadcast(TestNetwork::new(11, 5, adversary), b"Foo");
fn test_broadcast_nodes_random_delivery_adv_propose() {
let new_adversary = |num_good_nodes: usize, num_faulty_nodes: usize| {
let good_nodes: BTreeSet<NodeUid> = (0..num_good_nodes).map(NodeUid).collect();
let adv_nodes: BTreeSet<NodeUid> = (num_good_nodes..(num_good_nodes + num_faulty_nodes))
.map(NodeUid)
.collect();
ProposeAdversary::new(MessageScheduler::Random, good_nodes, adv_nodes)
};
test_broadcast_different_sizes(new_adversary, b"Foo");
}
#[test]
fn test_11_5_broadcast_nodes_first_delivery_silent() {
let adversary = SilentAdversary::new(MessageScheduler::First);
test_broadcast(TestNetwork::new(11, 5, adversary), b"Foo");
}
#[test]
fn test_3_1_broadcast_nodes_random_delivery_adv_propose() {
let good_nodes: BTreeSet<NodeUid> = (0..3).map(NodeUid).collect();
let adv_nodes: BTreeSet<NodeUid> = (3..4).map(NodeUid).collect();
let adversary = ProposeAdversary::new(MessageScheduler::Random, good_nodes, adv_nodes);
test_broadcast(TestNetwork::new(3, 1, adversary), b"Foo");
}
#[test]
fn test_11_5_broadcast_nodes_random_delivery_adv_propose() {
let good_nodes: BTreeSet<NodeUid> = (0..11).map(NodeUid).collect();
let adv_nodes: BTreeSet<NodeUid> = (11..16).map(NodeUid).collect();
let adversary = ProposeAdversary::new(MessageScheduler::Random, good_nodes, adv_nodes);
test_broadcast(TestNetwork::new(11, 5, adversary), b"Foo");
}
#[test]
fn test_11_5_broadcast_nodes_first_delivery_adv_propose() {
let good_nodes: BTreeSet<NodeUid> = (0..11).map(NodeUid).collect();
let adv_nodes: BTreeSet<NodeUid> = (11..16).map(NodeUid).collect();
let adversary = ProposeAdversary::new(MessageScheduler::First, good_nodes, adv_nodes);
test_broadcast(TestNetwork::new(11, 5, adversary), b"Foo");
fn test_broadcast_nodes_first_delivery_adv_propose() {
let new_adversary = |num_good_nodes: usize, num_faulty_nodes: usize| {
let good_nodes: BTreeSet<NodeUid> = (0..num_good_nodes).map(NodeUid).collect();
let adv_nodes: BTreeSet<NodeUid> = (num_good_nodes..(num_good_nodes + num_faulty_nodes))
.map(NodeUid)
.collect();
ProposeAdversary::new(MessageScheduler::First, good_nodes, adv_nodes)
};
test_broadcast_different_sizes(new_adversary, b"Foo");
}