2018-03-14 17:03:21 -07:00
|
|
|
|
//! Binary Byzantine agreement protocol from a common coin protocol.
|
2018-05-01 10:12:05 -07:00
|
|
|
|
|
2018-05-10 01:57:58 -07:00
|
|
|
|
use itertools::Itertools;
|
2018-05-10 04:09:22 -07:00
|
|
|
|
use std::collections::{BTreeSet, HashMap, VecDeque};
|
2018-05-06 14:39:01 -07:00
|
|
|
|
use std::hash::Hash;
|
|
|
|
|
|
2018-05-09 07:27:31 -07:00
|
|
|
|
/// Type of output from the Agreement message handler. The first component is
|
|
|
|
|
/// the value on which the Agreement has decided, also called "output" in the
|
|
|
|
|
/// HoneyadgerBFT paper. The second component is a queue of messages to be sent
|
|
|
|
|
/// to remote nodes as a result of handling the incomming message.
|
2018-05-08 09:25:57 -07:00
|
|
|
|
type AgreementOutput = (Option<bool>, VecDeque<AgreementMessage>);
|
|
|
|
|
|
|
|
|
|
/// Messages sent during the binary Byzantine agreement stage.
|
2018-05-14 09:30:07 -07:00
|
|
|
|
#[cfg_attr(feature = "serialization-serde", derive(Serialize))]
|
2018-05-08 09:25:57 -07:00
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
|
|
|
pub enum AgreementMessage {
|
|
|
|
|
/// BVAL message with an epoch.
|
|
|
|
|
BVal((u32, bool)),
|
|
|
|
|
/// AUX message with an epoch.
|
|
|
|
|
Aux((u32, bool)),
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-10 04:09:22 -07:00
|
|
|
|
/// Binary Agreement instance.
|
2018-05-06 14:39:01 -07:00
|
|
|
|
pub struct Agreement<NodeUid> {
|
2018-05-09 01:55:34 -07:00
|
|
|
|
/// The UID of the corresponding proposer node.
|
2018-05-06 14:39:01 -07:00
|
|
|
|
uid: NodeUid,
|
|
|
|
|
num_nodes: usize,
|
|
|
|
|
num_faulty_nodes: usize,
|
|
|
|
|
epoch: u32,
|
|
|
|
|
/// Bin values. Reset on every epoch update.
|
|
|
|
|
bin_values: BTreeSet<bool>,
|
|
|
|
|
/// Values received in BVAL messages. Reset on every epoch update.
|
|
|
|
|
received_bval: HashMap<NodeUid, BTreeSet<bool>>,
|
|
|
|
|
/// Sent BVAL values. Reset on every epoch update.
|
|
|
|
|
sent_bval: BTreeSet<bool>,
|
|
|
|
|
/// Values received in AUX messages. Reset on every epoch update.
|
2018-05-09 07:36:02 -07:00
|
|
|
|
received_aux: HashMap<NodeUid, bool>,
|
2018-05-10 04:09:22 -07:00
|
|
|
|
/// The estimate of the decision value in the current epoch.
|
|
|
|
|
estimated: Option<bool>,
|
|
|
|
|
/// The value output by the agreement instance. It is set once to `Some(b)`
|
|
|
|
|
/// and then never changed. That is, no instance of Binary Agreement can
|
|
|
|
|
/// decide on two different values of output.
|
|
|
|
|
output: Option<bool>,
|
2018-05-09 07:27:31 -07:00
|
|
|
|
/// Termination flag. The Agreement instance doesn't terminate immediately
|
|
|
|
|
/// upon deciding on the agreed value. This is done in order to help other
|
|
|
|
|
/// nodes decide despite asynchrony of communication. Once the instance
|
|
|
|
|
/// determines that all the remote nodes have reached agreement, it sets the
|
|
|
|
|
/// `terminated` flag and accepts no more incoming messages.
|
2018-05-06 14:39:01 -07:00
|
|
|
|
terminated: bool,
|
2018-05-01 10:12:05 -07:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-06 14:39:01 -07:00
|
|
|
|
impl<NodeUid: Clone + Eq + Hash> Agreement<NodeUid> {
|
|
|
|
|
pub fn new(uid: NodeUid, num_nodes: usize) -> Self {
|
|
|
|
|
let num_faulty_nodes = (num_nodes - 1) / 3;
|
|
|
|
|
|
2018-05-03 01:05:26 -07:00
|
|
|
|
Agreement {
|
2018-05-06 14:39:01 -07:00
|
|
|
|
uid,
|
|
|
|
|
num_nodes,
|
|
|
|
|
num_faulty_nodes,
|
|
|
|
|
epoch: 0,
|
|
|
|
|
bin_values: BTreeSet::new(),
|
|
|
|
|
received_bval: HashMap::new(),
|
|
|
|
|
sent_bval: BTreeSet::new(),
|
|
|
|
|
received_aux: HashMap::new(),
|
2018-05-10 04:09:22 -07:00
|
|
|
|
estimated: None,
|
|
|
|
|
output: None,
|
2018-05-06 14:39:01 -07:00
|
|
|
|
terminated: false,
|
2018-05-03 01:05:26 -07:00
|
|
|
|
}
|
2018-05-02 06:10:26 -07:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-14 01:20:16 -07:00
|
|
|
|
pub fn our_id(&self) -> &NodeUid {
|
|
|
|
|
&self.uid
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-06 14:39:01 -07:00
|
|
|
|
/// Algorithm has terminated.
|
|
|
|
|
pub fn terminated(&self) -> bool {
|
|
|
|
|
self.terminated
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-10 04:09:22 -07:00
|
|
|
|
/// Sets the input value for agreement.
|
2018-05-09 07:27:31 -07:00
|
|
|
|
pub fn set_input(&mut self, input: bool) -> Result<AgreementMessage, Error> {
|
|
|
|
|
if self.epoch != 0 {
|
|
|
|
|
return Err(Error::InputNotAccepted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set the initial estimated value to the input value.
|
2018-05-10 04:09:22 -07:00
|
|
|
|
self.estimated = Some(input);
|
2018-05-06 14:39:01 -07:00
|
|
|
|
// Receive the BVAL message locally.
|
2018-05-08 09:25:57 -07:00
|
|
|
|
self.received_bval
|
|
|
|
|
.entry(self.uid.clone())
|
|
|
|
|
.or_insert_with(BTreeSet::new)
|
|
|
|
|
.insert(input);
|
2018-05-03 01:05:26 -07:00
|
|
|
|
// Multicast BVAL
|
2018-05-09 07:27:31 -07:00
|
|
|
|
Ok(AgreementMessage::BVal((self.epoch, input)))
|
2018-05-01 10:12:05 -07:00
|
|
|
|
}
|
2018-05-02 03:57:28 -07:00
|
|
|
|
|
2018-05-10 04:09:22 -07:00
|
|
|
|
/// Acceptance check to be performed before setting the input value.
|
|
|
|
|
pub fn accepts_input(&self) -> bool {
|
|
|
|
|
self.epoch == 0 && self.estimated.is_none()
|
2018-05-02 03:57:28 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Receive input from a remote node.
|
2018-05-06 14:39:01 -07:00
|
|
|
|
///
|
|
|
|
|
/// Outputs an optional agreement result and a queue of agreement messages
|
|
|
|
|
/// to remote nodes. There can be up to 2 messages.
|
2018-05-09 07:27:31 -07:00
|
|
|
|
pub fn handle_agreement_message(
|
2018-05-06 14:39:01 -07:00
|
|
|
|
&mut self,
|
2018-05-10 04:09:22 -07:00
|
|
|
|
sender_id: &NodeUid,
|
2018-05-06 14:39:01 -07:00
|
|
|
|
message: &AgreementMessage,
|
2018-05-08 09:25:57 -07:00
|
|
|
|
) -> Result<AgreementOutput, Error> {
|
|
|
|
|
match *message {
|
|
|
|
|
// The algorithm instance has already terminated.
|
|
|
|
|
_ if self.terminated => Err(Error::Terminated),
|
|
|
|
|
|
2018-05-09 07:27:31 -07:00
|
|
|
|
AgreementMessage::BVal((epoch, b)) if epoch == self.epoch => {
|
|
|
|
|
self.handle_bval(sender_id, b)
|
|
|
|
|
}
|
2018-05-08 09:25:57 -07:00
|
|
|
|
|
2018-05-09 07:27:31 -07:00
|
|
|
|
AgreementMessage::Aux((epoch, b)) if epoch == self.epoch => {
|
|
|
|
|
self.handle_aux(sender_id, b)
|
|
|
|
|
}
|
2018-05-08 09:25:57 -07:00
|
|
|
|
|
|
|
|
|
// Epoch does not match. Ignore the message.
|
|
|
|
|
_ => Ok((None, VecDeque::new())),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-10 04:09:22 -07:00
|
|
|
|
fn handle_bval(&mut self, sender_id: &NodeUid, b: bool) -> Result<AgreementOutput, Error> {
|
2018-05-06 14:39:01 -07:00
|
|
|
|
let mut outgoing = VecDeque::new();
|
|
|
|
|
|
2018-05-08 09:25:57 -07:00
|
|
|
|
self.received_bval
|
2018-05-10 04:09:22 -07:00
|
|
|
|
.entry(sender_id.clone())
|
2018-05-08 09:25:57 -07:00
|
|
|
|
.or_insert_with(BTreeSet::new)
|
|
|
|
|
.insert(b);
|
|
|
|
|
let count_bval = self.received_bval
|
|
|
|
|
.values()
|
|
|
|
|
.filter(|values| values.contains(&b))
|
|
|
|
|
.count();
|
|
|
|
|
|
|
|
|
|
// upon receiving BVAL_r(b) messages from 2f + 1 nodes,
|
|
|
|
|
// bin_values_r := bin_values_r ∪ {b}
|
|
|
|
|
if count_bval == 2 * self.num_faulty_nodes + 1 {
|
|
|
|
|
self.bin_values.insert(b);
|
2018-05-07 02:59:14 -07:00
|
|
|
|
|
2018-05-08 09:25:57 -07:00
|
|
|
|
// wait until bin_values_r != 0, then multicast AUX_r(w)
|
|
|
|
|
// where w ∈ bin_values_r
|
|
|
|
|
if self.bin_values.len() == 1 {
|
|
|
|
|
// Send an AUX message at most once per epoch.
|
|
|
|
|
outgoing.push_back(AgreementMessage::Aux((self.epoch, b)));
|
|
|
|
|
// Receive the AUX message locally.
|
2018-05-09 07:36:02 -07:00
|
|
|
|
self.received_aux.insert(self.uid.clone(), b);
|
2018-05-06 14:39:01 -07:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-10 04:44:33 -07:00
|
|
|
|
let (decision, maybe_message) = self.try_coin();
|
|
|
|
|
outgoing.extend(maybe_message);
|
|
|
|
|
Ok((decision, outgoing))
|
2018-05-08 09:25:57 -07:00
|
|
|
|
}
|
|
|
|
|
// 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) {
|
|
|
|
|
outgoing.push_back(AgreementMessage::BVal((self.epoch, b)));
|
|
|
|
|
// Receive the BVAL message locally.
|
|
|
|
|
self.received_bval
|
|
|
|
|
.entry(self.uid.clone())
|
|
|
|
|
.or_insert_with(BTreeSet::new)
|
|
|
|
|
.insert(b);
|
|
|
|
|
Ok((None, outgoing))
|
|
|
|
|
} else {
|
|
|
|
|
Ok((None, outgoing))
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-05-06 14:39:01 -07:00
|
|
|
|
|
2018-05-10 04:09:22 -07:00
|
|
|
|
fn handle_aux(&mut self, sender_id: &NodeUid, b: bool) -> Result<AgreementOutput, Error> {
|
|
|
|
|
self.received_aux.insert(sender_id.clone(), b);
|
2018-05-10 04:33:01 -07:00
|
|
|
|
let mut outgoing = VecDeque::new();
|
2018-05-08 09:25:57 -07:00
|
|
|
|
if !self.bin_values.is_empty() {
|
2018-05-10 04:44:33 -07:00
|
|
|
|
let (decision, maybe_message) = self.try_coin();
|
|
|
|
|
outgoing.extend(maybe_message);
|
|
|
|
|
Ok((decision, outgoing))
|
2018-05-08 09:25:57 -07:00
|
|
|
|
} else {
|
2018-05-10 04:33:01 -07:00
|
|
|
|
Ok((None, outgoing))
|
2018-05-06 14:39:01 -07:00
|
|
|
|
}
|
2018-05-02 03:57:28 -07:00
|
|
|
|
}
|
2018-05-06 14:39:01 -07:00
|
|
|
|
|
|
|
|
|
/// AUX_r messages such that the set of values carried by those messages is
|
|
|
|
|
/// a subset of bin_values_r. Outputs this subset.
|
|
|
|
|
///
|
|
|
|
|
/// FIXME: Clarify whether the values of AUX messages should be the same or
|
|
|
|
|
/// not. It is assumed in `count_aux` that they can differ.
|
2018-05-08 09:25:57 -07:00
|
|
|
|
///
|
|
|
|
|
/// In general, we can't expect every good node to send the same AUX value,
|
|
|
|
|
/// so waiting for N - f agreeing messages would not always terminate. We
|
|
|
|
|
/// can, however, expect every good node to send an AUX value that will
|
|
|
|
|
/// eventually end up in our bin_values.
|
2018-05-06 14:39:01 -07:00
|
|
|
|
fn count_aux(&self) -> (usize, BTreeSet<bool>) {
|
2018-05-10 01:57:58 -07:00
|
|
|
|
let (vals_cnt, vals) = self.received_aux
|
2018-05-09 08:39:45 -07:00
|
|
|
|
.values()
|
2018-05-10 01:57:58 -07:00
|
|
|
|
.filter(|b| self.bin_values.contains(b))
|
|
|
|
|
.tee();
|
|
|
|
|
|
|
|
|
|
(vals_cnt.count(), vals.cloned().collect())
|
2018-05-06 14:39:01 -07:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-08 09:25:57 -07:00
|
|
|
|
/// Waits until at least (N − f) AUX_r messages have been received, such that
|
2018-05-06 14:39:01 -07:00
|
|
|
|
/// the set of values carried by these messages, vals, are a subset of
|
|
|
|
|
/// bin_values_r (note that bin_values_r may continue to change as BVAL_r
|
|
|
|
|
/// messages are received, thus this condition may be triggered upon arrival
|
|
|
|
|
/// of either an AUX_r or a BVAL_r message).
|
|
|
|
|
///
|
2018-05-10 04:09:22 -07:00
|
|
|
|
/// Once the (N - f) messages are received, gets a common coin and uses it
|
2018-05-10 04:44:33 -07:00
|
|
|
|
/// to compute the next decision estimate and outputs the optional decision
|
|
|
|
|
/// value. The function may start the next epoch. In that case, it also
|
2018-05-10 04:33:01 -07:00
|
|
|
|
/// returns a message for broadcast.
|
2018-05-10 04:44:33 -07:00
|
|
|
|
fn try_coin(&mut self) -> (Option<bool>, VecDeque<AgreementMessage>) {
|
2018-05-06 14:39:01 -07:00
|
|
|
|
let (count_aux, vals) = self.count_aux();
|
2018-05-08 09:25:57 -07:00
|
|
|
|
if count_aux < self.num_nodes - self.num_faulty_nodes {
|
|
|
|
|
// Continue waiting for the (N - f) AUX messages.
|
2018-05-10 04:44:33 -07:00
|
|
|
|
return (None, VecDeque::new());
|
2018-05-09 07:27:31 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FIXME: Implement the Common Coin algorithm. At the moment the
|
|
|
|
|
// coin value is common across different nodes but not random.
|
2018-05-10 04:09:22 -07:00
|
|
|
|
let coin = (self.epoch % 2) == 0;
|
2018-05-09 07:27:31 -07:00
|
|
|
|
|
|
|
|
|
// Check the termination condition: "continue looping until both a
|
|
|
|
|
// value b is output in some round r, and the value Coin_r' = b for
|
|
|
|
|
// some round r' > r."
|
2018-05-10 04:09:22 -07:00
|
|
|
|
self.terminated = self.terminated || self.output == Some(coin);
|
2018-05-09 07:27:31 -07:00
|
|
|
|
|
|
|
|
|
// Start the next epoch.
|
|
|
|
|
self.bin_values.clear();
|
2018-05-09 07:36:02 -07:00
|
|
|
|
self.received_aux.clear();
|
2018-05-09 07:27:31 -07:00
|
|
|
|
self.epoch += 1;
|
|
|
|
|
|
2018-05-10 04:44:33 -07:00
|
|
|
|
let decision = if vals.len() != 1 {
|
2018-05-10 04:09:22 -07:00
|
|
|
|
self.estimated = Some(coin);
|
2018-05-10 04:44:33 -07:00
|
|
|
|
None
|
2018-05-10 04:33:01 -07:00
|
|
|
|
} else {
|
|
|
|
|
// NOTE: `vals` has exactly one element due to `vals.len() == 1`
|
|
|
|
|
let v: Vec<bool> = vals.into_iter().collect();
|
|
|
|
|
let b = v[0];
|
|
|
|
|
self.estimated = Some(b);
|
2018-05-10 04:44:33 -07:00
|
|
|
|
// Outputting a value is allowed only once.
|
2018-05-10 04:33:01 -07:00
|
|
|
|
if self.output.is_none() && b == coin {
|
|
|
|
|
// Output the agreement value.
|
|
|
|
|
self.output = Some(b);
|
2018-05-10 04:44:33 -07:00
|
|
|
|
self.output
|
|
|
|
|
} else {
|
|
|
|
|
None
|
2018-05-10 04:33:01 -07:00
|
|
|
|
}
|
2018-05-10 04:44:33 -07:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
(
|
|
|
|
|
decision,
|
|
|
|
|
vec![AgreementMessage::BVal((
|
|
|
|
|
self.epoch,
|
|
|
|
|
self.estimated.unwrap(),
|
|
|
|
|
))].into_iter()
|
|
|
|
|
.collect(),
|
|
|
|
|
)
|
2018-05-06 14:39:01 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-02 03:57:28 -07:00
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub enum Error {
|
2018-05-07 02:59:14 -07:00
|
|
|
|
Terminated,
|
2018-05-09 07:27:31 -07:00
|
|
|
|
InputNotAccepted,
|
2018-05-01 10:12:05 -07:00
|
|
|
|
}
|