mirror of https://github.com/poanetwork/hbbft.git
ported Common Subset tests to TestNetwork
This commit is contained in:
parent
e4843b496f
commit
962a618ffd
|
@ -3,7 +3,7 @@
|
|||
// TODO: This module is work in progress. Remove this attribute when it's not needed anymore.
|
||||
#![allow(unused)]
|
||||
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
|
||||
|
@ -18,7 +18,7 @@ use messaging::{DistAlgorithm, Target, TargetedMessage};
|
|||
type ProposedValue = Vec<u8>;
|
||||
// Type of output from the Common Subset message handler.
|
||||
type CommonSubsetOutput<NodeUid> = (
|
||||
Option<HashMap<NodeUid, ProposedValue>>,
|
||||
Option<BTreeMap<NodeUid, ProposedValue>>,
|
||||
VecDeque<TargetedMessage<Message<NodeUid>, NodeUid>>,
|
||||
);
|
||||
|
||||
|
@ -60,18 +60,18 @@ pub struct CommonSubset<NodeUid: Eq + Hash + Ord> {
|
|||
uid: NodeUid,
|
||||
num_nodes: usize,
|
||||
num_faulty_nodes: usize,
|
||||
broadcast_instances: HashMap<NodeUid, Broadcast<NodeUid>>,
|
||||
agreement_instances: HashMap<NodeUid, Agreement<NodeUid>>,
|
||||
broadcast_results: HashMap<NodeUid, ProposedValue>,
|
||||
agreement_results: HashMap<NodeUid, bool>,
|
||||
broadcast_instances: BTreeMap<NodeUid, Broadcast<NodeUid>>,
|
||||
agreement_instances: BTreeMap<NodeUid, Agreement<NodeUid>>,
|
||||
broadcast_results: BTreeMap<NodeUid, ProposedValue>,
|
||||
agreement_results: BTreeMap<NodeUid, bool>,
|
||||
messages: VecDeque<TargetedMessage<Message<NodeUid>, NodeUid>>,
|
||||
output: Option<HashMap<NodeUid, ProposedValue>>,
|
||||
output: Option<BTreeMap<NodeUid, ProposedValue>>,
|
||||
}
|
||||
|
||||
impl<NodeUid: Clone + Debug + Eq + Hash + Ord> DistAlgorithm for CommonSubset<NodeUid> {
|
||||
type NodeUid = NodeUid;
|
||||
type Input = ProposedValue;
|
||||
type Output = HashMap<NodeUid, ProposedValue>;
|
||||
type Output = BTreeMap<NodeUid, ProposedValue>;
|
||||
type Message = Message<NodeUid>;
|
||||
type Error = Error;
|
||||
|
||||
|
@ -112,12 +112,12 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> DistAlgorithm for CommonSubset<No
|
|||
}
|
||||
|
||||
impl<NodeUid: Clone + Debug + Eq + Hash + Ord> CommonSubset<NodeUid> {
|
||||
pub fn new(uid: NodeUid, all_uids: &HashSet<NodeUid>) -> Result<Self, Error> {
|
||||
pub fn new(uid: NodeUid, all_uids: &BTreeSet<NodeUid>) -> Result<Self, Error> {
|
||||
let num_nodes = all_uids.len();
|
||||
let num_faulty_nodes = (num_nodes - 1) / 3;
|
||||
|
||||
// Create all broadcast instances.
|
||||
let mut broadcast_instances: HashMap<NodeUid, Broadcast<NodeUid>> = HashMap::new();
|
||||
let mut broadcast_instances: BTreeMap<NodeUid, Broadcast<NodeUid>> = BTreeMap::new();
|
||||
for uid0 in all_uids {
|
||||
broadcast_instances.insert(
|
||||
uid0.clone(),
|
||||
|
@ -130,7 +130,7 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> CommonSubset<NodeUid> {
|
|||
}
|
||||
|
||||
// Create all agreement instances.
|
||||
let mut agreement_instances: HashMap<NodeUid, Agreement<NodeUid>> = HashMap::new();
|
||||
let mut agreement_instances: BTreeMap<NodeUid, Agreement<NodeUid>> = BTreeMap::new();
|
||||
for uid0 in all_uids {
|
||||
agreement_instances.insert(uid0.clone(), Agreement::new(uid0.clone(), num_nodes));
|
||||
}
|
||||
|
@ -141,8 +141,8 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> CommonSubset<NodeUid> {
|
|||
num_faulty_nodes,
|
||||
broadcast_instances,
|
||||
agreement_instances,
|
||||
broadcast_results: HashMap::new(),
|
||||
agreement_results: HashMap::new(),
|
||||
broadcast_results: BTreeMap::new(),
|
||||
agreement_results: BTreeMap::new(),
|
||||
messages: VecDeque::new(),
|
||||
output: None,
|
||||
})
|
||||
|
@ -299,7 +299,7 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> CommonSubset<NodeUid> {
|
|||
}
|
||||
debug!("{:?} All Agreement instances have terminated", self.uid);
|
||||
// All instances of Agreement that delivered `true` (or "1" in the paper).
|
||||
let delivered_1: HashSet<&NodeUid> = self.agreement_results
|
||||
let delivered_1: BTreeSet<&NodeUid> = self.agreement_results
|
||||
.iter()
|
||||
.filter(|(_, v)| **v)
|
||||
.map(|(k, _)| k)
|
||||
|
@ -307,7 +307,7 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> CommonSubset<NodeUid> {
|
|||
debug!("Agreement instances that delivered 1: {:?}", delivered_1);
|
||||
|
||||
// Results of Broadcast instances in `delivered_1`
|
||||
let broadcast_results: HashMap<NodeUid, ProposedValue> = self.broadcast_results
|
||||
let broadcast_results: BTreeMap<NodeUid, ProposedValue> = self.broadcast_results
|
||||
.iter()
|
||||
.filter(|(k, _)| delivered_1.contains(k))
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::collections::btree_map::Entry;
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
|
||||
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::{cmp, iter};
|
||||
|
@ -24,7 +24,7 @@ pub struct HoneyBadger<T, N: Eq + Hash + Ord + Clone> {
|
|||
/// This node's ID.
|
||||
id: N,
|
||||
/// The set of all node IDs of the participants (including ourselves).
|
||||
all_uids: HashSet<N>,
|
||||
all_uids: BTreeSet<N>,
|
||||
/// The target number of transactions to be included in each batch.
|
||||
// TODO: Do experiments and recommend a batch size. It should be proportional to
|
||||
// `num_nodes * num_nodes * log(num_nodes)`.
|
||||
|
@ -90,7 +90,7 @@ where
|
|||
I: IntoIterator<Item = N>,
|
||||
TI: IntoIterator<Item = T>,
|
||||
{
|
||||
let all_uids: HashSet<N> = all_uids_iter.into_iter().collect();
|
||||
let all_uids: BTreeSet<N> = all_uids_iter.into_iter().collect();
|
||||
if !all_uids.contains(&id) {
|
||||
return Err(Error::OwnIdMissing);
|
||||
}
|
||||
|
@ -217,7 +217,7 @@ where
|
|||
}
|
||||
|
||||
/// Returns the output of the current epoch's `CommonSubset` instance, if any.
|
||||
fn take_current_output(&mut self) -> Option<HashMap<N, Vec<u8>>> {
|
||||
fn take_current_output(&mut self) -> Option<BTreeMap<N, Vec<u8>>> {
|
||||
self.common_subsets
|
||||
.get_mut(&self.epoch)
|
||||
.and_then(CommonSubset::next_output)
|
||||
|
|
|
@ -1,187 +1,79 @@
|
|||
//! Integration tests of the Asynchronous Common Subset protocol.
|
||||
|
||||
extern crate env_logger;
|
||||
extern crate hbbft;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate env_logger;
|
||||
extern crate rand;
|
||||
|
||||
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
|
||||
mod network;
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use hbbft::common_subset;
|
||||
use hbbft::common_subset::CommonSubset;
|
||||
use hbbft::messaging::{DistAlgorithm, Target, TargetedMessage};
|
||||
use hbbft::messaging::DistAlgorithm;
|
||||
|
||||
use network::{Adversary, MessageScheduler, NodeUid, SilentAdversary, TestNetwork};
|
||||
|
||||
type ProposedValue = Vec<u8>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
struct NodeUid(usize);
|
||||
fn test_common_subset<A: Adversary<CommonSubset<NodeUid>>>(
|
||||
mut network: TestNetwork<A, CommonSubset<NodeUid>>,
|
||||
inputs: &BTreeMap<NodeUid, ProposedValue>,
|
||||
) {
|
||||
let ids: Vec<NodeUid> = network.nodes.keys().cloned().collect();
|
||||
let mut decided_nodes: BTreeSet<NodeUid> = BTreeSet::new();
|
||||
|
||||
/// The queue of messages of a particular Common Subset instance received by a node or output from a
|
||||
/// Common Subset instance.
|
||||
type MessageQueue = VecDeque<TargetedMessage<common_subset::Message<NodeUid>, NodeUid>>;
|
||||
|
||||
struct TestNode {
|
||||
/// Sender ID.
|
||||
id: NodeUid,
|
||||
/// The Common Subset algorithm.
|
||||
cs: CommonSubset<NodeUid>,
|
||||
/// Queue of tuples of a sender ID and a message.
|
||||
queue: VecDeque<(NodeUid, common_subset::Message<NodeUid>)>,
|
||||
/// The output of the Common Subset algorithm, if there is one.
|
||||
decision: Option<HashMap<NodeUid, ProposedValue>>,
|
||||
}
|
||||
|
||||
impl TestNode {
|
||||
fn new(id: NodeUid, cs: CommonSubset<NodeUid>) -> TestNode {
|
||||
TestNode {
|
||||
id,
|
||||
cs,
|
||||
queue: VecDeque::new(),
|
||||
decision: None,
|
||||
for id in ids {
|
||||
if let Some(value) = inputs.get(&id) {
|
||||
network.input(id, value.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_message(&mut self) -> (Option<HashMap<NodeUid, Vec<u8>>>, MessageQueue) {
|
||||
let (sender_id, message) = self.queue
|
||||
.pop_front()
|
||||
.expect("popping a message off the queue");
|
||||
self.cs
|
||||
.handle_message(&sender_id, message)
|
||||
.expect("handling a Common Subset message");
|
||||
let output = self.cs.next_output();
|
||||
let messages = self.cs.message_iter().collect();
|
||||
debug!("{:?} produced messages: {:?}", self.id, messages);
|
||||
if let Some(ref decision) = output {
|
||||
self.decision = Some(decision.clone());
|
||||
}
|
||||
(output, messages)
|
||||
}
|
||||
}
|
||||
// Terminate when all good nodes do.
|
||||
while network
|
||||
.nodes
|
||||
.values()
|
||||
.any(|node| network.adv_nodes.contains(&node.algo.our_id()) || node.algo.terminated())
|
||||
{
|
||||
let id = network.step();
|
||||
if let Some(output) = network.nodes[&id].outputs().iter().next() {
|
||||
assert_eq!(inputs, output);
|
||||
debug!("Node {:?} decided: {:?}", id, output);
|
||||
|
||||
struct TestNetwork {
|
||||
nodes: BTreeMap<NodeUid, TestNode>,
|
||||
/// The next node to handle a message in its queue.
|
||||
scheduled_node_id: NodeUid,
|
||||
}
|
||||
|
||||
impl TestNetwork {
|
||||
fn new(all_ids: &HashSet<NodeUid>) -> TestNetwork {
|
||||
let num_nodes = all_ids.len();
|
||||
// Make a node with an Agreement instance associated with the proposer node 0.
|
||||
let make_node = |id: NodeUid| {
|
||||
let cs = CommonSubset::new(id, all_ids).expect("Node creation");
|
||||
(id, TestNode::new(id, cs))
|
||||
};
|
||||
TestNetwork {
|
||||
nodes: (0..num_nodes).map(NodeUid).map(make_node).collect(),
|
||||
scheduled_node_id: NodeUid(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_messages(&mut self, sender_id: NodeUid, messages: MessageQueue) {
|
||||
for message in messages {
|
||||
match message {
|
||||
TargetedMessage {
|
||||
target: Target::Node(id),
|
||||
message,
|
||||
} => {
|
||||
let node = self.nodes.get_mut(&id).expect("finding recipient node");
|
||||
node.queue.push_back((sender_id, message));
|
||||
}
|
||||
TargetedMessage {
|
||||
target: Target::All,
|
||||
message,
|
||||
} => {
|
||||
// Multicast the message to other nodes.
|
||||
let _: Vec<()> = self.nodes
|
||||
.iter_mut()
|
||||
.filter(|(id, _)| **id != sender_id)
|
||||
.map(|(_, node)| node.queue.push_back((sender_id, message.clone())))
|
||||
.collect();
|
||||
}
|
||||
// Test uniqueness of output of the good nodes.
|
||||
if !network.adv_nodes.contains(&id) {
|
||||
assert!(!decided_nodes.insert(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gets a node for receiving a message and picks the next node with a
|
||||
// non-empty message queue in a cyclic order.
|
||||
fn pick_node(&mut self) -> NodeUid {
|
||||
let id = self.scheduled_node_id;
|
||||
// Try a node with a higher ID for fairness.
|
||||
if let Some(next_id) = self.nodes
|
||||
.iter()
|
||||
.find(|(&next_id, node)| id < next_id && !node.queue.is_empty())
|
||||
.map(|(id, _)| *id)
|
||||
{
|
||||
self.scheduled_node_id = next_id;
|
||||
} else {
|
||||
// Fall back to nodes up to the currently scheduled ID.
|
||||
self.scheduled_node_id = self.nodes
|
||||
.iter()
|
||||
.find(|(&next_id, node)| id >= next_id && !node.queue.is_empty())
|
||||
.map(|(id, _)| *id)
|
||||
.expect("no more messages in any node's queue")
|
||||
}
|
||||
debug!("Picked node {:?}", self.scheduled_node_id);
|
||||
id
|
||||
}
|
||||
|
||||
fn step(&mut self) -> (NodeUid, Option<HashMap<NodeUid, ProposedValue>>) {
|
||||
let sender_id = self.pick_node();
|
||||
let (output, messages) = self.nodes.get_mut(&sender_id).unwrap().handle_message();
|
||||
self.dispatch_messages(sender_id, messages);
|
||||
(sender_id, output)
|
||||
}
|
||||
|
||||
/// Make Node 0 propose a value.
|
||||
fn send_proposed_value(&mut self, sender_id: NodeUid, value: ProposedValue) {
|
||||
let messages = {
|
||||
let cs = &mut self.nodes.get_mut(&sender_id).unwrap().cs;
|
||||
cs.send_proposed_value(value).expect("send proposed value");
|
||||
cs.message_iter().collect()
|
||||
};
|
||||
self.dispatch_messages(sender_id, messages);
|
||||
}
|
||||
}
|
||||
|
||||
fn test_common_subset(mut network: TestNetwork) -> BTreeMap<NodeUid, TestNode> {
|
||||
fn new_network<A: Adversary<CommonSubset<NodeUid>>>(
|
||||
good_num: usize,
|
||||
bad_num: usize,
|
||||
adversary: A,
|
||||
) -> TestNetwork<A, CommonSubset<NodeUid>> {
|
||||
// This returns an error in all but the first test.
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
// Pick the first node with a non-empty queue.
|
||||
network.pick_node();
|
||||
|
||||
while network.nodes.values().any(|node| !node.cs.terminated()) {
|
||||
let (NodeUid(id), output) = network.step();
|
||||
if let Some(decision) = output {
|
||||
debug!("Node {} output {:?}", id, decision);
|
||||
}
|
||||
}
|
||||
network.nodes
|
||||
let new_common_subset = |id, all_ids: BTreeSet<_>| {
|
||||
CommonSubset::new(id, &all_ids).expect("new Common Subset instance")
|
||||
};
|
||||
TestNetwork::new(good_num, bad_num, adversary, new_common_subset)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_common_subset_3_out_of_4_nodes_propose() {
|
||||
let proposed_value = Vec::from("Fake news");
|
||||
let all_ids: HashSet<NodeUid> = (0..4).map(NodeUid).collect();
|
||||
let mut network = TestNetwork::new(&all_ids);
|
||||
|
||||
let proposing_ids: HashSet<NodeUid> = (0..3).map(NodeUid).collect();
|
||||
let expected_node_decision: HashMap<NodeUid, ProposedValue> = proposing_ids
|
||||
let proposing_ids: BTreeSet<NodeUid> = (0..3).map(NodeUid).collect();
|
||||
let proposals: BTreeMap<NodeUid, ProposedValue> = proposing_ids
|
||||
.iter()
|
||||
.map(|id| (*id, proposed_value.clone()))
|
||||
.collect();
|
||||
|
||||
// Nodes propose values.
|
||||
let _: Vec<()> = proposing_ids
|
||||
.iter()
|
||||
.map(|id| network.send_proposed_value(*id, proposed_value.clone()))
|
||||
.collect();
|
||||
|
||||
let nodes = test_common_subset(network);
|
||||
|
||||
for node in nodes.values() {
|
||||
assert_eq!(node.decision, Some(expected_node_decision.clone()));
|
||||
}
|
||||
let adversary = SilentAdversary::new(MessageScheduler::First);
|
||||
let network = new_network(3, 1, adversary);
|
||||
test_common_subset(network, &proposals);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -193,20 +85,12 @@ fn test_common_subset_5_nodes_different_proposed_values() {
|
|||
Vec::from("Delta"),
|
||||
Vec::from("Echo"),
|
||||
];
|
||||
let all_ids: HashSet<NodeUid> = (0..5).map(NodeUid).collect();
|
||||
let mut network = TestNetwork::new(&all_ids);
|
||||
let expected_node_decisions: HashMap<NodeUid, ProposedValue> =
|
||||
all_ids.into_iter().zip(proposed_values).collect();
|
||||
|
||||
// Nodes propose their values.
|
||||
let _: Vec<()> = expected_node_decisions
|
||||
.iter()
|
||||
.map(|(id, value)| network.send_proposed_value(*id, value.clone()))
|
||||
let proposals: BTreeMap<NodeUid, ProposedValue> = (0..5)
|
||||
.into_iter()
|
||||
.map(NodeUid)
|
||||
.zip(proposed_values)
|
||||
.collect();
|
||||
|
||||
let nodes = test_common_subset(network);
|
||||
|
||||
for node in nodes.values() {
|
||||
assert_eq!(node.decision, Some(expected_node_decisions.clone()));
|
||||
}
|
||||
let adversary = SilentAdversary::new(MessageScheduler::Random);
|
||||
let network = new_network(5, 0, adversary);
|
||||
test_common_subset(network, &proposals);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ pub struct TestNode<D: DistAlgorithm> {
|
|||
/// This node's own ID.
|
||||
id: D::NodeUid,
|
||||
/// The instance of the broadcast algorithm.
|
||||
algo: D,
|
||||
pub algo: D,
|
||||
/// Incoming messages from other nodes that this node has not yet handled.
|
||||
pub queue: VecDeque<(D::NodeUid, D::Message)>,
|
||||
/// The values this node has output so far.
|
||||
|
@ -136,7 +136,7 @@ impl<D: DistAlgorithm> Adversary<D> for SilentAdversary {
|
|||
/// A collection of `TestNode`s representing a network.
|
||||
pub struct TestNetwork<A: Adversary<D>, D: DistAlgorithm> {
|
||||
pub nodes: BTreeMap<D::NodeUid, TestNode<D>>,
|
||||
adv_nodes: BTreeSet<D::NodeUid>,
|
||||
pub adv_nodes: BTreeSet<D::NodeUid>,
|
||||
adversary: A,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue