2018-07-09 05:29:01 -07:00
|
|
|
//! # Queueing Honey Badger
|
|
|
|
//!
|
2018-07-18 07:46:46 -07:00
|
|
|
//! This works exactly like Dynamic Honey Badger, but it has a transaction queue built in. Whenever
|
|
|
|
//! an epoch is output, it will automatically select a list of pending transactions and propose it
|
|
|
|
//! for the next one. The user can continuously add more pending transactions to the queue.
|
|
|
|
//!
|
2018-08-01 01:41:09 -07:00
|
|
|
//! If there are no pending transactions, no validators in the process of being added or
|
|
|
|
//! removed and not enough other nodes have proposed yet, no automatic proposal will be made: The
|
|
|
|
//! network then waits until at least _f + 1_ have any content for the next epoch.
|
2018-07-18 07:46:46 -07:00
|
|
|
//!
|
|
|
|
//! ## How it works
|
|
|
|
//!
|
|
|
|
//! Queueing Honey Badger runs a Dynamic Honey Badger internally, and automatically inputs a list
|
|
|
|
//! of pending transactions as its contribution at the beginning of each epoch. These are selected
|
|
|
|
//! by making a random choice of _B / N_ out of the first _B_ entries in the queue, where _B_ is the
|
|
|
|
//! configurable `batch_size` parameter, and _N_ is the current number of validators.
|
|
|
|
//!
|
|
|
|
//! After each output, the transactions that made it into the new batch are removed from the queue.
|
|
|
|
//!
|
|
|
|
//! The random choice of transactions is made to reduce redundancy even if all validators have
|
|
|
|
//! roughly the same entries in their queues. By selecting a random fraction of the first _B_
|
2018-07-18 12:41:21 -07:00
|
|
|
//! entries, any two nodes will likely make almost disjoint contributions instead of proposing
|
2018-07-18 07:46:46 -07:00
|
|
|
//! the same transaction multiple times.
|
2018-07-09 05:29:01 -07:00
|
|
|
|
2018-07-12 08:53:12 -07:00
|
|
|
use std::cmp;
|
2018-07-25 14:38:33 -07:00
|
|
|
use std::fmt::{self, Display};
|
2018-07-09 05:29:01 -07:00
|
|
|
use std::marker::PhantomData;
|
|
|
|
|
2018-07-25 14:38:33 -07:00
|
|
|
use failure::{Backtrace, Context, Fail};
|
2018-07-14 00:20:02 -07:00
|
|
|
use rand::Rand;
|
2018-07-09 05:29:01 -07:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
use dynamic_honey_badger::{self, Batch as DhbBatch, DynamicHoneyBadger, Message};
|
2018-07-21 01:18:08 -07:00
|
|
|
use messaging::{self, DistAlgorithm};
|
2018-08-29 09:08:35 -07:00
|
|
|
use traits::{Contribution, NodeIdT};
|
2018-07-10 08:29:31 -07:00
|
|
|
use transaction_queue::TransactionQueue;
|
2018-07-09 05:29:01 -07:00
|
|
|
|
|
|
|
pub use dynamic_honey_badger::{Change, ChangeState, Input};
|
|
|
|
|
2018-07-25 14:38:33 -07:00
|
|
|
/// Queueing honey badger error variants.
|
|
|
|
#[derive(Debug, Fail)]
|
|
|
|
pub enum ErrorKind {
|
|
|
|
#[fail(display = "Input error: {}", _0)]
|
|
|
|
Input(dynamic_honey_badger::Error),
|
|
|
|
#[fail(display = "Handle message error: {}", _0)]
|
|
|
|
HandleMessage(dynamic_honey_badger::Error),
|
|
|
|
#[fail(display = "Propose error: {}", _0)]
|
|
|
|
Propose(dynamic_honey_badger::Error),
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A queueing honey badger error.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Error {
|
|
|
|
inner: Context<ErrorKind>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Fail for Error {
|
|
|
|
fn cause(&self) -> Option<&Fail> {
|
|
|
|
self.inner.cause()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn backtrace(&self) -> Option<&Backtrace> {
|
|
|
|
self.inner.backtrace()
|
2018-07-09 05:29:01 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-25 14:38:33 -07:00
|
|
|
impl Error {
|
|
|
|
pub fn kind(&self) -> &ErrorKind {
|
|
|
|
self.inner.get_context()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<ErrorKind> for Error {
|
|
|
|
fn from(kind: ErrorKind) -> Error {
|
|
|
|
Error {
|
|
|
|
inner: Context::new(kind),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Context<ErrorKind>> for Error {
|
|
|
|
fn from(inner: Context<ErrorKind>) -> Error {
|
|
|
|
Error { inner }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Display for Error {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
Display::fmt(&self.inner, f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub type Result<T> = ::std::result::Result<T, Error>;
|
|
|
|
|
2018-07-09 05:29:01 -07:00
|
|
|
/// A Queueing Honey Badger builder, to configure the parameters and create new instances of
|
|
|
|
/// `QueueingHoneyBadger`.
|
2018-08-02 14:27:55 -07:00
|
|
|
pub struct QueueingHoneyBadgerBuilder<T, N: Rand> {
|
2018-07-09 05:29:01 -07:00
|
|
|
/// Shared network data.
|
2018-08-02 14:27:55 -07:00
|
|
|
dyn_hb: DynamicHoneyBadger<Vec<T>, N>,
|
2018-07-09 05:29:01 -07:00
|
|
|
/// The target number of transactions to be included in each batch.
|
|
|
|
batch_size: usize,
|
2018-08-02 14:27:55 -07:00
|
|
|
_phantom: PhantomData<T>,
|
2018-07-09 05:29:01 -07:00
|
|
|
}
|
|
|
|
|
2018-08-02 14:27:55 -07:00
|
|
|
impl<T, N> QueueingHoneyBadgerBuilder<T, N>
|
2018-07-09 05:29:01 -07:00
|
|
|
where
|
2018-08-02 14:27:55 -07:00
|
|
|
T: Contribution + Serialize + for<'r> Deserialize<'r> + Clone,
|
2018-08-29 09:08:35 -07:00
|
|
|
N: NodeIdT + Serialize + for<'r> Deserialize<'r> + Rand,
|
2018-07-09 05:29:01 -07:00
|
|
|
{
|
|
|
|
/// Returns a new `QueueingHoneyBadgerBuilder` configured to use the node IDs and cryptographic
|
|
|
|
/// keys specified by `netinfo`.
|
2018-07-21 01:18:08 -07:00
|
|
|
// TODO: Make it easier to build a `QueueingHoneyBadger` with a `JoinPlan`. Handle `Step`
|
|
|
|
// conversion internally.
|
2018-08-02 14:27:55 -07:00
|
|
|
pub fn new(dyn_hb: DynamicHoneyBadger<Vec<T>, N>) -> Self {
|
2018-07-09 05:29:01 -07:00
|
|
|
// TODO: Use the defaults from `HoneyBadgerBuilder`.
|
|
|
|
QueueingHoneyBadgerBuilder {
|
2018-07-17 06:54:12 -07:00
|
|
|
dyn_hb,
|
2018-07-09 05:29:01 -07:00
|
|
|
batch_size: 100,
|
|
|
|
_phantom: PhantomData,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the target number of transactions per batch.
|
2018-07-17 06:54:12 -07:00
|
|
|
pub fn batch_size(mut self, batch_size: usize) -> Self {
|
2018-07-09 05:29:01 -07:00
|
|
|
self.batch_size = batch_size;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates a new Queueing Honey Badger instance with an empty buffer.
|
2018-08-02 14:27:55 -07:00
|
|
|
pub fn build(self) -> (QueueingHoneyBadger<T, N>, Step<T, N>)
|
2018-07-09 05:29:01 -07:00
|
|
|
where
|
2018-08-02 14:27:55 -07:00
|
|
|
T: Contribution + Serialize + for<'r> Deserialize<'r>,
|
2018-07-09 05:29:01 -07:00
|
|
|
{
|
|
|
|
self.build_with_transactions(None)
|
|
|
|
.expect("building without transactions cannot fail")
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a new Queueing Honey Badger instance that starts with the given transactions in its
|
|
|
|
/// buffer.
|
2018-07-21 01:18:08 -07:00
|
|
|
pub fn build_with_transactions<TI>(
|
|
|
|
self,
|
|
|
|
txs: TI,
|
2018-08-02 14:27:55 -07:00
|
|
|
) -> Result<(QueueingHoneyBadger<T, N>, Step<T, N>)>
|
2018-07-09 05:29:01 -07:00
|
|
|
where
|
2018-08-02 14:27:55 -07:00
|
|
|
TI: IntoIterator<Item = T>,
|
|
|
|
T: Contribution + Serialize + for<'r> Deserialize<'r>,
|
2018-07-09 05:29:01 -07:00
|
|
|
{
|
|
|
|
let queue = TransactionQueue(txs.into_iter().collect());
|
|
|
|
let mut qhb = QueueingHoneyBadger {
|
2018-07-17 06:54:12 -07:00
|
|
|
dyn_hb: self.dyn_hb,
|
2018-07-09 05:29:01 -07:00
|
|
|
queue,
|
|
|
|
batch_size: self.batch_size,
|
|
|
|
};
|
2018-07-21 01:18:08 -07:00
|
|
|
let step = qhb.propose()?;
|
|
|
|
Ok((qhb, step))
|
2018-07-09 05:29:01 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A Honey Badger instance that can handle adding and removing nodes and manages a transaction
|
|
|
|
/// queue.
|
2018-07-24 04:12:06 -07:00
|
|
|
#[derive(Debug)]
|
2018-08-02 14:27:55 -07:00
|
|
|
pub struct QueueingHoneyBadger<T, N>
|
2018-07-09 05:29:01 -07:00
|
|
|
where
|
2018-08-02 14:27:55 -07:00
|
|
|
T: Contribution + Serialize + for<'r> Deserialize<'r>,
|
2018-08-29 09:08:35 -07:00
|
|
|
N: NodeIdT + Serialize + for<'r> Deserialize<'r> + Rand,
|
2018-07-09 05:29:01 -07:00
|
|
|
{
|
|
|
|
/// The target number of transactions to be included in each batch.
|
|
|
|
batch_size: usize,
|
|
|
|
/// The internal `DynamicHoneyBadger` instance.
|
2018-08-02 14:27:55 -07:00
|
|
|
dyn_hb: DynamicHoneyBadger<Vec<T>, N>,
|
2018-07-09 05:29:01 -07:00
|
|
|
/// The queue of pending transactions that haven't been output in a batch yet.
|
2018-08-02 14:27:55 -07:00
|
|
|
queue: TransactionQueue<T>,
|
2018-07-09 05:29:01 -07:00
|
|
|
}
|
|
|
|
|
2018-08-02 14:27:55 -07:00
|
|
|
pub type Step<T, N> = messaging::Step<QueueingHoneyBadger<T, N>>;
|
2018-07-09 04:35:26 -07:00
|
|
|
|
2018-08-02 14:27:55 -07:00
|
|
|
impl<T, N> DistAlgorithm for QueueingHoneyBadger<T, N>
|
2018-07-09 05:29:01 -07:00
|
|
|
where
|
2018-08-02 14:27:55 -07:00
|
|
|
T: Contribution + Serialize + for<'r> Deserialize<'r> + Clone,
|
2018-08-29 09:08:35 -07:00
|
|
|
N: NodeIdT + Serialize + for<'r> Deserialize<'r> + Rand,
|
2018-07-09 05:29:01 -07:00
|
|
|
{
|
2018-08-29 09:08:35 -07:00
|
|
|
type NodeId = N;
|
2018-08-02 14:27:55 -07:00
|
|
|
type Input = Input<T, N>;
|
|
|
|
type Output = Batch<T, N>;
|
|
|
|
type Message = Message<N>;
|
2018-07-09 05:29:01 -07:00
|
|
|
type Error = Error;
|
|
|
|
|
2018-08-29 08:28:02 -07:00
|
|
|
fn handle_input(&mut self, input: Self::Input) -> Result<Step<T, N>> {
|
2018-07-09 05:29:01 -07:00
|
|
|
// User transactions are forwarded to `HoneyBadger` right away. Internal messages are
|
|
|
|
// in addition signed and broadcast.
|
2018-08-01 01:41:09 -07:00
|
|
|
let mut step = match input {
|
2018-07-09 05:29:01 -07:00
|
|
|
Input::User(tx) => {
|
|
|
|
self.queue.0.push_back(tx);
|
2018-08-01 01:41:09 -07:00
|
|
|
Step::default()
|
2018-07-09 05:29:01 -07:00
|
|
|
}
|
2018-08-01 01:41:09 -07:00
|
|
|
Input::Change(change) => self
|
2018-07-25 14:38:33 -07:00
|
|
|
.dyn_hb
|
2018-08-29 08:28:02 -07:00
|
|
|
.handle_input(Input::Change(change))
|
2018-07-25 14:38:33 -07:00
|
|
|
.map_err(ErrorKind::Input)?
|
2018-08-01 01:41:09 -07:00
|
|
|
.convert(),
|
|
|
|
};
|
|
|
|
step.extend(self.propose()?);
|
|
|
|
Ok(step)
|
2018-07-09 05:29:01 -07:00
|
|
|
}
|
|
|
|
|
2018-08-02 14:27:55 -07:00
|
|
|
fn handle_message(&mut self, sender_id: &N, message: Self::Message) -> Result<Step<T, N>> {
|
2018-07-21 01:18:08 -07:00
|
|
|
let mut step = self
|
|
|
|
.dyn_hb
|
2018-07-25 14:38:33 -07:00
|
|
|
.handle_message(sender_id, message)
|
|
|
|
.map_err(ErrorKind::HandleMessage)?
|
2018-07-21 01:18:08 -07:00
|
|
|
.convert::<Self>();
|
|
|
|
for batch in &step.output {
|
2018-07-09 05:29:01 -07:00
|
|
|
self.queue.remove_all(batch.iter());
|
|
|
|
}
|
2018-07-21 01:18:08 -07:00
|
|
|
step.extend(self.propose()?);
|
|
|
|
Ok(step)
|
2018-07-09 05:29:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn terminated(&self) -> bool {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2018-08-02 14:27:55 -07:00
|
|
|
fn our_id(&self) -> &N {
|
2018-07-09 05:29:01 -07:00
|
|
|
self.dyn_hb.our_id()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-02 14:27:55 -07:00
|
|
|
impl<T, N> QueueingHoneyBadger<T, N>
|
2018-07-09 05:29:01 -07:00
|
|
|
where
|
2018-08-02 14:27:55 -07:00
|
|
|
T: Contribution + Serialize + for<'r> Deserialize<'r> + Clone,
|
2018-08-29 09:08:35 -07:00
|
|
|
N: NodeIdT + Serialize + for<'r> Deserialize<'r> + Rand,
|
2018-07-09 05:29:01 -07:00
|
|
|
{
|
|
|
|
/// Returns a new `QueueingHoneyBadgerBuilder` configured to use the node IDs and cryptographic
|
|
|
|
/// keys specified by `netinfo`.
|
2018-08-02 14:27:55 -07:00
|
|
|
pub fn builder(dyn_hb: DynamicHoneyBadger<Vec<T>, N>) -> QueueingHoneyBadgerBuilder<T, N> {
|
2018-07-17 06:54:12 -07:00
|
|
|
QueueingHoneyBadgerBuilder::new(dyn_hb)
|
2018-07-09 05:29:01 -07:00
|
|
|
}
|
|
|
|
|
2018-07-13 08:30:02 -07:00
|
|
|
/// Returns a reference to the internal `DynamicHoneyBadger` instance.
|
2018-08-02 14:27:55 -07:00
|
|
|
pub fn dyn_hb(&self) -> &DynamicHoneyBadger<Vec<T>, N> {
|
2018-07-13 08:30:02 -07:00
|
|
|
&self.dyn_hb
|
|
|
|
}
|
|
|
|
|
2018-08-01 01:41:09 -07:00
|
|
|
/// Returns `true` if we are ready to propose our contribution for the next epoch, i.e. if the
|
|
|
|
/// previous epoch has completed and we have either pending transactions or we are required to
|
|
|
|
/// make a proposal to avoid stalling the network.
|
|
|
|
fn can_propose(&self) -> bool {
|
|
|
|
if self.dyn_hb.has_input() {
|
|
|
|
return false; // Previous epoch is still in progress.
|
|
|
|
}
|
|
|
|
!self.queue.0.is_empty() || self.dyn_hb.should_propose()
|
|
|
|
}
|
|
|
|
|
2018-07-09 05:29:01 -07:00
|
|
|
/// Initiates the next epoch by proposing a batch from the queue.
|
2018-08-02 14:27:55 -07:00
|
|
|
fn propose(&mut self) -> Result<Step<T, N>> {
|
2018-07-24 05:46:48 -07:00
|
|
|
let mut step = Step::default();
|
2018-08-01 01:41:09 -07:00
|
|
|
while self.can_propose() {
|
|
|
|
let amount = cmp::max(1, self.batch_size / self.dyn_hb.netinfo().num_nodes());
|
2018-07-12 08:53:12 -07:00
|
|
|
let proposal = self.queue.choose(amount, self.batch_size);
|
2018-07-25 14:38:33 -07:00
|
|
|
step.extend(
|
|
|
|
self.dyn_hb
|
2018-08-29 08:28:02 -07:00
|
|
|
.handle_input(Input::User(proposal))
|
2018-07-25 14:38:33 -07:00
|
|
|
.map_err(ErrorKind::Propose)?
|
|
|
|
.convert(),
|
|
|
|
);
|
2018-07-12 08:53:12 -07:00
|
|
|
}
|
2018-07-24 05:46:48 -07:00
|
|
|
Ok(step)
|
2018-07-09 05:29:01 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-02 14:27:55 -07:00
|
|
|
pub type Batch<T, N> = DhbBatch<Vec<T>, N>;
|