diff --git a/Cargo.toml b/Cargo.toml index ba6f4ab..d213493 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ merkle = { git = "https://github.com/afck/merkle.rs", branch = "public-proof", f pairing = { version = "0.14.2", features = ["u128-support"] } protobuf = { version = "2.0.0", optional = true } rand = "0.4.2" +rand_derive = "0.3.1" reed-solomon-erasure = "3.1.0" ring = "^0.12" serde = "1.0.55" diff --git a/examples/simulation.rs b/examples/simulation.rs index 87850cf..5fb8efc 100644 --- a/examples/simulation.rs +++ b/examples/simulation.rs @@ -6,6 +6,8 @@ extern crate hbbft; extern crate itertools; extern crate pairing; extern crate rand; +#[macro_use] +extern crate rand_derive; extern crate serde; #[macro_use(Deserialize, Serialize)] extern crate serde_derive; @@ -62,7 +64,7 @@ struct Args { } /// A node identifier. In the simulation, nodes are simply numbered. -#[derive(Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Clone, Copy)] +#[derive(Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Clone, Copy, Rand)] pub struct NodeUid(pub usize); /// A transaction. diff --git a/src/agreement/bin_values.rs b/src/agreement/bin_values.rs index ac2b931..12081bc 100644 --- a/src/agreement/bin_values.rs +++ b/src/agreement/bin_values.rs @@ -3,7 +3,7 @@ use std::mem::replace; /// A lattice-valued description of the state of `bin_values`, essentially the same as the set of /// subsets of `bool`. -#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Rand)] pub enum BinValues { None, False, @@ -63,7 +63,7 @@ impl BinValues { } } - pub fn contains(&self, b: bool) -> bool { + pub fn contains(self, b: bool) -> bool { match self { BinValues::None => false, BinValues::Both => true, @@ -73,7 +73,7 @@ impl BinValues { } } - pub fn is_subset(&self, other: BinValues) -> bool { + pub fn is_subset(self, other: BinValues) -> bool { match self { BinValues::None => true, BinValues::False if other == BinValues::False || other == BinValues::Both => true, @@ -83,7 +83,7 @@ impl BinValues { } } - pub fn definite(&self) -> Option { + pub fn definite(self) -> Option { match self { BinValues::False => Some(false), BinValues::True => Some(true), diff --git a/src/agreement/mod.rs b/src/agreement/mod.rs index 8dfdb0c..1c7b65b 100644 --- a/src/agreement/mod.rs +++ b/src/agreement/mod.rs @@ -65,6 +65,7 @@ pub mod bin_values; +use rand; use std::collections::{BTreeMap, BTreeSet, VecDeque}; use std::fmt::Debug; use std::mem::replace; @@ -122,12 +123,32 @@ impl AgreementContent { } /// Messages sent during the binary Byzantine agreement stage. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Rand)] pub struct AgreementMessage { pub epoch: u32, pub content: AgreementContent, } +// NOTE: Extending rand_derive to correctly generate random values from boxes would make this +// implementation obsolete; however at the time of this writing, `rand::Rand` is already deprecated +// with no replacement in sight. +impl rand::Rand for AgreementContent { + fn rand(rng: &mut R) -> Self { + let message_type = *rng + .choose(&["bval", "aux", "conf", "term", "coin"]) + .unwrap(); + + match message_type { + "bval" => AgreementContent::BVal(rand::random()), + "aux" => AgreementContent::Aux(rand::random()), + "conf" => AgreementContent::Conf(rand::random()), + "term" => AgreementContent::Term(rand::random()), + "coin" => AgreementContent::Coin(Box::new(rand::random())), + _ => unreachable!(), + } + } +} + /// Possible values of the common coin schedule defining the method to derive the common coin in a /// given epoch: as a constant value or a distributed computation. enum CoinSchedule { diff --git a/src/broadcast.rs b/src/broadcast.rs index 3a2704b..1aacb40 100644 --- a/src/broadcast.rs +++ b/src/broadcast.rs @@ -40,6 +40,102 @@ //! node will eventually be able to decode (i.e. receive at least _2 f + 1_ `Echo` messages). //! * So a node with _2 f + 1_ `Ready`s and _2 f + 1_ `Echos` will decode and _output_ the value, //! knowing that every other correct node will eventually do the same. +//! +//! ## Example usage +//! +//! ```rust +//!# extern crate clear_on_drop; +//!# extern crate hbbft; +//!# extern crate rand; +//!# fn main() { +//!# +//! use clear_on_drop::ClearOnDrop; +//! use hbbft::broadcast::Broadcast; +//! use hbbft::crypto::SecretKeySet; +//! use hbbft::messaging::{DistAlgorithm, NetworkInfo, Target, TargetedMessage}; +//! use rand::{Rng, thread_rng}; +//! use std::collections::{BTreeSet, BTreeMap}; +//! use std::sync::Arc; +//! +//! // In the example, we will "simulate" a network by passing messages by hand between +//! // instantiated nodes. We use u64 as network ids, and start by creating a common +//! // network info. +//! +//! // Our simulated network will use seven nodes in total, node 3 will be the proposer. +//! const NUM_NODES: u64 = 7; +//! const PROPOSER_ID: u64 = 3; +//! +//! // Create set of node ids. +//! let all_uids: BTreeSet<_> = (0..NUM_NODES).collect(); +//! +//! // Secret keys are required to complete the NetworkInfo structure, but not used in the +//! // broadcast algorithm. +//! let mut rng = thread_rng(); +//! let secret_keys = SecretKeySet::random(4, &mut rng); +//! +//! // Create initial nodes by instantiating a `NetworkInfo` for each: +//! let mut nodes: BTreeMap<_, _> = all_uids.iter().cloned().map(|i| { +//! let netinfo = NetworkInfo::new( +//! i, +//! all_uids.clone(), +//! secret_keys.secret_key_share(i), +//! secret_keys.public_keys(), +//! ); +//! +//! let bc = Broadcast::new(Arc::new(netinfo), PROPOSER_ID) +//! .expect("could not instantiate Broadcast"); +//! +//! (i, bc) +//! }).collect(); +//! +//! // We are ready to start. First we generate a payload to broadcast: +//! let mut payload: Vec<_> = vec![0; 128]; +//! rng.fill_bytes(&mut payload[..]); +//! +//! // Now we can start the algorithm, its input is the payload to be broadcast. +//! let mut next_message = { +//! let proposer = nodes.get_mut(&PROPOSER_ID).unwrap(); +//! proposer.input(payload.clone()).unwrap(); +//! +//! // attach the sender to the resulting message +//! proposer.next_message().map(|tm| (PROPOSER_ID, tm)) +//! }; +//! +//! // We can sanity-check that a message is scheduled by the proposer: +//! assert!(next_message.is_some()); +//! +//! // The network is simulated by passing messages around from node to node. +//! while let Some((sender, TargetedMessage { target, message })) = next_message { +//! println!("Message [{:?} -> {:?}]: {:?}", sender, target, message); +//! +//! match target { +//! Target::All => { +//! let msg = &message; +//! nodes.iter_mut() +//! .for_each(|(_, node)| { node.handle_message(&sender, msg.clone()) +//! .expect("could not handle message"); }); +//! }, +//! Target::Node(ref dest) => { +//! let dest_node = nodes.get_mut(dest).expect("destination node not found"); +//! dest_node.handle_message(&sender, message) +//! .expect("could not handle message"); +//! }, +//! } +//! +//! // We have handled the message, now we check all nodes for new messages, in order: +//! next_message = nodes +//! .iter_mut() +//! .filter_map(|(&id, node)| node.next_message() +//! .map(|tm| (id, tm))) +//! .next(); +//! } +//! +//! // The algorithm output of every node will be the original payload. +//! for (_, mut node) in nodes { +//! assert_eq!(node.next_output().expect("missing output"), payload); +//! } +//!# } +//! ``` use std::collections::{BTreeMap, VecDeque}; use std::fmt::{self, Debug}; @@ -48,6 +144,7 @@ use std::sync::Arc; use byteorder::{BigEndian, ByteOrder}; use merkle::{MerkleTree, Proof}; +use rand; use reed_solomon_erasure as rse; use reed_solomon_erasure::ReedSolomon; use ring::digest; @@ -84,6 +181,29 @@ pub enum BroadcastMessage { Ready(Vec), } +// A random generation impl is provided for test cases. Unfortunately `#[cfg(test)]` does not work +// for integration tests. +impl rand::Rand for BroadcastMessage { + fn rand(rng: &mut R) -> Self { + let message_type = *rng.choose(&["value", "echo", "ready"]).unwrap(); + + // Create a random buffer for our proof. + let mut buffer: [u8; 32] = [0; 32]; + rng.fill_bytes(&mut buffer); + + // Generate a dummy proof to fill broadcast messages with. + let tree = MerkleTree::from_vec(&digest::SHA256, vec![buffer.to_vec()]); + let proof = tree.gen_proof(buffer.to_vec()).unwrap(); + + match message_type { + "value" => BroadcastMessage::Value(proof), + "echo" => BroadcastMessage::Echo(proof), + "ready" => BroadcastMessage::Ready(b"dummy-ready".to_vec()), + _ => unreachable!(), + } + } +} + impl Debug for BroadcastMessage { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { diff --git a/src/common_coin.rs b/src/common_coin.rs index 0939ae7..4d210b9 100644 --- a/src/common_coin.rs +++ b/src/common_coin.rs @@ -45,7 +45,7 @@ error_chain! { } } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Rand)] pub struct CommonCoinMessage(Signature); impl CommonCoinMessage { diff --git a/src/common_subset.rs b/src/common_subset.rs index 4dadeed..4936aed 100644 --- a/src/common_subset.rs +++ b/src/common_subset.rs @@ -32,6 +32,7 @@ use broadcast::{self, Broadcast, BroadcastMessage, BroadcastResult}; use fault_log::FaultLog; use fmt::HexBytes; use messaging::{DistAlgorithm, NetworkInfo, TargetedMessage}; +use rand::Rand; error_chain!{ types { @@ -54,8 +55,8 @@ error_chain!{ type ProposedValue = Vec; /// Message from Common Subset to remote nodes. -#[derive(Serialize, Deserialize, Clone, Debug)] -pub enum Message { +#[derive(Serialize, Deserialize, Clone, Debug, Rand)] +pub enum Message { /// A message for the broadcast algorithm concerning the set element proposed by the given node. Broadcast(NodeUid, BroadcastMessage), /// A message for the agreement algorithm concerning the set element proposed by the given @@ -65,9 +66,9 @@ pub enum Message { /// The queue of outgoing messages in a `CommonSubset` instance. #[derive(Deref, DerefMut)] -struct MessageQueue(VecDeque, NodeUid>>); +struct MessageQueue(VecDeque, NodeUid>>); -impl MessageQueue { +impl MessageQueue { /// Appends to the queue the messages from `agr`, wrapped with `proposer_id`. fn extend_agreement(&mut self, proposer_id: &NodeUid, agr: &mut Agreement) { let convert = |msg: TargetedMessage| { @@ -86,7 +87,7 @@ impl MessageQueue { } /// Asynchronous Common Subset algorithm instance -pub struct CommonSubset { +pub struct CommonSubset { /// Shared network information. netinfo: Arc>, broadcast_instances: BTreeMap>, @@ -101,7 +102,7 @@ pub struct CommonSubset { decided: bool, } -impl DistAlgorithm for CommonSubset { +impl DistAlgorithm for CommonSubset { type NodeUid = NodeUid; type Input = ProposedValue; type Output = BTreeMap; @@ -145,7 +146,7 @@ impl DistAlgorithm for CommonSubset { } } -impl CommonSubset { +impl CommonSubset { pub fn new(netinfo: Arc>, session_id: u64) -> CommonSubsetResult { // Create all broadcast instances. let mut broadcast_instances: BTreeMap> = BTreeMap::new(); diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index d25a40d..a5ce582 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -16,7 +16,7 @@ use clear_on_drop::ClearOnDrop; use init_with::InitWith; use pairing::bls12_381::{Bls12, Fr, FrRepr, G1, G1Affine, G2, G2Affine}; use pairing::{CurveAffine, CurveProjective, Engine, Field, PrimeField}; -use rand::{ChaChaRng, OsRng, Rand, Rng, SeedableRng}; +use rand::{ChaChaRng, OsRng, Rng, SeedableRng}; use ring::digest; use self::error::{ErrorKind, Result}; @@ -83,7 +83,8 @@ impl PublicKey { } /// A signature, or a signature share. -#[derive(Deserialize, Serialize, Clone, PartialEq, Eq)] +// note: random signatures can be generated for testing +#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, Rand)] pub struct Signature(#[serde(with = "serde_impl::projective")] G2); impl fmt::Debug for Signature { @@ -112,7 +113,7 @@ impl Signature { } /// A secret key, or a secret key share. -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq, Rand)] pub struct SecretKey(Fr); impl fmt::Debug for SecretKey { @@ -129,12 +130,6 @@ impl Default for SecretKey { } } -impl Rand for SecretKey { - fn rand(rng: &mut R) -> Self { - SecretKey(rng.gen()) - } -} - impl SecretKey { /// Creates a secret key from an existing value pub fn from_value(f: Fr) -> Self { @@ -203,7 +198,7 @@ impl Ciphertext { } /// A decryption share. A threshold of decryption shares can be used to decrypt a message. -#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq)] +#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq, Rand)] pub struct DecryptionShare(#[serde(with = "serde_impl::projective")] G1); impl Hash for DecryptionShare { diff --git a/src/dynamic_honey_badger/batch.rs b/src/dynamic_honey_badger/batch.rs index e0a4ecb..4271948 100644 --- a/src/dynamic_honey_badger/batch.rs +++ b/src/dynamic_honey_badger/batch.rs @@ -1,4 +1,5 @@ use super::ChangeState; +use rand::Rand; use std::collections::BTreeMap; /// A batch of transactions the algorithm has output. @@ -13,7 +14,7 @@ pub struct Batch { pub change: ChangeState, } -impl Batch { +impl Batch { /// Returns a new, empty batch with the given epoch. pub fn new(epoch: u64) -> Self { Batch { diff --git a/src/dynamic_honey_badger/builder.rs b/src/dynamic_honey_badger/builder.rs index aff47fc..2be5b1f 100644 --- a/src/dynamic_honey_badger/builder.rs +++ b/src/dynamic_honey_badger/builder.rs @@ -4,6 +4,7 @@ use std::hash::Hash; use std::marker::PhantomData; use std::sync::Arc; +use rand::Rand; use serde::{Deserialize, Serialize}; use super::{DynamicHoneyBadger, MessageQueue, VoteCounter}; @@ -25,7 +26,7 @@ pub struct DynamicHoneyBadgerBuilder { impl DynamicHoneyBadgerBuilder where C: Eq + Serialize + for<'r> Deserialize<'r> + Debug + Hash, - NodeUid: Eq + Ord + Clone + Debug + Serialize + for<'r> Deserialize<'r> + Hash, + NodeUid: Eq + Ord + Clone + Debug + Serialize + for<'r> Deserialize<'r> + Hash + Rand, { /// Returns a new `DynamicHoneyBadgerBuilder` configured to use the node IDs and cryptographic /// keys specified by `netinfo`. diff --git a/src/dynamic_honey_badger/mod.rs b/src/dynamic_honey_badger/mod.rs index cf5b748..e3fdfbd 100644 --- a/src/dynamic_honey_badger/mod.rs +++ b/src/dynamic_honey_badger/mod.rs @@ -44,6 +44,7 @@ //! `SyncKeyGen` instance is dropped, and a new one is started to create keys according to the new //! pending change. +use rand::Rand; use std::collections::VecDeque; use std::fmt::Debug; use std::hash::Hash; @@ -84,7 +85,7 @@ pub enum Input { } /// A Honey Badger instance that can handle adding and removing nodes. -pub struct DynamicHoneyBadger +pub struct DynamicHoneyBadger where C: Eq + Serialize + for<'r> Deserialize<'r> + Debug + Hash, NodeUid: Ord + Clone + Serialize + for<'r> Deserialize<'r> + Debug, @@ -114,7 +115,7 @@ where impl DistAlgorithm for DynamicHoneyBadger where C: Eq + Serialize + for<'r> Deserialize<'r> + Debug + Hash, - NodeUid: Eq + Ord + Clone + Serialize + for<'r> Deserialize<'r> + Debug + Hash, + NodeUid: Eq + Ord + Clone + Serialize + for<'r> Deserialize<'r> + Debug + Hash + Rand, { type NodeUid = NodeUid; type Input = Input; @@ -178,7 +179,7 @@ where impl DynamicHoneyBadger where C: Eq + Serialize + for<'r> Deserialize<'r> + Debug + Hash, - NodeUid: Eq + Ord + Clone + Debug + Serialize + for<'r> Deserialize<'r> + Hash, + NodeUid: Eq + Ord + Clone + Debug + Serialize + for<'r> Deserialize<'r> + Hash + Rand, { /// Returns a new `DynamicHoneyBadgerBuilder` configured to use the node IDs and cryptographic /// keys specified by `netinfo`. @@ -487,7 +488,7 @@ pub enum KeyGenMessage { /// A message sent to or received from another node's Honey Badger instance. #[derive(Serialize, Deserialize, Debug, Clone)] -pub enum Message { +pub enum Message { /// A message belonging to the `HoneyBadger` algorithm started in the given epoch. HoneyBadger(u64, HbMessage), /// A transaction to be committed, signed by a node. @@ -496,7 +497,7 @@ pub enum Message { SignedVote(SignedVote), } -impl Message { +impl Message { fn start_epoch(&self) -> u64 { match *self { Message::HoneyBadger(epoch, _) => epoch, @@ -516,11 +517,11 @@ impl Message { /// The queue of outgoing messages in a `HoneyBadger` instance. #[derive(Deref, DerefMut)] -struct MessageQueue(VecDeque, NodeUid>>); +struct MessageQueue(VecDeque, NodeUid>>); impl MessageQueue where - NodeUid: Eq + Hash + Ord + Clone + Debug + Serialize + for<'r> Deserialize<'r>, + NodeUid: Eq + Hash + Ord + Clone + Debug + Serialize + for<'r> Deserialize<'r> + Rand, { /// Appends to the queue the messages from `hb`, wrapped with `epoch`. fn extend_with_epoch(&mut self, epoch: u64, hb: &mut HoneyBadger) diff --git a/src/dynamic_honey_badger/votes.rs b/src/dynamic_honey_badger/votes.rs index c0edaf8..7addac9 100644 --- a/src/dynamic_honey_badger/votes.rs +++ b/src/dynamic_honey_badger/votes.rs @@ -85,7 +85,7 @@ where /// Returns an iterator over all pending votes that are newer than their voter's committed /// vote. - pub fn pending_votes<'a>(&'a self) -> impl Iterator> { + pub fn pending_votes(&self) -> impl Iterator> { self.pending.values().filter(move |signed_vote| { self.committed .get(&signed_vote.voter) diff --git a/src/honey_badger.rs b/src/honey_badger.rs index 49e6dbe..19d3485 100644 --- a/src/honey_badger.rs +++ b/src/honey_badger.rs @@ -1,3 +1,4 @@ +use rand::Rand; use std::collections::btree_map::Entry; use std::collections::{BTreeMap, BTreeSet, VecDeque}; use std::fmt::Debug; @@ -44,7 +45,7 @@ pub struct HoneyBadgerBuilder { impl HoneyBadgerBuilder where C: Serialize + for<'r> Deserialize<'r> + Debug + Hash + Eq, - NodeUid: Ord + Clone + Debug, + NodeUid: Ord + Clone + Debug + Rand, { /// Returns a new `HoneyBadgerBuilder` configured to use the node IDs and cryptographic keys /// specified by `netinfo`. @@ -81,7 +82,7 @@ where } /// An instance of the Honey Badger Byzantine fault tolerant consensus algorithm. -pub struct HoneyBadger { +pub struct HoneyBadger { /// Shared network data. netinfo: Arc>, /// The earliest epoch from which we have not yet received output. @@ -112,7 +113,7 @@ pub struct HoneyBadger { impl DistAlgorithm for HoneyBadger where C: Serialize + for<'r> Deserialize<'r> + Debug + Hash + Eq, - NodeUid: Ord + Clone + Debug, + NodeUid: Ord + Clone + Debug + Rand, { type NodeUid = NodeUid; type Input = C; @@ -168,7 +169,7 @@ where impl HoneyBadger where C: Serialize + for<'r> Deserialize<'r> + Debug + Hash + Eq, - NodeUid: Ord + Clone + Debug, + NodeUid: Ord + Clone + Debug + Rand, { /// Returns a new `HoneyBadgerBuilder` configured to use the node IDs and cryptographic keys /// specified by `netinfo`. @@ -634,8 +635,8 @@ impl Batch { } /// The content of a `HoneyBadger` message. It should be further annotated with an epoch. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum MessageContent { +#[derive(Clone, Debug, Deserialize, Rand, Serialize)] +pub enum MessageContent { /// A message belonging to the common subset algorithm in the given epoch. CommonSubset(common_subset::Message), /// A decrypted share of the output of `proposer_id`. @@ -645,7 +646,7 @@ pub enum MessageContent { }, } -impl MessageContent { +impl MessageContent { pub fn with_epoch(self, epoch: u64) -> Message { Message { epoch, @@ -655,13 +656,13 @@ impl MessageContent { } /// A message sent to or received from another node's Honey Badger instance. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Message { +#[derive(Clone, Debug, Deserialize, Rand, Serialize)] +pub struct Message { epoch: u64, content: MessageContent, } -impl Message { +impl Message { pub fn epoch(&self) -> u64 { self.epoch } @@ -669,9 +670,9 @@ impl Message { /// The queue of outgoing messages in a `HoneyBadger` instance. #[derive(Deref, DerefMut)] -struct MessageQueue(VecDeque, NodeUid>>); +struct MessageQueue(VecDeque, NodeUid>>); -impl MessageQueue { +impl MessageQueue { /// Appends to the queue the messages from `cs`, wrapped with `epoch`. fn extend_with_epoch(&mut self, epoch: u64, cs: &mut CommonSubset) { let convert = |msg: TargetedMessage, NodeUid>| { diff --git a/src/lib.rs b/src/lib.rs index e815b87..8e14f0a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,6 +114,8 @@ extern crate pairing; #[cfg(feature = "serialization-protobuf")] extern crate protobuf; extern crate rand; +#[macro_use] +extern crate rand_derive; extern crate reed_solomon_erasure; extern crate ring; extern crate serde; diff --git a/src/queueing_honey_badger.rs b/src/queueing_honey_badger.rs index 90aceee..84a6644 100644 --- a/src/queueing_honey_badger.rs +++ b/src/queueing_honey_badger.rs @@ -8,6 +8,7 @@ use std::fmt::Debug; use std::hash::Hash; use std::marker::PhantomData; +use rand::Rand; use serde::{Deserialize, Serialize}; use dynamic_honey_badger::{self, Batch as DhbBatch, DynamicHoneyBadger, Message}; @@ -40,7 +41,7 @@ pub struct QueueingHoneyBadgerBuilder { impl QueueingHoneyBadgerBuilder where Tx: Eq + Serialize + for<'r> Deserialize<'r> + Debug + Hash + Clone, - NodeUid: Eq + Ord + Clone + Debug + Serialize + for<'r> Deserialize<'r> + Hash, + NodeUid: Eq + Ord + Clone + Debug + Serialize + for<'r> Deserialize<'r> + Hash + Rand, { /// Returns a new `QueueingHoneyBadgerBuilder` configured to use the node IDs and cryptographic /// keys specified by `netinfo`. @@ -110,7 +111,7 @@ where pub struct QueueingHoneyBadger where Tx: Eq + Serialize + for<'r> Deserialize<'r> + Debug + Hash, - NodeUid: Ord + Clone + Serialize + for<'r> Deserialize<'r> + Debug, + NodeUid: Ord + Clone + Serialize + for<'r> Deserialize<'r> + Debug + Rand, { /// The target number of transactions to be included in each batch. batch_size: usize, @@ -125,7 +126,7 @@ where impl DistAlgorithm for QueueingHoneyBadger where Tx: Eq + Serialize + for<'r> Deserialize<'r> + Debug + Hash + Clone, - NodeUid: Eq + Ord + Clone + Serialize + for<'r> Deserialize<'r> + Debug + Hash, + NodeUid: Eq + Ord + Clone + Serialize + for<'r> Deserialize<'r> + Debug + Hash + Rand, { type NodeUid = NodeUid; type Input = Input; @@ -179,7 +180,7 @@ where impl QueueingHoneyBadger where Tx: Eq + Serialize + for<'r> Deserialize<'r> + Debug + Hash + Clone, - NodeUid: Eq + Ord + Clone + Debug + Serialize + for<'r> Deserialize<'r> + Hash, + NodeUid: Eq + Ord + Clone + Debug + Serialize + for<'r> Deserialize<'r> + Hash + Rand, { /// Returns a new `QueueingHoneyBadgerBuilder` configured to use the node IDs and cryptographic /// keys specified by `netinfo`. @@ -187,6 +188,11 @@ where QueueingHoneyBadgerBuilder::new(netinfo) } + /// Returns a reference to the internal `DynamicHoneyBadger` instance. + pub fn dyn_hb(&self) -> &DynamicHoneyBadger, NodeUid> { + &self.dyn_hb + } + /// Initiates the next epoch by proposing a batch from the queue. fn propose(&mut self) -> Result> { let amount = cmp::max(1, self.batch_size / self.dyn_hb.netinfo().num_nodes()); diff --git a/tests/agreement.rs b/tests/agreement.rs index 4f9b52f..e40fee1 100644 --- a/tests/agreement.rs +++ b/tests/agreement.rs @@ -21,6 +21,8 @@ extern crate pairing; extern crate rand; #[macro_use] extern crate serde_derive; +#[macro_use] +extern crate rand_derive; mod network; diff --git a/tests/broadcast.rs b/tests/broadcast.rs index 4103097..7f10d0e 100644 --- a/tests/broadcast.rs +++ b/tests/broadcast.rs @@ -8,6 +8,8 @@ extern crate pairing; extern crate rand; #[macro_use] extern crate serde_derive; +#[macro_use] +extern crate rand_derive; mod network; @@ -19,8 +21,11 @@ use rand::Rng; use hbbft::broadcast::{Broadcast, BroadcastMessage}; use hbbft::crypto::SecretKeySet; -use hbbft::messaging::{DistAlgorithm, NetworkInfo, TargetedMessage}; -use network::{Adversary, MessageScheduler, NodeUid, SilentAdversary, TestNetwork, TestNode}; +use hbbft::messaging::{DistAlgorithm, NetworkInfo, Target, TargetedMessage}; +use network::{ + Adversary, MessageScheduler, MessageWithSender, NodeUid, RandomAdversary, SilentAdversary, + TestNetwork, TestNode, +}; /// An adversary that inputs an alternate value. struct ProposeAdversary { @@ -55,7 +60,7 @@ impl Adversary> for ProposeAdversary { // All messages are ignored. } - fn step(&mut self) -> Vec<(NodeUid, TargetedMessage)> { + fn step(&mut self) -> Vec>> { if self.has_sent { return vec![]; } @@ -84,7 +89,9 @@ impl Adversary> for ProposeAdversary { )); let mut bc = Broadcast::new(netinfo, id).expect("broadcast instance"); bc.input(b"Fake news".to_vec()).expect("propose"); - bc.message_iter().map(|msg| (id, msg)).collect() + bc.message_iter() + .map(|msg| MessageWithSender::new(id, msg)) + .collect() } } @@ -182,3 +189,15 @@ fn test_broadcast_first_delivery_adv_propose() { }; test_broadcast_different_sizes(new_adversary, b"Foo"); } + +#[test] +fn test_broadcast_random_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, + message: rand::random(), + }) + }; + test_broadcast_different_sizes(new_adversary, b"RandomFoo"); +} diff --git a/tests/common_coin.rs b/tests/common_coin.rs index 7b0e9d3..23ab884 100644 --- a/tests/common_coin.rs +++ b/tests/common_coin.rs @@ -8,6 +8,8 @@ extern crate pairing; extern crate rand; #[macro_use] extern crate serde_derive; +#[macro_use] +extern crate rand_derive; mod network; diff --git a/tests/common_subset.rs b/tests/common_subset.rs index 658bd93..7af95c7 100644 --- a/tests/common_subset.rs +++ b/tests/common_subset.rs @@ -8,6 +8,8 @@ extern crate pairing; extern crate rand; #[macro_use] extern crate serde_derive; +#[macro_use] +extern crate rand_derive; mod network; diff --git a/tests/dynamic_honey_badger.rs b/tests/dynamic_honey_badger.rs index d053591..cbdbcba 100644 --- a/tests/dynamic_honey_badger.rs +++ b/tests/dynamic_honey_badger.rs @@ -8,6 +8,8 @@ extern crate pairing; extern crate rand; #[macro_use] extern crate serde_derive; +#[macro_use] +extern crate rand_derive; mod network; diff --git a/tests/honey_badger.rs b/tests/honey_badger.rs index 8b3a58b..fdc71b7 100644 --- a/tests/honey_badger.rs +++ b/tests/honey_badger.rs @@ -8,6 +8,8 @@ extern crate env_logger; extern crate pairing; extern crate rand; #[macro_use] +extern crate rand_derive; +#[macro_use] extern crate serde_derive; mod network; @@ -22,7 +24,8 @@ use hbbft::messaging::{NetworkInfo, Target, TargetedMessage}; use hbbft::transaction_queue::TransactionQueue; use network::{ - Adversary, MessageScheduler, MessageWithSender, NodeUid, SilentAdversary, TestNetwork, TestNode, + Adversary, MessageScheduler, MessageWithSender, NodeUid, RandomAdversary, SilentAdversary, + TestNetwork, TestNode, }; type UsizeHoneyBadger = HoneyBadger, NodeUid>; @@ -99,7 +102,7 @@ impl Adversary for FaultyShareAdversary { .expect("decryption share"); // Send the share to remote nodes. for proposer_id in 0..self.num_good + self.num_adv { - outgoing.push(( + outgoing.push(MessageWithSender::new( NodeUid(sender_id), Target::All.message( MessageContent::DecryptionShare { @@ -237,3 +240,15 @@ fn test_honey_badger_faulty_share() { }; test_honey_badger_different_sizes(new_adversary, 8); } + +#[test] +fn test_honey_badger_random_adversary() { + let new_adversary = |_, _, _| { + // A 10% injection chance is roughly ~13k extra messages added. + RandomAdversary::new(0.1, 0.1, || TargetedMessage { + target: Target::All, + message: rand::random(), + }) + }; + test_honey_badger_different_sizes(new_adversary, 8); +} diff --git a/tests/network/mod.rs b/tests/network/mod.rs index 714643d..821aa01 100644 --- a/tests/network/mod.rs +++ b/tests/network/mod.rs @@ -1,6 +1,6 @@ use std::collections::{BTreeMap, BTreeSet, VecDeque}; -use std::fmt::Debug; -use std::hash::Hash; +use std::fmt::{self, Debug}; +use std::mem; use std::sync::Arc; use rand::{self, Rng}; @@ -9,7 +9,7 @@ use hbbft::crypto::{PublicKeySet, SecretKeySet}; use hbbft::messaging::{DistAlgorithm, NetworkInfo, Target, TargetedMessage}; /// A node identifier. In the tests, nodes are simply numbered. -#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Clone, Copy, Serialize, Deserialize)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Clone, Copy, Serialize, Deserialize, Rand)] pub struct NodeUid(pub usize); /// A "node" running an instance of the algorithm `D`. @@ -68,6 +68,11 @@ impl TestNode { .expect("handling message"); self.outputs.extend(self.algo.output_iter()); } + + /// Checks whether the node has messages to process + fn is_idle(&self) -> bool { + self.queue.is_empty() + } } /// A strategy for picking the next good node to handle a message. @@ -105,21 +110,61 @@ impl MessageScheduler { } } -pub type MessageWithSender = ( - ::NodeUid, - TargetedMessage<::Message, ::NodeUid>, -); +/// A message combined with a sender. +pub struct MessageWithSender { + /// The sender of the message. + pub sender: ::NodeUid, + /// The targeted message (recipient and message body). + pub tm: TargetedMessage<::Message, ::NodeUid>, +} + +// The Debug implementation cannot be derived automatically, possibly due to a compiler bug. For +// this reason, it is implemented manually here. +impl fmt::Debug for MessageWithSender { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "MessageWithSender {{ sender: {:?}, tm: {:?} }}", + self.sender, self.tm.target + ) + } +} + +impl MessageWithSender { + /// Creates a new message with a sender. + pub fn new( + sender: D::NodeUid, + tm: TargetedMessage, + ) -> MessageWithSender { + MessageWithSender { sender, tm } + } +} /// An adversary that can control a set of nodes and pick the next good node to receive a message. +/// +/// See `TestNetwork::step()` for a more detailed description of its capabilities. pub trait Adversary { /// Chooses a node to be the next one to handle a message. + /// + /// Starvation is illegal, i.e. in every iteration a node that has pending incoming messages + /// must be chosen. fn pick_node(&self, nodes: &BTreeMap>) -> D::NodeUid; - /// Adds a message sent to one of the adversary's nodes. + /// Called when a node controlled by the adversary receives a message. fn push_message(&mut self, sender_id: D::NodeUid, msg: TargetedMessage); /// Produces a list of messages to be sent from the adversary's nodes. fn step(&mut self) -> Vec>; + + /// Initialize an adversary. This function's primary purpose is to inform the adversary over + /// some aspects of the network, such as which nodes they control. + fn init( + &mut self, + _all_nodes: &BTreeMap>, + _adv_nodes: &BTreeMap>>, + ) { + // default: does nothing + } } /// An adversary whose nodes never send any messages. @@ -148,11 +193,165 @@ impl Adversary for SilentAdversary { } } -/// A collection of `TestNode`s representing a network. -pub struct TestNetwork, D: DistAlgorithm> -where - ::NodeUid: Hash, +/// Return true with a certain `probability` ([0 .. 1.0]). +fn randomly(probability: f32) -> bool { + assert!(probability <= 1.0); + assert!(probability >= 0.0); + + let mut rng = rand::thread_rng(); + rng.gen_range(0.0, 1.0) <= probability +} + +#[test] +fn test_randomly() { + assert!(randomly(1.0)); + assert!(!randomly(0.0)); +} + +/// An adversary that performs naive replay attacks. +/// +/// The adversary will randomly take a message that is sent to one of its nodes and re-send it to +/// a different node. Additionally, it will inject unrelated messages at random. +#[allow(unused)] // not used in all tests +pub struct RandomAdversary { + /// The underlying scheduler used + scheduler: MessageScheduler, + + /// Node ids seen by the adversary. + known_node_ids: Vec, + /// Node ids under control of adversary + known_adversarial_ids: Vec, + + /// Internal queue for messages to be returned on the next `Adversary::step()` call + outgoing: Vec>, + /// Generates random messages to be injected + generator: F, + + /// Probability of a message replay + p_replay: f32, + /// Probability of a message injection + p_inject: f32, +} + +impl RandomAdversary { + /// Creates a new random adversary instance. + #[allow(unused)] + pub fn new(p_replay: f32, p_inject: f32, generator: F) -> RandomAdversary { + assert!( + p_inject < 0.95, + "injections are repeated, p_inject must be smaller than 0.95" + ); + + RandomAdversary { + // The random adversary, true to its name, always schedules randomly. + scheduler: MessageScheduler::Random, + known_node_ids: Vec::new(), + known_adversarial_ids: Vec::new(), + outgoing: Vec::new(), + generator, + p_replay, + p_inject, + } + } +} + +impl TargetedMessage> Adversary + for RandomAdversary { + fn init( + &mut self, + all_nodes: &BTreeMap>, + nodes: &BTreeMap>>, + ) { + self.known_adversarial_ids = nodes.keys().cloned().collect(); + self.known_node_ids = all_nodes.keys().cloned().collect(); + } + + fn pick_node(&self, nodes: &BTreeMap>) -> D::NodeUid { + // Just let the scheduler pick a node. + self.scheduler.pick_node(nodes) + } + + fn push_message(&mut self, _: D::NodeUid, msg: TargetedMessage) { + // If we have not discovered the network topology yet, abort. + if self.known_node_ids.is_empty() { + return; + } + + // only replay a message in some cases + if !randomly(self.p_replay) { + return; + } + + let TargetedMessage { message, target } = msg; + + match target { + Target::All => { + // Ideally, we would want to handle broadcast messages as well; however the + // adversary API is quite cumbersome at the moment in regards to access to the + // network topology. To re-send a broadcast message from one of the attacker + // controlled nodes, we would have to get a list of attacker controlled nodes + // here and use a random one as the origin/sender, this is not done here. + return; + } + Target::Node(our_node_id) => { + // Choose a new target to send the message to. The unwrap never fails, because we + // ensured that `known_node_ids` is non-empty earlier. + let mut rng = rand::thread_rng(); + let new_target_node = rng.choose(&self.known_node_ids).unwrap().clone(); + + // TODO: We could randomly broadcast it instead, if we had access to topology + // information. + self.outgoing.push(MessageWithSender::new( + our_node_id, + TargetedMessage { + target: Target::Node(new_target_node), + message, + }, + )); + } + } + } + + fn step(&mut self) -> Vec> { + // Clear messages. + let mut tmp = Vec::new(); + mem::swap(&mut tmp, &mut self.outgoing); + + // Possibly inject more messages: + while randomly(self.p_inject) { + let mut rng = rand::thread_rng(); + + // Pick a random adversarial node and create a message using the generator. + if let Some(sender) = rng.choose(&self.known_adversarial_ids[..]) { + let tm = (self.generator)(); + + // Add to outgoing queue. + tmp.push(MessageWithSender::new(sender.clone(), tm)); + } + } + + if !tmp.is_empty() { + println!("Injecting random messages: {:?}", tmp); + } + tmp + } +} + +/// A collection of `TestNode`s representing a network. +/// +/// Each `TestNetwork` type is tied to a specific adversary and a distributed algorithm. It consists +/// of a set of nodes, some of which are controlled by the adversary and some of which may be +/// observer nodes, as well as a set of threshold-cryptography public keys. +/// +/// In addition to being able to participate correctly in the network using his nodes, the +/// adversary can: +/// +/// 1. Decide which node is the next one to make progress, +/// 2. Send arbitrary messages to any node originating from one of the nodes they control. +/// +/// See the `step` function for details on actual operation of the network. +pub struct TestNetwork, D: DistAlgorithm> { pub nodes: BTreeMap>, pub observer: TestNode, pub adv_nodes: BTreeMap>>, @@ -208,6 +407,7 @@ where .map(NodeUid) .map(new_adv_node_by_id) .collect(); + let mut network = TestNetwork { nodes: (0..good_num).map(NodeUid).map(new_node_by_id).collect(), observer: new_node_by_id(NodeUid(good_num + adv_num)).1, @@ -215,9 +415,13 @@ where pk_set: pk_set.clone(), adv_nodes, }; + + // Inform the adversary about their nodes. + network.adversary.init(&network.nodes, &network.adv_nodes); + let msgs = network.adversary.step(); - for (sender_id, msg) in msgs { - network.dispatch_messages(sender_id, vec![msg]); + for MessageWithSender { sender, tm } in msgs { + network.dispatch_messages(sender, vec![tm]); } let mut initial_msgs: Vec<(D::NodeUid, Vec<_>)> = Vec::new(); for (id, node) in &mut network.nodes { @@ -266,20 +470,40 @@ where } } - /// Handles a queued message in a randomly selected node and returns the selected node's ID. + /// Performs one iteration of the network, consisting of the following steps: + /// + /// 1. Give the adversary a chance to send messages of his choosing through `Adversary::step()`, + /// 2. Let the adversary pick a node that receives its next message through + /// `Adversary::pick_node()`. + /// + /// Returns the node ID of the node that made progress. pub fn step(&mut self) -> NodeUid { + // We let the adversary send out messages to any number of nodes. let msgs = self.adversary.step(); - for (sender_id, msg) in msgs { - self.dispatch_messages(sender_id, Some(msg)); + for MessageWithSender { sender, tm } in msgs { + self.dispatch_messages(sender, Some(tm)); } - // Pick a random non-idle node.. + + // Now one node is chosen to make progress, we let the adversary decide which. 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 node = self.nodes.get_mut(&id).unwrap(); + + // Ensure the adversary is playing fair by selecting a node that will result in actual + // progress being made, otherwise `TestNode::handle_message()` will panic on `expect()` + // with a much more cryptic error message. + assert!( + !node.is_idle(), + "adversary illegally selected an idle node in pick_node()" + ); + node.handle_message(); node.algo.message_iter().collect() }; self.dispatch_messages(id, msgs); + id } diff --git a/tests/queueing_honey_badger.rs b/tests/queueing_honey_badger.rs index f8a2ec0..b978ba7 100644 --- a/tests/queueing_honey_badger.rs +++ b/tests/queueing_honey_badger.rs @@ -8,6 +8,8 @@ extern crate pairing; extern crate rand; #[macro_use] extern crate serde_derive; +#[macro_use] +extern crate rand_derive; mod network;