mirror of https://github.com/poanetwork/hbbft.git
Extend tests and fix CommonSubset.
Verify termination and more outputs. `CommonSubset` now instantiates `Agreement` with the correct ID.
This commit is contained in:
parent
8a406dd154
commit
b8a2463d1c
|
@ -5,6 +5,7 @@ authors = ["Vladimir Komendantskiy <komendantsky@gmail.com>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bincode = "1.0.0"
|
bincode = "1.0.0"
|
||||||
|
derive_deref = "1.0.1"
|
||||||
env_logger = "0.5.10"
|
env_logger = "0.5.10"
|
||||||
itertools = "0.7"
|
itertools = "0.7"
|
||||||
log = "0.4.1"
|
log = "0.4.1"
|
||||||
|
|
|
@ -187,18 +187,11 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> Agreement<NodeUid> {
|
||||||
// where w ∈ bin_values_r
|
// where w ∈ bin_values_r
|
||||||
if bin_values_was_empty {
|
if bin_values_was_empty {
|
||||||
// Send an AUX message at most once per epoch.
|
// Send an AUX message at most once per epoch.
|
||||||
self.messages
|
self.send_aux(b)?;
|
||||||
.push_back(AgreementMessage::Aux(self.epoch, b));
|
|
||||||
// Receive the AUX message locally.
|
|
||||||
let our_uid = self.uid.clone();
|
|
||||||
self.handle_aux(&our_uid, b)?;
|
|
||||||
}
|
}
|
||||||
|
} else if count_bval == self.num_faulty_nodes + 1 && !self.sent_bval.contains(&b) {
|
||||||
self.try_coin()?;
|
// upon receiving BVAL_r(b) messages from f + 1 nodes, if
|
||||||
}
|
// BVAL_r(b) has not been sent, multicast BVAL_r(b)
|
||||||
// upon receiving BVAL_r(b) messages from f + 1 nodes, if
|
|
||||||
// BVAL_r(b) has not been sent, multicast BVAL_r(b)
|
|
||||||
else if count_bval == self.num_faulty_nodes + 1 && !self.sent_bval.contains(&b) {
|
|
||||||
self.send_bval(b)?;
|
self.send_bval(b)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -217,10 +210,16 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> Agreement<NodeUid> {
|
||||||
|
|
||||||
fn handle_aux(&mut self, sender_id: &NodeUid, b: bool) -> Result<(), Error> {
|
fn handle_aux(&mut self, sender_id: &NodeUid, b: bool) -> Result<(), Error> {
|
||||||
self.received_aux.insert(sender_id.clone(), b);
|
self.received_aux.insert(sender_id.clone(), b);
|
||||||
if !self.bin_values.is_empty() {
|
self.try_coin()
|
||||||
self.try_coin()?;
|
}
|
||||||
}
|
|
||||||
Ok(())
|
fn send_aux(&mut self, b: bool) -> Result<(), Error> {
|
||||||
|
// Multicast AUX.
|
||||||
|
self.messages
|
||||||
|
.push_back(AgreementMessage::Aux(self.epoch, b));
|
||||||
|
// Receive the AUX message locally.
|
||||||
|
let our_uid = self.uid.clone();
|
||||||
|
self.handle_aux(&our_uid, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// AUX_r messages such that the set of values carried by those messages is
|
/// AUX_r messages such that the set of values carried by those messages is
|
||||||
|
@ -253,6 +252,9 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> Agreement<NodeUid> {
|
||||||
/// value. The function may start the next epoch. In that case, it also
|
/// value. The function may start the next epoch. In that case, it also
|
||||||
/// returns a message for broadcast.
|
/// returns a message for broadcast.
|
||||||
fn try_coin(&mut self) -> Result<(), Error> {
|
fn try_coin(&mut self) -> Result<(), Error> {
|
||||||
|
if self.bin_values.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let (count_aux, vals) = self.count_aux();
|
let (count_aux, vals) = self.count_aux();
|
||||||
if count_aux < self.num_nodes - self.num_faulty_nodes {
|
if count_aux < self.num_nodes - self.num_faulty_nodes {
|
||||||
// Continue waiting for the (N - f) AUX messages.
|
// Continue waiting for the (N - f) AUX messages.
|
||||||
|
@ -288,8 +290,7 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> Agreement<NodeUid> {
|
||||||
self.estimated = Some(coin);
|
self.estimated = Some(coin);
|
||||||
} else {
|
} else {
|
||||||
// NOTE: `vals` has exactly one element due to `vals.len() == 1`
|
// NOTE: `vals` has exactly one element due to `vals.len() == 1`
|
||||||
let v: Vec<bool> = vals.into_iter().collect();
|
let b = vals.into_iter().next().unwrap();
|
||||||
let b = v[0];
|
|
||||||
self.estimated = Some(b);
|
self.estimated = Some(b);
|
||||||
// Outputting a value is allowed only once.
|
// Outputting a value is allowed only once.
|
||||||
if self.decision.is_none() && b == coin {
|
if self.decision.is_none() && b == coin {
|
||||||
|
|
|
@ -33,6 +33,28 @@ pub enum Message<NodeUid> {
|
||||||
Agreement(NodeUid, AgreementMessage),
|
Agreement(NodeUid, AgreementMessage),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The queue of outgoing messages in a `CommonSubset` instance.
|
||||||
|
#[derive(Deref, DerefMut)]
|
||||||
|
struct MessageQueue<NodeUid>(VecDeque<TargetedMessage<Message<NodeUid>, NodeUid>>);
|
||||||
|
|
||||||
|
impl<NodeUid: Clone + Debug + Eq + Hash + Ord> MessageQueue<NodeUid> {
|
||||||
|
/// Appends to the queue the messages from `agr`, wrapped with `proposer_id`.
|
||||||
|
fn extend_agreement(&mut self, proposer_id: &NodeUid, agr: &mut Agreement<NodeUid>) {
|
||||||
|
let convert = |msg: TargetedMessage<AgreementMessage, NodeUid>| {
|
||||||
|
msg.map(|a_msg| Message::Agreement(proposer_id.clone(), a_msg))
|
||||||
|
};
|
||||||
|
self.extend(agr.message_iter().map(convert));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends to the queue the messages from `bc`, wrapped with `proposer_id`.
|
||||||
|
fn extend_broadcast(&mut self, proposer_id: &NodeUid, bc: &mut Broadcast<NodeUid>) {
|
||||||
|
let convert = |msg: TargetedMessage<BroadcastMessage, NodeUid>| {
|
||||||
|
msg.map(|b_msg| Message::Broadcast(proposer_id.clone(), b_msg))
|
||||||
|
};
|
||||||
|
self.extend(bc.message_iter().map(convert));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Asynchronous Common Subset algorithm instance
|
/// Asynchronous Common Subset algorithm instance
|
||||||
///
|
///
|
||||||
/// The Asynchronous Common Subset protocol assumes a network of `N` nodes that send signed
|
/// The Asynchronous Common Subset protocol assumes a network of `N` nodes that send signed
|
||||||
|
@ -64,8 +86,9 @@ pub struct CommonSubset<NodeUid: Eq + Hash + Ord> {
|
||||||
agreement_instances: BTreeMap<NodeUid, Agreement<NodeUid>>,
|
agreement_instances: BTreeMap<NodeUid, Agreement<NodeUid>>,
|
||||||
broadcast_results: BTreeMap<NodeUid, ProposedValue>,
|
broadcast_results: BTreeMap<NodeUid, ProposedValue>,
|
||||||
agreement_results: BTreeMap<NodeUid, bool>,
|
agreement_results: BTreeMap<NodeUid, bool>,
|
||||||
messages: VecDeque<TargetedMessage<Message<NodeUid>, NodeUid>>,
|
messages: MessageQueue<NodeUid>,
|
||||||
output: Option<BTreeMap<NodeUid, ProposedValue>>,
|
output: Option<BTreeMap<NodeUid, ProposedValue>>,
|
||||||
|
decided: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<NodeUid: Clone + Debug + Eq + Hash + Ord> DistAlgorithm for CommonSubset<NodeUid> {
|
impl<NodeUid: Clone + Debug + Eq + Hash + Ord> DistAlgorithm for CommonSubset<NodeUid> {
|
||||||
|
@ -86,7 +109,7 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> DistAlgorithm for CommonSubset<No
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
match message {
|
match message {
|
||||||
Message::Broadcast(p_id, b_msg) => self.handle_broadcast(sender_id, &p_id, b_msg),
|
Message::Broadcast(p_id, b_msg) => self.handle_broadcast(sender_id, &p_id, b_msg),
|
||||||
Message::Agreement(p_id, a_msg) => self.handle_agreement(sender_id, &p_id, &a_msg),
|
Message::Agreement(p_id, a_msg) => self.handle_agreement(sender_id, &p_id, a_msg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,10 +122,6 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> DistAlgorithm for CommonSubset<No
|
||||||
}
|
}
|
||||||
|
|
||||||
fn terminated(&self) -> bool {
|
fn terminated(&self) -> bool {
|
||||||
debug!(
|
|
||||||
"Termination check. Terminated Agreement instances: {:?}",
|
|
||||||
self.agreement_instances.values().all(Agreement::terminated)
|
|
||||||
);
|
|
||||||
self.messages.is_empty() && self.agreement_instances.values().all(Agreement::terminated)
|
self.messages.is_empty() && self.agreement_instances.values().all(Agreement::terminated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +151,7 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> CommonSubset<NodeUid> {
|
||||||
// Create all agreement instances.
|
// Create all agreement instances.
|
||||||
let mut agreement_instances: BTreeMap<NodeUid, Agreement<NodeUid>> = BTreeMap::new();
|
let mut agreement_instances: BTreeMap<NodeUid, Agreement<NodeUid>> = BTreeMap::new();
|
||||||
for uid0 in all_uids {
|
for uid0 in all_uids {
|
||||||
agreement_instances.insert(uid0.clone(), Agreement::new(uid0.clone(), num_nodes));
|
agreement_instances.insert(uid0.clone(), Agreement::new(uid.clone(), num_nodes));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(CommonSubset {
|
Ok(CommonSubset {
|
||||||
|
@ -143,47 +162,18 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> CommonSubset<NodeUid> {
|
||||||
agreement_instances,
|
agreement_instances,
|
||||||
broadcast_results: BTreeMap::new(),
|
broadcast_results: BTreeMap::new(),
|
||||||
agreement_results: BTreeMap::new(),
|
agreement_results: BTreeMap::new(),
|
||||||
messages: VecDeque::new(),
|
messages: MessageQueue(VecDeque::new()),
|
||||||
output: None,
|
output: None,
|
||||||
|
decided: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Common Subset input message handler. It receives a value for broadcast
|
/// Common Subset input message handler. It receives a value for broadcast
|
||||||
/// and redirects it to the corresponding broadcast instance.
|
/// and redirects it to the corresponding broadcast instance.
|
||||||
pub fn send_proposed_value(&mut self, value: ProposedValue) -> Result<(), Error> {
|
pub fn send_proposed_value(&mut self, value: ProposedValue) -> Result<(), Error> {
|
||||||
|
let uid = self.uid.clone();
|
||||||
// Upon receiving input v_i , input v_i to RBC_i. See Figure 2.
|
// Upon receiving input v_i , input v_i to RBC_i. See Figure 2.
|
||||||
if let Some(instance) = self.broadcast_instances.get_mut(&self.uid) {
|
self.process_broadcast(&uid, |bc| bc.input(value))
|
||||||
instance.input(value)?;
|
|
||||||
let uid = self.uid.clone();
|
|
||||||
self.messages.extend(
|
|
||||||
instance
|
|
||||||
.message_iter()
|
|
||||||
.map(|msg| msg.map(|b_msg| Message::Broadcast(uid.clone(), b_msg))),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Err(Error::NoSuchBroadcastInstance);
|
|
||||||
}
|
|
||||||
self.try_agreement_completion();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Upon delivery of v_j from RBC_j, if input has not yet been provided to
|
|
||||||
/// BA_j, then provide input 1 to BA_j. See Figure 11.
|
|
||||||
fn on_broadcast_result(&mut self, uid: &NodeUid) -> Result<(), Error> {
|
|
||||||
if let Some(agreement_instance) = self.agreement_instances.get_mut(&uid) {
|
|
||||||
if agreement_instance.accepts_input() {
|
|
||||||
agreement_instance.set_input(true)?;
|
|
||||||
self.messages.extend(
|
|
||||||
agreement_instance
|
|
||||||
.message_iter()
|
|
||||||
.map(|msg| msg.map(|a_msg| Message::Agreement(uid.clone(), a_msg))),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Error::NoSuchBroadcastInstance);
|
|
||||||
}
|
|
||||||
self.try_agreement_completion();
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Receives a broadcast message from a remote node `sender_id` concerning a
|
/// Receives a broadcast message from a remote node `sender_id` concerning a
|
||||||
|
@ -194,23 +184,7 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> CommonSubset<NodeUid> {
|
||||||
proposer_id: &NodeUid,
|
proposer_id: &NodeUid,
|
||||||
bmessage: BroadcastMessage,
|
bmessage: BroadcastMessage,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let value;
|
self.process_broadcast(proposer_id, |bc| bc.handle_message(sender_id, bmessage))
|
||||||
if let Some(broadcast_instance) = self.broadcast_instances.get_mut(proposer_id) {
|
|
||||||
broadcast_instance.handle_message(sender_id, bmessage)?;
|
|
||||||
self.messages.extend(
|
|
||||||
broadcast_instance
|
|
||||||
.message_iter()
|
|
||||||
.map(|msg| msg.map(|b_msg| Message::Broadcast(proposer_id.clone(), b_msg))),
|
|
||||||
);
|
|
||||||
value = match broadcast_instance.next_output() {
|
|
||||||
None => return Ok(()),
|
|
||||||
Some(result) => result,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return Err(Error::NoSuchBroadcastInstance);
|
|
||||||
}
|
|
||||||
self.broadcast_results.insert(proposer_id.clone(), value);
|
|
||||||
self.on_broadcast_result(proposer_id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Receives an agreement message from a remote node `sender_id` concerning
|
/// Receives an agreement message from a remote node `sender_id` concerning
|
||||||
|
@ -219,69 +193,90 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> CommonSubset<NodeUid> {
|
||||||
&mut self,
|
&mut self,
|
||||||
sender_id: &NodeUid,
|
sender_id: &NodeUid,
|
||||||
proposer_id: &NodeUid,
|
proposer_id: &NodeUid,
|
||||||
amessage: &AgreementMessage,
|
amessage: AgreementMessage,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let input_result;
|
|
||||||
// Send the message to the local instance of Agreement
|
// Send the message to the local instance of Agreement
|
||||||
if let Some(agreement_instance) = self.agreement_instances.get_mut(proposer_id) {
|
self.process_agreement(proposer_id, |agreement| {
|
||||||
// Optional output of agreement and outgoing agreement
|
agreement.handle_message(sender_id, amessage)
|
||||||
// messages to remote nodes.
|
})
|
||||||
if agreement_instance.terminated() {
|
}
|
||||||
// This instance has terminated and does not accept input.
|
|
||||||
|
/// Upon delivery of v_j from RBC_j, if input has not yet been provided to
|
||||||
|
/// BA_j, then provide input 1 to BA_j. See Figure 11.
|
||||||
|
fn process_broadcast<F>(&mut self, proposer_id: &NodeUid, f: F) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Broadcast<NodeUid>) -> Result<(), broadcast::Error>,
|
||||||
|
{
|
||||||
|
let value = {
|
||||||
|
let broadcast = self.broadcast_instances
|
||||||
|
.get_mut(proposer_id)
|
||||||
|
.ok_or(Error::NoSuchBroadcastInstance)?;
|
||||||
|
f(broadcast)?;
|
||||||
|
self.messages.extend_broadcast(&proposer_id, broadcast);
|
||||||
|
if let Some(output) = broadcast.next_output() {
|
||||||
|
output
|
||||||
|
} else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
// Send the message to the agreement instance.
|
};
|
||||||
agreement_instance.handle_message(sender_id, amessage.clone())?;
|
self.broadcast_results.insert(proposer_id.clone(), value);
|
||||||
self.messages.extend(
|
self.process_agreement(proposer_id, |agreement| {
|
||||||
agreement_instance
|
if agreement.accepts_input() {
|
||||||
.message_iter()
|
agreement.set_input(true)
|
||||||
.map(|msg| msg.map(|a_msg| Message::Agreement(proposer_id.clone(), a_msg))),
|
} else {
|
||||||
);
|
Ok(())
|
||||||
input_result = agreement_instance.next_output();
|
}
|
||||||
} else {
|
})
|
||||||
debug!("Proposer {:?} does not exist.", proposer_id);
|
|
||||||
return Err(Error::NoSuchAgreementInstance);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(output) = input_result {
|
|
||||||
// Process Agreement outputs.
|
|
||||||
self.on_agreement_output(proposer_id, output)?;
|
|
||||||
}
|
|
||||||
// Check whether Agreement has completed.
|
|
||||||
self.try_agreement_completion();
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Callback to be invoked on receipt of the decision value of the Agreement
|
/// Callback to be invoked on receipt of the decision value of the Agreement
|
||||||
/// instance `uid`.
|
/// instance `uid`.
|
||||||
fn on_agreement_output(
|
fn process_agreement<F>(&mut self, proposer_id: &NodeUid, f: F) -> Result<(), Error>
|
||||||
&mut self,
|
where
|
||||||
element_proposer_id: &NodeUid,
|
F: FnOnce(&mut Agreement<NodeUid>) -> Result<(), agreement::Error>,
|
||||||
result: bool,
|
{
|
||||||
) -> Result<(), Error> {
|
let value = {
|
||||||
self.agreement_results
|
let agreement = self.agreement_instances
|
||||||
.insert(element_proposer_id.clone(), result);
|
.get_mut(proposer_id)
|
||||||
|
.ok_or(Error::NoSuchAgreementInstance)?;
|
||||||
|
if agreement.terminated() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
f(agreement);
|
||||||
|
self.messages.extend_agreement(proposer_id, agreement);
|
||||||
|
if let Some(output) = agreement.next_output() {
|
||||||
|
output
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if self.agreement_results
|
||||||
|
.insert(proposer_id.clone(), value)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
return Err(Error::MultipleAgreementResults);
|
||||||
|
}
|
||||||
debug!(
|
debug!(
|
||||||
"{:?} Updated Agreement results: {:?}",
|
"{:?} Updated Agreement results: {:?}",
|
||||||
self.uid, self.agreement_results
|
self.uid, self.agreement_results
|
||||||
);
|
);
|
||||||
if !result || self.count_true() < self.num_nodes - self.num_faulty_nodes {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upon delivery of value 1 from at least N − f instances of BA, provide
|
if value && self.count_true() == self.num_nodes - self.num_faulty_nodes {
|
||||||
// input 0 to each instance of BA that has not yet been provided input.
|
// Upon delivery of value 1 from at least N − f instances of BA, provide
|
||||||
for agreement_instance in self.agreement_instances.values_mut() {
|
// input 0 to each instance of BA that has not yet been provided input.
|
||||||
if agreement_instance.accepts_input() {
|
for (uid, agreement) in &mut self.agreement_instances {
|
||||||
agreement_instance.set_input(false)?;
|
if agreement.accepts_input() {
|
||||||
let uid = agreement_instance.our_id().clone();
|
agreement.set_input(false)?;
|
||||||
self.messages.extend(
|
self.messages.extend_agreement(uid, agreement);
|
||||||
agreement_instance
|
if let Some(output) = agreement.next_output() {
|
||||||
.message_iter()
|
if self.agreement_results.insert(uid.clone(), output).is_some() {
|
||||||
.map(|msg| msg.map(|a_msg| Message::Agreement(uid.clone(), a_msg))),
|
return Err(Error::MultipleAgreementResults);
|
||||||
);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.try_agreement_completion();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,6 +286,9 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> CommonSubset<NodeUid> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_agreement_completion(&mut self) {
|
fn try_agreement_completion(&mut self) {
|
||||||
|
if self.decided || self.count_true() < self.num_nodes - self.num_faulty_nodes {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Once all instances of BA have completed, let C ⊂ [1..N] be
|
// Once all instances of BA have completed, let C ⊂ [1..N] be
|
||||||
// the indexes of each BA that delivered 1. Wait for the output
|
// the indexes of each BA that delivered 1. Wait for the output
|
||||||
// v_j for each RBC_j such that j∈C. Finally output ∪ j∈C v_j.
|
// v_j for each RBC_j such that j∈C. Finally output ∪ j∈C v_j.
|
||||||
|
@ -318,6 +316,7 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> CommonSubset<NodeUid> {
|
||||||
for (uid, result) in &broadcast_results {
|
for (uid, result) in &broadcast_results {
|
||||||
debug!(" {:?} → {:?}", uid, HexBytes(&result));
|
debug!(" {:?} → {:?}", uid, HexBytes(&result));
|
||||||
}
|
}
|
||||||
|
self.decided = true;
|
||||||
self.output = Some(broadcast_results)
|
self.output = Some(broadcast_results)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -329,6 +328,7 @@ pub enum Error {
|
||||||
NotImplemented,
|
NotImplemented,
|
||||||
NoSuchBroadcastInstance,
|
NoSuchBroadcastInstance,
|
||||||
NoSuchAgreementInstance,
|
NoSuchAgreementInstance,
|
||||||
|
MultipleAgreementResults,
|
||||||
Broadcast(broadcast::Error),
|
Broadcast(broadcast::Error),
|
||||||
Agreement(agreement::Error),
|
Agreement(agreement::Error),
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub struct HoneyBadger<T, N: Eq + Hash + Ord + Clone> {
|
||||||
// `num_nodes * num_nodes * log(num_nodes)`.
|
// `num_nodes * num_nodes * log(num_nodes)`.
|
||||||
batch_size: usize,
|
batch_size: usize,
|
||||||
/// The messages that need to be sent to other nodes.
|
/// The messages that need to be sent to other nodes.
|
||||||
messages: VecDeque<TargetedMessage<Message<N>, N>>,
|
messages: MessageQueue<N>,
|
||||||
/// The outputs from completed epochs.
|
/// The outputs from completed epochs.
|
||||||
output: VecDeque<Batch<T>>,
|
output: VecDeque<Batch<T>>,
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ where
|
||||||
id,
|
id,
|
||||||
batch_size,
|
batch_size,
|
||||||
all_uids,
|
all_uids,
|
||||||
messages: VecDeque::new(),
|
messages: MessageQueue(VecDeque::new()),
|
||||||
output: VecDeque::new(),
|
output: VecDeque::new(),
|
||||||
};
|
};
|
||||||
honey_badger.propose()?;
|
honey_badger.propose()?;
|
||||||
|
@ -124,11 +124,7 @@ where
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cs.input(proposal)?;
|
cs.input(proposal)?;
|
||||||
for targeted_msg in cs.message_iter() {
|
self.messages.extend_with_epoch(self.epoch, cs);
|
||||||
let epoch = self.epoch;
|
|
||||||
let msg = targeted_msg.map(|cs_msg| Message::CommonSubset(epoch, cs_msg));
|
|
||||||
self.messages.push_back(msg);
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,10 +166,7 @@ where
|
||||||
};
|
};
|
||||||
// Handle the message and put the outgoing messages into the queue.
|
// Handle the message and put the outgoing messages into the queue.
|
||||||
cs.handle_message(sender_id, message)?;
|
cs.handle_message(sender_id, message)?;
|
||||||
for targeted_msg in cs.message_iter() {
|
self.messages.extend_with_epoch(epoch, cs);
|
||||||
let msg = targeted_msg.map(|cs_msg| Message::CommonSubset(epoch, cs_msg));
|
|
||||||
self.messages.push_back(msg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// If this is the current epoch, the message could cause a new output.
|
// If this is the current epoch, the message could cause a new output.
|
||||||
if epoch == self.epoch {
|
if epoch == self.epoch {
|
||||||
|
@ -253,6 +246,20 @@ pub enum Message<N> {
|
||||||
// TODO: Decryption share.
|
// TODO: Decryption share.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The queue of outgoing messages in a `HoneyBadger` instance.
|
||||||
|
#[derive(Deref, DerefMut)]
|
||||||
|
struct MessageQueue<N>(VecDeque<TargetedMessage<Message<N>, N>>);
|
||||||
|
|
||||||
|
impl<N: Clone + Debug + Eq + Hash + Ord> MessageQueue<N> {
|
||||||
|
/// Appends to the queue the messages from `cs`, wrapped with `epoch`.
|
||||||
|
fn extend_with_epoch(&mut self, epoch: u64, cs: &mut CommonSubset<N>) {
|
||||||
|
let convert = |msg: TargetedMessage<common_subset::Message<N>, N>| {
|
||||||
|
msg.map(|cs_msg| Message::CommonSubset(epoch, cs_msg))
|
||||||
|
};
|
||||||
|
self.extend(cs.message_iter().map(convert));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A Honey Badger error.
|
/// A Honey Badger error.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
#![feature(optin_builtin_traits)]
|
#![feature(optin_builtin_traits)]
|
||||||
|
|
||||||
extern crate bincode;
|
extern crate bincode;
|
||||||
|
#[macro_use(Deref, DerefMut)]
|
||||||
|
extern crate derive_deref;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
extern crate itertools;
|
extern crate itertools;
|
||||||
|
|
|
@ -27,7 +27,7 @@ use std::iter;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
use hbbft::agreement::Agreement;
|
use hbbft::agreement::Agreement;
|
||||||
use network::{Adversary, MessageScheduler, NodeUid, SilentAdversary, TestNetwork};
|
use network::{Adversary, MessageScheduler, NodeUid, SilentAdversary, TestNetwork, TestNode};
|
||||||
|
|
||||||
fn test_agreement<A: Adversary<Agreement<NodeUid>>>(
|
fn test_agreement<A: Adversary<Agreement<NodeUid>>>(
|
||||||
mut network: TestNetwork<A, Agreement<NodeUid>>,
|
mut network: TestNetwork<A, Agreement<NodeUid>>,
|
||||||
|
@ -39,13 +39,17 @@ fn test_agreement<A: Adversary<Agreement<NodeUid>>>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle messages in random order until all nodes have output the proposed value.
|
// Handle messages in random order until all nodes have output the proposed value.
|
||||||
while network.nodes.values().any(|node| node.outputs().is_empty()) {
|
while !network.nodes.values().all(TestNode::terminated) {
|
||||||
let id = network.step();
|
network.step();
|
||||||
if let Some(&b) = network.nodes[&id].outputs().iter().next() {
|
}
|
||||||
if let Some(expected) = input {
|
// Verify that all instances output the same value.
|
||||||
assert_eq!(expected, b);
|
let mut expected = input;
|
||||||
}
|
for node in network.nodes.values() {
|
||||||
debug!("Node {:?} decided: {}", id, b);
|
if let Some(b) = expected {
|
||||||
|
assert!(iter::once(&b).eq(node.outputs()));
|
||||||
|
} else {
|
||||||
|
assert_eq!(1, node.outputs().len());
|
||||||
|
expected = Some(node.outputs()[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,13 +84,13 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_agreement_random_silent_all_true() {
|
fn test_agreement_random_silent() {
|
||||||
let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::Random);
|
let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::Random);
|
||||||
test_agreement_different_sizes(new_adversary);
|
test_agreement_different_sizes(new_adversary);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_agreement_first_silent_all_true() {
|
fn test_agreement_first_silent() {
|
||||||
let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::First);
|
let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::First);
|
||||||
test_agreement_different_sizes(new_adversary);
|
test_agreement_different_sizes(new_adversary);
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,12 +82,12 @@ fn test_broadcast<A: Adversary<Broadcast<NodeUid>>>(
|
||||||
network.input(NodeUid(0), proposed_value.to_vec());
|
network.input(NodeUid(0), proposed_value.to_vec());
|
||||||
|
|
||||||
// Handle messages in random order until all nodes have output the proposed value.
|
// Handle messages in random order until all nodes have output the proposed value.
|
||||||
while network.nodes.values().any(|node| node.outputs().is_empty()) {
|
while !network.nodes.values().all(TestNode::terminated) {
|
||||||
let id = network.step();
|
network.step();
|
||||||
if !network.nodes[&id].outputs().is_empty() {
|
}
|
||||||
assert_eq!(vec![proposed_value.to_vec()], network.nodes[&id].outputs());
|
// Verify that all instances output the proposed value.
|
||||||
debug!("Node {:?} received", id);
|
for node in network.nodes.values() {
|
||||||
}
|
assert!(iter::once(&proposed_value.to_vec()).eq(node.outputs()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,11 @@ extern crate rand;
|
||||||
mod network;
|
mod network;
|
||||||
|
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
use hbbft::common_subset::CommonSubset;
|
use hbbft::common_subset::CommonSubset;
|
||||||
use hbbft::messaging::DistAlgorithm;
|
|
||||||
|
|
||||||
use network::{Adversary, MessageScheduler, NodeUid, SilentAdversary, TestNetwork};
|
use network::{Adversary, MessageScheduler, NodeUid, SilentAdversary, TestNetwork, TestNode};
|
||||||
|
|
||||||
type ProposedValue = Vec<u8>;
|
type ProposedValue = Vec<u8>;
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ fn test_common_subset<A: Adversary<CommonSubset<NodeUid>>>(
|
||||||
inputs: &BTreeMap<NodeUid, ProposedValue>,
|
inputs: &BTreeMap<NodeUid, ProposedValue>,
|
||||||
) {
|
) {
|
||||||
let ids: Vec<NodeUid> = network.nodes.keys().cloned().collect();
|
let ids: Vec<NodeUid> = network.nodes.keys().cloned().collect();
|
||||||
let mut decided_nodes: BTreeSet<NodeUid> = BTreeSet::new();
|
|
||||||
|
|
||||||
for id in ids {
|
for id in ids {
|
||||||
if let Some(value) = inputs.get(&id) {
|
if let Some(value) = inputs.get(&id) {
|
||||||
|
@ -31,21 +30,23 @@ fn test_common_subset<A: Adversary<CommonSubset<NodeUid>>>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terminate when all good nodes do.
|
// Terminate when all good nodes do.
|
||||||
while network
|
while !network.nodes.values().all(TestNode::terminated) {
|
||||||
.nodes
|
network.step();
|
||||||
.values()
|
}
|
||||||
.any(|node| network.adv_nodes.contains(&node.algo.our_id()) || node.algo.terminated())
|
// Verify that all instances output the same set.
|
||||||
{
|
let mut expected = None;
|
||||||
let id = network.step();
|
for node in network.nodes.values() {
|
||||||
if let Some(output) = network.nodes[&id].outputs().iter().next() {
|
if let Some(output) = expected.as_ref() {
|
||||||
assert_eq!(inputs, output);
|
assert!(iter::once(output).eq(node.outputs()));
|
||||||
debug!("Node {:?} decided: {:?}", id, output);
|
continue;
|
||||||
|
|
||||||
// Test uniqueness of output of the good nodes.
|
|
||||||
if !network.adv_nodes.contains(&id) {
|
|
||||||
assert!(!decided_nodes.insert(id));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
assert_eq!(1, node.outputs().len());
|
||||||
|
expected = Some(node.outputs()[0].clone());
|
||||||
|
}
|
||||||
|
let output = expected.unwrap();
|
||||||
|
assert!(output.len() * 3 > inputs.len() * 2);
|
||||||
|
for (id, value) in output {
|
||||||
|
assert_eq!(inputs[&id], value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,9 +72,7 @@ where
|
||||||
.chain(iter::once(rng.gen_range(6, 10)))
|
.chain(iter::once(rng.gen_range(6, 10)))
|
||||||
.chain(iter::once(rng.gen_range(11, 15)));
|
.chain(iter::once(rng.gen_range(11, 15)));
|
||||||
for size in sizes {
|
for size in sizes {
|
||||||
// TODO: Fix `CommonSubset` to tolerate faulty nodes.
|
let num_faulty_nodes = (size - 1) / 3;
|
||||||
// let num_faulty_nodes = (size - 1) / 3;
|
|
||||||
let num_faulty_nodes = 0;
|
|
||||||
let num_good_nodes = size - num_faulty_nodes;
|
let num_good_nodes = size - num_faulty_nodes;
|
||||||
info!(
|
info!(
|
||||||
"Network size: {} good nodes, {} faulty nodes",
|
"Network size: {} good nodes, {} faulty nodes",
|
||||||
|
|
|
@ -14,7 +14,7 @@ pub struct TestNode<D: DistAlgorithm> {
|
||||||
/// This node's own ID.
|
/// This node's own ID.
|
||||||
id: D::NodeUid,
|
id: D::NodeUid,
|
||||||
/// The instance of the broadcast algorithm.
|
/// The instance of the broadcast algorithm.
|
||||||
pub algo: D,
|
algo: D,
|
||||||
/// Incoming messages from other nodes that this node has not yet handled.
|
/// Incoming messages from other nodes that this node has not yet handled.
|
||||||
pub queue: VecDeque<(D::NodeUid, D::Message)>,
|
pub queue: VecDeque<(D::NodeUid, D::Message)>,
|
||||||
/// The values this node has output so far.
|
/// The values this node has output so far.
|
||||||
|
@ -27,6 +27,12 @@ impl<D: DistAlgorithm> TestNode<D> {
|
||||||
&self.outputs
|
&self.outputs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether the algorithm has terminated.
|
||||||
|
#[allow(unused)] // Not used in all tests.
|
||||||
|
pub fn terminated(&self) -> bool {
|
||||||
|
self.algo.terminated()
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new test node with the given broadcast instance.
|
/// Creates a new test node with the given broadcast instance.
|
||||||
fn new(mut algo: D) -> TestNode<D> {
|
fn new(mut algo: D) -> TestNode<D> {
|
||||||
let outputs = algo.output_iter().collect();
|
let outputs = algo.output_iter().collect();
|
||||||
|
|
Loading…
Reference in New Issue