merge from upstream

This commit is contained in:
Logan Collins 2018-10-23 23:54:54 -05:00
commit 40e611c824
20 changed files with 382 additions and 333 deletions

View File

@ -1,18 +1,20 @@
[package]
name = "hbbft"
version = "0.1.0"
authors = ["Vladimir Komendantskiy <komendantsky@gmail.com>",
"Andreas Fackler <AndreasFackler@gmx.de>",
"Peter van Nostrand <jnz@riseup.net>",
"Andrew Gross <andogro@gmail.com>",
"Nick Sanders <nsan1129@gmail.com>",
"Marc Brinkmann <git@marcbrinkmann.de>"]
description = "Honey Badger Byzantine fault tolerant consensus algorithm"
license = "MIT/Apache-2.0"
repository = "https://github.com/poanetwork/hbbft"
readme = "README.md"
keywords = ["consensus", "asynchronous", "threshold"]
authors = [
"Vladimir Komendantskiy <komendantsky@gmail.com>",
"Andreas Fackler <AndreasFackler@gmx.de>",
"Peter van Nostrand <jnz@riseup.net>",
"Andrew Gross <andogro@gmail.com>",
"Nick Sanders <nsan1129@gmail.com>",
"Marc Brinkmann <git@marcbrinkmann.de>",
]
categories = ["algorithms", "asynchronous", "cryptography", "network-programming"]
keywords = ["consensus", "asynchronous", "threshold"]
license = "MIT/Apache-2.0"
readme = "README.md"
repository = "https://github.com/poanetwork/hbbft"
description = "The Honey Badger of Byzantine Fault Tolerant Consensus Protocols"
[badges]
travis-ci = { repository = "poanetwork/hbbft" }
@ -30,7 +32,7 @@ rand_derive = "0.3.1"
reed-solomon-erasure = "3.1.0"
serde = "1.0.55"
serde_derive = "1.0.55"
threshold_crypto = { git = "https://github.com/poanetwork/threshold_crypto", tag = "0.2.0-beta2" }
threshold_crypto = "0.2.1"
tiny-keccak = "1.4"
[dev-dependencies]

View File

@ -37,7 +37,8 @@ In addition to **validators**, the algorithms support **observers**: These don't
- **[Binary Agreement](src/binary_agreement/binary_agreement.rs):** Each node inputs a binary value. The nodes agree on a value that was input by at least one correct node.
- **[Coin](src/coin.rs):** A pseudorandom binary value used by the Binary Agreement protocol.
- **[Threshold Sign](src/threshold_sign.rs):**
Each node inputs the same data to be signed, and outputs the unique valid signature matching the public master key. It is used as a pseudorandom value in the Binary Agreement protocol.
- **[Threshold Decryption](src/threshold_decryption.rs):**
Each node inputs the same ciphertext, encrypted to the public master key, and outputs the decrypted data.
@ -147,7 +148,6 @@ We have simplified algorithm naming conventions from the original paper.
| Subset | Asynchronous Common Subset (ACS) |
| Broadcast | Reliable Broadcast (RBC) |
| Binary Agreement | Binary Byzantine Agreement (BBA) |
| Coin | Common Coin |
## Contributing

View File

@ -5,7 +5,7 @@ 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 coin::{self, Coin, CoinMessage};
use threshold_sign::{self, ThresholdSign};
use {DistAlgorithm, NetworkInfo, NodeIdT, Target};
/// The state of the current epoch's coin. In some epochs this is fixed, in others it starts
@ -15,7 +15,7 @@ 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(Coin<N, Nonce>),
InProgress(Box<ThresholdSign<N>>),
}
impl<N> CoinState<N> {
@ -82,18 +82,7 @@ impl<N: NodeIdT> DistAlgorithm for BinaryAgreement<N> {
/// Receive input from a remote node.
fn handle_message(&mut self, sender_id: &Self::NodeId, msg: Message) -> Result<Step<N>> {
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.
Ok(Step::default())
} else if epoch > self.epoch {
// Message is for a later epoch. We can't handle that yet.
let queue = self.incoming_queue.entry(epoch).or_insert_with(Vec::new);
queue.push((sender_id.clone(), content));
Ok(Step::default())
} else {
self.handle_message_content(sender_id, content)
}
self.handle_message(sender_id, msg)
}
/// Whether the algorithm has terminated.
@ -107,6 +96,10 @@ 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);
@ -128,9 +121,9 @@ impl<N: NodeIdT> BinaryAgreement<N> {
}
/// Sets the input value for Binary Agreement.
fn handle_input(&mut self, input: bool) -> Result<Step<N>> {
if self.epoch != 0 || self.estimated.is_some() {
return Err(Error::InputNotAccepted);
pub fn handle_input(&mut self, input: bool) -> Result<Step<N>> {
if !self.can_input() {
return Ok(Step::default());
}
// Set the initial estimated value to the input value.
self.estimated = Some(input);
@ -139,8 +132,25 @@ impl<N: NodeIdT> BinaryAgreement<N> {
self.handle_sbvb_step(sbvb_step)
}
/// Acceptance check to be performed before setting the input value.
pub fn accepts_input(&self) -> bool {
/// Handles an incoming message.
pub fn handle_message(&mut self, sender_id: &N, msg: Message) -> Result<Step<N>> {
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.
Ok(Step::default())
} else if epoch > self.epoch {
// Message is for a later epoch. We can't handle that yet.
let queue = self.incoming_queue.entry(epoch).or_insert_with(Vec::new);
queue.push((sender_id.clone(), content));
Ok(Step::default())
} else {
self.handle_message_content(sender_id, content)
}
}
/// Whether we can still input a value. It is not an error to input if this returns `false`,
/// but it will have no effect on the outcome.
pub fn can_input(&self) -> bool {
self.epoch == 0 && self.estimated.is_none()
}
@ -221,16 +231,16 @@ impl<N: NodeIdT> BinaryAgreement<N> {
}
}
/// Handles a Coin message. If there is output from Coin, starts the next
/// epoch. The function may output a decision value.
fn handle_coin(&mut self, sender_id: &N, msg: CoinMessage) -> Result<Step<N>> {
let coin_step = match self.coin_state {
/// 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>> {
let ts_step = match self.coin_state {
CoinState::Decided(_) => return Ok(Step::default()), // Coin value is already decided.
CoinState::InProgress(ref mut coin) => coin
CoinState::InProgress(ref mut ts) => ts
.handle_message(sender_id, msg)
.map_err(Error::HandleCoin)?,
.map_err(Error::HandleThresholdSign)?,
};
self.on_coin_step(coin_step)
self.on_coin_step(ts_step)
}
/// Multicasts a `Conf(values)` message, and handles it.
@ -263,14 +273,15 @@ impl<N: NodeIdT> BinaryAgreement<N> {
Ok(step)
}
/// Handles a step returned from the `Coin`.
fn on_coin_step(&mut self, coin_step: coin::Step<N, Nonce>) -> Result<Step<N>> {
/// Handles a step returned from the `ThresholdSign`.
fn on_coin_step(&mut self, ts_step: threshold_sign::Step<N>) -> Result<Step<N>> {
let mut step = Step::default();
let epoch = self.epoch;
let to_msg = |c_msg| MessageContent::Coin(Box::new(c_msg)).with_epoch(epoch);
let coin_output = step.extend_with(coin_step, to_msg);
if let Some(coin) = coin_output.into_iter().next() {
self.coin_state = coin.into();
let ts_output = step.extend_with(ts_step, to_msg);
if let Some(sig) = ts_output.into_iter().next() {
// Take the parity of the signature as the coin value.
self.coin_state = sig.parity().into();
step.extend(self.try_update_epoch()?);
}
Ok(step)
@ -304,7 +315,7 @@ 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 `Coin` instance.
/// value, or initializes a `ThresholdSign` instance.
fn coin_state(&self) -> CoinState<N> {
match self.epoch % 3 {
0 => CoinState::Decided(true),
@ -316,7 +327,7 @@ impl<N: NodeIdT> BinaryAgreement<N> {
self.netinfo.node_index(&self.proposer_id).unwrap(),
self.epoch,
);
CoinState::InProgress(Coin::new(self.netinfo.clone(), nonce))
CoinState::InProgress(Box::new(ThresholdSign::new(self.netinfo.clone(), nonce)))
}
}
}
@ -352,13 +363,11 @@ impl<N: NodeIdT> BinaryAgreement<N> {
}
// Invoke the coin.
let coin_step = match self.coin_state {
let ts_step = match self.coin_state {
CoinState::Decided(_) => return Ok(Step::default()), // Coin has already decided.
CoinState::InProgress(ref mut coin) => coin
.handle_input(())
.map_err(Error::TryFinishConfRoundCoin)?,
CoinState::InProgress(ref mut ts) => ts.handle_input(()).map_err(Error::InvokeCoin)?,
};
let mut step = self.on_coin_step(coin_step)?;
let mut step = self.on_coin_step(ts_step)?;
step.extend(self.try_update_epoch()?);
Ok(step)
}

View File

@ -71,21 +71,19 @@ mod sbv_broadcast;
use rand;
use self::bool_set::BoolSet;
use coin::{self, CoinMessage};
use threshold_sign;
pub use self::binary_agreement::BinaryAgreement;
/// An Binary Agreement error.
#[derive(Clone, Eq, PartialEq, Debug, Fail)]
pub enum Error {
#[fail(display = "HandleCoin error: {}", _0)]
HandleCoin(coin::Error),
#[fail(display = "TryFinishConfRoundCoin error: {}", _0)]
TryFinishConfRoundCoin(coin::Error),
#[fail(display = "Error handling threshold sign message: {}", _0)]
HandleThresholdSign(threshold_sign::Error),
#[fail(display = "Error invoking the common coin: {}", _0)]
InvokeCoin(threshold_sign::Error),
#[fail(display = "Unknown proposer")]
UnknownProposer,
#[fail(display = "Input not accepted")]
InputNotAccepted,
}
/// An Binary Agreement result.
@ -101,8 +99,8 @@ pub enum MessageContent {
Conf(BoolSet),
/// `Term` message.
Term(bool),
/// Coin message,
Coin(Box<CoinMessage>),
/// `ThresholdSign` message used for the common coin,
Coin(Box<threshold_sign::Message>),
}
impl MessageContent {

View File

@ -45,27 +45,11 @@ impl<N: NodeIdT> DistAlgorithm for Broadcast<N> {
type Error = Error;
fn handle_input(&mut self, input: Self::Input) -> Result<Step<N>> {
if *self.netinfo.our_id() != self.proposer_id {
return Err(Error::InstanceCannotPropose);
}
// Split the value into chunks/shards, encode them with erasure codes.
// Assemble a Merkle tree from data and parity shards. Take all proofs
// from this tree and send them, each to its own node.
let (proof, mut step) = self.send_shards(input)?;
let our_id = &self.netinfo.our_id().clone();
step.extend(self.handle_value(our_id, proof)?);
Ok(step)
self.broadcast(input)
}
fn handle_message(&mut self, sender_id: &N, message: Self::Message) -> Result<Step<N>> {
if !self.netinfo.is_node_validator(sender_id) {
return Err(Error::UnknownSender);
}
match message {
Message::Value(p) => self.handle_value(sender_id, p),
Message::Echo(p) => self.handle_echo(sender_id, p),
Message::Ready(ref hash) => self.handle_ready(sender_id, hash),
}
self.handle_message(sender_id, message)
}
fn terminated(&self) -> bool {
@ -98,6 +82,32 @@ impl<N: NodeIdT> Broadcast<N> {
})
}
/// Initiates the broadcast. This must only be called in the proposer node.
pub fn broadcast(&mut self, input: Vec<u8>) -> Result<Step<N>> {
if *self.netinfo.our_id() != self.proposer_id {
return Err(Error::InstanceCannotPropose);
}
// Split the value into chunks/shards, encode them with erasure codes.
// Assemble a Merkle tree from data and parity shards. Take all proofs
// from this tree and send them, each to its own node.
let (proof, mut step) = self.send_shards(input)?;
let our_id = &self.netinfo.our_id().clone();
step.extend(self.handle_value(our_id, proof)?);
Ok(step)
}
/// Handles an incoming message.
pub fn handle_message(&mut self, sender_id: &N, message: Message) -> Result<Step<N>> {
if !self.netinfo.is_node_validator(sender_id) {
return Err(Error::UnknownSender);
}
match message {
Message::Value(p) => self.handle_value(sender_id, p),
Message::Echo(p) => self.handle_echo(sender_id, p),
Message::Ready(ref hash) => self.handle_ready(sender_id, hash),
}
}
/// Breaks the input value into shards of equal length and encodes them --
/// and some extra parity shards -- with a Reed-Solomon erasure coding
/// scheme. The returned value contains the shard assigned to this

View File

@ -56,7 +56,7 @@
//! extern crate rand;
//!
//! use hbbft::broadcast::{Broadcast, Error, Step};
//! use hbbft::{DistAlgorithm, NetworkInfo, SourcedMessage, Target, TargetedMessage};
//! use hbbft::{NetworkInfo, SourcedMessage, Target, TargetedMessage};
//! use rand::{thread_rng, Rng};
//! use std::collections::{BTreeMap, BTreeSet, VecDeque};
//! use std::iter::once;
@ -111,7 +111,7 @@
//! // Now we can start the algorithm, its input is the payload.
//! let initial_step = {
//! let proposer = nodes.get_mut(&PROPOSER_ID).unwrap();
//! proposer.handle_input(payload.clone()).unwrap()
//! proposer.broadcast(payload.clone()).unwrap()
//! };
//! on_step(
//! PROPOSER_ID,

View File

@ -82,29 +82,7 @@ where
}
fn handle_message(&mut self, sender_id: &N, message: Self::Message) -> Result<Step<C, N>> {
let epoch = message.start_epoch();
if epoch < self.start_epoch {
// Obsolete message.
Ok(Step::default())
} else if epoch > self.start_epoch {
// Message cannot be handled yet. Save it for later.
let entry = (sender_id.clone(), message);
self.incoming_queue.push(entry);
Ok(Step::default())
} else {
match message {
Message::HoneyBadger(_, hb_msg) => {
self.handle_honey_badger_message(sender_id, hb_msg)
}
Message::KeyGen(_, kg_msg, sig) => self
.handle_key_gen_message(sender_id, kg_msg, *sig)
.map(FaultLog::into),
Message::SignedVote(signed_vote) => self
.vote_counter
.add_pending_vote(sender_id, signed_vote)
.map(FaultLog::into),
}
}
self.handle_message(sender_id, message)
}
fn terminated(&self) -> bool {
@ -133,17 +111,23 @@ where
/// Proposes a contribution in the current epoch.
pub fn propose(&mut self, contrib: C) -> Result<Step<C, N>> {
let key_gen_messages = self
.key_gen_msg_buffer
.iter()
.filter(|kg_msg| kg_msg.epoch() == self.start_epoch)
.cloned()
.collect();
let step = self
.honey_badger
.handle_input(InternalContrib {
contrib,
key_gen_messages: self.key_gen_msg_buffer.clone(),
key_gen_messages,
votes: self.vote_counter.pending_votes().cloned().collect(),
}).map_err(ErrorKind::ProposeHoneyBadger)?;
self.process_output(step)
}
/// Cast a vote to change the set of validators.
/// Casts a vote to change the set of validators.
pub fn vote_for(&mut self, change: Change<N>) -> Result<Step<C, N>> {
if !self.netinfo.is_validator() {
return Ok(Step::default()); // TODO: Return an error?
@ -153,6 +137,33 @@ where
Ok(Target::All.message(msg).into())
}
/// Handles an incoming message.
pub fn handle_message(&mut self, sender_id: &N, message: Message<N>) -> Result<Step<C, N>> {
let epoch = message.start_epoch();
if epoch < self.start_epoch {
// Obsolete message.
Ok(Step::default())
} else if epoch > self.start_epoch {
// Message cannot be handled yet. Save it for later.
let entry = (sender_id.clone(), message);
self.incoming_queue.push(entry);
Ok(Step::default())
} else {
match message {
Message::HoneyBadger(_, hb_msg) => {
self.handle_honey_badger_message(sender_id, hb_msg)
}
Message::KeyGen(_, kg_msg, sig) => self
.handle_key_gen_message(sender_id, kg_msg, *sig)
.map(FaultLog::into),
Message::SignedVote(signed_vote) => self
.vote_counter
.add_pending_vote(sender_id, signed_vote)
.map(FaultLog::into),
}
}
}
/// Returns the information about the node IDs in the network, and the cryptographic keys.
pub fn netinfo(&self) -> &NetworkInfo<N> {
&self.netinfo
@ -228,19 +239,11 @@ where
}
};
// If the joining node is correct, it will send at most (N + 1)² + 1 key generation
// messages.
if Some(sender_id) == kgs.change.candidate() {
let n = self.netinfo.num_nodes() + 1;
if kgs.candidate_msg_count > n * n {
info!(
"Too many key gen messages from candidate {:?}: {:?}.",
sender_id, kg_msg
);
let fault_kind = FaultKind::TooManyCandidateKeyGenMessages;
return Ok(Fault::new(sender_id.clone(), fault_kind).into());
}
kgs.candidate_msg_count += 1;
// If the sender is correct, it will send at most _N + 1_ key generation messages:
// one `Part`, and for each validator an `Ack`. _N_ is the node number _after_ the change.
if kgs.count_messages(sender_id) > kgs.key_gen.num_nodes() + 1 {
let fault_kind = FaultKind::TooManyKeyGenMessages;
return Ok(Fault::new(sender_id.clone(), fault_kind).into());
}
let tx = SignedKeyGenMsg(self.start_epoch, sender_id.clone(), kg_msg, sig);
@ -273,23 +276,18 @@ where
self.key_gen_msg_buffer
.retain(|skgm| !key_gen_messages.contains(skgm));
for SignedKeyGenMsg(epoch, s_id, kg_msg, sig) in key_gen_messages {
if epoch < self.start_epoch {
info!("Obsolete key generation message: {:?}.", kg_msg);
continue;
}
if !self.verify_signature(&s_id, &sig, &kg_msg)? {
info!(
"Invalid signature in {:?}'s batch from {:?} for: {:?}.",
id, s_id, kg_msg
);
if epoch != self.start_epoch {
let fault_kind = FaultKind::InvalidKeyGenMessageEpoch;
step.fault_log.append(id.clone(), fault_kind);
} else if !self.verify_signature(&s_id, &sig, &kg_msg)? {
let fault_kind = FaultKind::InvalidKeyGenMessageSignature;
step.fault_log.append(id.clone(), fault_kind);
continue;
} else {
step.extend(match kg_msg {
KeyGenMessage::Part(part) => self.handle_part(&s_id, part)?,
KeyGenMessage::Ack(ack) => self.handle_ack(&s_id, ack)?.into(),
});
}
step.extend(match kg_msg {
KeyGenMessage::Part(part) => self.handle_part(&s_id, part)?,
KeyGenMessage::Ack(ack) => self.handle_ack(&s_id, ack)?.into(),
});
}
}
@ -369,8 +367,7 @@ where
self.start_epoch = epoch;
self.key_gen_msg_buffer.retain(|kg_msg| kg_msg.0 >= epoch);
let netinfo = Arc::new(self.netinfo.clone());
let counter = VoteCounter::new(netinfo.clone(), epoch);
mem::replace(&mut self.vote_counter, counter);
self.vote_counter = VoteCounter::new(netinfo.clone(), epoch);
self.honey_badger = HoneyBadger::builder(netinfo)
.max_future_epochs(self.max_future_epochs)
.rng(self.rng.sub_rng())
@ -383,8 +380,9 @@ where
let outcome = if let Some(kgs) = self.key_gen_state.as_mut() {
kgs.key_gen.handle_part(&mut self.rng, &sender_id, part)
} else {
// No key generation ongoing. Return early.
return Ok(Step::default());
// No key generation ongoing.
let fault_kind = FaultKind::UnexpectedKeyGenPart;
return Ok(Fault::new(sender_id.clone(), fault_kind).into());
};
match outcome {
@ -399,7 +397,9 @@ where
if let Some(kgs) = self.key_gen_state.as_mut() {
Ok(kgs.key_gen.handle_ack(sender_id, ack))
} else {
Ok(FaultLog::new())
// No key generation ongoing.
let fault_kind = FaultKind::UnexpectedKeyGenAck;
return Ok(Fault::new(sender_id.clone(), fault_kind).into());
}
}

View File

@ -152,7 +152,7 @@ struct KeyGenState<N> {
change: NodeChange<N>,
/// The number of key generation messages received from the candidate. At most _N² + 1_ are
/// accepted.
candidate_msg_count: usize,
msg_count: BTreeMap<N, usize>,
}
impl<N: NodeIdT> KeyGenState<N> {
@ -160,7 +160,7 @@ impl<N: NodeIdT> KeyGenState<N> {
KeyGenState {
key_gen,
change,
candidate_msg_count: 0,
msg_count: BTreeMap::new(),
}
}
@ -178,6 +178,13 @@ impl<N: NodeIdT> KeyGenState<N> {
NodeChange::Add(_, _) | NodeChange::Remove(_) => None,
}
}
/// Increments the message count for the given node, and returns the new count.
fn count_messages(&mut self, node_id: &N) -> usize {
let count = self.msg_count.entry(node_id.clone()).or_insert(0);
*count += 1;
*count
}
}
/// The contribution for the internal `HoneyBadger` instance: this includes a user-defined
@ -195,3 +202,10 @@ struct InternalContrib<C, N> {
/// A signed internal message.
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Hash, Clone)]
struct SignedKeyGenMsg<N>(u64, N, KeyGenMessage, Signature);
impl<N> SignedKeyGenMsg<N> {
/// Returns the start epoch of the ongoing key generation.
fn epoch(&self) -> u64 {
self.0
}
}

View File

@ -12,8 +12,6 @@ pub enum AckMessageFault {
NodeCount,
#[fail(display = "Sender does not exist")]
SenderExist,
#[fail(display = "Duplicate ack")]
DuplicateAck,
#[fail(display = "Value decryption failed")]
ValueDecryption,
#[fail(display = "Value deserialization failed")]
@ -44,14 +42,19 @@ pub enum FaultKind {
/// `HoneyBadger` could not deserialize bytes (i.e. a serialized Batch)
/// from a given proposer into a vector of transactions.
BatchDeserializationFailed,
/// `DynamicHoneyBadger` received a key generation message with an invalid
/// signature.
/// `DynamicHoneyBadger` received a key generation message with an invalid signature.
InvalidKeyGenMessageSignature,
/// `DynamicHoneyBadger` received a key generation message with an invalid epoch.
InvalidKeyGenMessageEpoch,
/// `DynamicHoneyBadger` received a key generation message when there was no key generation in
/// progress.
UnexpectedKeyGenMessage,
/// `DynamicHoneyBadger` received more key generation messages from the candidate than expected.
TooManyCandidateKeyGenMessages,
/// `DynamicHoneyBadger` received a signed `Ack` when no key generation in progress.
UnexpectedKeyGenAck,
/// `DynamicHoneyBadger` received a signed `Part` when no key generation in progress.
UnexpectedKeyGenPart,
/// `DynamicHoneyBadger` received more key generation messages from the peer than expected.
TooManyKeyGenMessages,
/// `DynamicHoneyBadger` received a message (Accept, Propose, or Change)
/// with an invalid signature.
IncorrectPayloadSignature,
@ -59,6 +62,8 @@ pub enum FaultKind {
AckMessage(AckMessageFault),
/// `DynamicHoneyBadger`/`SyncKeyGen` received an invalid Part message.
InvalidPartMessage,
/// `DynamicHoneyBadger`/`SyncKeyGen` received multiple Part messages from a node.
MultiplePartMessages,
/// `DynamicHoneyBadger` received a change vote with an invalid signature.
InvalidVoteSignature,
/// A validator committed an invalid vote in `DynamicHoneyBadger`.

View File

@ -48,14 +48,6 @@ where
DecryptionState::Complete(_) => Ok(td::Step::default()),
}
}
/// Returns the plaintext, if it has already been decrypted.
fn plaintext(&self) -> Option<&[u8]> {
match self {
DecryptionState::Ongoing(_) => None,
DecryptionState::Complete(ref plaintext) => Some(&plaintext[..]),
}
}
}
/// The status of the subset algorithm.
@ -266,13 +258,13 @@ where
/// epoch, moves those contributions into a batch, outputs the batch and updates the epoch.
pub fn try_output_batch(&self) -> Option<(Batch<C, N>, FaultLog<N>)> {
let proposer_ids = self.subset.accepted_ids()?;
let plaintexts: BTreeMap<N, &[u8]> = self
.decryption
.iter()
.flat_map(|(id, dec_state)| dec_state.plaintext().map(|pt| (id.clone(), pt)))
.collect();
if !proposer_ids.iter().eq(plaintexts.keys()) {
return None; // Not all accepted contributions are decrypted yet.
let mut plaintexts = Vec::new();
// Collect accepted plaintexts. Return if some are not decrypted yet.
for id in proposer_ids {
match self.decryption.get(id) {
None | Some(DecryptionState::Ongoing(_)) => return None,
Some(DecryptionState::Complete(ref pt)) => plaintexts.push((id.clone(), pt)),
}
}
let mut fault_log = FaultLog::default();

View File

@ -85,12 +85,11 @@
//! This is used in Subset to decide whether each node's proposal should be included in the subset
//! or not.
//!
//! [**Coin**](coin/index.html)
//! [**Threshold Sign**](threshold_sign/index.html)
//!
//! Each node inputs `()` to initiate a coin flip. Once _f + 1_ nodes have input, either all nodes
//! receive `true` or all nodes receive `false`. The outcome cannot be known by the adversary
//! before at least one correct node has provided input, and is uniformly distributed and
//! pseudorandom.
//! Each node inputs `()` to broadcast signature shares. Once _f + 1_ nodes have input, all nodes
//! receive a valid signature. The outcome cannot be known by the adversary before at least one
//! correct node has provided input, and can be used as a source of pseudorandomness.
//!
//! [**Threshold Decryption**](threshold_decryption/index.html)
//!
@ -137,24 +136,25 @@ extern crate tiny_keccak;
pub extern crate threshold_crypto as crypto;
mod fault_log;
mod messaging;
mod network_info;
mod traits;
pub mod binary_agreement;
pub mod broadcast;
pub mod coin;
pub mod dynamic_honey_badger;
pub mod fault_log;
pub mod honey_badger;
pub mod queueing_honey_badger;
pub mod subset;
pub mod sync_key_gen;
pub mod threshold_decryption;
pub mod threshold_sign;
pub mod transaction_queue;
pub mod util;
pub use crypto::pairing;
pub use fault_log::{AckMessageFault, Fault, FaultKind, FaultLog};
pub use messaging::{SourcedMessage, Target, TargetedMessage};
pub use network_info::NetworkInfo;
pub use traits::{Contribution, DistAlgorithm, Message, NodeIdT, Step};

View File

@ -222,32 +222,14 @@ where
fn handle_input(&mut self, input: Self::Input) -> Result<Step<T, N, Q>> {
// User transactions are forwarded to `HoneyBadger` right away. Internal messages are
// in addition signed and broadcast.
let mut step = match input {
Input::User(tx) => {
self.queue.extend(iter::once(tx));
Step::default()
}
Input::Change(change) => self
.dyn_hb
.handle_input(Input::Change(change))
.map_err(ErrorKind::Input)?
.convert(),
};
step.extend(self.propose()?);
Ok(step)
match input {
Input::User(tx) => self.push_transaction(tx),
Input::Change(change) => self.vote_for(change),
}
}
fn handle_message(&mut self, sender_id: &N, message: Self::Message) -> Result<Step<T, N, Q>> {
let mut step = self
.dyn_hb
.handle_message(sender_id, message)
.map_err(ErrorKind::HandleMessage)?
.convert::<Self>();
for batch in &step.output {
self.queue.remove_multiple(batch.iter());
}
step.extend(self.propose()?);
Ok(step)
self.handle_message(sender_id, message)
}
fn terminated(&self) -> bool {
@ -271,6 +253,37 @@ where
QueueingHoneyBadgerBuilder::new(dyn_hb)
}
/// Adds a transaction to the queue.
pub fn push_transaction(&mut self, tx: T) -> Result<Step<T, N, Q>> {
self.queue.extend(iter::once(tx));
self.propose()
}
/// Casts a vote to change the set of validators.
pub fn vote_for(&mut self, change: Change<N>) -> Result<Step<T, N, Q>> {
let mut step = self
.dyn_hb
.handle_input(Input::Change(change))
.map_err(ErrorKind::Input)?
.convert();
step.extend(self.propose()?);
Ok(step)
}
/// Handles an incoming message.
pub fn handle_message(&mut self, sender_id: &N, message: Message<N>) -> Result<Step<T, N, Q>> {
let mut step = self
.dyn_hb
.handle_message(sender_id, message)
.map_err(ErrorKind::HandleMessage)?
.convert::<Self>();
for batch in &step.output {
self.queue.remove_multiple(batch.iter());
}
step.extend(self.propose()?);
Ok(step)
}
/// Returns a reference to the internal `DynamicHoneyBadger` instance.
pub fn dyn_hb(&self) -> &DynamicHoneyBadger<Vec<T>, N> {
&self.dyn_hb

View File

@ -95,25 +95,11 @@ impl<N: NodeIdT + Rand> DistAlgorithm for Subset<N> {
type Error = Error;
fn handle_input(&mut self, input: Self::Input) -> Result<Step<N>> {
debug!(
"{:?} Proposing {:0.10}",
self.netinfo.our_id(),
HexFmt(&input)
);
self.send_proposed_value(input)
}
fn handle_message(
&mut self,
sender_id: &Self::NodeId,
message: Self::Message,
) -> Result<Step<N>> {
match message {
Message::Broadcast(p_id, b_msg) => self.handle_broadcast(sender_id, &p_id, b_msg),
Message::BinaryAgreement(p_id, a_msg) => {
self.handle_binary_agreement(sender_id, &p_id, a_msg)
}
}
fn handle_message(&mut self, sender_id: &N, message: Message<N>) -> Result<Step<N>> {
self.handle_message(sender_id, message)
}
fn terminated(&self) -> bool {
@ -170,10 +156,20 @@ impl<N: NodeIdT + Rand> Subset<N> {
return Ok(Step::default());
}
let id = self.netinfo.our_id().clone();
// Upon receiving input v_i , input v_i to RBC_i. See Figure 2.
debug!("{:?} Proposing {:0.10}", id, HexFmt(&value));
self.process_broadcast(&id, |bc| bc.handle_input(value))
}
/// Handles an incoming message.
pub fn handle_message(&mut self, sender_id: &N, message: Message<N>) -> Result<Step<N>> {
match message {
Message::Broadcast(p_id, b_msg) => self.handle_broadcast(sender_id, &p_id, b_msg),
Message::BinaryAgreement(p_id, a_msg) => {
self.handle_binary_agreement(sender_id, &p_id, a_msg)
}
}
}
/// Returns the number of validators from which we have already received a proposal.
pub(crate) fn received_proposals(&self) -> usize {
self.broadcast_results.len()
@ -243,13 +239,7 @@ impl<N: NodeIdT + Rand> Subset<N> {
{
error!("Duplicate insert in broadcast_results: {:?}", inval)
}
let set_binary_agreement_input = |ba: &mut BinaryAgreement<N>| {
if ba.accepts_input() {
ba.handle_input(true)
} else {
Ok(binary_agreement::Step::default())
}
};
let set_binary_agreement_input = |ba: &mut BinaryAgreement<N>| ba.handle_input(true);
step.extend(self.process_binary_agreement(proposer_id, set_binary_agreement_input)?);
Ok(step)
}
@ -301,17 +291,15 @@ impl<N: NodeIdT + Rand> Subset<N> {
// Upon delivery of value 1 from at least N f instances of BA, provide
// input 0 to each instance of BA that has not yet been provided input.
for (id, binary_agreement) in &mut self.ba_instances {
if binary_agreement.accepts_input() {
let to_msg = |a_msg| Message::BinaryAgreement(id.clone(), a_msg);
for output in step.extend_with(
binary_agreement
.handle_input(false)
.map_err(Error::ProcessBinaryAgreement1)?,
to_msg,
) {
if self.ba_results.insert(id.clone(), output).is_some() {
return Err(Error::MultipleBinaryAgreementResults);
}
let to_msg = |a_msg| Message::BinaryAgreement(id.clone(), a_msg);
for output in step.extend_with(
binary_agreement
.handle_input(false)
.map_err(Error::ProcessBinaryAgreement1)?,
to_msg,
) {
if self.ba_results.insert(id.clone(), output).is_some() {
return Err(Error::MultipleBinaryAgreementResults);
}
}
}

View File

@ -117,7 +117,7 @@
//! secret_key_shares.insert(id, sks);
//! }
//!
//! // Three out of four nodes can now sign a message. Each share can be verified individually.
//! // Two out of four nodes can now sign a message. Each share can be verified individually.
//! let msg = "Nodes 0 and 1 does not agree with this.";
//! let mut sig_shares: BTreeMap<usize, SignatureShare> = BTreeMap::new();
//! for (&id, sks) in &secret_key_shares {
@ -226,7 +226,7 @@ impl Debug for Ack {
}
/// The information needed to track a single proposer's secret sharing process.
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq)]
struct ProposalState {
/// The proposer's commitment.
commit: BivarCommitment,
@ -275,7 +275,7 @@ pub struct SyncKeyGen<N> {
our_idx: Option<u64>,
/// Our secret key.
sec_key: SecretKey,
/// The public keys of all nodes, by node index.
/// The public keys of all nodes, by node ID.
pub_keys: BTreeMap<N, PublicKey>,
/// Proposed bivariate polynomials.
parts: BTreeMap<u64, ProposalState>,
@ -340,11 +340,16 @@ impl<N: NodeIdT> SyncKeyGen<N> {
sender_id: &N,
Part(commit, rows): Part,
) -> Option<PartOutcome<N>> {
let sender_idx = self.node_index(sender_id)?;
let sender_idx = self.node_index(sender_id)?; // TODO: Return an error.
let opt_commit_row = self.our_idx.map(|idx| commit.row(idx + 1));
match self.parts.entry(sender_idx) {
Entry::Occupied(_) => {
debug!("Received multiple parts from node {:?}.", sender_id);
Entry::Occupied(entry) => {
if *entry.get() != ProposalState::new(commit) {
debug!("Received multiple parts from node {:?}.", sender_id);
let fault_log =
FaultLog::init(sender_id.clone(), FaultKind::MultiplePartMessages);
return Some(PartOutcome::Invalid(fault_log));
}
return None;
}
Entry::Vacant(entry) => {
@ -456,6 +461,11 @@ impl<N: NodeIdT> SyncKeyGen<N> {
Ok(netinfo)
}
/// Returns the number of nodes participating in the key generation.
pub fn num_nodes(&self) -> usize {
self.pub_keys.len()
}
/// Handles an `Ack` message or returns an error string.
fn handle_ack_or_err(
&mut self,
@ -470,7 +480,7 @@ impl<N: NodeIdT> SyncKeyGen<N> {
.get_mut(&proposer_idx)
.ok_or_else(|| Fault::SenderExist)?;
if !part.acks.insert(sender_idx) {
return Err(Fault::DuplicateAck);
return Ok(());
}
let our_idx = match self.our_idx {
Some(our_idx) => our_idx,

View File

@ -110,6 +110,7 @@ impl<N: NodeIdT> ThresholdDecryption<N> {
/// Sets the ciphertext, sends the decryption share, and tries to decrypt it.
/// This must be called exactly once, with the same ciphertext in all participating nodes.
/// If we have enough shares, outputs the plaintext.
pub fn set_ciphertext(&mut self, ct: Ciphertext) -> Result<Step<N>> {
if self.ciphertext.is_some() {
return Err(Error::MultipleInputs(Box::new(ct)));
@ -136,7 +137,8 @@ impl<N: NodeIdT> ThresholdDecryption<N> {
self.shares.keys()
}
fn handle_message(&mut self, sender_id: &N, message: Message) -> Result<Step<N>> {
/// Handles an incoming message. If we have collected enough shares, outputs the plaintext.
pub fn handle_message(&mut self, sender_id: &N, message: Message) -> Result<Step<N>> {
if self.terminated {
return Ok(Step::default()); // Don't waste time on redundant shares.
}

View File

@ -1,34 +1,26 @@
//! # A Cryptographic Coin
//! # Collaborative Threshold Signing
//!
//! The Coin produces a pseudorandom binary value that the correct nodes agree on, and that
//! cannot be known beforehand.
//! The algorithm is instantiated with data to sign, and waits for the input (no data, just `()`),
//! then sends a signature share to the others. When at least _f + 1_ correct validators have done
//! so, each node outputs the same, valid signature of the data.
//!
//! Every Coin instance has a _nonce_ that determines the value, without giving it away: It
//! is not feasible to compute the output from the nonce alone, and the output is uniformly
//! distributed.
//!
//! The nodes input a signal (no data, just `()`), and after _2 f + 1_ nodes have provided input,
//! everyone receives the output value. In particular, the adversary cannot know the output value
//! before at least one correct node has provided input.
//! In addition to signing, this can also be used as a source of pseudorandomness: The signature
//! cannot be known until more than _f_ validators have contributed their shares.
//!
//! ## How it works
//!
//! The algorithm uses a threshold signature scheme with the uniqueness property: For each public
//! key and message, there is exactly one valid signature. This group signature is produced using
//! signature shares from any combination of _2 f + 1_ secret key share holders.
//!
//! * On input, a node signs the nonce and sends its signature share to everyone else.
//! * When a node has received _2 f + 1_ shares, it computes the main signature and outputs the XOR
//! of its bits.
//! signature shares from any combination of _f + 1_ secret key share holders.
use std::collections::BTreeMap;
use std::sync::Arc;
use crypto::{self, Signature, SignatureShare};
use crypto::{self, hash_g2, Signature, SignatureShare, G2};
use fault_log::{Fault, FaultKind};
use {DistAlgorithm, NetworkInfo, NodeIdT, Target};
/// A coin error.
/// A threshold signing error.
#[derive(Clone, Eq, PartialEq, Debug, Fail)]
pub enum Error {
#[fail(display = "CombineAndVerifySigCrypto error: {}", _0)]
@ -39,15 +31,15 @@ pub enum Error {
VerificationFailed,
}
/// A coin result.
/// A threshold signing result.
pub type Result<T> = ::std::result::Result<T, Error>;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Rand)]
pub struct CoinMessage(SignatureShare);
pub struct Message(SignatureShare);
impl CoinMessage {
impl Message {
pub fn new(sig: SignatureShare) -> Self {
CoinMessage(sig)
Message(sig)
}
pub fn to_sig(&self) -> &SignatureShare {
@ -55,57 +47,39 @@ impl CoinMessage {
}
}
/// A coin algorithm instance. On input, broadcasts our threshold signature share. Upon
/// A threshold signing algorithm instance. On input, broadcasts our threshold signature share. Upon
/// receiving at least `num_faulty + 1` shares, attempts to combine them into a signature. If that
/// signature is valid, the instance outputs it and terminates; otherwise the instance aborts.
#[derive(Debug)]
pub struct Coin<N, T> {
pub struct ThresholdSign<N> {
netinfo: Arc<NetworkInfo<N>>,
/// The name of this coin. It is required to be unique for each coin round.
nonce: T,
/// The hash of the data to be signed.
msg_hash: G2,
/// All received threshold signature shares.
received_shares: BTreeMap<N, SignatureShare>,
/// Whether we provided input to the coin.
/// Whether we already sent our shares.
had_input: bool,
/// Termination flag.
terminated: bool,
}
pub type Step<N, T> = ::Step<Coin<N, T>>;
pub type Step<N> = ::Step<ThresholdSign<N>>;
impl<N, T> DistAlgorithm for Coin<N, T>
where
N: NodeIdT,
T: Clone + AsRef<[u8]> + Send + Sync,
{
impl<N: NodeIdT> DistAlgorithm for ThresholdSign<N> {
type NodeId = N;
type Input = ();
type Output = bool;
type Message = CoinMessage;
type Output = Signature;
type Message = Message;
type Error = Error;
/// Sends our threshold signature share if not yet sent.
fn handle_input(&mut self, _input: Self::Input) -> Result<Step<N, T>> {
if !self.had_input {
self.had_input = true;
self.get_coin()
} else {
Ok(Step::default())
}
fn handle_input(&mut self, _input: ()) -> Result<Step<N>> {
self.sign()
}
/// Receives input from a remote node.
fn handle_message(
&mut self,
sender_id: &Self::NodeId,
message: Self::Message,
) -> Result<Step<N, T>> {
if !self.terminated {
let CoinMessage(share) = message;
self.handle_share(sender_id, share)
} else {
Ok(Step::default())
}
fn handle_message(&mut self, sender_id: &N, message: Message) -> Result<Step<N>> {
self.handle_message(sender_id, message)
}
/// Whether the algorithm has terminated.
@ -118,35 +92,42 @@ where
}
}
impl<N, T> Coin<N, T>
where
N: NodeIdT,
T: Clone + AsRef<[u8]> + Send + Sync,
{
pub fn new(netinfo: Arc<NetworkInfo<N>>, nonce: T) -> Self {
Coin {
impl<N: NodeIdT> ThresholdSign<N> {
/// Creates a new instance of `ThresholdSign`, with the goal to collaboratively sign `msg`.
pub fn new<M: AsRef<[u8]>>(netinfo: Arc<NetworkInfo<N>>, msg: M) -> Self {
ThresholdSign {
netinfo,
nonce,
msg_hash: hash_g2(msg),
received_shares: BTreeMap::new(),
had_input: false,
terminated: false,
}
}
fn get_coin(&mut self) -> Result<Step<N, T>> {
/// Sends our signature shares, and if we have collected enough, returns the full signature.
pub fn sign(&mut self) -> Result<Step<N>> {
if self.had_input {
return Ok(Step::default());
}
self.had_input = true;
if !self.netinfo.is_validator() {
return self.try_output();
}
let share = self.netinfo.secret_key_share().sign(&self.nonce);
let mut step: Step<_, _> = Target::All.message(CoinMessage(share.clone())).into();
let msg = Message(self.netinfo.secret_key_share().sign_g2(self.msg_hash));
let mut step: Step<_> = Target::All.message(msg.clone()).into();
let id = self.netinfo.our_id().clone();
step.extend(self.handle_share(&id, share)?);
step.extend(self.handle_message(&id, msg)?);
Ok(step)
}
fn handle_share(&mut self, sender_id: &N, share: SignatureShare) -> Result<Step<N, T>> {
/// Handles an incoming share. If we have collected enough, returns the full signature.
pub fn handle_message(&mut self, sender_id: &N, message: Message) -> Result<Step<N>> {
if self.terminated {
return Ok(Step::default());
}
let Message(share) = message;
if let Some(pk_i) = self.netinfo.public_key_share(sender_id) {
if !pk_i.verify(&share, &self.nonce) {
if !pk_i.verify_g2(&share, self.msg_hash) {
// Log the faulty node and ignore the invalid share.
let fault_kind = FaultKind::UnverifiedSignatureShareSender;
return Ok(Fault::new(sender_id.clone(), fault_kind).into());
@ -158,7 +139,7 @@ where
self.try_output()
}
fn try_output(&mut self) -> Result<Step<N, T>> {
fn try_output(&mut self) -> Result<Step<N>> {
debug!(
"{:?} received {} shares, had_input = {}",
self.netinfo.our_id(),
@ -167,12 +148,10 @@ where
);
if self.had_input && self.received_shares.len() > self.netinfo.num_faulty() {
let sig = self.combine_and_verify_sig()?;
// Output the parity of the verified signature.
let parity = sig.parity();
debug!("{:?} output {}", self.netinfo.our_id(), parity);
debug!("{:?} output {:?}", self.netinfo.our_id(), sig);
self.terminated = true;
let step = self.handle_input(())?; // Before terminating, make sure we sent our share.
Ok(step.with_output(parity))
Ok(step.with_output(sig))
} else {
Ok(Step::default())
}
@ -191,7 +170,7 @@ where
.netinfo
.public_key_set()
.public_key()
.verify(&sig, &self.nonce)
.verify_g2(&sig, self.msg_hash)
{
// Abort
error!(

View File

@ -102,7 +102,7 @@ Adversaries can be introduced through the `.adversary` method on the constructor
By default, all network tests write traces of every network message into logfiles, named `net-trace_*.txt` in the current working directory. Each log stores one message per line, in the format of `[SENDER] -> [RECEIVER]: MSG`.
This behavior can be controlled using the `HBBFT_TEST_TRACE` environment variable; if set and equal to `0` or `false`, this functionality is disabled. Tracing is enabled by default.
This behavior can be controlled using the `HBBFT_TEST_TRACE` environment variable; if set and equal to `1` or `true`, this functionality is enabled. Tracing is disabled by default.
The `NetBuilder` allows hard-coding the trace setting, any value passed will override environment settings:

View File

@ -500,10 +500,12 @@ where
net.adversary = self.adversary;
}
// If the trace setting is not overriden, we use the setting from the environment.
let trace = self.trace.unwrap_or_else(|| {
// If the trace setting is not overriden, we use the setting from the environment.
let setting = env::var("HBBFT_TEST_TRACE").unwrap_or_else(|_| "true".to_string());
!(setting == "false" || setting == "0")
match env::var("HBBFT_TEST_TRACE").as_ref().map(|s| s.as_str()) {
Ok("true") | Ok("1") => true,
_ => false,
}
});
if trace {

View File

@ -7,7 +7,7 @@ use crypto::SecretKeyShare;
use rand::{self, Rng};
use hbbft::dynamic_honey_badger::Batch;
use hbbft::{Contribution, DistAlgorithm, NetworkInfo, Step, Target, TargetedMessage};
use hbbft::{Contribution, DistAlgorithm, Fault, NetworkInfo, Step, Target, TargetedMessage};
/// A node identifier. In the tests, nodes are simply numbered.
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Clone, Copy, Serialize, Deserialize, Rand)]
@ -25,6 +25,8 @@ pub struct TestNode<D: DistAlgorithm> {
outputs: Vec<D::Output>,
/// Outgoing messages to be sent to other nodes.
messages: VecDeque<TargetedMessage<D::Message, D::NodeId>>,
/// Collected fault logs.
faults: Vec<Fault<D::NodeId>>,
}
impl<D: DistAlgorithm> TestNode<D> {
@ -44,6 +46,7 @@ impl<D: DistAlgorithm> TestNode<D> {
let step = self.algo.handle_input(input).expect("input");
self.outputs.extend(step.output);
self.messages.extend(step.messages);
self.faults.extend(step.fault_log.0);
}
/// Returns the internal algorithm's instance.
@ -60,6 +63,7 @@ impl<D: DistAlgorithm> TestNode<D> {
queue: VecDeque::new(),
outputs: step.output.into_iter().collect(),
messages: step.messages,
faults: step.fault_log.0,
}
}
@ -73,9 +77,10 @@ impl<D: DistAlgorithm> TestNode<D> {
.expect("handling message");
self.outputs.extend(step.output);
self.messages.extend(step.messages);
self.faults.extend(step.fault_log.0);
}
/// Checks whether the node has messages to process
/// Checks whether the node has messages to process.
fn is_idle(&self) -> bool {
self.queue.is_empty()
}
@ -480,6 +485,8 @@ where
}
while !self.observer.queue.is_empty() {
self.observer.handle_message();
let faults: Vec<_> = self.observer.faults.drain(..).collect();
self.check_faults(faults);
}
}
@ -501,7 +508,7 @@ where
let id = self.adversary.pick_node(&self.nodes);
// The node handles the incoming message and creates new outgoing ones to be dispatched.
let msgs: Vec<_> = {
let (msgs, faults): (Vec<_>, Vec<_>) = {
let mut node = self.nodes.get_mut(&id).unwrap();
// Ensure the adversary is playing fair by selecting a node that will result in actual
@ -513,8 +520,12 @@ where
);
node.handle_message();
node.messages.drain(..).collect()
(
node.messages.drain(..).collect(),
node.faults.drain(..).collect(),
)
};
self.check_faults(faults);
self.dispatch_messages(id, msgs);
id
@ -522,11 +533,15 @@ where
/// Inputs a value in node `id`.
pub fn input(&mut self, id: NodeId, value: D::Input) {
let msgs: Vec<_> = {
let (msgs, faults): (Vec<_>, Vec<_>) = {
let mut node = self.nodes.get_mut(&id).expect("input instance");
node.handle_input(value);
node.messages.drain(..).collect()
(
node.messages.drain(..).collect(),
node.faults.drain(..).collect(),
)
};
self.check_faults(faults);
self.dispatch_messages(id, msgs);
}
@ -541,6 +556,15 @@ where
self.input(id, value.clone());
}
}
/// Verifies that no correct node is reported as faulty.
fn check_faults<I: IntoIterator<Item = Fault<D::NodeId>>>(&self, faults: I) {
for fault in faults {
if self.nodes.contains_key(&fault.node_id) {
panic!("Unexpected fault: {:?}", fault);
}
}
}
}
impl<A: Adversary<D>, C, D> TestNetwork<A, D>

View File

@ -1,5 +1,5 @@
#![deny(unused_must_use)]
//! Coin tests
//! Threshold signing tests
extern crate env_logger;
extern crate hbbft;
@ -18,15 +18,16 @@ use std::iter::once;
use rand::Rng;
use hbbft::coin::Coin;
use crypto::Signature;
use hbbft::threshold_sign::ThresholdSign;
use network::{Adversary, MessageScheduler, NodeId, SilentAdversary, TestNetwork, TestNode};
/// Tests a network of Coin instances with an optional expected value. Outputs the computed
/// coin value if the test is successful.
fn test_coin<A>(mut network: TestNetwork<A, Coin<NodeId, String>>) -> bool
/// Tests a network of threshold signing instances with an optional expected value. Outputs the
/// computed signature if the test is successful.
fn test_threshold_sign<A>(mut network: TestNetwork<A, ThresholdSign<NodeId>>) -> Signature
where
A: Adversary<Coin<NodeId, String>>,
A: Adversary<ThresholdSign<NodeId>>,
{
network.input_all(());
network.observer.handle_input(()); // Observer will only return after `input` was called.
@ -38,11 +39,11 @@ where
let mut expected = None;
// Verify that all instances output the same value.
for node in network.nodes.values() {
if let Some(b) = expected {
assert!(once(&b).eq(node.outputs()));
if let Some(ref b) = expected {
assert!(once(b).eq(node.outputs()));
} else {
assert_eq!(1, node.outputs().len());
expected = Some(node.outputs()[0]);
expected = Some(node.outputs()[0].clone());
}
}
// Now `expected` is the unique output of all good nodes.
@ -71,9 +72,9 @@ fn check_coin_distribution(num_samples: usize, count_true: usize, count_false: u
assert!(count_false > min_throws);
}
fn test_coin_different_sizes<A, F>(new_adversary: F, num_samples: usize)
fn test_threshold_sign_different_sizes<A, F>(new_adversary: F, num_samples: usize)
where
A: Adversary<Coin<NodeId, String>>,
A: Adversary<ThresholdSign<NodeId>>,
F: Fn(usize, usize) -> A,
{
assert!(num_samples > 0);
@ -105,9 +106,9 @@ where
let adversary = |_| new_adversary(num_good_nodes, num_faulty_nodes);
let nonce = format!("My very unique nonce {:x}:{}", unique_id, i);
info!("Nonce: {}", nonce);
let new_coin = |netinfo: _| Coin::new(netinfo, nonce.clone());
let new_coin = |netinfo: _| ThresholdSign::new(netinfo, nonce.clone());
let network = TestNetwork::new(num_good_nodes, num_faulty_nodes, adversary, new_coin);
let coin = test_coin(network);
let coin = test_threshold_sign(network).parity();
if coin {
count_true += 1;
} else {
@ -119,13 +120,13 @@ where
}
#[test]
fn test_coin_random_silent_200_samples() {
fn test_threshold_sign_random_silent_200_samples() {
let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::Random);
test_coin_different_sizes(new_adversary, 200);
test_threshold_sign_different_sizes(new_adversary, 200);
}
#[test]
fn test_coin_first_silent_50_samples() {
fn test_threshold_sign_first_silent_50_samples() {
let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::First);
test_coin_different_sizes(new_adversary, 50);
test_threshold_sign_different_sizes(new_adversary, 50);
}