Merge branch 'master' into optional_threshold_encryption_209

This commit is contained in:
Logan Collins 2018-10-31 01:09:35 -05:00 committed by GitHub
commit ad5f725b2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 892 additions and 267 deletions

View File

@ -1,6 +1,6 @@
language: rust
rust:
- 1.29.0
- 1.30.0
cache:
cargo: true
timeout: 1200

View File

@ -58,7 +58,7 @@ _**Note:** Additional examples are currently in progress._
### Build
Requires Rust 1.29 or higher and `cargo`: [installation instructions.](https://www.rust-lang.org/en-US/install.html) The library is tested against the `stable` release channel.
Requires Rust 1.30 or higher and `cargo`: [installation instructions.](https://www.rust-lang.org/en-US/install.html) The library is tested against the `stable` release channel.
```
$ cargo build [--release]

4
ci.sh
View File

@ -9,8 +9,8 @@ export RUSTFLAGS="-D warnings -C target-cpu=native"
# Currently, mlocking secrets is disabled due to secure memory limit issues.
export MLOCK_SECRETS=false
cargo clippy --all-targets -- --deny clippy
cargo clippy --all-features --all-targets -- --deny clippy
cargo clippy --all-targets -- --deny clippy::all
cargo clippy --all-features --all-targets -- --deny clippy::all
cargo fmt -- --check
# We only test with mocktography, to ensure tests aren't unreasonably long.

View File

@ -2,12 +2,10 @@
//! running the distributed consensus state machine.
extern crate bincode;
extern crate crossbeam;
#[macro_use]
extern crate crossbeam_channel;
extern crate docopt;
extern crate env_logger;
extern crate hbbft;
#[macro_use]
extern crate log;
extern crate serde;
extern crate threshold_crypto as crypto;

View File

@ -1,12 +1,14 @@
//! Comms task structure. A comms task communicates with a remote node through a
//! socket. Local communication with coordinating threads is made via
//! `crossbeam_channel::unbounded()`.
use std::io;
use std::net::TcpStream;
use bincode;
use crossbeam;
use crossbeam_channel::{Receiver, Sender};
use log::{debug, info};
use serde::{de::DeserializeOwned, Serialize};
use std::io;
use std::net::TcpStream;
use hbbft::SourcedMessage;

View File

@ -1,7 +1,6 @@
//! The local message delivery system.
use crossbeam::{Scope, ScopedJoinHandle};
use crossbeam_channel;
use crossbeam_channel::{bounded, unbounded, Receiver, Sender};
use crossbeam_channel::{self, bounded, select_loop, unbounded, Receiver, Sender};
use hbbft::{SourcedMessage, Target, TargetedMessage};
/// The queue functionality for messages sent between algorithm instances.

View File

@ -44,6 +44,7 @@ use std::{io, iter, process, thread, time};
use crossbeam;
use crypto::poly::Poly;
use crypto::{SecretKey, SecretKeySet};
use log::{debug, error};
use hbbft::broadcast::{Broadcast, Message};
use hbbft::{DistAlgorithm, NetworkInfo, SourcedMessage};

View File

@ -5,10 +5,8 @@ extern crate env_logger;
extern crate hbbft;
extern crate itertools;
extern crate rand;
#[macro_use]
extern crate rand_derive;
extern crate serde;
#[macro_use(Deserialize, Serialize)]
extern crate serde_derive;
extern crate signifix;
@ -20,8 +18,10 @@ use colored::*;
use docopt::Docopt;
use itertools::Itertools;
use rand::{Isaac64Rng, Rng};
use rand_derive::Rand;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_derive::{Deserialize, Serialize};
use signifix::{metric, TryFrom};
use hbbft::dynamic_honey_badger::DynamicHoneyBadger;

View File

@ -1,12 +1,17 @@
use std::collections::BTreeMap;
use std::fmt::{self, Display};
use std::result;
use std::sync::Arc;
use bincode;
use log::debug;
use super::bool_multimap::BoolMultimap;
use super::bool_set::BoolSet;
use super::sbv_broadcast::{self, SbvBroadcast};
use super::{Error, Message, MessageContent, Nonce, Result, Step};
use super::{Error, Message, MessageContent, Result, Step};
use threshold_sign::{self, ThresholdSign};
use {DistAlgorithm, NetworkInfo, NodeIdT, Target};
use {DistAlgorithm, NetworkInfo, NodeIdT, SessionIdT, Target};
/// The state of the current epoch's coin. In some epochs this is fixed, in others it starts
/// with in `InProgress`.
@ -36,13 +41,11 @@ impl<N> From<bool> for CoinState<N> {
/// Binary Agreement instance
#[derive(Debug)]
pub struct BinaryAgreement<N> {
pub struct BinaryAgreement<N, S> {
/// Shared network information.
netinfo: Arc<NetworkInfo<N>>,
/// Session ID, e.g, the Honey Badger algorithm epoch.
session_id: u64,
/// The ID of the proposer of the value for this Binary Agreement instance.
proposer_id: N,
/// Session identifier, to prevent replaying messages in other instances.
session_id: S,
/// Binary Agreement algorithm epoch.
epoch: u32,
/// This epoch's Synchronized Binary Value Broadcast instance.
@ -69,19 +72,19 @@ pub struct BinaryAgreement<N> {
coin_state: CoinState<N>,
}
impl<N: NodeIdT> DistAlgorithm for BinaryAgreement<N> {
impl<N: NodeIdT, S: SessionIdT> DistAlgorithm for BinaryAgreement<N, S> {
type NodeId = N;
type Input = bool;
type Output = bool;
type Message = Message;
type Error = Error;
fn handle_input(&mut self, input: Self::Input) -> Result<Step<N>> {
fn handle_input(&mut self, input: Self::Input) -> Result<Step<N, S>> {
self.propose(input)
}
/// Receive input from a remote node.
fn handle_message(&mut self, sender_id: &Self::NodeId, msg: Message) -> Result<Step<N>> {
fn handle_message(&mut self, sender_id: &Self::NodeId, msg: Message) -> Result<Step<N, S>> {
self.handle_message(sender_id, msg)
}
@ -95,19 +98,13 @@ impl<N: NodeIdT> DistAlgorithm for BinaryAgreement<N> {
}
}
impl<N: NodeIdT> BinaryAgreement<N> {
/// Creates a new `BinaryAgreement` instance. The `session_id` and `proposer_id` are used to
/// uniquely identify this instance: its messages cannot be replayed in an instance with
/// different values.
// TODO: Use a generic type argument for that instead of something `Subset`-specific.
pub fn new(netinfo: Arc<NetworkInfo<N>>, session_id: u64, proposer_id: N) -> Result<Self> {
if !netinfo.is_node_validator(&proposer_id) {
return Err(Error::UnknownProposer);
}
impl<N: NodeIdT, S: SessionIdT> BinaryAgreement<N, S> {
/// Creates a new `BinaryAgreement` instance with the given session identifier, to prevent
/// replaying messages in other instances.
pub fn new(netinfo: Arc<NetworkInfo<N>>, session_id: S) -> Result<Self> {
Ok(BinaryAgreement {
netinfo: netinfo.clone(),
session_id,
proposer_id,
epoch: 0,
sbv_broadcast: SbvBroadcast::new(netinfo),
received_conf: BTreeMap::new(),
@ -126,13 +123,13 @@ impl<N: NodeIdT> BinaryAgreement<N> {
/// output. Otherwise either output is possible.
///
/// Note that if `can_propose` returns `false`, it is already too late to affect the outcome.
pub fn propose(&mut self, input: bool) -> Result<Step<N>> {
pub fn propose(&mut self, input: bool) -> Result<Step<N, S>> {
if !self.can_propose() {
return Ok(Step::default());
}
// Set the initial estimated value to the input value.
self.estimated = Some(input);
debug!("{:?}/{:?} Input {}", self.our_id(), self.proposer_id, input);
debug!("{}: Input {}", self, input);
let sbvb_step = self.sbv_broadcast.handle_input(input)?;
self.handle_sbvb_step(sbvb_step)
}
@ -140,7 +137,7 @@ impl<N: NodeIdT> BinaryAgreement<N> {
/// Handles a message received from `sender_id`.
///
/// This must be called with every message we receive from another node.
pub fn handle_message(&mut self, sender_id: &N, msg: Message) -> Result<Step<N>> {
pub fn handle_message(&mut self, sender_id: &N, msg: Message) -> Result<Step<N, S>> {
let Message { epoch, content } = msg;
if self.decision.is_some() || (epoch < self.epoch && content.can_expire()) {
// Message is obsolete: We are already in a later epoch or terminated.
@ -166,7 +163,7 @@ impl<N: NodeIdT> BinaryAgreement<N> {
&mut self,
sender_id: &N,
content: MessageContent,
) -> Result<Step<N>> {
) -> Result<Step<N, S>> {
match content {
MessageContent::SbvBroadcast(msg) => self.handle_sbv_broadcast(sender_id, msg),
MessageContent::Conf(v) => self.handle_conf(sender_id, v),
@ -180,14 +177,14 @@ impl<N: NodeIdT> BinaryAgreement<N> {
&mut self,
sender_id: &N,
msg: sbv_broadcast::Message,
) -> Result<Step<N>> {
) -> Result<Step<N, S>> {
let sbvb_step = self.sbv_broadcast.handle_message(sender_id, msg)?;
self.handle_sbvb_step(sbvb_step)
}
/// Handles a Synchronized Binary Value Broadcast step. On output, starts the `Conf` round or
/// decides.
fn handle_sbvb_step(&mut self, sbvb_step: sbv_broadcast::Step<N>) -> Result<Step<N>> {
fn handle_sbvb_step(&mut self, sbvb_step: sbv_broadcast::Step<N>) -> Result<Step<N, S>> {
let mut step = Step::default();
let output = step.extend_with(sbvb_step, |msg| {
MessageContent::SbvBroadcast(msg).with_epoch(self.epoch)
@ -213,7 +210,7 @@ impl<N: NodeIdT> BinaryAgreement<N> {
/// Handles a `Conf` message. When _N - f_ `Conf` messages with values in `bin_values` have
/// been received, updates the epoch or decides.
fn handle_conf(&mut self, sender_id: &N, v: BoolSet) -> Result<Step<N>> {
fn handle_conf(&mut self, sender_id: &N, v: BoolSet) -> Result<Step<N, S>> {
self.received_conf.insert(sender_id.clone(), v);
self.try_finish_conf_round()
}
@ -221,7 +218,7 @@ impl<N: NodeIdT> BinaryAgreement<N> {
/// Handles a `Term(v)` message. If we haven't yet decided on a value and there are more than
/// _f_ such messages with the same value from different nodes, performs expedite termination:
/// decides on `v`, broadcasts `Term(v)` and terminates the instance.
fn handle_term(&mut self, sender_id: &N, b: bool) -> Result<Step<N>> {
fn handle_term(&mut self, sender_id: &N, b: bool) -> Result<Step<N, S>> {
self.received_term[b].insert(sender_id.clone());
// Check for the expedite termination condition.
if self.decision.is_some() {
@ -239,7 +236,7 @@ impl<N: NodeIdT> BinaryAgreement<N> {
/// Handles a `ThresholdSign` message. If there is output, starts the next epoch. The function
/// may output a decision value.
fn handle_coin(&mut self, sender_id: &N, msg: threshold_sign::Message) -> Result<Step<N>> {
fn handle_coin(&mut self, sender_id: &N, msg: threshold_sign::Message) -> Result<Step<N, S>> {
let ts_step = match self.coin_state {
CoinState::Decided(_) => return Ok(Step::default()), // Coin value is already decided.
CoinState::InProgress(ref mut ts) => ts
@ -250,7 +247,7 @@ impl<N: NodeIdT> BinaryAgreement<N> {
}
/// Multicasts a `Conf(values)` message, and handles it.
fn send_conf(&mut self, values: BoolSet) -> Result<Step<N>> {
fn send_conf(&mut self, values: BoolSet) -> Result<Step<N, S>> {
if self.conf_values.is_some() {
// Only one `Conf` message is allowed in an epoch.
return Ok(Step::default());
@ -267,11 +264,11 @@ impl<N: NodeIdT> BinaryAgreement<N> {
}
/// Multicasts and handles a message. Does nothing if we are only an observer.
fn send(&mut self, content: MessageContent) -> Result<Step<N>> {
fn send(&mut self, content: MessageContent) -> Result<Step<N, S>> {
if !self.netinfo.is_validator() {
return Ok(Step::default());
}
let step: Step<_> = Target::All
let step: Step<N, S> = Target::All
.message(content.clone().with_epoch(self.epoch))
.into();
let our_id = &self.our_id().clone();
@ -279,7 +276,7 @@ impl<N: NodeIdT> BinaryAgreement<N> {
}
/// Handles a step returned from the `ThresholdSign`.
fn on_coin_step(&mut self, ts_step: threshold_sign::Step<N>) -> Result<Step<N>> {
fn on_coin_step(&mut self, ts_step: threshold_sign::Step<N>) -> Result<Step<N, S>> {
let mut step = Step::default();
let epoch = self.epoch;
let to_msg = |c_msg| MessageContent::Coin(Box::new(c_msg)).with_epoch(epoch);
@ -298,7 +295,7 @@ impl<N: NodeIdT> BinaryAgreement<N> {
/// With two conf values, the next epoch's estimate is the coin value. If there is only one conf
/// value and that disagrees with the coin, the conf value is the next epoch's estimate. If
/// the unique conf value agrees with the coin, terminates and decides on that value.
fn try_update_epoch(&mut self) -> Result<Step<N>> {
fn try_update_epoch(&mut self) -> Result<Step<N, S>> {
if self.decision.is_some() {
// Avoid an infinite regression without making a Binary Agreement step.
return Ok(Step::default());
@ -321,24 +318,19 @@ impl<N: NodeIdT> BinaryAgreement<N> {
/// Creates the initial coin state for the current epoch, i.e. sets it to the predetermined
/// value, or initializes a `ThresholdSign` instance.
fn coin_state(&self) -> CoinState<N> {
match self.epoch % 3 {
fn coin_state(&self) -> Result<CoinState<N>> {
Ok(match self.epoch % 3 {
0 => CoinState::Decided(true),
1 => CoinState::Decided(false),
_ => {
let nonce = Nonce::new(
self.netinfo.invocation_id().as_ref(),
self.session_id,
self.netinfo.node_index(&self.proposer_id).unwrap(),
self.epoch,
);
CoinState::InProgress(Box::new(ThresholdSign::new(self.netinfo.clone(), nonce)))
let coin_id = bincode::serialize(&(&self.session_id, self.epoch))?;
CoinState::InProgress(Box::new(ThresholdSign::new(self.netinfo.clone(), coin_id)))
}
}
})
}
/// Decides on a value and broadcasts a `Term` message with that value.
fn decide(&mut self, b: bool) -> Step<N> {
fn decide(&mut self, b: bool) -> Step<N, S> {
if self.decision.is_some() {
return Step::default();
}
@ -347,13 +339,7 @@ impl<N: NodeIdT> BinaryAgreement<N> {
step.output.push(b);
// Latch the decided state.
self.decision = Some(b);
debug!(
"{:?}/{:?} (is_validator: {}) decision: {}",
self.our_id(),
self.proposer_id,
self.netinfo.is_validator(),
b
);
debug!("{}: decision: {}", self, b);
if self.netinfo.is_validator() {
let msg = MessageContent::Term(b).with_epoch(self.epoch + 1);
step.messages.push(Target::All.message(msg));
@ -362,7 +348,7 @@ impl<N: NodeIdT> BinaryAgreement<N> {
}
/// Checks whether the _N - f_ `Conf` messages have arrived, and if so, activates the coin.
fn try_finish_conf_round(&mut self) -> Result<Step<N>> {
fn try_finish_conf_round(&mut self) -> Result<Step<N, S>> {
if self.conf_values.is_none() || self.count_conf() < self.netinfo.num_correct() {
return Ok(Step::default());
}
@ -382,7 +368,7 @@ impl<N: NodeIdT> BinaryAgreement<N> {
}
/// Increments the epoch, sets the new estimate and handles queued messages.
fn update_epoch(&mut self, b: bool) -> Result<Step<N>> {
fn update_epoch(&mut self, b: bool) -> Result<Step<N, S>> {
self.sbv_broadcast.clear(&self.received_term);
self.received_conf.clear();
for (v, id) in &self.received_term {
@ -390,12 +376,10 @@ impl<N: NodeIdT> BinaryAgreement<N> {
}
self.conf_values = None;
self.epoch += 1;
self.coin_state = self.coin_state();
self.coin_state = self.coin_state()?;
debug!(
"{:?} BinaryAgreement instance {:?} started epoch {}, {} terminated",
self.our_id(),
self.proposer_id,
self.epoch,
"{}: epoch started, {} terminated",
self,
self.received_conf.len(),
);
@ -416,3 +400,20 @@ impl<N: NodeIdT> BinaryAgreement<N> {
Ok(step)
}
}
impl<N: NodeIdT, S: SessionIdT> Display for BinaryAgreement<N, S> {
fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
write!(
f,
"{:?} BA {} epoch {} ({})",
self.our_id(),
self.session_id,
self.epoch,
if self.netinfo.is_validator() {
"validator"
} else {
"observer"
}
)
}
}

View File

@ -1,3 +1,6 @@
use rand_derive::Rand;
use serde_derive::{Deserialize, Serialize};
/// The empty set of boolean values.
pub const NONE: BoolSet = BoolSet(0b00);

View File

@ -68,12 +68,17 @@ mod bool_multimap;
pub mod bool_set;
mod sbv_broadcast;
use bincode;
use failure::Fail;
use rand;
use rand_derive::Rand;
use serde_derive::{Deserialize, Serialize};
use self::bool_set::BoolSet;
use threshold_sign;
pub use self::binary_agreement::BinaryAgreement;
pub use self::sbv_broadcast::Message as SbvMessage;
/// An Binary Agreement error.
#[derive(Clone, Eq, PartialEq, Debug, Fail)]
@ -82,14 +87,23 @@ pub enum Error {
HandleThresholdSign(threshold_sign::Error),
#[fail(display = "Error invoking the common coin: {}", _0)]
InvokeCoin(threshold_sign::Error),
#[fail(display = "Unknown proposer")]
UnknownProposer,
// Strings because `io` and `bincode` errors lack `Eq` and `Clone`.
#[fail(display = "Error writing epoch for nonce: {}", _0)]
Io(String),
#[fail(display = "Error serializing session ID for nonce: {}", _0)]
Serialize(String),
}
impl From<bincode::Error> for Error {
fn from(err: bincode::Error) -> Error {
Error::Io(format!("{:?}", err))
}
}
/// An Binary Agreement result.
pub type Result<T> = ::std::result::Result<T, Error>;
pub type Step<N> = ::Step<BinaryAgreement<N>>;
pub type Step<N, T> = ::Step<BinaryAgreement<N, T>>;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum MessageContent {
@ -144,26 +158,3 @@ impl rand::Rand for MessageContent {
}
}
}
#[derive(Clone, Debug)]
struct Nonce(Vec<u8>);
impl Nonce {
pub fn new(
invocation_id: &[u8],
session_id: u64,
proposer_id: usize,
binary_agreement_epoch: u32,
) -> Self {
Nonce(Vec::from(format!(
"Nonce for Honey Badger {:?}@{}:{}:{}",
invocation_id, session_id, binary_agreement_epoch, proposer_id
)))
}
}
impl AsRef<[u8]> for Nonce {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}

View File

@ -9,9 +9,11 @@
//! It will only output once, but can continue handling messages and will keep track of the set
//! `bin_values` of values for which _2 f + 1_ `BVal`s were received.
use rand;
use std::sync::Arc;
use rand;
use serde_derive::{Deserialize, Serialize};
use super::bool_multimap::BoolMultimap;
use super::bool_set::{self, BoolSet};
use super::{Error, Result};
@ -145,7 +147,7 @@ impl<N: NodeIdT> SbvBroadcast<N> {
/// Multicasts and handles a message. Does nothing if we are only an observer.
fn send(&mut self, msg: Message) -> Result<Step<N>> {
if !self.netinfo.is_validator() {
return Ok(Step::default());
return self.try_output();
}
let step: Step<_> = Target::All.message(msg.clone()).into();
let our_id = &self.our_id().clone();

View File

@ -3,6 +3,7 @@ use std::sync::Arc;
use byteorder::{BigEndian, ByteOrder};
use hex_fmt::{HexFmt, HexList};
use log::{debug, error, info};
use reed_solomon_erasure as rse;
use reed_solomon_erasure::ReedSolomon;

View File

@ -1,3 +1,4 @@
use failure::Fail;
use reed_solomon_erasure as rse;
/// A broadcast error.

View File

@ -1,5 +1,6 @@
use std::mem;
use serde_derive::{Deserialize, Serialize};
use tiny_keccak::sha3_256;
pub type Digest = [u8; 32];

View File

@ -2,6 +2,7 @@ use std::fmt::{self, Debug};
use hex_fmt::HexFmt;
use rand;
use serde_derive::{Deserialize, Serialize};
use super::merkle::{Digest, MerkleTree, Proof};

View File

@ -102,6 +102,7 @@ where
let max_future_epochs = *max_future_epochs;
let arc_netinfo = Arc::new(netinfo.clone());
let honey_badger = HoneyBadger::builder(arc_netinfo.clone())
.session_id(epoch)
.max_future_epochs(max_future_epochs)
.rng(rng.sub_rng())
.subset_handling_strategy(subset_handling_strategy.clone())

View File

@ -1,5 +1,6 @@
use crypto::PublicKey;
use threshold_decryption::EncryptionSchedule;
use serde_derive::{Deserialize, Serialize};
#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash, Debug)]
pub enum NodeChange<N> {

View File

@ -4,6 +4,8 @@ use std::sync::Arc;
use bincode;
use crypto::Signature;
use derivative::Derivative;
use log::{debug, info};
use rand::{self, Rand};
use serde::{de::DeserializeOwned, Serialize};
@ -379,6 +381,7 @@ where
let netinfo = Arc::new(self.netinfo.clone());
self.vote_counter = VoteCounter::new(netinfo.clone(), epoch);
self.honey_badger = HoneyBadger::builder(netinfo)
.session_id(epoch)
.max_future_epochs(self.max_future_epochs)
.rng(self.rng.sub_rng())
.encryption_schedule(if let Some(schedule) = encryption_schedule {

View File

@ -64,6 +64,7 @@ mod votes;
use crypto::{PublicKey, PublicKeySet, Signature};
use rand::Rand;
use serde_derive::{Deserialize, Serialize};
use std::collections::BTreeMap;
use self::votes::{SignedVote, VoteCounter};

View File

@ -4,6 +4,7 @@ use std::sync::Arc;
use bincode;
use crypto::Signature;
use serde::Serialize;
use serde_derive::{Deserialize, Serialize};
use super::{Change, ErrorKind, Result};
use fault_log::{FaultKind, FaultLog};

View File

@ -15,6 +15,9 @@ use {Contribution, NetworkInfo, NodeIdT};
pub struct HoneyBadgerBuilder<C, N> {
/// Shared network data.
netinfo: Arc<NetworkInfo<N>>,
/// A session identifier. Different session IDs foil replay attacks in two instances with the
/// same epoch numbers and the same validators.
session_id: u64,
/// Start in this epoch.
epoch: u64,
/// The maximum number of future epochs for which we handle messages simultaneously.
@ -38,6 +41,7 @@ where
pub fn new(netinfo: Arc<NetworkInfo<N>>) -> Self {
HoneyBadgerBuilder {
netinfo,
session_id: 0,
epoch: 0,
max_future_epochs: 3,
rng: Box::new(rand::thread_rng()),
@ -53,6 +57,15 @@ where
self
}
/// Sets the session identifier.
///
/// Different session IDs foil replay attacks in two instances with the same epoch numbers and
/// the same validators.
pub fn session_id(&mut self, session_id: u64) -> &mut Self {
self.session_id = session_id;
self
}
/// Sets the starting epoch to the given value.
pub fn epoch(&mut self, epoch: u64) -> &mut Self {
self.epoch = epoch;
@ -84,6 +97,7 @@ where
pub fn build(&mut self) -> HoneyBadger<C, N> {
HoneyBadger {
netinfo: self.netinfo.clone(),
session_id: self.session_id,
epoch: self.epoch,
has_input: false,
epochs: BTreeMap::new(),

View File

@ -2,14 +2,18 @@
use std::collections::btree_map::Entry;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::{self, Display};
use std::marker::PhantomData;
use std::mem::replace;
use std::result;
use std::sync::Arc;
use bincode;
use crypto::Ciphertext;
use log::{debug, error, warn};
use rand::{Rand, Rng};
use serde::{de::DeserializeOwned, Serialize};
use serde_derive::Serialize;
use super::{Batch, ErrorKind, MessageContent, Result, Step};
use fault_log::{Fault, FaultKind, FaultLog};
@ -17,6 +21,8 @@ use subset::{self as cs, Subset, SubsetOutput};
use threshold_decryption::{self as td, ThresholdDecryption};
use {Contribution, DistAlgorithm, NetworkInfo, NodeIdT};
type CsStep<N> = cs::Step<N, EpochId>;
/// The status of an encrypted contribution.
#[derive(Debug)]
enum DecryptionState<N> {
@ -56,7 +62,7 @@ where
#[derive(Debug)]
enum SubsetState<N: Rand> {
/// The algorithm is ongoing: the set of accepted contributions is still undecided.
Ongoing(Subset<N>),
Ongoing(Subset<N, EpochId>),
/// The algorithm is complete. This contains the set of accepted proposers.
Complete(BTreeSet<N>),
}
@ -66,7 +72,7 @@ where
N: NodeIdT + Rand,
{
/// Provides input to the Subset instance, unless it has already completed.
fn handle_input(&mut self, proposal: Vec<u8>) -> Result<cs::Step<N>> {
fn handle_input(&mut self, proposal: Vec<u8>) -> Result<CsStep<N>> {
match self {
SubsetState::Ongoing(ref mut cs) => cs.handle_input(proposal),
SubsetState::Complete(_) => return Ok(cs::Step::default()),
@ -74,7 +80,7 @@ where
}
/// Handles a message in the Subset instance, unless it has already completed.
fn handle_message(&mut self, sender_id: &N, msg: cs::Message<N>) -> Result<cs::Step<N>> {
fn handle_message(&mut self, sender_id: &N, msg: cs::Message<N>) -> Result<CsStep<N>> {
match self {
SubsetState::Ongoing(ref mut cs) => cs.handle_message(sender_id, msg),
SubsetState::Complete(_) => return Ok(cs::Step::default()),
@ -197,11 +203,13 @@ where
/// Creates a new `Subset` instance.
pub fn new(
netinfo: Arc<NetworkInfo<N>>,
hb_id: u64,
epoch: u64,
subset_handling_strategy: SubsetHandlingStrategy,
require_decryption: bool,
) -> Result<Self> {
let cs = Subset::new(netinfo.clone(), epoch).map_err(ErrorKind::CreateSubset)?;
let epoch_id = EpochId { hb_id, epoch };
let cs = Subset::new(netinfo.clone(), &epoch_id).map_err(ErrorKind::CreateSubset)?;
Ok(EpochState {
epoch,
netinfo,
@ -304,7 +312,7 @@ where
}
/// Checks whether the subset has output, and if it does, sends out our decryption shares.
fn process_subset(&mut self, cs_step: cs::Step<N>) -> Result<Step<C, N>> {
fn process_subset(&mut self, cs_step: CsStep<N>) -> Result<Step<C, N>> {
let mut step = Step::default();
let cs_outputs = step.extend_with(cs_step, |cs_msg| {
MessageContent::Subset(cs_msg).with_epoch(self.epoch)
@ -396,3 +404,17 @@ where
}
}
}
/// A session identifier for a `Subset` sub-algorithm run within an epoch. It consists of the epoch
/// number, and an optional `HoneyBadger` session identifier.
#[derive(Clone, Debug, Serialize)]
struct EpochId {
hb_id: u64,
epoch: u64,
}
impl Display for EpochId {
fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
write!(f, "{}/{}", self.hb_id, self.epoch)
}
}

View File

@ -2,6 +2,8 @@ use std::collections::btree_map::Entry;
use std::collections::BTreeMap;
use std::sync::Arc;
use bincode;
use derivative::Derivative;
use rand::{Rand, Rng};
use serde::{de::DeserializeOwned, Serialize};
@ -18,6 +20,9 @@ use threshold_decryption::EncryptionSchedule;
pub struct HoneyBadger<C, N: Rand> {
/// Shared network data.
pub(super) netinfo: Arc<NetworkInfo<N>>,
/// A session identifier. Different session IDs foil replay attacks in two instances with the
/// same epoch numbers and the same validators.
pub(super) session_id: u64,
/// The earliest epoch from which we have not yet received output.
pub(super) epoch: u64,
/// Whether we have already submitted a proposal for the current epoch.
@ -184,6 +189,7 @@ where
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => entry.insert(EpochState::new(
self.netinfo.clone(),
self.session_id,
epoch,
self.subset_handling_strategy.clone(),
self.encryption_schedule.use_on_epoch(epoch),

View File

@ -1,4 +1,6 @@
use rand::Rand;
use rand_derive::Rand;
use serde_derive::{Deserialize, Serialize};
use subset;
use threshold_decryption;

View File

@ -117,20 +117,15 @@
extern crate bincode;
extern crate byteorder;
#[macro_use(Derivative)]
extern crate derivative;
#[macro_use]
extern crate failure;
extern crate hex_fmt;
extern crate init_with;
#[macro_use]
extern crate log;
extern crate rand;
#[macro_use]
extern crate rand_derive;
extern crate reed_solomon_erasure;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate tiny_keccak;
@ -157,4 +152,4 @@ pub use crypto::pairing;
pub use fault_log::{Fault, FaultKind, FaultLog};
pub use messaging::{SourcedMessage, Target, TargetedMessage};
pub use network_info::NetworkInfo;
pub use traits::{Contribution, DistAlgorithm, Message, NodeIdT, Step};
pub use traits::{Contribution, DistAlgorithm, Message, NodeIdT, SessionIdT, Step};

View File

@ -128,23 +128,13 @@ impl<N: NodeIdT> NetworkInfo<N> {
&self.public_keys
}
/// The index of a node in a canonical numbering of all nodes.
/// The index of a node in a canonical numbering of all nodes. This is the index where the
/// node appears in `all_ids`.
#[inline]
pub fn node_index(&self, id: &N) -> Option<usize> {
self.node_indices.get(id).cloned()
}
/// Returns the unique ID of the Honey Badger invocation.
///
/// FIXME: Using the public key as the invocation ID either requires agreeing on the keys on
/// each invocation, or makes it unsafe to reuse keys for different invocations. A better
/// invocation ID would be one that is distributed to all nodes on each invocation and would be
/// independent from the public key, so that reusing keys would be safer.
#[inline]
pub fn invocation_id(&self) -> Vec<u8> {
self.public_key_set.public_key().to_bytes()
}
/// Returns `true` if this node takes part in the consensus itself. If not, it is only an
/// observer.
#[inline]

View File

@ -26,6 +26,7 @@ use std::fmt::{self, Display};
use std::marker::PhantomData;
use std::{cmp, iter};
use derivative::Derivative;
use failure::{Backtrace, Context, Fail};
use rand::{Rand, Rng};
use serde::{de::DeserializeOwned, Serialize};

View File

@ -23,16 +23,25 @@
//! * Once all `BinaryAgreement` instances have decided, `Subset` returns the set of all proposed
//! values for which the decision was "yes".
use std::borrow::Borrow;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::{self, Display};
use std::result;
use std::sync::Arc;
use failure::Fail;
use hex_fmt::HexFmt;
use log::{debug, error};
use rand_derive::Rand;
use serde_derive::{Deserialize, Serialize};
use binary_agreement::{self, BinaryAgreement};
use binary_agreement;
use broadcast::{self, Broadcast};
use rand::Rand;
use {DistAlgorithm, NetworkInfo, NodeIdT};
use {DistAlgorithm, NetworkInfo, NodeIdT, SessionIdT};
type BaInstance<N, S> = binary_agreement::BinaryAgreement<N, BaSessionId<S>>;
type BaStep<N, S> = binary_agreement::Step<N, BaSessionId<S>>;
/// A subset error.
#[derive(Clone, PartialEq, Debug, Fail)]
@ -56,7 +65,7 @@ pub enum Error {
}
/// A subset result.
pub type Result<T> = ::std::result::Result<T, Error>;
pub type Result<T> = result::Result<T, Error>;
// TODO: Make this a generic argument of `Subset`.
type ProposedValue = Vec<u8>;
@ -66,18 +75,18 @@ type ProposedValue = Vec<u8>;
pub enum Message<N: Rand> {
/// A message for the broadcast algorithm concerning the set element proposed by the given node.
Broadcast(N, broadcast::Message),
/// A message for the Binary Agreement algorithm concerning the set element proposed by the given
/// node.
/// A message for the Binary Agreement algorithm concerning the set element proposed by the
/// given node.
BinaryAgreement(N, binary_agreement::Message),
}
/// Subset algorithm instance
#[derive(Debug)]
pub struct Subset<N: Rand> {
pub struct Subset<N: Rand, S> {
/// Shared network information.
netinfo: Arc<NetworkInfo<N>>,
broadcast_instances: BTreeMap<N, Broadcast<N>>,
ba_instances: BTreeMap<N, BinaryAgreement<N>>,
ba_instances: BTreeMap<N, BaInstance<N, S>>,
/// `None` means that that item has already been output.
broadcast_results: BTreeMap<N, Option<ProposedValue>>,
ba_results: BTreeMap<N, bool>,
@ -85,25 +94,25 @@ pub struct Subset<N: Rand> {
decided: bool,
}
pub type Step<N> = ::Step<Subset<N>>;
pub type Step<N, S> = ::Step<Subset<N, S>>;
impl<N: NodeIdT + Rand> DistAlgorithm for Subset<N> {
impl<N: NodeIdT + Rand, S: SessionIdT> DistAlgorithm for Subset<N, S> {
type NodeId = N;
type Input = ProposedValue;
type Output = SubsetOutput<N>;
type Message = Message<N>;
type Error = Error;
fn handle_input(&mut self, input: Self::Input) -> Result<Step<N>> {
fn handle_input(&mut self, input: Self::Input) -> Result<Step<N, S>> {
self.propose(input)
}
fn handle_message(&mut self, sender_id: &N, message: Message<N>) -> Result<Step<N>> {
fn handle_message(&mut self, sender_id: &N, message: Message<N>) -> Result<Step<N, S>> {
self.handle_message(sender_id, message)
}
fn terminated(&self) -> bool {
self.ba_instances.values().all(BinaryAgreement::terminated)
self.ba_instances.values().all(BaInstance::terminated)
}
fn our_id(&self) -> &Self::NodeId {
@ -117,12 +126,12 @@ pub enum SubsetOutput<N> {
Done,
}
impl<N: NodeIdT + Rand> Subset<N> {
impl<N: NodeIdT + Rand, S: SessionIdT> Subset<N, S> {
/// Creates a new `Subset` instance with the given session identifier.
///
/// If multiple `Subset`s are instantiated within a single network, they must use different
/// session identifiers to foil replay attacks.
pub fn new(netinfo: Arc<NetworkInfo<N>>, session_id: u64) -> Result<Self> {
pub fn new<T: Borrow<S>>(netinfo: Arc<NetworkInfo<N>>, session_id: T) -> Result<Self> {
// Create all broadcast instances.
let mut broadcast_instances: BTreeMap<N, Broadcast<N>> = BTreeMap::new();
for proposer_id in netinfo.all_ids() {
@ -134,12 +143,15 @@ impl<N: NodeIdT + Rand> Subset<N> {
}
// Create all Binary Agreement instances.
let mut ba_instances: BTreeMap<N, BinaryAgreement<N>> = BTreeMap::new();
for proposer_id in netinfo.all_ids() {
let mut ba_instances: BTreeMap<N, BaInstance<N, S>> = BTreeMap::new();
for (proposer_idx, proposer_id) in netinfo.all_ids().enumerate() {
let s_id = BaSessionId {
subset_id: session_id.borrow().clone(),
proposer_idx: proposer_idx as u32,
};
ba_instances.insert(
proposer_id.clone(),
BinaryAgreement::new(netinfo.clone(), session_id, proposer_id.clone())
.map_err(Error::NewBinaryAgreement)?,
BaInstance::new(netinfo.clone(), s_id).map_err(Error::NewBinaryAgreement)?,
);
}
@ -156,7 +168,7 @@ impl<N: NodeIdT + Rand> Subset<N> {
/// Proposes a value for the subset.
///
/// Returns an error if we already made a proposal.
pub fn propose(&mut self, value: ProposedValue) -> Result<Step<N>> {
pub fn propose(&mut self, value: ProposedValue) -> Result<Step<N, S>> {
if !self.netinfo.is_validator() {
return Ok(Step::default());
}
@ -168,7 +180,7 @@ impl<N: NodeIdT + Rand> Subset<N> {
/// Handles a message received from `sender_id`.
///
/// This must be called with every message we receive from another node.
pub fn handle_message(&mut self, sender_id: &N, message: Message<N>) -> Result<Step<N>> {
pub fn handle_message(&mut self, sender_id: &N, message: Message<N>) -> Result<Step<N, S>> {
match message {
Message::Broadcast(p_id, b_msg) => self.handle_broadcast(sender_id, &p_id, b_msg),
Message::BinaryAgreement(p_id, a_msg) => {
@ -189,7 +201,7 @@ impl<N: NodeIdT + Rand> Subset<N> {
sender_id: &N,
proposer_id: &N,
bmessage: broadcast::Message,
) -> Result<Step<N>> {
) -> Result<Step<N, S>> {
self.process_broadcast(proposer_id, |bc| bc.handle_message(sender_id, bmessage))
}
@ -200,7 +212,7 @@ impl<N: NodeIdT + Rand> Subset<N> {
sender_id: &N,
proposer_id: &N,
amessage: binary_agreement::Message,
) -> Result<Step<N>> {
) -> Result<Step<N, S>> {
// Send the message to the local instance of Binary Agreement.
self.process_binary_agreement(proposer_id, |binary_agreement| {
binary_agreement.handle_message(sender_id, amessage)
@ -209,7 +221,7 @@ impl<N: NodeIdT + Rand> Subset<N> {
/// 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: &N, f: F) -> Result<Step<N>>
fn process_broadcast<F>(&mut self, proposer_id: &N, f: F) -> Result<Step<N, S>>
where
F: FnOnce(&mut Broadcast<N>) -> result::Result<broadcast::Step<N>, broadcast::Error>,
{
@ -246,15 +258,16 @@ impl<N: NodeIdT + Rand> Subset<N> {
{
error!("Duplicate insert in broadcast_results: {:?}", inval)
}
let set_binary_agreement_input = |ba: &mut BinaryAgreement<N>| ba.handle_input(true);
Ok(step.join(self.process_binary_agreement(proposer_id, set_binary_agreement_input)?))
let set_binary_agreement_input = |ba: &mut BaInstance<N, S>| ba.handle_input(true);
step.extend(self.process_binary_agreement(proposer_id, set_binary_agreement_input)?);
Ok(step.with_output(self.try_binary_agreement_completion()))
}
/// Callback to be invoked on receipt of the decision value of the Binary Agreement
/// instance `id`.
fn process_binary_agreement<F>(&mut self, proposer_id: &N, f: F) -> Result<Step<N>>
fn process_binary_agreement<F>(&mut self, proposer_id: &N, f: F) -> Result<Step<N, S>>
where
F: FnOnce(&mut BinaryAgreement<N>) -> binary_agreement::Result<binary_agreement::Step<N>>,
F: FnOnce(&mut BaInstance<N, S>) -> binary_agreement::Result<BaStep<N, S>>,
{
let mut step = Step::default();
let accepted = {
@ -351,7 +364,8 @@ impl<N: NodeIdT + Rand> Subset<N> {
.map(|(k, _)| k)
.collect();
debug!(
"Binary Agreement instances that delivered 1: {:?}",
"{:?} Binary Agreement instances that delivered 1: {:?}",
self.our_id(),
delivered_1
);
@ -372,3 +386,22 @@ impl<N: NodeIdT + Rand> Subset<N> {
}
}
}
/// A session identifier for a `BinaryAgreement` instance run as a `Subset` sub-algorithm. It
/// consists of the `Subset` instance's own session ID, and the index of the proposer whose
/// contribution this `BinaryAgreement` is about.
#[derive(Clone, Debug, Serialize)]
struct BaSessionId<S> {
subset_id: S,
proposer_idx: u32,
}
impl<S: Display> Display for BaSessionId<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
write!(
f,
"subset {}, proposer #{}",
self.subset_id, self.proposer_idx
)
}
}

View File

@ -175,11 +175,13 @@ use crypto::{
error::Error as CryptoError,
poly::{BivarCommitment, BivarPoly, Poly},
serde_impl::field_vec::FieldWrap,
Ciphertext, PublicKey, PublicKeySet, SecretKey, SecretKeyShare,
Ciphertext, Fr, G1Affine, PublicKey, PublicKeySet, SecretKey, SecretKeyShare,
};
use crypto::{Fr, G1Affine};
use failure::Fail;
use log::error;
use pairing::{CurveAffine, Field};
use rand;
use serde_derive::{Deserialize, Serialize};
use {NetworkInfo, NodeIdT};

View File

@ -14,6 +14,10 @@ use std::collections::BTreeMap;
use std::sync::Arc;
use crypto::{self, Ciphertext, DecryptionShare};
use failure::Fail;
use rand_derive::Rand;
use serde_derive::{Deserialize, Serialize};
use fault_log::{Fault, FaultKind, FaultLog};
use {DistAlgorithm, NetworkInfo, NodeIdT, Target};
@ -115,6 +119,10 @@ impl<N: NodeIdT> ThresholdDecryption<N> {
if self.ciphertext.is_some() {
return Err(Error::MultipleInputs(Box::new(ct)));
}
if !self.netinfo.is_validator() {
self.ciphertext = Some(ct);
return Ok(self.try_output()?);
}
let share = match self.netinfo.secret_key_share().decrypt_share(&ct) {
None => return Err(Error::InvalidCiphertext(Box::new(ct))),
Some(share) => share,
@ -123,11 +131,9 @@ impl<N: NodeIdT> ThresholdDecryption<N> {
let our_id = self.our_id().clone();
let mut step = Step::default();
step.fault_log.extend(self.remove_invalid_shares());
if self.netinfo.is_validator() {
let msg = Target::All.message(Message(share.clone()));
step.messages.push(msg);
self.shares.insert(our_id, share);
}
let msg = Target::All.message(Message(share.clone()));
step.messages.push(msg);
self.shares.insert(our_id, share);
step.extend(self.try_output()?);
Ok(step)
}

View File

@ -17,6 +17,11 @@ use std::collections::BTreeMap;
use std::sync::Arc;
use crypto::{self, hash_g2, Signature, SignatureShare, G2};
use failure::Fail;
use log::{debug, error};
use rand_derive::Rand;
use serde_derive::{Deserialize, Serialize};
use fault_log::{Fault, FaultKind};
use {DistAlgorithm, NetworkInfo, NodeIdT, Target};

View File

@ -1,15 +1,16 @@
//! Common supertraits for distributed algorithms.
use std::fmt::Debug;
use std::fmt::{Debug, Display};
use std::hash::Hash;
use std::iter::once;
use failure::Fail;
use serde::Serialize;
use fault_log::{Fault, FaultLog};
use TargetedMessage;
/// A transaction, user message, etc.
/// A transaction, user message, or other user data.
pub trait Contribution: Eq + Debug + Hash + Send + Sync {}
impl<C> Contribution for C where C: Eq + Debug + Hash + Send + Sync {}
@ -21,8 +22,32 @@ impl<N> NodeIdT for N where N: Eq + Ord + Clone + Debug + Hash + Send + Sync {}
pub trait Message: Debug + Send + Sync {}
impl<M> Message for M where M: Debug + Send + Sync {}
/// Result of one step of the local state machine of a distributed algorithm. Such a result should
/// be used and never discarded by the client of the algorithm.
/// Session identifiers.
pub trait SessionIdT: Display + Serialize + Send + Sync + Clone {}
impl<S> SessionIdT for S where S: Display + Serialize + Send + Sync + Clone {}
/// Single algorithm step outcome.
///
/// Each time input (typically in the form of user input or incoming network messages) is provided
/// to an instance of an algorithm, a `Step` is produced, potentially containing output values,
/// a fault log, and network messages.
///
/// Any `Step` **must always be used** by the client application; at the very least the resulting
/// messages must be queued.
///
/// ## Handling unused Steps
///
/// In the (rare) case of a `Step` not being of any interest at all, instead of discarding it
/// through `let _ = ...` or similar constructs, the implicit assumption should explicitly be
/// checked instead:
///
/// ```ignore
/// assert!(alg.propose(123).expect("Could not propose value").is_empty(),
/// "Algorithm will never output anything on first proposal");
/// ```
///
/// If an edge case occurs and outgoing messages are generated as a result, the `assert!` will
/// catch it, instead of potentially stalling the algorithm.
#[must_use = "The algorithm step result must be used."]
#[derive(Debug)]
pub struct Step<D>
@ -125,7 +150,7 @@ where
}
}
/// Returns `true` if there are now messages, faults or outputs.
/// Returns `true` if there are no messages, faults or outputs.
pub fn is_empty(&self) -> bool {
self.output.is_empty() && self.fault_log.is_empty() && self.messages.is_empty()
}

View File

@ -16,13 +16,10 @@
extern crate env_logger;
extern crate hbbft;
#[macro_use]
extern crate log;
extern crate rand;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate rand_derive;
extern crate serde_derive;
extern crate threshold_crypto as crypto;
mod network;
@ -30,6 +27,7 @@ mod network;
use std::iter::once;
use std::sync::Arc;
use log::info;
use rand::Rng;
use hbbft::binary_agreement::BinaryAgreement;
@ -37,8 +35,8 @@ use hbbft::NetworkInfo;
use network::{Adversary, MessageScheduler, NodeId, SilentAdversary, TestNetwork, TestNode};
fn test_binary_agreement<A: Adversary<BinaryAgreement<NodeId>>>(
mut network: TestNetwork<A, BinaryAgreement<NodeId>>,
fn test_binary_agreement<A: Adversary<BinaryAgreement<NodeId, u8>>>(
mut network: TestNetwork<A, BinaryAgreement<NodeId, u8>>,
input: Option<bool>,
) {
let ids: Vec<NodeId> = network.nodes.keys().cloned().collect();
@ -65,7 +63,7 @@ fn test_binary_agreement<A: Adversary<BinaryAgreement<NodeId>>>(
fn test_binary_agreement_different_sizes<A, F>(new_adversary: F)
where
A: Adversary<BinaryAgreement<NodeId>>,
A: Adversary<BinaryAgreement<NodeId, u8>>,
F: Fn(usize, usize) -> A,
{
// This returns an error in all but the first test.
@ -85,7 +83,7 @@ where
);
let adversary = |_| new_adversary(num_good_nodes, num_faulty_nodes);
let new_ba = |netinfo: Arc<NetworkInfo<NodeId>>| {
BinaryAgreement::new(netinfo, 0, NodeId(0)).expect("Binary Agreement instance")
BinaryAgreement::new(netinfo, 0).expect("Binary Agreement instance")
};
let network = TestNetwork::new(num_good_nodes, num_faulty_nodes, adversary, new_ba);
test_binary_agreement(network, input);

View File

@ -0,0 +1,480 @@
#![deny(unused_must_use)]
//! Tests the BinaryAgreement protocol with a MTIM adversary.
extern crate env_logger;
extern crate failure;
extern crate hbbft;
extern crate integer_sqrt;
extern crate proptest;
extern crate rand;
extern crate threshold_crypto;
pub mod net;
use std::iter;
use std::sync::{Arc, Mutex};
use hbbft::binary_agreement::{BinaryAgreement, MessageContent, SbvMessage};
use hbbft::threshold_sign::ThresholdSign;
use hbbft::{DistAlgorithm, NetworkInfo, Step};
use net::adversary::{NetMutHandle, QueuePosition};
use net::err::CrankError;
use net::{Adversary, NetBuilder, NetMessage};
type NodeId = usize;
type SessionId = u8;
type Algo = BinaryAgreement<NodeId, SessionId>;
/// The state of the current epoch's coin. In some epochs this is fixed, in others it starts
/// with in `InProgress`.
#[derive(Debug)]
enum CoinState<N> {
/// The value was fixed in the current epoch, or the coin has already terminated.
Decided(bool),
/// The coin value is not known yet.
InProgress(Box<ThresholdSign<N>>),
}
impl<N> CoinState<N> {
/// Returns the value, if this coin has already decided.
fn value(&self) -> Option<bool> {
match self {
CoinState::Decided(value) => Some(*value),
CoinState::InProgress(_) => None,
}
}
}
impl<N> From<bool> for CoinState<N> {
fn from(value: bool) -> Self {
CoinState::Decided(value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MessageType {
BVal,
Aux,
Coin,
}
fn message_type_and_content(msg: &MessageContent) -> Option<(MessageType, Option<bool>)> {
match msg {
MessageContent::SbvBroadcast(sbv_msg) => match sbv_msg {
SbvMessage::BVal(v) => Some((MessageType::BVal, Some(*v))),
SbvMessage::Aux(v) => Some((MessageType::Aux, Some(*v))),
},
MessageContent::Coin(_) => Some((MessageType::Coin, None)),
_ => None,
}
}
/// A boolean XOR a value from the state.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BoolFromState {
AEstimated(bool),
CoinValue(bool),
}
struct Stage {
source_groups: &'static [usize],
dest_groups: &'static [usize],
msg_type: MessageType,
msg_contents: Option<BoolFromState>,
msg_count: usize,
}
// Group IDs
const A0: usize = 0;
const A1: usize = 1;
const B: usize = 2;
const F: usize = 3;
// Comments from https://github.com/amiller/HoneyBadgerBFT/issues/59#issue-310368284
const STAGES: &[Stage] = &[
// x sends BVAL(\neg v) to the nodes in A0
Stage {
source_groups: &[F],
dest_groups: &[A0],
msg_type: MessageType::BVal,
msg_contents: Some(BoolFromState::AEstimated(true)),
msg_count: NODES_PER_GROUP,
},
// and BVAL(v) to the nodes in A1.
Stage {
source_groups: &[F],
dest_groups: &[A1],
msg_type: MessageType::BVal,
msg_contents: Some(BoolFromState::AEstimated(false)),
msg_count: NODES_PER_GROUP,
},
// Also, all votes from nodes in B are delivered to all nodes in A.
Stage {
source_groups: &[B],
dest_groups: &[A0, A1],
msg_type: MessageType::BVal,
msg_contents: None,
msg_count: NODES_PER_GROUP * (NODES_PER_GROUP * 2),
},
// Messages within A0 are delivered.
// Thus nodes in A0 see |B|+|F|=f+1 votes for \neg v;
// so all nodes in A0 broadcast BVAL(\neg v)
// and all nodes in A0 see |A0|+|B|+|F|=2f+1 votes for \neg v;
// so all nodes in A0 broadcast AUX(\neg v).
Stage {
source_groups: &[A0],
dest_groups: &[A0],
msg_type: MessageType::BVal,
msg_contents: None,
msg_count: NODES_PER_GROUP * (NODES_PER_GROUP - 1),
},
// Then all messages within A1 are delivered,
Stage {
source_groups: &[A1],
dest_groups: &[A1],
msg_type: MessageType::BVal,
msg_contents: None,
msg_count: NODES_PER_GROUP * (NODES_PER_GROUP - 1),
},
// as well as the BVAL(v) messages from A0 to A1.
// Thus the nodes in A1 see |A0|+|A1|+|F|=2f+1 votes for v and broadcast AUX(v).
Stage {
source_groups: &[A0],
dest_groups: &[A1],
msg_type: MessageType::BVal,
msg_contents: Some(BoolFromState::AEstimated(false)),
msg_count: NODES_PER_GROUP * NODES_PER_GROUP,
},
// After this all messages within A are delivered
Stage {
source_groups: &[A0, A1],
dest_groups: &[A0, A1],
msg_type: MessageType::BVal,
msg_contents: None,
msg_count: (NODES_PER_GROUP * 2) * (NODES_PER_GROUP * 2 - 1),
},
Stage {
source_groups: &[A0, A1],
dest_groups: &[A0, A1],
msg_type: MessageType::Aux,
msg_contents: None,
msg_count: (NODES_PER_GROUP * 2) * (NODES_PER_GROUP * 2 - 1),
},
// and x sends both BVAL(0) and BVAL(1) to every node in A.
// Thus every node in A broadcasts both BVAL(0) and BVAL(1) and sets bin_values=\{0,1\}.
Stage {
source_groups: &[F],
dest_groups: &[A0, A1],
msg_type: MessageType::BVal,
msg_contents: Some(BoolFromState::AEstimated(false)),
msg_count: NODES_PER_GROUP * 2,
},
Stage {
source_groups: &[F],
dest_groups: &[A0, A1],
msg_type: MessageType::BVal,
msg_contents: Some(BoolFromState::AEstimated(true)),
msg_count: NODES_PER_GROUP * 2,
},
// !! Not mentioned in the GitHub issue, but seems necessary.
// F sends Aux(_) to A, because nodes in A need 2f+1 Aux messages
// before they broadcast their coins.
Stage {
source_groups: &[F],
dest_groups: &[A0, A1],
msg_type: MessageType::Aux,
msg_contents: Some(BoolFromState::AEstimated(false)),
msg_count: NODES_PER_GROUP * 2,
},
// Now all nodes in A broadcast their threshold shares over the coin,
// so since |A|+|F|=2f+1, the adversary can construct the random coin value s.
Stage {
source_groups: &[A0, A1],
dest_groups: &[F],
msg_type: MessageType::Coin,
msg_contents: None,
msg_count: NODES_PER_GROUP * 2,
},
// The nodes in F send BVAL(\neg s) to all the nodes in B,
// and all the BVAL(\neg s) messages from nodes in A are delivered to all nodes in B.
// Thus all the nodes in B broadcast AUX(\neg s).
Stage {
source_groups: &[A0, A1, F],
dest_groups: &[B],
msg_type: MessageType::BVal,
msg_contents: Some(BoolFromState::CoinValue(true)),
msg_count: (NODES_PER_GROUP * 2 + 1) * NODES_PER_GROUP,
},
// Deliver all AUX(\neg s) messages; there are 2f+1 of them,
// since either every node in A0 broadcast AUX(\neg s)
// or every node in A1 broadcast AUX(\neg s).
// Thus all nodes in B see 2f+1 AUX(\neg s) messages
// and get to the end of the round with bin_values=\neg s.
// Thus the nodes in B continue to the next round voting \neg s
// while the nodes in A continue to the next round voting s.
Stage {
source_groups: &[A0, A1, B, F],
dest_groups: &[B],
msg_type: MessageType::Aux,
msg_contents: Some(BoolFromState::CoinValue(true)),
msg_count: (NODES_PER_GROUP + 1) * (NODES_PER_GROUP)
+ (NODES_PER_GROUP * (NODES_PER_GROUP - 1)),
},
// At this point all messages from the round are delivered, and the process repeats.
];
/// An adversary for the reordering attack.
/// Described here: https://github.com/amiller/HoneyBadgerBFT/issues/59#issue-310368284
/// Excluding the first node, which is F,
/// A0 is the first third of nodes, A1 is the second third, and the rest are B.
struct AbaCommonCoinAdversary {
stage: usize,
stage_progress: usize,
sent_stage_messages: bool,
epoch: u32,
coin_state: CoinState<NodeId>,
/// The estimated value for nodes in A.
a_estimated: bool,
// TODO this is really hacky but there's no better way to get this value
netinfo_mutex: Arc<Mutex<Option<Arc<NetworkInfo<NodeId>>>>>,
}
const NODES_PER_GROUP: usize = 2;
const NUM_NODES: usize = (NODES_PER_GROUP * 3 + 1);
impl AbaCommonCoinAdversary {
fn new(netinfo_mutex: Arc<Mutex<Option<Arc<NetworkInfo<NodeId>>>>>) -> Self {
Self::new_with_epoch(netinfo_mutex, 0, false)
}
fn new_with_epoch(
netinfo_mutex: Arc<Mutex<Option<Arc<NetworkInfo<NodeId>>>>>,
epoch: u32,
a_estimated: bool,
) -> Self {
AbaCommonCoinAdversary {
stage: 0,
stage_progress: 0,
sent_stage_messages: false,
epoch,
coin_state: match epoch % 3 {
0 => CoinState::Decided(true),
1 => CoinState::Decided(false),
2 => {
let netinfo = netinfo_mutex
.lock()
.unwrap()
.as_ref()
.cloned()
.expect("Adversary netinfo mutex not populated");
let coin_id = bincode::serialize(&(0 as SessionId, epoch))
.expect("Failed to serialize coin_id");
let mut coin = ThresholdSign::new(netinfo, coin_id);
let _ = coin
.handle_input(())
.expect("Calling handle_input on Coin failed");
CoinState::InProgress(Box::new(coin))
}
_ => unreachable!(),
},
netinfo_mutex,
a_estimated,
}
}
fn eval_state_bool(&self, state_bool: BoolFromState) -> bool {
match state_bool {
BoolFromState::AEstimated(v) => self.a_estimated ^ v,
BoolFromState::CoinValue(v) => {
self.coin_state
.value()
.expect("State relied upon coin value before it was known")
^ v
}
}
}
fn inject_stage_messages(&mut self, net: &mut NetMutHandle<Algo>) {
if self.sent_stage_messages {
return;
}
self.sent_stage_messages = true;
if let Some(stage) = STAGES.get(self.stage) {
if stage.source_groups.iter().any(|&x| x == F) {
let contents = self.eval_state_bool(
stage
.msg_contents
.expect("Stage has adversary as source but no contents"),
);
let message_content = match stage.msg_type {
MessageType::BVal => MessageContent::SbvBroadcast(SbvMessage::BVal(contents)),
MessageType::Aux => MessageContent::SbvBroadcast(SbvMessage::Aux(contents)),
MessageType::Coin => {
panic!("Stage expected adversary node to send Coin message");
}
};
let message = message_content.with_epoch(self.epoch);
for &dst_grp in stage.dest_groups {
if dst_grp == F {
continue;
}
for i in 0..NODES_PER_GROUP {
let dst = 1 + NODES_PER_GROUP * dst_grp + i;
net.inject_message(
QueuePosition::Front,
NetMessage::<Algo>::new(0, message.clone(), dst),
)
}
}
}
}
}
/// Should be called whenever stage_progress is changed.
fn on_stage_progress_update(&mut self) {
let stage_finished = STAGES
.get(self.stage)
.map(|x| {
(x.msg_type == MessageType::Coin && self.coin_state.value().is_some())
|| self.stage_progress >= x.msg_count
}).unwrap_or(false);
if stage_finished {
self.stage += 1;
self.stage_progress = 0;
self.sent_stage_messages = false;
self.on_stage_progress_update();
}
}
fn stage_matches_msg(&self, message: &NetMessage<Algo>) -> bool {
if let Some(stage) = STAGES.get(self.stage) {
let from = *message.from();
let src_group = if from == 0 {
3
} else {
(from - 1) / NODES_PER_GROUP
};
let to = *message.to();
let dst_group = if to == 0 {
3
} else {
(to - 1) / NODES_PER_GROUP
};
if let Some((ty, content)) = message_type_and_content(&message.payload().content) {
let content_matches = match (stage.msg_contents, content) {
(Some(x), Some(y)) => self.eval_state_bool(x) == y,
_ => true,
};
return stage.source_groups.iter().any(|&x| x == src_group)
&& stage.dest_groups.iter().any(|&x| x == dst_group)
&& stage.msg_type == ty
&& content_matches;
}
}
false
}
}
impl Adversary<Algo> for AbaCommonCoinAdversary {
fn pre_crank(&mut self, mut net: NetMutHandle<Algo>) {
self.inject_stage_messages(&mut net);
net.sort_messages_by(|a, b| {
a.payload()
.epoch
.cmp(&b.payload().epoch)
.then_with(|| self.stage_matches_msg(b).cmp(&self.stage_matches_msg(a)))
});
let mut redo_crank = false;
if let Some(msg) = net.get_messages().front() {
if msg.payload().epoch == self.epoch && self.stage_matches_msg(&msg) {
self.stage_progress += 1;
self.on_stage_progress_update();
}
if msg.payload().epoch > self.epoch {
// This assert should fail if the attack is prevented:
// assert_eq!(self.stage, STAGES.len());
let netinfo = self.netinfo_mutex.clone();
*self = Self::new_with_epoch(
netinfo,
msg.payload().epoch,
self.coin_state
.value()
.expect("Coin value not known at end of epoch"),
);
redo_crank = true;
}
}
if redo_crank {
self.pre_crank(net);
}
}
fn tamper(
&mut self,
_: NetMutHandle<Algo>,
msg: NetMessage<Algo>,
) -> Result<Step<Algo>, CrankError<Algo>> {
if let MessageContent::Coin(ref coin_msg) = msg.payload().content {
let mut new_coin_state = None;
if let CoinState::InProgress(ref mut coin) = self.coin_state {
let res = coin.handle_message(msg.from(), *coin_msg.clone());
if let Ok(step) = res {
if let Some(coin) = step.output.into_iter().next() {
new_coin_state = Some(coin.parity().into());
}
}
}
if let Some(new_coin_state) = new_coin_state {
self.coin_state = new_coin_state;
}
}
Ok(Step::default())
}
}
#[test]
fn reordering_attack() {
let _ = env_logger::try_init();
let ids: Vec<NodeId> = (0..NUM_NODES).collect();
let adversary_netinfo: Arc<Mutex<Option<Arc<NetworkInfo<NodeId>>>>> = Default::default();
let mut net = NetBuilder::new(ids.iter().cloned())
.adversary(AbaCommonCoinAdversary::new(adversary_netinfo.clone()))
.crank_limit(10000)
.using(move |info| {
let netinfo = Arc::new(info.netinfo);
if info.id == 0 {
*adversary_netinfo.lock().unwrap() = Some(netinfo.clone());
}
BinaryAgreement::new(netinfo, 0).expect("failed to create BinaryAgreement instance")
}).num_faulty(1)
.build()
.unwrap();
for id in ids {
if id == 0 {
// This is the faulty node.
} else if id < (1 + NODES_PER_GROUP * 2) {
// Group A
let _ = net.send_input(id, false).unwrap();
} else {
// Group B
let _ = net.send_input(id, true).unwrap();
}
}
while !net.nodes().skip(1).all(|n| n.algorithm().terminated()) {
net.crank_expect();
}
// Verify that all instances output the same value.
let mut estimated = None;
for node in net.nodes().skip(1) {
if let Some(b) = estimated {
assert!(iter::once(&b).eq(node.outputs()));
} else {
assert_eq!(1, node.outputs().len());
estimated = Some(node.outputs()[0]);
}
}
}

View File

@ -1,23 +1,21 @@
#![deny(unused_must_use)]
//! Integration test of the reliable broadcast protocol.
extern crate hbbft;
#[macro_use]
extern crate log;
extern crate env_logger;
extern crate hbbft;
extern crate log;
extern crate rand;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate rand_derive;
extern crate serde_derive;
extern crate threshold_crypto as crypto;
mod network;
use std::collections::{BTreeMap, BTreeSet};
use std::collections::BTreeMap;
use std::iter::once;
use std::sync::Arc;
use log::info;
use rand::Rng;
use hbbft::broadcast::{Broadcast, Message};
@ -30,8 +28,7 @@ use network::{
/// An adversary that inputs an alternate value.
struct ProposeAdversary {
scheduler: MessageScheduler,
good_nodes: BTreeSet<NodeId>,
adv_nodes: BTreeSet<NodeId>,
adv_nodes: BTreeMap<NodeId, Arc<NetworkInfo<NodeId>>>,
has_sent: bool,
}
@ -39,12 +36,10 @@ impl ProposeAdversary {
/// Creates a new replay adversary with the given message scheduler.
fn new(
scheduler: MessageScheduler,
good_nodes: BTreeSet<NodeId>,
adv_nodes: BTreeSet<NodeId>,
adv_nodes: BTreeMap<NodeId, Arc<NetworkInfo<NodeId>>>,
) -> ProposeAdversary {
ProposeAdversary {
scheduler,
good_nodes,
adv_nodes,
has_sent: false,
}
@ -65,31 +60,17 @@ impl Adversary<Broadcast<NodeId>> for ProposeAdversary {
return vec![];
}
self.has_sent = true;
let node_ids: BTreeSet<NodeId> = self
.adv_nodes
self.adv_nodes
.iter()
.chain(self.good_nodes.iter())
.cloned()
.collect();
let id = match self.adv_nodes.iter().next() {
Some(id) => *id,
None => return vec![],
};
// FIXME: Take the correct, known keys from the network.
let netinfo = Arc::new(
NetworkInfo::generate_map(node_ids, &mut rand::thread_rng())
.expect("Failed to create `NetworkInfo` map")
.remove(&id)
.unwrap(),
);
let mut bc = Broadcast::new(netinfo, id).expect("broadcast instance");
// FIXME: Use the output.
let step = bc.handle_input(b"Fake news".to_vec()).expect("propose");
step.messages
.into_iter()
.map(|msg| MessageWithSender::new(id, msg))
.collect()
.flat_map(|(&id, netinfo)| {
Broadcast::new(netinfo.clone(), id)
.expect("broadcast instance")
.handle_input(b"Fake news".to_vec())
.expect("propose")
.messages
.into_iter()
.map(move |msg| MessageWithSender::new(id, msg))
}).collect()
}
}
@ -122,7 +103,7 @@ fn new_broadcast(netinfo: Arc<NetworkInfo<NodeId>>) -> Broadcast<NodeId> {
fn test_broadcast_different_sizes<A, F>(new_adversary: F, proposed_value: &[u8])
where
A: Adversary<Broadcast<NodeId>>,
F: Fn(usize, usize) -> A,
F: Fn(BTreeMap<NodeId, Arc<NetworkInfo<NodeId>>>) -> A,
{
let mut rng = rand::thread_rng();
let sizes = (1..6)
@ -135,7 +116,7 @@ where
"Network size: {} good nodes, {} faulty nodes",
num_good_nodes, num_faulty_nodes
);
let adversary = |_| new_adversary(num_good_nodes, num_faulty_nodes);
let adversary = |adv_nodes| new_adversary(adv_nodes);
let network = TestNetwork::new(num_good_nodes, num_faulty_nodes, adversary, new_broadcast);
test_broadcast(network, proposed_value);
}
@ -154,43 +135,31 @@ fn test_8_broadcast_equal_leaves_silent() {
#[test]
fn test_broadcast_random_delivery_silent() {
let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::Random);
let new_adversary = |_| SilentAdversary::new(MessageScheduler::Random);
test_broadcast_different_sizes(new_adversary, b"Foo");
}
#[test]
fn test_broadcast_first_delivery_silent() {
let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::First);
let new_adversary = |_| SilentAdversary::new(MessageScheduler::First);
test_broadcast_different_sizes(new_adversary, b"Foo");
}
#[test]
fn test_broadcast_random_delivery_adv_propose() {
let new_adversary = |num_good_nodes: usize, num_faulty_nodes: usize| {
let good_nodes: BTreeSet<NodeId> = (0..num_good_nodes).map(NodeId).collect();
let adv_nodes: BTreeSet<NodeId> = (num_good_nodes..(num_good_nodes + num_faulty_nodes))
.map(NodeId)
.collect();
ProposeAdversary::new(MessageScheduler::Random, good_nodes, adv_nodes)
};
let new_adversary = |adv_nodes| ProposeAdversary::new(MessageScheduler::Random, adv_nodes);
test_broadcast_different_sizes(new_adversary, b"Foo");
}
#[test]
fn test_broadcast_first_delivery_adv_propose() {
let new_adversary = |num_good_nodes: usize, num_faulty_nodes: usize| {
let good_nodes: BTreeSet<NodeId> = (0..num_good_nodes).map(NodeId).collect();
let adv_nodes: BTreeSet<NodeId> = (num_good_nodes..(num_good_nodes + num_faulty_nodes))
.map(NodeId)
.collect();
ProposeAdversary::new(MessageScheduler::First, good_nodes, adv_nodes)
};
let new_adversary = |adv_nodes| ProposeAdversary::new(MessageScheduler::First, adv_nodes);
test_broadcast_different_sizes(new_adversary, b"Foo");
}
#[test]
fn test_broadcast_random_adversary() {
let new_adversary = |_, _| {
let new_adversary = |_| {
// Note: Set this to 0.8 to watch 30 gigs of RAM disappear.
RandomAdversary::new(0.2, 0.2, || TargetedMessage {
target: Target::All,

View File

@ -1,16 +1,13 @@
#![deny(unused_must_use)]
//! Network tests for Dynamic Honey Badger.
extern crate env_logger;
extern crate hbbft;
extern crate itertools;
#[macro_use]
extern crate log;
extern crate env_logger;
extern crate rand;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate rand_derive;
extern crate serde_derive;
extern crate threshold_crypto as crypto;
mod network;
@ -19,6 +16,7 @@ use std::collections::BTreeMap;
use std::sync::Arc;
use itertools::Itertools;
use log::info;
use rand::{Isaac64Rng, Rng};
use hbbft::dynamic_honey_badger::{

View File

@ -2,15 +2,12 @@
//! Network tests for Honey Badger.
extern crate bincode;
extern crate env_logger;
extern crate hbbft;
extern crate itertools;
#[macro_use]
extern crate log;
extern crate env_logger;
extern crate rand;
#[macro_use]
extern crate rand_derive;
#[macro_use]
extern crate serde_derive;
extern crate threshold_crypto as crypto;
@ -20,6 +17,7 @@ use std::collections::BTreeMap;
use std::sync::Arc;
use itertools::Itertools;
use log::info;
use rand::Rng;
use hbbft::honey_badger::{self, Batch, HoneyBadger, MessageContent};

View File

@ -33,7 +33,10 @@
//! some cases be upgraded to actual references, if the underlying node is faulty (see
//! `NodeHandle::node()` and `NodeHandle::node_mut()`).
use std::cmp;
use std::collections::VecDeque;
use std::{cmp, fmt};
use rand::Rng;
use hbbft::{DistAlgorithm, Step};
@ -154,7 +157,7 @@ where
/// Panics if `position` is equal to `Before(idx)`, with `idx` being out of bounds.
#[inline]
pub fn inject_message(&mut self, position: QueuePosition, msg: NetMessage<D>) {
// Ensure the node is not faulty.
// Ensure the source node is faulty.
assert!(
self.0
.get(msg.from.clone())
@ -199,6 +202,12 @@ where
{
self.0.sort_messages_by(f)
}
/// Returns a reference to the queue of messages
#[inline]
pub fn get_messages(&self) -> &VecDeque<NetMessage<D>> {
&self.0.messages
}
}
// Downgrade-conversion.
@ -383,3 +392,46 @@ where
net.sort_messages_by(|a, b| a.to.cmp(&b.to))
}
}
/// Message reordering adversary.
///
/// An adversary that swaps the message at the front of the message queue for a random message
/// within the queue before every `crank`. Thus the order in which messages are received by nodes is
/// random, which allows to test randomized message delivery.
pub struct ReorderingAdversary {
/// Random number generator to reorder messages.
rng: Box<dyn Rng>,
}
impl fmt::Debug for ReorderingAdversary {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("ReorderingAdversary")
.field("rng", &"<RNG>")
.finish()
}
}
impl ReorderingAdversary {
#[inline]
pub fn new<R>(rng: R) -> Self
where
R: 'static + Rng,
{
ReorderingAdversary { rng: Box::new(rng) }
}
}
impl<D> Adversary<D> for ReorderingAdversary
where
D: DistAlgorithm,
D::Message: Clone,
D::Output: Clone,
{
#[inline]
fn pre_crank(&mut self, mut net: NetMutHandle<D>) {
let l = net.0.messages_len();
if l > 0 {
net.swap_messages(0, self.rng.gen_range(0, l));
}
}
}

View File

@ -15,7 +15,6 @@
pub mod adversary;
pub mod err;
pub mod proptest;
#[macro_use]
pub mod util;
use std::io::Write;
@ -29,6 +28,8 @@ use hbbft::dynamic_honey_badger::Batch;
use hbbft::util::SubRng;
use hbbft::{self, Contribution, DistAlgorithm, NetworkInfo, NodeIdT, Step};
use try_some;
pub use self::adversary::Adversary;
pub use self::err::CrankError;
@ -149,9 +150,27 @@ pub struct NetworkMessage<M, N> {
impl<M, N> NetworkMessage<M, N> {
/// Create a new network message.
#[inline]
fn new(from: N, payload: M, to: N) -> NetworkMessage<M, N> {
pub fn new(from: N, payload: M, to: N) -> NetworkMessage<M, N> {
NetworkMessage { from, to, payload }
}
/// Returns the source of the message
#[inline]
pub fn from(&self) -> &N {
&self.from
}
/// Returns the destination of the message
#[inline]
pub fn to(&self) -> &N {
&self.to
}
/// Returns the contents of the message
#[inline]
pub fn payload(&self) -> &M {
&self.payload
}
}
/// Mapping from node IDs to actual node instances.
@ -648,6 +667,12 @@ where
self.messages.iter_mut()
}
/// Length of the message queue.
#[inline]
pub fn messages_len(&self) -> usize {
self.messages.len()
}
/// Swap two queued messages at indices `i` and `j`.
#[inline]
pub fn swap_messages(&mut self, i: usize, j: usize) {

View File

@ -135,7 +135,7 @@ impl NetworkDimensionTree {
NetworkDimensionTree {
high: high.into(),
current: high.into(),
low: 0,
low: u32::from(min_size),
}
}
}

View File

@ -1,8 +1,7 @@
extern crate failure;
extern crate hbbft;
#[macro_use]
extern crate proptest;
extern crate integer_sqrt;
extern crate proptest;
extern crate rand;
extern crate threshold_crypto;
@ -12,9 +11,10 @@ use std::{collections, time};
use hbbft::dynamic_honey_badger::{Change, ChangeState, DynamicHoneyBadger, Input, NodeChange};
use hbbft::DistAlgorithm;
use net::adversary::ReorderingAdversary;
use net::proptest::{gen_seed, NetworkDimension, TestRng, TestRngSeed};
use net::NetBuilder;
use proptest::prelude::ProptestConfig;
use proptest::{prelude::ProptestConfig, prop_compose, proptest, proptest_helper};
use rand::{Rng, SeedableRng};
/// Choose a node's contribution for an epoch.
@ -101,6 +101,7 @@ fn do_drop_and_readd(cfg: TestConfig) {
.time_limit(time::Duration::from_secs(30 * cfg.dimension.size() as u64))
// Ensure runs are reproducible.
.rng(rng.gen::<TestRng>())
.adversary(ReorderingAdversary::new(rng.gen::<TestRng>()))
.using(move |node| {
println!("Constructing new dynamic honey badger node #{}", node.id);
DynamicHoneyBadger::builder()
@ -148,7 +149,7 @@ fn do_drop_and_readd(cfg: TestConfig) {
net.correct_nodes().map(|n| *n.id()).collect();
let mut expected_outputs: collections::BTreeMap<_, collections::BTreeSet<_>> = net
.correct_nodes()
.map(|n| (*n.id(), (0..10).into_iter().collect()))
.map(|n| (*n.id(), (0..10).collect()))
.collect();
// Run the network:

View File

@ -1,8 +1,7 @@
extern crate failure;
extern crate hbbft;
#[macro_use]
extern crate proptest;
extern crate integer_sqrt;
extern crate proptest;
extern crate rand;
extern crate rand_core;
extern crate threshold_crypto;
@ -10,8 +9,8 @@ extern crate threshold_crypto;
pub mod net;
use proptest::arbitrary::any;
use proptest::prelude::RngCore;
use proptest::strategy::{Strategy, ValueTree};
use proptest::{prelude::RngCore, proptest, proptest_helper};
use rand::{Rng as Rng4, SeedableRng as SeedableRng4};
use net::proptest::{max_sum, NetworkDimension, NetworkDimensionTree};
@ -181,6 +180,7 @@ fn network_to_u32_is_correct() {
}
#[test]
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
fn network_from_u32_is_correct() {
assert_eq!(NetworkDimension::new(1, 0), NetworkDimension::from(0u32));
assert_eq!(NetworkDimension::new(2, 0), NetworkDimension::from(1u32));

View File

@ -4,7 +4,10 @@ use std::mem;
use std::sync::Arc;
use crypto::SecretKeyShare;
use log::{debug, warn};
use rand::{self, Rng};
use rand_derive::Rand;
use serde_derive::{Deserialize, Serialize};
use hbbft::dynamic_honey_badger::Batch;
use hbbft::{Contribution, DistAlgorithm, Fault, NetworkInfo, Step, Target, TargetedMessage};

View File

@ -4,13 +4,10 @@
extern crate env_logger;
extern crate hbbft;
extern crate itertools;
#[macro_use]
extern crate log;
extern crate rand;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate rand_derive;
extern crate serde_derive;
extern crate threshold_crypto as crypto;
mod network;
@ -19,6 +16,7 @@ use std::collections::BTreeMap;
use std::sync::Arc;
use itertools::Itertools;
use log::info;
use rand::{Isaac64Rng, Rng};
use hbbft::dynamic_honey_badger::DynamicHoneyBadger;

View File

@ -3,13 +3,10 @@
extern crate env_logger;
extern crate hbbft;
#[macro_use]
extern crate log;
extern crate rand;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate rand_derive;
extern crate serde_derive;
extern crate threshold_crypto as crypto;
mod network;
@ -25,8 +22,8 @@ use network::{Adversary, MessageScheduler, NodeId, SilentAdversary, TestNetwork,
type ProposedValue = Vec<u8>;
fn test_subset<A: Adversary<Subset<NodeId>>>(
mut network: TestNetwork<A, Subset<NodeId>>,
fn test_subset<A: Adversary<Subset<NodeId, u8>>>(
mut network: TestNetwork<A, Subset<NodeId, u8>>,
inputs: &BTreeMap<NodeId, ProposedValue>,
) {
let ids: Vec<NodeId> = network.nodes.keys().cloned().collect();
@ -75,9 +72,9 @@ fn new_network<A, F>(
good_num: usize,
bad_num: usize,
adversary: F,
) -> TestNetwork<A, Subset<NodeId>>
) -> TestNetwork<A, Subset<NodeId, u8>>
where
A: Adversary<Subset<NodeId>>,
A: Adversary<Subset<NodeId, u8>>,
F: Fn(BTreeMap<NodeId, Arc<NetworkInfo<NodeId>>>) -> A,
{
// This returns an error in all but the first test.
@ -110,11 +107,8 @@ fn test_subset_5_nodes_different_proposed_values() {
Vec::from("Delta"),
Vec::from("Echo"),
];
let proposals: BTreeMap<NodeId, ProposedValue> = (0..5)
.into_iter()
.map(NodeId)
.zip(proposed_values)
.collect();
let proposals: BTreeMap<NodeId, ProposedValue> =
(0..5).map(NodeId).zip(proposed_values).collect();
let adversary = |_| SilentAdversary::new(MessageScheduler::Random);
let network = new_network(5, 0, adversary);
test_subset(network, &proposals);

View File

@ -3,20 +3,20 @@
extern crate env_logger;
extern crate hbbft;
#[macro_use]
extern crate log;
extern crate rand;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate rand_derive;
extern crate serde_derive;
extern crate threshold_crypto as crypto;
mod network;
use std::iter::once;
use log::info;
use rand::Rng;
use rand_derive::Rand;
use serde_derive::{Deserialize, Serialize};
use crypto::Signature;
use hbbft::threshold_sign::ThresholdSign;