2018-05-16 05:23:57 -07:00
|
|
|
use std::collections::btree_map::Entry;
|
2018-06-19 07:17:16 -07:00
|
|
|
use std::collections::{BTreeMap, BTreeSet, HashSet, 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-06-25 04:07:31 -07:00
|
|
|
use std::{cmp, iter, mem};
|
2018-05-12 07:09:07 -07:00
|
|
|
|
|
|
|
use bincode;
|
2018-06-28 08:17:07 -07:00
|
|
|
use itertools::Itertools;
|
2018-05-16 05:23:57 -07:00
|
|
|
use rand;
|
2018-06-25 04:07:31 -07:00
|
|
|
use serde::{Deserialize, Serialize};
|
2018-05-12 07:09:07 -07:00
|
|
|
|
|
|
|
use common_subset::{self, CommonSubset};
|
2018-06-19 07:17:16 -07:00
|
|
|
use crypto::{Ciphertext, DecryptionShare};
|
|
|
|
use messaging::{DistAlgorithm, NetworkInfo, Target, 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 {
|
|
|
|
UnknownSender
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-28 12:31:27 -07:00
|
|
|
/// A Honey Badger builder, to configure the parameters and create new instances of `HoneyBadger`.
|
|
|
|
pub struct HoneyBadgerBuilder<NodeUid> {
|
|
|
|
/// Shared network data.
|
|
|
|
netinfo: Rc<NetworkInfo<NodeUid>>,
|
|
|
|
/// The target number of transactions to be included in each batch.
|
|
|
|
// TODO: Do experiments and pick a suitable default.
|
|
|
|
batch_size: usize,
|
|
|
|
/// The maximum number of future epochs for which we handle messages simultaneously.
|
|
|
|
max_future_epochs: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<NodeUid: Ord + Clone + Debug> HoneyBadgerBuilder<NodeUid> {
|
|
|
|
/// Creates a new builder for Honey Badger.
|
|
|
|
pub fn new(netinfo: Rc<NetworkInfo<NodeUid>>) -> Self {
|
|
|
|
HoneyBadgerBuilder {
|
|
|
|
netinfo,
|
|
|
|
batch_size: 100,
|
|
|
|
max_future_epochs: 3,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the target number of transactions per batch.
|
|
|
|
pub fn batch_size(&mut self, batch_size: usize) -> &mut Self {
|
|
|
|
self.batch_size = batch_size;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the maximum number of future epochs for which we handle messages simultaneously.
|
|
|
|
pub fn max_future_epochs(&mut self, max_future_epochs: usize) -> &mut Self {
|
|
|
|
self.max_future_epochs = max_future_epochs;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates a new Honey Badger instance with an empty buffer.
|
|
|
|
pub fn build<Tx>(&self) -> HoneyBadgerResult<HoneyBadger<Tx, NodeUid>>
|
|
|
|
where
|
|
|
|
Tx: Serialize + for<'r> Deserialize<'r> + Debug + Hash + Eq,
|
|
|
|
{
|
|
|
|
self.build_with_transactions(None)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a new Honey Badger instance that starts with the given transactions in its buffer.
|
|
|
|
pub fn build_with_transactions<Tx, TI>(
|
|
|
|
&self,
|
|
|
|
txs: TI,
|
|
|
|
) -> HoneyBadgerResult<HoneyBadger<Tx, NodeUid>>
|
|
|
|
where
|
|
|
|
TI: IntoIterator<Item = Tx>,
|
|
|
|
Tx: Serialize + for<'r> Deserialize<'r> + Debug + Hash + Eq,
|
|
|
|
{
|
|
|
|
let mut honey_badger = HoneyBadger {
|
|
|
|
netinfo: self.netinfo.clone(),
|
|
|
|
buffer: Vec::new(),
|
|
|
|
epoch: 0,
|
|
|
|
common_subsets: BTreeMap::new(),
|
|
|
|
batch_size: self.batch_size,
|
|
|
|
max_future_epochs: self.max_future_epochs as u64,
|
|
|
|
messages: MessageQueue(VecDeque::new()),
|
|
|
|
output: VecDeque::new(),
|
|
|
|
incoming_queue: BTreeMap::new(),
|
|
|
|
received_shares: BTreeMap::new(),
|
|
|
|
decrypted_selections: BTreeMap::new(),
|
|
|
|
ciphertexts: BTreeMap::new(),
|
|
|
|
};
|
|
|
|
honey_badger.buffer.extend(txs);
|
|
|
|
honey_badger.propose()?;
|
|
|
|
Ok(honey_badger)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-12 07:09:07 -07:00
|
|
|
/// An instance of the Honey Badger Byzantine fault tolerant consensus algorithm.
|
2018-06-22 01:19:29 -07:00
|
|
|
pub struct HoneyBadger<Tx, NodeUid> {
|
2018-05-29 05:17:30 -07:00
|
|
|
/// Shared network data.
|
2018-06-18 07:14:17 -07:00
|
|
|
netinfo: Rc<NetworkInfo<NodeUid>>,
|
2018-05-16 05:23:57 -07:00
|
|
|
/// The buffer of transactions that have not yet been included in any output batch.
|
2018-06-18 07:14:17 -07:00
|
|
|
buffer: Vec<Tx>,
|
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.
|
2018-06-18 07:14:17 -07:00
|
|
|
common_subsets: BTreeMap<u64, CommonSubset<NodeUid>>,
|
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-06-28 08:17:07 -07:00
|
|
|
/// The maximum number of `CommonSubset` instances that we run simultaneously.
|
|
|
|
max_future_epochs: u64,
|
2018-05-14 05:35:06 -07:00
|
|
|
/// The messages that need to be sent to other nodes.
|
2018-06-18 07:14:17 -07:00
|
|
|
messages: MessageQueue<NodeUid>,
|
2018-05-14 05:35:06 -07:00
|
|
|
/// The outputs from completed epochs.
|
2018-06-18 07:14:17 -07:00
|
|
|
output: VecDeque<Batch<Tx, NodeUid>>,
|
2018-06-28 08:17:07 -07:00
|
|
|
/// Messages for future epochs that couldn't be handled yet.
|
|
|
|
incoming_queue: BTreeMap<u64, Vec<(NodeUid, MessageContent<NodeUid>)>>,
|
2018-06-19 07:17:16 -07:00
|
|
|
/// Received decryption shares for an epoch. Each decryption share has a sender and a
|
|
|
|
/// proposer. The outer `BTreeMap` has epochs as its key. The next `BTreeMap` has proposers as
|
|
|
|
/// its key. The inner `BTreeMap` has the sender as its key.
|
2018-06-22 02:17:11 -07:00
|
|
|
received_shares: BTreeMap<u64, BTreeMap<NodeUid, BTreeMap<NodeUid, DecryptionShare>>>,
|
2018-06-19 07:17:16 -07:00
|
|
|
/// Decoded accepted proposals.
|
2018-06-20 08:47:52 -07:00
|
|
|
decrypted_selections: BTreeMap<NodeUid, Vec<u8>>,
|
|
|
|
/// Ciphertexts output by Common Subset in an epoch.
|
2018-06-22 02:17:11 -07:00
|
|
|
ciphertexts: BTreeMap<u64, BTreeMap<NodeUid, Ciphertext>>,
|
2018-05-14 05:35:06 -07:00
|
|
|
}
|
|
|
|
|
2018-06-18 07:14:17 -07:00
|
|
|
impl<Tx, NodeUid> DistAlgorithm for HoneyBadger<Tx, NodeUid>
|
2018-05-14 05:35:06 -07:00
|
|
|
where
|
2018-06-25 04:07:31 -07:00
|
|
|
Tx: Serialize + for<'r> Deserialize<'r> + Debug + Hash + Eq,
|
2018-06-22 01:19:29 -07:00
|
|
|
NodeUid: Ord + Clone + Debug,
|
2018-05-14 05:35:06 -07:00
|
|
|
{
|
2018-06-18 07:14:17 -07:00
|
|
|
type NodeUid = NodeUid;
|
|
|
|
type Input = Tx;
|
|
|
|
type Output = Batch<Tx, NodeUid>;
|
|
|
|
type Message = Message<NodeUid>;
|
2018-05-14 05:35:06 -07:00
|
|
|
type Error = Error;
|
|
|
|
|
2018-05-20 04:51:33 -07:00
|
|
|
fn input(&mut self, input: Self::Input) -> HoneyBadgerResult<()> {
|
2018-06-27 04:45:25 -07:00
|
|
|
self.add_transactions(iter::once(input));
|
|
|
|
Ok(())
|
2018-05-14 05:35:06 -07:00
|
|
|
}
|
|
|
|
|
2018-06-18 07:14:17 -07:00
|
|
|
fn handle_message(
|
|
|
|
&mut self,
|
|
|
|
sender_id: &NodeUid,
|
|
|
|
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
|
|
|
}
|
2018-06-19 07:17:16 -07:00
|
|
|
let Message { epoch, content } = message;
|
|
|
|
if epoch < self.epoch {
|
|
|
|
// Ignore all messages from past epochs.
|
|
|
|
return Ok(());
|
|
|
|
}
|
2018-06-28 08:17:07 -07:00
|
|
|
if epoch > self.epoch + self.max_future_epochs {
|
|
|
|
// Postpone handling this message.
|
|
|
|
self.incoming_queue
|
|
|
|
.entry(epoch)
|
|
|
|
.or_insert_with(Vec::new)
|
|
|
|
.push((sender_id.clone(), content));
|
|
|
|
return Ok(());
|
2018-05-14 05:35:06 -07:00
|
|
|
}
|
2018-06-28 08:17:07 -07:00
|
|
|
self.handle_message_content(sender_id, epoch, content)
|
2018-05-14 05:35:06 -07:00
|
|
|
}
|
|
|
|
|
2018-06-18 07:14:17 -07:00
|
|
|
fn next_message(&mut self) -> Option<TargetedMessage<Self::Message, NodeUid>> {
|
2018-06-22 01:19:29 -07:00
|
|
|
self.messages.pop_front()
|
2018-05-14 05:35:06 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn next_output(&mut self) -> Option<Self::Output> {
|
|
|
|
self.output.pop_front()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn terminated(&self) -> bool {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2018-06-18 07:14:17 -07:00
|
|
|
fn our_id(&self) -> &NodeUid {
|
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
|
|
|
}
|
|
|
|
|
2018-06-18 07:14:17 -07:00
|
|
|
impl<Tx, NodeUid> HoneyBadger<Tx, NodeUid>
|
2018-05-12 07:09:07 -07:00
|
|
|
where
|
2018-06-25 04:07:31 -07:00
|
|
|
Tx: Serialize + for<'r> Deserialize<'r> + Debug + Hash + Eq,
|
2018-06-22 01:19:29 -07:00
|
|
|
NodeUid: Ord + Clone + Debug,
|
2018-05-12 07:09:07 -07:00
|
|
|
{
|
|
|
|
/// Adds transactions into the buffer.
|
2018-06-27 04:45:25 -07:00
|
|
|
pub fn add_transactions<I: IntoIterator<Item = Tx>>(&mut self, txs: I) {
|
|
|
|
self.buffer.extend(txs);
|
2018-05-16 05:23:57 -07:00
|
|
|
}
|
|
|
|
|
2018-06-25 04:07:31 -07:00
|
|
|
/// Empties and returns the transaction buffer.
|
|
|
|
pub fn drain_buffer(&mut self) -> Vec<Tx> {
|
|
|
|
mem::replace(&mut self.buffer, Vec::new())
|
|
|
|
}
|
|
|
|
|
2018-05-16 05:23:57 -07:00
|
|
|
/// Proposes a new batch in the current epoch.
|
2018-05-20 04:51:33 -07:00
|
|
|
fn propose(&mut self) -> HoneyBadgerResult<()> {
|
2018-06-29 08:20:54 -07:00
|
|
|
if !self.netinfo.is_validator() {
|
2018-06-25 12:09:45 -07:00
|
|
|
return Ok(());
|
|
|
|
}
|
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-06-12 02:24:09 -07:00
|
|
|
Entry::Vacant(entry) => {
|
|
|
|
entry.insert(CommonSubset::new(self.netinfo.clone(), self.epoch)?)
|
|
|
|
}
|
2018-05-16 05:23:57 -07:00
|
|
|
};
|
2018-06-19 07:17:16 -07:00
|
|
|
let ciphertext = self.netinfo.public_key_set().public_key().encrypt(proposal);
|
|
|
|
cs.input(bincode::serialize(&ciphertext).unwrap())?;
|
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-06-28 08:17:07 -07:00
|
|
|
/// Handles a message for the given epoch.
|
|
|
|
fn handle_message_content(
|
|
|
|
&mut self,
|
|
|
|
sender_id: &NodeUid,
|
|
|
|
epoch: u64,
|
|
|
|
content: MessageContent<NodeUid>,
|
|
|
|
) -> HoneyBadgerResult<()> {
|
|
|
|
match content {
|
|
|
|
MessageContent::CommonSubset(cs_msg) => {
|
|
|
|
self.handle_common_subset_message(sender_id, epoch, cs_msg)
|
|
|
|
}
|
|
|
|
MessageContent::DecryptionShare { proposer_id, share } => {
|
|
|
|
self.handle_decryption_share_message(sender_id, epoch, proposer_id, share)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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,
|
2018-06-18 07:14:17 -07:00
|
|
|
sender_id: &NodeUid,
|
2018-05-12 07:09:07 -07:00
|
|
|
epoch: u64,
|
2018-06-18 07:14:17 -07:00
|
|
|
message: common_subset::Message<NodeUid>,
|
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-06-12 02:24:09 -07:00
|
|
|
entry.insert(CommonSubset::new(self.netinfo.clone(), epoch)?)
|
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-06-19 07:17:16 -07:00
|
|
|
/// Handles decryption shares sent by `HoneyBadger` instances.
|
2018-06-20 08:47:52 -07:00
|
|
|
fn handle_decryption_share_message(
|
2018-06-19 07:17:16 -07:00
|
|
|
&mut self,
|
|
|
|
sender_id: &NodeUid,
|
|
|
|
epoch: u64,
|
2018-06-21 05:29:40 -07:00
|
|
|
proposer_id: NodeUid,
|
2018-06-22 02:17:11 -07:00
|
|
|
share: DecryptionShare,
|
2018-06-19 07:17:16 -07:00
|
|
|
) -> HoneyBadgerResult<()> {
|
2018-06-21 10:38:07 -07:00
|
|
|
if let Some(ciphertext) = self
|
|
|
|
.ciphertexts
|
|
|
|
.get(&self.epoch)
|
|
|
|
.and_then(|cts| cts.get(&proposer_id))
|
|
|
|
{
|
|
|
|
if !self.verify_decryption_share(sender_id, &share, ciphertext) {
|
|
|
|
// TODO: Log the incorrect sender.
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-21 05:29:40 -07:00
|
|
|
{
|
|
|
|
// Insert the share.
|
2018-06-21 10:38:07 -07:00
|
|
|
let proposer_shares = self
|
2018-06-21 05:29:40 -07:00
|
|
|
.received_shares
|
|
|
|
.entry(epoch)
|
2018-06-21 10:38:07 -07:00
|
|
|
.or_insert_with(BTreeMap::new)
|
2018-06-21 05:29:40 -07:00
|
|
|
.entry(proposer_id.clone())
|
|
|
|
.or_insert_with(BTreeMap::new);
|
|
|
|
proposer_shares.insert(sender_id.clone(), share);
|
|
|
|
}
|
2018-06-20 08:47:52 -07:00
|
|
|
|
|
|
|
if epoch == self.epoch && self.try_decrypt_proposer_selection(proposer_id) {
|
|
|
|
self.try_output_batch()?;
|
|
|
|
}
|
2018-06-19 07:17:16 -07:00
|
|
|
|
2018-06-20 08:47:52 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
2018-06-19 07:17:16 -07:00
|
|
|
|
2018-06-21 10:38:07 -07:00
|
|
|
/// Verifies a given decryption share using the sender's public key and the proposer's
|
|
|
|
/// ciphertext. Returns `true` if verification has been successful and `false` if verification
|
|
|
|
/// has failed.
|
|
|
|
fn verify_decryption_share(
|
|
|
|
&self,
|
|
|
|
sender_id: &NodeUid,
|
2018-06-22 02:17:11 -07:00
|
|
|
share: &DecryptionShare,
|
|
|
|
ciphertext: &Ciphertext,
|
2018-06-21 10:38:07 -07:00
|
|
|
) -> bool {
|
2018-06-27 05:25:32 -07:00
|
|
|
if let Some(pk) = self.netinfo.public_key_share(sender_id) {
|
|
|
|
pk.verify_decryption_share(&share, ciphertext)
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
2018-06-21 10:38:07 -07:00
|
|
|
}
|
|
|
|
|
2018-06-20 08:47:52 -07:00
|
|
|
/// When selections of transactions have been decrypted for all valid proposers in this epoch,
|
|
|
|
/// moves those transactions into a batch, outputs the batch and updates the epoch.
|
|
|
|
fn try_output_batch(&mut self) -> HoneyBadgerResult<bool> {
|
2018-06-19 07:17:16 -07:00
|
|
|
// Wait until selections have been successfully decoded for all proposer nodes with correct
|
|
|
|
// ciphertext outputs.
|
2018-06-20 08:47:52 -07:00
|
|
|
if !self.all_selections_decrypted() {
|
|
|
|
return Ok(false);
|
2018-05-16 05:23:57 -07:00
|
|
|
}
|
2018-06-20 08:47:52 -07:00
|
|
|
|
|
|
|
// Deserialize the output.
|
|
|
|
let transactions: BTreeMap<NodeUid, Vec<Tx>> = self
|
|
|
|
.decrypted_selections
|
|
|
|
.iter()
|
|
|
|
.flat_map(|(proposer_id, ser_batch)| {
|
2018-06-21 05:29:40 -07:00
|
|
|
// If deserialization fails, the proposer of that batch is faulty. Ignore it.
|
2018-06-20 08:47:52 -07:00
|
|
|
bincode::deserialize::<Vec<Tx>>(&ser_batch)
|
|
|
|
.ok()
|
|
|
|
.map(|proposed| (proposer_id.clone(), proposed))
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
let batch = Batch {
|
|
|
|
epoch: self.epoch,
|
|
|
|
transactions,
|
|
|
|
};
|
|
|
|
{
|
|
|
|
let tx_set: HashSet<&Tx> = batch.iter().collect();
|
|
|
|
// Remove the output transactions from our buffer.
|
|
|
|
self.buffer.retain(|tx| !tx_set.contains(&tx));
|
2018-05-14 05:35:06 -07:00
|
|
|
}
|
2018-06-20 08:47:52 -07:00
|
|
|
debug!(
|
|
|
|
"{:?} Epoch {} output {:?}",
|
|
|
|
self.netinfo.our_uid(),
|
|
|
|
self.epoch,
|
|
|
|
batch.transactions
|
|
|
|
);
|
|
|
|
// Queue the output and advance the epoch.
|
|
|
|
self.output.push_back(batch);
|
|
|
|
self.update_epoch()?;
|
|
|
|
Ok(true)
|
2018-05-12 07:09:07 -07:00
|
|
|
}
|
2018-05-16 05:23:57 -07:00
|
|
|
|
2018-06-19 07:17:16 -07:00
|
|
|
/// Increments the epoch number and clears any state that is local to the finished epoch.
|
2018-06-20 08:47:52 -07:00
|
|
|
fn update_epoch(&mut self) -> HoneyBadgerResult<()> {
|
2018-06-19 07:17:16 -07:00
|
|
|
// Clear the state of the old epoch.
|
2018-06-20 08:47:52 -07:00
|
|
|
self.ciphertexts.remove(&self.epoch);
|
|
|
|
self.decrypted_selections.clear();
|
2018-06-19 07:17:16 -07:00
|
|
|
self.received_shares.remove(&self.epoch);
|
|
|
|
self.epoch += 1;
|
2018-06-28 08:17:07 -07:00
|
|
|
let max_epoch = self.epoch + self.max_future_epochs;
|
|
|
|
// TODO: Once stable, use `Iterator::flatten`.
|
|
|
|
for (sender_id, content) in
|
|
|
|
Itertools::flatten(self.incoming_queue.remove(&max_epoch).into_iter())
|
|
|
|
{
|
|
|
|
self.handle_message_content(&sender_id, max_epoch, content)?;
|
|
|
|
}
|
2018-06-20 08:47:52 -07:00
|
|
|
// Handle any decryption shares received for the new epoch.
|
|
|
|
if !self.try_decrypt_and_output_batch()? {
|
|
|
|
// Continue with this epoch if a batch is not output by `try_decrypt_and_output_batch`.
|
|
|
|
self.propose()?;
|
|
|
|
}
|
|
|
|
Ok(())
|
2018-06-19 07:17:16 -07:00
|
|
|
}
|
|
|
|
|
2018-06-20 08:47:52 -07:00
|
|
|
/// Tries to decrypt transaction selections from all proposers and output those transactions in
|
|
|
|
/// a batch.
|
|
|
|
fn try_decrypt_and_output_batch(&mut self) -> HoneyBadgerResult<bool> {
|
2018-06-21 05:29:40 -07:00
|
|
|
if let Some(proposer_ids) = self
|
|
|
|
.received_shares
|
|
|
|
.get(&self.epoch)
|
|
|
|
.map(|shares| shares.keys().cloned().collect::<BTreeSet<NodeUid>>())
|
|
|
|
{
|
2018-06-20 08:47:52 -07:00
|
|
|
// Try to output a batch if there is a non-empty set of proposers for which we have already received
|
|
|
|
// decryption shares.
|
|
|
|
if !proposer_ids.is_empty()
|
|
|
|
&& proposer_ids
|
|
|
|
.iter()
|
2018-06-21 05:29:40 -07:00
|
|
|
.all(|proposer_id| self.try_decrypt_proposer_selection(proposer_id.clone()))
|
2018-06-20 08:47:52 -07:00
|
|
|
{
|
2018-06-21 05:29:40 -07:00
|
|
|
self.try_output_batch()
|
|
|
|
} else {
|
|
|
|
Ok(false)
|
2018-06-20 08:47:52 -07:00
|
|
|
}
|
2018-06-21 05:29:40 -07:00
|
|
|
} else {
|
|
|
|
Ok(false)
|
2018-06-20 08:47:52 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns true if and only if transaction selections have been decrypted for all proposers in
|
|
|
|
/// this epoch.
|
|
|
|
fn all_selections_decrypted(&mut self) -> bool {
|
2018-06-21 05:29:40 -07:00
|
|
|
let ciphertexts = self
|
|
|
|
.ciphertexts
|
|
|
|
.entry(self.epoch)
|
|
|
|
.or_insert_with(BTreeMap::new);
|
2018-06-20 08:47:52 -07:00
|
|
|
let all_ciphertext_proposers: BTreeSet<_> = ciphertexts.keys().collect();
|
|
|
|
let all_decrypted_selection_proposers: BTreeSet<_> =
|
|
|
|
self.decrypted_selections.keys().collect();
|
2018-06-21 05:29:40 -07:00
|
|
|
all_ciphertext_proposers == all_decrypted_selection_proposers
|
2018-06-19 07:17:16 -07:00
|
|
|
}
|
|
|
|
|
2018-06-20 08:47:52 -07:00
|
|
|
/// Tries to decrypt the selection of transactions from a given proposer. Outputs `true` if and
|
2018-06-21 05:29:40 -07:00
|
|
|
/// only if decryption finished without errors.
|
|
|
|
fn try_decrypt_proposer_selection(&mut self, proposer_id: NodeUid) -> bool {
|
|
|
|
let shares = &self.received_shares[&self.epoch][&proposer_id];
|
|
|
|
if shares.len() <= self.netinfo.num_faulty() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(ciphertext) = self
|
|
|
|
.ciphertexts
|
|
|
|
.get(&self.epoch)
|
|
|
|
.and_then(|cts| cts.get(&proposer_id))
|
|
|
|
{
|
|
|
|
let ids_u64: BTreeMap<&NodeUid, u64> = shares
|
|
|
|
.keys()
|
|
|
|
.map(|id| (id, *self.netinfo.node_index(id).unwrap() as u64))
|
|
|
|
.collect();
|
|
|
|
let indexed_shares: BTreeMap<&u64, _> = shares
|
|
|
|
.into_iter()
|
|
|
|
.map(|(id, share)| (&ids_u64[id], share))
|
|
|
|
.collect();
|
|
|
|
if let Ok(decrypted_selection) = self
|
|
|
|
.netinfo
|
|
|
|
.public_key_set()
|
|
|
|
.decrypt(indexed_shares, ciphertext)
|
|
|
|
{
|
|
|
|
self.decrypted_selections
|
|
|
|
.insert(proposer_id, decrypted_selection);
|
|
|
|
return true;
|
2018-06-19 07:17:16 -07:00
|
|
|
}
|
|
|
|
}
|
2018-06-20 08:47:52 -07:00
|
|
|
false
|
2018-06-19 07:17:16 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn send_decryption_shares(
|
|
|
|
&mut self,
|
|
|
|
cs_output: BTreeMap<NodeUid, Vec<u8>>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
for (proposer_id, v) in cs_output {
|
2018-06-22 02:17:11 -07:00
|
|
|
let mut ciphertext: Ciphertext;
|
2018-06-19 07:17:16 -07:00
|
|
|
if let Ok(ct) = bincode::deserialize(&v) {
|
|
|
|
ciphertext = ct;
|
|
|
|
} else {
|
|
|
|
warn!("Invalid ciphertext from proposer {:?} ignored", proposer_id);
|
|
|
|
// TODO: Log the incorrect node `j`.
|
|
|
|
continue;
|
|
|
|
}
|
2018-06-21 10:38:07 -07:00
|
|
|
let incorrect_senders =
|
|
|
|
self.verify_pending_decryption_shares(&proposer_id, &ciphertext);
|
|
|
|
self.remove_incorrect_decryption_shares(&proposer_id, incorrect_senders);
|
|
|
|
|
2018-06-26 05:50:06 -07:00
|
|
|
if !self.send_decryption_share(&proposer_id, &ciphertext)? {
|
|
|
|
warn!("Share decryption failed for proposer {:?}", proposer_id);
|
|
|
|
// TODO: Log the decryption failure.
|
|
|
|
continue;
|
2018-06-19 07:17:16 -07:00
|
|
|
}
|
2018-06-21 05:29:40 -07:00
|
|
|
let ciphertexts = self
|
|
|
|
.ciphertexts
|
|
|
|
.entry(self.epoch)
|
|
|
|
.or_insert_with(BTreeMap::new);
|
2018-06-20 08:47:52 -07:00
|
|
|
ciphertexts.insert(proposer_id, ciphertext);
|
2018-06-19 07:17:16 -07:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2018-06-26 05:50:06 -07:00
|
|
|
/// Verifies the ciphertext and sends decryption shares. Returns whether it is valid.
|
|
|
|
fn send_decryption_share(
|
|
|
|
&mut self,
|
|
|
|
proposer_id: &NodeUid,
|
|
|
|
ciphertext: &Ciphertext,
|
|
|
|
) -> HoneyBadgerResult<bool> {
|
2018-06-29 08:20:54 -07:00
|
|
|
if !self.netinfo.is_validator() {
|
2018-06-26 05:50:06 -07:00
|
|
|
return Ok(ciphertext.verify());
|
|
|
|
}
|
|
|
|
let share = match self.netinfo.secret_key().decrypt_share(&ciphertext) {
|
|
|
|
None => return Ok(false),
|
|
|
|
Some(share) => share,
|
|
|
|
};
|
|
|
|
// Send the share to remote nodes.
|
|
|
|
let content = MessageContent::DecryptionShare {
|
|
|
|
proposer_id: proposer_id.clone(),
|
|
|
|
share: share.clone(),
|
|
|
|
};
|
|
|
|
let message = Target::All.message(content.with_epoch(self.epoch));
|
|
|
|
self.messages.0.push_back(message);
|
|
|
|
let our_id = self.netinfo.our_uid().clone();
|
|
|
|
let epoch = self.epoch;
|
|
|
|
// Receive the share locally.
|
|
|
|
self.handle_decryption_share_message(&our_id, epoch, proposer_id.clone(), share)?;
|
|
|
|
Ok(true)
|
|
|
|
}
|
|
|
|
|
2018-06-22 01:24:49 -07:00
|
|
|
/// Verifies the shares of the current epoch that are pending verification. Returned are the
|
|
|
|
/// senders with incorrect pending shares.
|
2018-06-21 10:38:07 -07:00
|
|
|
fn verify_pending_decryption_shares(
|
|
|
|
&self,
|
|
|
|
proposer_id: &NodeUid,
|
2018-06-22 02:17:11 -07:00
|
|
|
ciphertext: &Ciphertext,
|
2018-06-21 10:38:07 -07:00
|
|
|
) -> BTreeSet<NodeUid> {
|
|
|
|
let mut incorrect_senders = BTreeSet::new();
|
2018-06-22 01:24:49 -07:00
|
|
|
if let Some(sender_shares) = self
|
|
|
|
.received_shares
|
2018-06-21 10:38:07 -07:00
|
|
|
.get(&self.epoch)
|
|
|
|
.and_then(|e| e.get(proposer_id))
|
|
|
|
{
|
2018-06-22 01:24:49 -07:00
|
|
|
for (sender_id, share) in sender_shares {
|
|
|
|
if !self.verify_decryption_share(sender_id, share, ciphertext) {
|
|
|
|
incorrect_senders.insert(sender_id.clone());
|
2018-06-21 10:38:07 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
incorrect_senders
|
|
|
|
}
|
|
|
|
|
|
|
|
fn remove_incorrect_decryption_shares(
|
|
|
|
&mut self,
|
|
|
|
proposer_id: &NodeUid,
|
|
|
|
incorrect_senders: BTreeSet<NodeUid>,
|
|
|
|
) {
|
|
|
|
if let Some(sender_shares) = self
|
|
|
|
.received_shares
|
|
|
|
.get_mut(&self.epoch)
|
|
|
|
.and_then(|e| e.get_mut(proposer_id))
|
|
|
|
{
|
|
|
|
for sender_id in incorrect_senders {
|
|
|
|
sender_shares.remove(&sender_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-21 05:29:40 -07:00
|
|
|
/// Checks whether the current epoch has output, and if it does, sends out our decryption shares.
|
2018-06-19 07:17:16 -07:00
|
|
|
fn process_output(&mut self) -> Result<(), Error> {
|
|
|
|
if let Some(cs_output) = self.take_current_output() {
|
|
|
|
self.send_decryption_shares(cs_output)?;
|
|
|
|
// TODO: May also check that there is no further output from Common Subset.
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2018-05-16 05:23:57 -07:00
|
|
|
/// Returns the output of the current epoch's `CommonSubset` instance, if any.
|
2018-06-18 07:14:17 -07:00
|
|
|
fn take_current_output(&mut self) -> Option<BTreeMap<NodeUid, 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-06-21 05:29:40 -07:00
|
|
|
///
|
|
|
|
/// TODO: Consider adding a `faulty_nodes` field to describe and report failures detected by `HoneyBadger`.
|
2018-05-17 02:51:14 -07:00
|
|
|
#[derive(Clone)]
|
2018-06-18 07:14:17 -07:00
|
|
|
pub struct Batch<Tx, NodeUid> {
|
2018-05-12 07:09:07 -07:00
|
|
|
pub epoch: u64,
|
2018-06-18 07:14:17 -07:00
|
|
|
pub transactions: BTreeMap<NodeUid, Vec<Tx>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<Tx, NodeUid: Ord> Batch<Tx, NodeUid> {
|
2018-06-30 04:09:47 -07:00
|
|
|
/// Returns an iterator over references to all transactions included in the batch.
|
2018-06-18 07:14:17 -07:00
|
|
|
pub fn iter(&self) -> impl Iterator<Item = &Tx> {
|
|
|
|
self.transactions.values().flat_map(|vec| vec)
|
|
|
|
}
|
|
|
|
|
2018-06-30 04:09:47 -07:00
|
|
|
/// Returns an iterator over all transactions included in the batch. Consumes the batch.
|
|
|
|
pub fn into_tx_iter(self) -> impl Iterator<Item = Tx> {
|
|
|
|
self.transactions.into_iter().flat_map(|(_, vec)| vec)
|
|
|
|
}
|
|
|
|
|
2018-06-18 07:14:17 -07:00
|
|
|
/// Returns the number of transactions in the batch (without detecting duplicates).
|
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
self.transactions.values().map(Vec::len).sum()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns `true` if the batch contains no transactions.
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
self.transactions.values().all(Vec::is_empty)
|
|
|
|
}
|
2018-05-12 07:09:07 -07:00
|
|
|
}
|
|
|
|
|
2018-06-19 07:17:16 -07:00
|
|
|
/// The content of a `HoneyBadger` message. It should be further annotated with an epoch.
|
2018-06-20 10:22:10 -07:00
|
|
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
2018-06-19 07:17:16 -07:00
|
|
|
pub enum MessageContent<NodeUid> {
|
2018-05-12 07:09:07 -07:00
|
|
|
/// A message belonging to the common subset algorithm in the given epoch.
|
2018-06-19 07:17:16 -07:00
|
|
|
CommonSubset(common_subset::Message<NodeUid>),
|
|
|
|
/// A decrypted share of the output of `proposer_id`.
|
|
|
|
DecryptionShare {
|
|
|
|
proposer_id: NodeUid,
|
2018-06-22 02:17:11 -07:00
|
|
|
share: DecryptionShare,
|
2018-06-19 07:17:16 -07:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2018-06-22 01:19:29 -07:00
|
|
|
impl<NodeUid> MessageContent<NodeUid> {
|
2018-06-25 11:22:08 -07:00
|
|
|
pub fn with_epoch(self, epoch: u64) -> Message<NodeUid> {
|
2018-06-22 01:19:29 -07:00
|
|
|
Message {
|
2018-06-19 07:17:16 -07:00
|
|
|
epoch,
|
|
|
|
content: self,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-22 01:19:29 -07:00
|
|
|
/// A message sent to or received from another node's Honey Badger instance.
|
|
|
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
|
|
pub struct Message<NodeUid> {
|
2018-06-19 07:17:16 -07:00
|
|
|
epoch: u64,
|
2018-06-22 01:19:29 -07:00
|
|
|
content: MessageContent<NodeUid>,
|
2018-05-12 07:09:07 -07:00
|
|
|
}
|
|
|
|
|
2018-06-22 01:19:29 -07:00
|
|
|
impl<NodeUid> Message<NodeUid> {
|
2018-06-20 08:47:52 -07:00
|
|
|
pub fn epoch(&self) -> u64 {
|
|
|
|
self.epoch
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-19 05:29:31 -07:00
|
|
|
/// The queue of outgoing messages in a `HoneyBadger` instance.
|
|
|
|
#[derive(Deref, DerefMut)]
|
2018-06-22 01:19:29 -07:00
|
|
|
struct MessageQueue<NodeUid>(VecDeque<TargetedMessage<Message<NodeUid>, NodeUid>>);
|
2018-05-19 05:29:31 -07:00
|
|
|
|
2018-06-22 01:19:29 -07:00
|
|
|
impl<NodeUid: Clone + Debug + Ord> MessageQueue<NodeUid> {
|
2018-05-19 05:29:31 -07:00
|
|
|
/// Appends to the queue the messages from `cs`, wrapped with `epoch`.
|
2018-06-18 07:14:17 -07:00
|
|
|
fn extend_with_epoch(&mut self, epoch: u64, cs: &mut CommonSubset<NodeUid>) {
|
|
|
|
let convert = |msg: TargetedMessage<common_subset::Message<NodeUid>, NodeUid>| {
|
2018-06-22 01:19:29 -07:00
|
|
|
msg.map(|cs_msg| MessageContent::CommonSubset(cs_msg).with_epoch(epoch))
|
2018-05-19 05:29:31 -07:00
|
|
|
};
|
|
|
|
self.extend(cs.message_iter().map(convert));
|
|
|
|
}
|
|
|
|
}
|