2018-05-16 05:23:57 -07:00
|
|
|
use std::collections::btree_map::Entry;
|
2018-05-18 14:04:09 -07:00
|
|
|
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
2018-05-16 05:23:57 -07:00
|
|
|
use std::fmt::Debug;
|
2018-05-12 07:09:07 -07:00
|
|
|
use std::hash::Hash;
|
2018-05-29 05:17:30 -07:00
|
|
|
use std::rc::Rc;
|
2018-05-16 05:23:57 -07:00
|
|
|
use std::{cmp, iter};
|
2018-05-12 07:09:07 -07:00
|
|
|
|
|
|
|
use bincode;
|
2018-05-16 05:23:57 -07:00
|
|
|
use rand;
|
2018-05-12 07:09:07 -07:00
|
|
|
use serde::de::DeserializeOwned;
|
|
|
|
use serde::Serialize;
|
|
|
|
|
|
|
|
use common_subset::{self, CommonSubset};
|
2018-05-29 05:17:30 -07:00
|
|
|
use messaging::{DistAlgorithm, NetworkInfo, TargetedMessage};
|
2018-05-12 07:09:07 -07:00
|
|
|
|
2018-05-20 04:51:33 -07:00
|
|
|
error_chain!{
|
|
|
|
types {
|
|
|
|
Error, ErrorKind, ResultExt, HoneyBadgerResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
links {
|
|
|
|
CommonSubset(common_subset::Error, common_subset::ErrorKind);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreign_links {
|
|
|
|
Bincode(Box<bincode::ErrorKind>);
|
|
|
|
}
|
|
|
|
|
|
|
|
errors {
|
|
|
|
OwnIdMissing
|
|
|
|
UnknownSender
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-12 07:09:07 -07:00
|
|
|
/// An instance of the Honey Badger Byzantine fault tolerant consensus algorithm.
|
2018-05-16 05:23:57 -07:00
|
|
|
pub struct HoneyBadger<T, N: Eq + Hash + Ord + Clone> {
|
2018-05-29 05:17:30 -07:00
|
|
|
/// Shared network data.
|
|
|
|
netinfo: Rc<NetworkInfo<N>>,
|
2018-05-16 05:23:57 -07:00
|
|
|
/// The buffer of transactions that have not yet been included in any output batch.
|
2018-05-17 02:51:14 -07:00
|
|
|
buffer: Vec<T>,
|
2018-05-16 05:23:57 -07:00
|
|
|
/// The earliest epoch from which we have not yet received output.
|
2018-05-12 07:09:07 -07:00
|
|
|
epoch: u64,
|
2018-05-16 05:23:57 -07:00
|
|
|
/// The Asynchronous Common Subset instance that decides which nodes' transactions to include,
|
|
|
|
/// indexed by epoch.
|
|
|
|
common_subsets: BTreeMap<u64, CommonSubset<N>>,
|
2018-05-12 07:09:07 -07:00
|
|
|
/// 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)`.
|
|
|
|
batch_size: usize,
|
2018-05-14 05:35:06 -07:00
|
|
|
/// The messages that need to be sent to other nodes.
|
2018-05-19 05:29:31 -07:00
|
|
|
messages: MessageQueue<N>,
|
2018-05-14 05:35:06 -07:00
|
|
|
/// The outputs from completed epochs.
|
|
|
|
output: VecDeque<Batch<T>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T, N> DistAlgorithm for HoneyBadger<T, N>
|
|
|
|
where
|
2018-05-16 05:23:57 -07:00
|
|
|
T: Ord + Serialize + DeserializeOwned + Debug,
|
|
|
|
N: Eq + Hash + Ord + Clone + Debug,
|
2018-05-14 05:35:06 -07:00
|
|
|
{
|
|
|
|
type NodeUid = N;
|
|
|
|
type Input = T;
|
|
|
|
type Output = Batch<T>;
|
|
|
|
type Message = Message<N>;
|
|
|
|
type Error = Error;
|
|
|
|
|
2018-05-20 04:51:33 -07:00
|
|
|
fn input(&mut self, input: Self::Input) -> HoneyBadgerResult<()> {
|
2018-05-14 05:35:06 -07:00
|
|
|
self.add_transactions(iter::once(input))
|
|
|
|
}
|
|
|
|
|
2018-05-20 04:51:33 -07:00
|
|
|
fn handle_message(&mut self, sender_id: &N, message: Self::Message) -> HoneyBadgerResult<()> {
|
2018-05-29 05:17:30 -07:00
|
|
|
if !self.netinfo.all_uids().contains(sender_id) {
|
2018-05-20 04:51:33 -07:00
|
|
|
return Err(ErrorKind::UnknownSender.into());
|
2018-05-14 05:35:06 -07:00
|
|
|
}
|
|
|
|
match message {
|
|
|
|
Message::CommonSubset(epoch, cs_msg) => {
|
|
|
|
self.handle_common_subset_message(sender_id, epoch, cs_msg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn next_message(&mut self) -> Option<TargetedMessage<Self::Message, N>> {
|
|
|
|
self.messages.pop_front()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn next_output(&mut self) -> Option<Self::Output> {
|
|
|
|
self.output.pop_front()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn terminated(&self) -> bool {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
fn our_id(&self) -> &N {
|
2018-05-29 05:17:30 -07:00
|
|
|
self.netinfo.our_uid()
|
2018-05-14 05:35:06 -07:00
|
|
|
}
|
2018-05-12 07:09:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Use a threshold encryption scheme to encrypt the proposed transactions.
|
|
|
|
impl<T, N> HoneyBadger<T, N>
|
|
|
|
where
|
2018-05-16 05:23:57 -07:00
|
|
|
T: Ord + Serialize + DeserializeOwned + Debug,
|
|
|
|
N: Eq + Hash + Ord + Clone + Debug,
|
2018-05-12 07:09:07 -07:00
|
|
|
{
|
|
|
|
/// Returns a new Honey Badger instance with the given parameters, starting at epoch `0`.
|
2018-05-21 02:01:49 -07:00
|
|
|
pub fn new<I, TI>(
|
2018-05-29 05:17:30 -07:00
|
|
|
our_uid: N,
|
2018-05-21 02:01:49 -07:00
|
|
|
all_uids_iter: I,
|
|
|
|
batch_size: usize,
|
|
|
|
txs: TI,
|
|
|
|
) -> HoneyBadgerResult<Self>
|
2018-05-12 07:09:07 -07:00
|
|
|
where
|
|
|
|
I: IntoIterator<Item = N>,
|
2018-05-16 05:23:57 -07:00
|
|
|
TI: IntoIterator<Item = T>,
|
2018-05-12 07:09:07 -07:00
|
|
|
{
|
2018-05-18 14:04:09 -07:00
|
|
|
let all_uids: BTreeSet<N> = all_uids_iter.into_iter().collect();
|
2018-05-29 05:17:30 -07:00
|
|
|
if !all_uids.contains(&our_uid) {
|
2018-05-20 04:51:33 -07:00
|
|
|
return Err(ErrorKind::OwnIdMissing.into());
|
2018-05-12 07:09:07 -07:00
|
|
|
}
|
2018-05-16 05:23:57 -07:00
|
|
|
let mut honey_badger = HoneyBadger {
|
2018-05-29 05:17:30 -07:00
|
|
|
netinfo: Rc::new(NetworkInfo::new(our_uid, all_uids)),
|
2018-05-16 05:23:57 -07:00
|
|
|
buffer: txs.into_iter().collect(),
|
2018-05-12 07:09:07 -07:00
|
|
|
epoch: 0,
|
2018-05-16 05:23:57 -07:00
|
|
|
common_subsets: BTreeMap::new(),
|
2018-05-12 07:09:07 -07:00
|
|
|
batch_size,
|
2018-05-19 05:29:31 -07:00
|
|
|
messages: MessageQueue(VecDeque::new()),
|
2018-05-14 05:35:06 -07:00
|
|
|
output: VecDeque::new(),
|
2018-05-16 05:23:57 -07:00
|
|
|
};
|
|
|
|
honey_badger.propose()?;
|
|
|
|
Ok(honey_badger)
|
2018-05-12 07:09:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Adds transactions into the buffer.
|
2018-05-20 04:51:33 -07:00
|
|
|
pub fn add_transactions<I: IntoIterator<Item = T>>(&mut self, txs: I) -> HoneyBadgerResult<()> {
|
2018-05-12 07:09:07 -07:00
|
|
|
self.buffer.extend(txs);
|
2018-05-16 05:23:57 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Proposes a new batch in the current epoch.
|
2018-05-20 04:51:33 -07:00
|
|
|
fn propose(&mut self) -> HoneyBadgerResult<()> {
|
2018-05-16 05:23:57 -07:00
|
|
|
let proposal = self.choose_transactions()?;
|
|
|
|
let cs = match self.common_subsets.entry(self.epoch) {
|
|
|
|
Entry::Occupied(entry) => entry.into_mut(),
|
2018-05-29 05:17:30 -07:00
|
|
|
Entry::Vacant(entry) => entry.insert(CommonSubset::new(self.netinfo.clone())?),
|
2018-05-16 05:23:57 -07:00
|
|
|
};
|
|
|
|
cs.input(proposal)?;
|
2018-05-19 05:29:31 -07:00
|
|
|
self.messages.extend_with_epoch(self.epoch, cs);
|
2018-05-14 05:35:06 -07:00
|
|
|
Ok(())
|
2018-05-12 07:09:07 -07:00
|
|
|
}
|
|
|
|
|
2018-05-16 05:23:57 -07:00
|
|
|
/// Returns a random choice of `batch_size / all_uids.len()` buffered transactions, and
|
|
|
|
/// serializes them.
|
2018-05-20 04:51:33 -07:00
|
|
|
fn choose_transactions(&self) -> HoneyBadgerResult<Vec<u8>> {
|
2018-05-16 05:23:57 -07:00
|
|
|
let mut rng = rand::thread_rng();
|
2018-05-29 05:17:30 -07:00
|
|
|
let amount = cmp::max(1, self.batch_size / self.netinfo.all_uids().len());
|
2018-05-17 02:51:14 -07:00
|
|
|
let batch_size = cmp::min(self.batch_size, self.buffer.len());
|
|
|
|
let sample = match rand::seq::sample_iter(&mut rng, &self.buffer[..batch_size], amount) {
|
2018-05-16 05:23:57 -07:00
|
|
|
Ok(choice) => choice,
|
|
|
|
Err(choice) => choice, // Fewer than `amount` were available, which is fine.
|
|
|
|
};
|
|
|
|
debug!(
|
|
|
|
"{:?} Proposing in epoch {}: {:?}",
|
2018-05-29 05:17:30 -07:00
|
|
|
self.netinfo.our_uid(),
|
|
|
|
self.epoch,
|
|
|
|
sample
|
2018-05-16 05:23:57 -07:00
|
|
|
);
|
|
|
|
Ok(bincode::serialize(&sample)?)
|
|
|
|
}
|
|
|
|
|
2018-05-14 05:35:06 -07:00
|
|
|
/// Handles a message for the common subset sub-algorithm.
|
2018-05-12 07:09:07 -07:00
|
|
|
fn handle_common_subset_message(
|
|
|
|
&mut self,
|
|
|
|
sender_id: &N,
|
|
|
|
epoch: u64,
|
|
|
|
message: common_subset::Message<N>,
|
2018-05-20 04:51:33 -07:00
|
|
|
) -> HoneyBadgerResult<()> {
|
2018-05-16 05:23:57 -07:00
|
|
|
{
|
|
|
|
// Borrow the instance for `epoch`, or create it.
|
|
|
|
let cs = match self.common_subsets.entry(epoch) {
|
|
|
|
Entry::Occupied(entry) => entry.into_mut(),
|
|
|
|
Entry::Vacant(entry) => {
|
|
|
|
if epoch < self.epoch {
|
|
|
|
return Ok(()); // Epoch has already terminated. Message is obsolete.
|
|
|
|
} else {
|
2018-05-29 05:17:30 -07:00
|
|
|
entry.insert(CommonSubset::new(self.netinfo.clone())?)
|
2018-05-16 05:23:57 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// Handle the message and put the outgoing messages into the queue.
|
|
|
|
cs.handle_message(sender_id, message)?;
|
2018-05-19 05:29:31 -07:00
|
|
|
self.messages.extend_with_epoch(epoch, cs);
|
2018-05-12 07:09:07 -07:00
|
|
|
}
|
2018-05-16 05:23:57 -07:00
|
|
|
// If this is the current epoch, the message could cause a new output.
|
|
|
|
if epoch == self.epoch {
|
|
|
|
self.process_output()?;
|
|
|
|
}
|
|
|
|
self.remove_terminated(epoch);
|
|
|
|
Ok(())
|
|
|
|
}
|
2018-05-15 10:18:05 -07:00
|
|
|
|
2018-05-16 05:23:57 -07:00
|
|
|
/// Checks whether the current epoch has output, and if it does, advances the epoch and
|
|
|
|
/// proposes a new batch.
|
|
|
|
fn process_output(&mut self) -> Result<(), Error> {
|
|
|
|
let old_epoch = self.epoch;
|
|
|
|
while let Some(ser_batches) = self.take_current_output() {
|
|
|
|
// Deserialize the output.
|
|
|
|
let transactions: BTreeSet<T> = ser_batches
|
|
|
|
.into_iter()
|
|
|
|
.map(|(_, ser_batch)| bincode::deserialize::<Vec<T>>(&ser_batch))
|
|
|
|
.collect::<Result<Vec<Vec<T>>, _>>()?
|
|
|
|
.into_iter()
|
|
|
|
.flat_map(|txs| txs)
|
|
|
|
.collect();
|
|
|
|
// Remove the output transactions from our buffer.
|
|
|
|
self.buffer.retain(|tx| !transactions.contains(tx));
|
|
|
|
debug!(
|
|
|
|
"{:?} Epoch {} output {:?}",
|
2018-05-29 05:17:30 -07:00
|
|
|
self.netinfo.our_uid(),
|
|
|
|
self.epoch,
|
|
|
|
transactions
|
2018-05-16 05:23:57 -07:00
|
|
|
);
|
|
|
|
// Queue the output and advance the epoch.
|
|
|
|
self.output.push_back(Batch {
|
|
|
|
epoch: self.epoch,
|
|
|
|
transactions,
|
|
|
|
});
|
|
|
|
self.epoch += 1;
|
|
|
|
}
|
|
|
|
// If we have moved to a new epoch, propose a new batch of transactions.
|
|
|
|
if self.epoch > old_epoch {
|
|
|
|
self.propose()?;
|
2018-05-14 05:35:06 -07:00
|
|
|
}
|
|
|
|
Ok(())
|
2018-05-12 07:09:07 -07:00
|
|
|
}
|
2018-05-16 05:23:57 -07:00
|
|
|
|
|
|
|
/// Returns the output of the current epoch's `CommonSubset` instance, if any.
|
2018-05-18 14:04:09 -07:00
|
|
|
fn take_current_output(&mut self) -> Option<BTreeMap<N, Vec<u8>>> {
|
2018-05-16 05:23:57 -07:00
|
|
|
self.common_subsets
|
|
|
|
.get_mut(&self.epoch)
|
|
|
|
.and_then(CommonSubset::next_output)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Removes all `CommonSubset` instances from _past_ epochs that have terminated.
|
|
|
|
fn remove_terminated(&mut self, from_epoch: u64) {
|
|
|
|
for epoch in from_epoch..self.epoch {
|
2018-05-21 02:01:49 -07:00
|
|
|
if self
|
|
|
|
.common_subsets
|
2018-05-16 05:23:57 -07:00
|
|
|
.get(&epoch)
|
|
|
|
.map_or(false, CommonSubset::terminated)
|
|
|
|
{
|
2018-05-29 05:17:30 -07:00
|
|
|
debug!(
|
|
|
|
"{:?} Epoch {} has terminated.",
|
|
|
|
self.netinfo.our_uid(),
|
|
|
|
epoch
|
|
|
|
);
|
2018-05-16 05:23:57 -07:00
|
|
|
self.common_subsets.remove(&epoch);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-05-12 07:09:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// A batch of transactions the algorithm has output.
|
2018-05-17 02:51:14 -07:00
|
|
|
#[derive(Clone)]
|
2018-05-12 07:09:07 -07:00
|
|
|
pub struct Batch<T> {
|
|
|
|
pub epoch: u64,
|
2018-05-16 05:23:57 -07:00
|
|
|
pub transactions: BTreeSet<T>,
|
2018-05-12 07:09:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// A message sent to or received from another node's Honey Badger instance.
|
2018-05-14 09:30:07 -07:00
|
|
|
#[cfg_attr(feature = "serialization-serde", derive(Serialize))]
|
2018-05-16 05:23:57 -07:00
|
|
|
#[derive(Debug, Clone)]
|
2018-05-12 07:09:07 -07:00
|
|
|
pub enum Message<N> {
|
|
|
|
/// A message belonging to the common subset algorithm in the given epoch.
|
|
|
|
CommonSubset(u64, common_subset::Message<N>),
|
|
|
|
// TODO: Decryption share.
|
|
|
|
}
|
|
|
|
|
2018-05-19 05:29:31 -07:00
|
|
|
/// 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));
|
|
|
|
}
|
|
|
|
}
|