mirror of https://github.com/poanetwork/hbbft.git
Fix broadcast for size < 4, and test more network sizes.
This commit is contained in:
parent
10d9aa73e3
commit
3895949cf6
|
@ -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>
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue