mirror of https://github.com/poanetwork/hbbft.git
Better proptest persistence through deterministic randomness. (#248)
* Add support for RNG instantiation in proptests. * Use `proptest` module strategy to create the rng for `net_dynamic_honey_badger`. * Use seed generation instead of RNG instantiation in tests. * Remove fixed RNG in `generate_map`. * `VirtualNet` now supports setting the random generator through the builder. * Add missing `time_limit` field to `::std::fmt::Debug` trait implementation on `NetBuilder`. * Pass an instantiated random number generator through `NewNodeInfo` as a convenience. * Make the random number generator of `DynamicHoneyBadgerBuilder` configurable, at the cost of now requiring mutability to call `build_first_node()`. * Ensure RNGs are derive from passed in seed in `net_dynamic_hb` tests. * Correct inappropriate use of `random::Random` instead of `Rng::gen` to generate dependent values in `binary_agreement`. The original implementation used `rand::random()`, which will always use the `thread_rng`, ignoring the fact that an RNG has actually been passed in. * Do not use `OsRng` but passed in RNG instead. * Use reference/non-reference passing of rngs more in line with the `rand` crates conventions. * Document `rng` field on `DynamicHoneyBadger`. * Make `SyncKeyGen` work with the extend (`encrypt_with_rng`) API of `threshold_crypto`. * Use passed-in random number generator in `HoneyBadger`. * Create `SubRng` crate in new `util` module to replace `create_rng()`. * Use an RNG seeded from the configure RNG when reinitializing `DynamicHoneyBadger`. * Use the correct branch of `threshold_crypto` with support for passing RNGs.
This commit is contained in:
parent
1d05ad60fb
commit
d2627272fe
|
@ -30,7 +30,7 @@ rand_derive = "0.3.1"
|
||||||
reed-solomon-erasure = "3.1.0"
|
reed-solomon-erasure = "3.1.0"
|
||||||
serde = "1.0.55"
|
serde = "1.0.55"
|
||||||
serde_derive = "1.0.55"
|
serde_derive = "1.0.55"
|
||||||
threshold_crypto = { git = "https://github.com/poanetwork/threshold_crypto", tag = "0.1.0" }
|
threshold_crypto = { git = "https://github.com/poanetwork/threshold_crypto", tag = "0.1.0-rng-fix" }
|
||||||
tiny-keccak = "1.4"
|
tiny-keccak = "1.4"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
@ -41,7 +41,7 @@ docopt = "1.0"
|
||||||
itertools = "0.7"
|
itertools = "0.7"
|
||||||
serde_derive = "1.0.55"
|
serde_derive = "1.0.55"
|
||||||
signifix = "0.9"
|
signifix = "0.9"
|
||||||
proptest = "0.8.6"
|
proptest = "0.8.7"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "consensus-node"
|
name = "consensus-node"
|
||||||
|
|
|
@ -267,8 +267,8 @@ where
|
||||||
F: Fn(NetworkInfo<NodeId>) -> (D, Step<D>),
|
F: Fn(NetworkInfo<NodeId>) -> (D, Step<D>),
|
||||||
{
|
{
|
||||||
let node_ids = (0..(good_num + adv_num)).map(NodeId);
|
let node_ids = (0..(good_num + adv_num)).map(NodeId);
|
||||||
let netinfos =
|
let netinfos = NetworkInfo::generate_map(node_ids, &mut rand::thread_rng())
|
||||||
NetworkInfo::generate_map(node_ids).expect("Failed to create `NetworkInfo` map");
|
.expect("Failed to create `NetworkInfo` map");
|
||||||
let new_node = |(id, netinfo): (NodeId, NetworkInfo<_>)| {
|
let new_node = |(id, netinfo): (NodeId, NetworkInfo<_>)| {
|
||||||
(id, TestNode::new(new_algo(netinfo), hw_quality))
|
(id, TestNode::new(new_algo(netinfo), hw_quality))
|
||||||
};
|
};
|
||||||
|
|
|
@ -139,10 +139,10 @@ impl rand::Rand for MessageContent {
|
||||||
let message_type = *rng.choose(&["sbvb", "conf", "term", "coin"]).unwrap();
|
let message_type = *rng.choose(&["sbvb", "conf", "term", "coin"]).unwrap();
|
||||||
|
|
||||||
match message_type {
|
match message_type {
|
||||||
"sbvb" => MessageContent::SbvBroadcast(rand::random()),
|
"sbvb" => MessageContent::SbvBroadcast(rng.gen()),
|
||||||
"conf" => MessageContent::Conf(rand::random()),
|
"conf" => MessageContent::Conf(rng.gen()),
|
||||||
"term" => MessageContent::Term(rand::random()),
|
"term" => MessageContent::Term(rng.gen()),
|
||||||
"coin" => MessageContent::Coin(Box::new(rand::random())),
|
"coin" => MessageContent::Coin(Box::new(rng.gen())),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,8 +35,8 @@ impl rand::Rand for Message {
|
||||||
let message_type = *rng.choose(&["bval", "aux"]).unwrap();
|
let message_type = *rng.choose(&["bval", "aux"]).unwrap();
|
||||||
|
|
||||||
match message_type {
|
match message_type {
|
||||||
"bval" => Message::BVal(rand::random()),
|
"bval" => Message::BVal(rng.gen()),
|
||||||
"aux" => Message::Aux(rand::random()),
|
"aux" => Message::Aux(rng.gen()),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
//! let mut rng = thread_rng();
|
//! let mut rng = thread_rng();
|
||||||
//!
|
//!
|
||||||
//! // Create a random set of keys for testing.
|
//! // Create a random set of keys for testing.
|
||||||
//! let netinfos = NetworkInfo::generate_map(0..NUM_NODES)
|
//! let netinfos = NetworkInfo::generate_map(0..NUM_NODES, &mut rng)
|
||||||
//! .expect("Failed to create `NetworkInfo` map");
|
//! .expect("Failed to create `NetworkInfo` map");
|
||||||
//!
|
//!
|
||||||
//! // Create initial nodes by instantiating a `Broadcast` for each.
|
//! // Create initial nodes by instantiating a `Broadcast` for each.
|
||||||
|
|
|
@ -11,12 +11,16 @@ use super::{ChangeState, DynamicHoneyBadger, JoinPlan, Result, Step, VoteCounter
|
||||||
use honey_badger::HoneyBadger;
|
use honey_badger::HoneyBadger;
|
||||||
use messaging::NetworkInfo;
|
use messaging::NetworkInfo;
|
||||||
use traits::{Contribution, NodeIdT};
|
use traits::{Contribution, NodeIdT};
|
||||||
|
use util::SubRng;
|
||||||
|
|
||||||
/// A Dynamic Honey Badger builder, to configure the parameters and create new instances of
|
/// A Dynamic Honey Badger builder, to configure the parameters and create new instances of
|
||||||
/// `DynamicHoneyBadger`.
|
/// `DynamicHoneyBadger`.
|
||||||
pub struct DynamicHoneyBadgerBuilder<C, N> {
|
pub struct DynamicHoneyBadgerBuilder<C, N> {
|
||||||
/// The maximum number of future epochs for which we handle messages simultaneously.
|
/// The maximum number of future epochs for which we handle messages simultaneously.
|
||||||
max_future_epochs: usize,
|
max_future_epochs: usize,
|
||||||
|
/// Random number generator passed on to algorithm instance for key generation. Also used to
|
||||||
|
/// instantiate `HoneyBadger`.
|
||||||
|
rng: Box<dyn rand::Rng>,
|
||||||
_phantom: PhantomData<(C, N)>,
|
_phantom: PhantomData<(C, N)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +29,7 @@ impl<C, N> Default for DynamicHoneyBadgerBuilder<C, N> {
|
||||||
// TODO: Use the defaults from `HoneyBadgerBuilder`.
|
// TODO: Use the defaults from `HoneyBadgerBuilder`.
|
||||||
DynamicHoneyBadgerBuilder {
|
DynamicHoneyBadgerBuilder {
|
||||||
max_future_epochs: 3,
|
max_future_epochs: 3,
|
||||||
|
rng: Box::new(rand::thread_rng()),
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,11 +52,21 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the random number generator to be used to instantiate cryptographic structures.
|
||||||
|
pub fn rng<R: rand::Rng + 'static>(&mut self, rng: R) -> &mut Self {
|
||||||
|
self.rng = Box::new(rng);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new Dynamic Honey Badger instance with an empty buffer.
|
/// Creates a new Dynamic Honey Badger instance with an empty buffer.
|
||||||
pub fn build(&self, netinfo: NetworkInfo<N>) -> Result<(DynamicHoneyBadger<C, N>, Step<C, N>)> {
|
pub fn build(
|
||||||
|
&mut self,
|
||||||
|
netinfo: NetworkInfo<N>,
|
||||||
|
) -> Result<(DynamicHoneyBadger<C, N>, Step<C, N>)> {
|
||||||
let arc_netinfo = Arc::new(netinfo.clone());
|
let arc_netinfo = Arc::new(netinfo.clone());
|
||||||
let (honey_badger, hb_step) = HoneyBadger::builder(arc_netinfo.clone())
|
let (honey_badger, hb_step) = HoneyBadger::builder(arc_netinfo.clone())
|
||||||
.max_future_epochs(self.max_future_epochs)
|
.max_future_epochs(self.max_future_epochs)
|
||||||
|
.rng(self.rng.sub_rng())
|
||||||
.build();
|
.build();
|
||||||
let mut dhb = DynamicHoneyBadger {
|
let mut dhb = DynamicHoneyBadger {
|
||||||
netinfo,
|
netinfo,
|
||||||
|
@ -62,18 +77,21 @@ where
|
||||||
honey_badger,
|
honey_badger,
|
||||||
key_gen_state: None,
|
key_gen_state: None,
|
||||||
incoming_queue: Vec::new(),
|
incoming_queue: Vec::new(),
|
||||||
|
rng: Box::new(self.rng.sub_rng()),
|
||||||
};
|
};
|
||||||
let step = dhb.process_output(hb_step)?;
|
let step = dhb.process_output(hb_step)?;
|
||||||
Ok((dhb, step))
|
Ok((dhb, step))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new `DynamicHoneyBadger` configured to start a new network as a single validator.
|
/// Creates a new `DynamicHoneyBadger` configured to start a new network as a single validator.
|
||||||
pub fn build_first_node(&self, our_id: N) -> Result<(DynamicHoneyBadger<C, N>, Step<C, N>)> {
|
pub fn build_first_node(
|
||||||
let mut rng = rand::thread_rng();
|
&mut self,
|
||||||
let sk_set = SecretKeySet::random(0, &mut rng)?;
|
our_id: N,
|
||||||
|
) -> Result<(DynamicHoneyBadger<C, N>, Step<C, N>)> {
|
||||||
|
let sk_set = SecretKeySet::random(0, &mut self.rng)?;
|
||||||
let pk_set = sk_set.public_keys();
|
let pk_set = sk_set.public_keys();
|
||||||
let sks = sk_set.secret_key_share(0)?;
|
let sks = sk_set.secret_key_share(0)?;
|
||||||
let sk: SecretKey = rng.gen();
|
let sk: SecretKey = self.rng.gen();
|
||||||
let pub_keys = once((our_id.clone(), sk.public_key())).collect();
|
let pub_keys = once((our_id.clone(), sk.public_key())).collect();
|
||||||
let netinfo = NetworkInfo::new(our_id, sks, pk_set, sk, pub_keys);
|
let netinfo = NetworkInfo::new(our_id, sks, pk_set, sk, pub_keys);
|
||||||
self.build(netinfo)
|
self.build(netinfo)
|
||||||
|
@ -82,7 +100,7 @@ where
|
||||||
/// Creates a new `DynamicHoneyBadger` configured to join the network at the epoch specified in
|
/// Creates a new `DynamicHoneyBadger` configured to join the network at the epoch specified in
|
||||||
/// the `JoinPlan`.
|
/// the `JoinPlan`.
|
||||||
pub fn build_joining(
|
pub fn build_joining(
|
||||||
&self,
|
&mut self,
|
||||||
our_id: N,
|
our_id: N,
|
||||||
secret_key: SecretKey,
|
secret_key: SecretKey,
|
||||||
join_plan: JoinPlan<N>,
|
join_plan: JoinPlan<N>,
|
||||||
|
@ -108,6 +126,7 @@ where
|
||||||
honey_badger,
|
honey_badger,
|
||||||
key_gen_state: None,
|
key_gen_state: None,
|
||||||
incoming_queue: Vec::new(),
|
incoming_queue: Vec::new(),
|
||||||
|
rng: Box::new(self.rng.sub_rng()),
|
||||||
};
|
};
|
||||||
let mut step = dhb.process_output(hb_step)?;
|
let mut step = dhb.process_output(hb_step)?;
|
||||||
match join_plan.change {
|
match join_plan.change {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use rand::Rand;
|
use rand::Rand;
|
||||||
use std::mem;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::{fmt, mem};
|
||||||
|
|
||||||
use bincode;
|
use bincode;
|
||||||
use crypto::Signature;
|
use crypto::Signature;
|
||||||
|
use rand;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::votes::{SignedVote, VoteCounter};
|
use super::votes::{SignedVote, VoteCounter};
|
||||||
|
@ -16,9 +17,9 @@ use honey_badger::{self, HoneyBadger, Message as HbMessage};
|
||||||
use messaging::{DistAlgorithm, NetworkInfo, Target};
|
use messaging::{DistAlgorithm, NetworkInfo, Target};
|
||||||
use sync_key_gen::{Ack, Part, PartOutcome, SyncKeyGen};
|
use sync_key_gen::{Ack, Part, PartOutcome, SyncKeyGen};
|
||||||
use traits::{Contribution, NodeIdT};
|
use traits::{Contribution, NodeIdT};
|
||||||
|
use util::SubRng;
|
||||||
|
|
||||||
/// A Honey Badger instance that can handle adding and removing nodes.
|
/// A Honey Badger instance that can handle adding and removing nodes.
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DynamicHoneyBadger<C, N: Rand> {
|
pub struct DynamicHoneyBadger<C, N: Rand> {
|
||||||
/// Shared network data.
|
/// Shared network data.
|
||||||
pub(super) netinfo: NetworkInfo<N>,
|
pub(super) netinfo: NetworkInfo<N>,
|
||||||
|
@ -36,6 +37,29 @@ pub struct DynamicHoneyBadger<C, N: Rand> {
|
||||||
pub(super) key_gen_state: Option<KeyGenState<N>>,
|
pub(super) key_gen_state: Option<KeyGenState<N>>,
|
||||||
/// A queue for messages from future epochs that cannot be handled yet.
|
/// A queue for messages from future epochs that cannot be handled yet.
|
||||||
pub(super) incoming_queue: Vec<(N, Message<N>)>,
|
pub(super) incoming_queue: Vec<(N, Message<N>)>,
|
||||||
|
/// A random number generator used for secret key generation.
|
||||||
|
// Boxed to avoid overloading the algorithm's type with more generics.
|
||||||
|
pub(super) rng: Box<dyn rand::Rng>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, N> fmt::Debug for DynamicHoneyBadger<C, N>
|
||||||
|
where
|
||||||
|
C: fmt::Debug,
|
||||||
|
N: Rand + fmt::Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("DynamicHoneyBadger")
|
||||||
|
.field("netinfo", &self.netinfo)
|
||||||
|
.field("max_future_epochs", &self.max_future_epochs)
|
||||||
|
.field("start_epoch", &self.start_epoch)
|
||||||
|
.field("vote_counter", &self.vote_counter)
|
||||||
|
.field("key_gen_msg_buffer", &self.key_gen_msg_buffer)
|
||||||
|
.field("honey_badger", &self.honey_badger)
|
||||||
|
.field("key_gen_state", &self.key_gen_state)
|
||||||
|
.field("incoming_queue", &self.incoming_queue)
|
||||||
|
.field("rng", &"<RNG>")
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C, N> DistAlgorithm for DynamicHoneyBadger<C, N>
|
impl<C, N> DistAlgorithm for DynamicHoneyBadger<C, N>
|
||||||
|
@ -312,7 +336,7 @@ where
|
||||||
let threshold = (pub_keys.len() - 1) / 3;
|
let threshold = (pub_keys.len() - 1) / 3;
|
||||||
let sk = self.netinfo.secret_key().clone();
|
let sk = self.netinfo.secret_key().clone();
|
||||||
let our_id = self.our_id().clone();
|
let our_id = self.our_id().clone();
|
||||||
let (key_gen, part) = SyncKeyGen::new(our_id, sk, pub_keys, threshold)?;
|
let (key_gen, part) = SyncKeyGen::new(&mut self.rng, our_id, sk, pub_keys, threshold)?;
|
||||||
self.key_gen_state = Some(KeyGenState::new(key_gen, change.clone()));
|
self.key_gen_state = Some(KeyGenState::new(key_gen, change.clone()));
|
||||||
if let Some(part) = part {
|
if let Some(part) = part {
|
||||||
let step_on_send = self.send_transaction(KeyGenMessage::Part(part))?;
|
let step_on_send = self.send_transaction(KeyGenMessage::Part(part))?;
|
||||||
|
@ -330,6 +354,7 @@ where
|
||||||
mem::replace(&mut self.vote_counter, counter);
|
mem::replace(&mut self.vote_counter, counter);
|
||||||
let (hb, hb_step) = HoneyBadger::builder(netinfo)
|
let (hb, hb_step) = HoneyBadger::builder(netinfo)
|
||||||
.max_future_epochs(self.max_future_epochs)
|
.max_future_epochs(self.max_future_epochs)
|
||||||
|
.rng(self.rng.sub_rng())
|
||||||
.build();
|
.build();
|
||||||
self.honey_badger = hb;
|
self.honey_badger = hb;
|
||||||
self.process_output(hb_step)
|
self.process_output(hb_step)
|
||||||
|
@ -337,8 +362,17 @@ where
|
||||||
|
|
||||||
/// Handles a `Part` message that was output by Honey Badger.
|
/// Handles a `Part` message that was output by Honey Badger.
|
||||||
fn handle_part(&mut self, sender_id: &N, part: Part) -> Result<Step<C, N>> {
|
fn handle_part(&mut self, sender_id: &N, part: Part) -> Result<Step<C, N>> {
|
||||||
let handle = |kgs: &mut KeyGenState<N>| kgs.key_gen.handle_part(&sender_id, part);
|
// Awkward construction due to borrowck shortcomings; we need to borrow two struct fields
|
||||||
match self.key_gen_state.as_mut().and_then(handle) {
|
// mutably and have the borrow end early enough for us to call `send_transaction`.
|
||||||
|
// FIXME: This part should be cleaned up and restructured.
|
||||||
|
let res = {
|
||||||
|
let kgs = self.key_gen_state.as_mut();
|
||||||
|
let rng = &mut self.rng;
|
||||||
|
let handle = |kgs: &mut KeyGenState<N>| kgs.key_gen.handle_part(rng, &sender_id, part);
|
||||||
|
kgs.and_then(handle)
|
||||||
|
};
|
||||||
|
|
||||||
|
match res {
|
||||||
Some(PartOutcome::Valid(ack)) => self.send_transaction(KeyGenMessage::Ack(ack)),
|
Some(PartOutcome::Valid(ack)) => self.send_transaction(KeyGenMessage::Ack(ack)),
|
||||||
Some(PartOutcome::Invalid(fault_log)) => Ok(fault_log.into()),
|
Some(PartOutcome::Invalid(fault_log)) => Ok(fault_log.into()),
|
||||||
None => Ok(Step::default()),
|
None => Ok(Step::default()),
|
||||||
|
|
|
@ -188,6 +188,8 @@ impl<N> SignedVote<N> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use rand;
|
||||||
|
|
||||||
use super::{Change, SignedVote, VoteCounter};
|
use super::{Change, SignedVote, VoteCounter};
|
||||||
use fault_log::{FaultKind, FaultLog};
|
use fault_log::{FaultKind, FaultLog};
|
||||||
use messaging::NetworkInfo;
|
use messaging::NetworkInfo;
|
||||||
|
@ -199,8 +201,8 @@ mod tests {
|
||||||
/// order.
|
/// order.
|
||||||
fn setup(node_num: usize, era: u64) -> (Vec<VoteCounter<usize>>, Vec<Vec<SignedVote<usize>>>) {
|
fn setup(node_num: usize, era: u64) -> (Vec<VoteCounter<usize>>, Vec<Vec<SignedVote<usize>>>) {
|
||||||
// Create keys for threshold cryptography.
|
// Create keys for threshold cryptography.
|
||||||
let netinfos =
|
let netinfos = NetworkInfo::generate_map(0..node_num, &mut rand::thread_rng())
|
||||||
NetworkInfo::generate_map(0..node_num).expect("Failed to generate `NetworkInfo` map");
|
.expect("Failed to generate `NetworkInfo` map");
|
||||||
|
|
||||||
// Create a `VoteCounter` instance for each node.
|
// Create a `VoteCounter` instance for each node.
|
||||||
let create_counter =
|
let create_counter =
|
||||||
|
|
|
@ -2,12 +2,13 @@ use std::collections::BTreeMap;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use rand::Rand;
|
use rand::{self, Rand, Rng};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::{HoneyBadger, Message, Step};
|
use super::{HoneyBadger, Message, Step};
|
||||||
use messaging::{NetworkInfo, Target};
|
use messaging::{NetworkInfo, Target};
|
||||||
use traits::{Contribution, NodeIdT};
|
use traits::{Contribution, NodeIdT};
|
||||||
|
use util::SubRng;
|
||||||
|
|
||||||
/// A Honey Badger builder, to configure the parameters and create new instances of `HoneyBadger`.
|
/// A Honey Badger builder, to configure the parameters and create new instances of `HoneyBadger`.
|
||||||
pub struct HoneyBadgerBuilder<C, N>
|
pub struct HoneyBadgerBuilder<C, N>
|
||||||
|
@ -18,6 +19,8 @@ where
|
||||||
netinfo: Arc<NetworkInfo<N>>,
|
netinfo: Arc<NetworkInfo<N>>,
|
||||||
/// The maximum number of future epochs for which we handle messages simultaneously.
|
/// The maximum number of future epochs for which we handle messages simultaneously.
|
||||||
max_future_epochs: usize,
|
max_future_epochs: usize,
|
||||||
|
/// Random number generator passed on to algorithm instance for signing and encrypting.
|
||||||
|
rng: Box<dyn Rng>,
|
||||||
_phantom: PhantomData<C>,
|
_phantom: PhantomData<C>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,10 +35,17 @@ where
|
||||||
HoneyBadgerBuilder {
|
HoneyBadgerBuilder {
|
||||||
netinfo,
|
netinfo,
|
||||||
max_future_epochs: 3,
|
max_future_epochs: 3,
|
||||||
|
rng: Box::new(rand::thread_rng()),
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the random number generator for the public key cryptography.
|
||||||
|
pub fn rng<R: Rng + 'static>(&mut self, rng: R) -> &mut Self {
|
||||||
|
self.rng = Box::new(rng);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the maximum number of future epochs for which we handle messages simultaneously.
|
/// Sets the maximum number of future epochs for which we handle messages simultaneously.
|
||||||
pub fn max_future_epochs(&mut self, max_future_epochs: usize) -> &mut Self {
|
pub fn max_future_epochs(&mut self, max_future_epochs: usize) -> &mut Self {
|
||||||
self.max_future_epochs = max_future_epochs;
|
self.max_future_epochs = max_future_epochs;
|
||||||
|
@ -44,7 +54,7 @@ where
|
||||||
|
|
||||||
/// Creates a new Honey Badger instance in epoch 0 and makes the initial `Step` on that
|
/// Creates a new Honey Badger instance in epoch 0 and makes the initial `Step` on that
|
||||||
/// instance.
|
/// instance.
|
||||||
pub fn build(&self) -> (HoneyBadger<C, N>, Step<C, N>) {
|
pub fn build(&mut self) -> (HoneyBadger<C, N>, Step<C, N>) {
|
||||||
let hb = HoneyBadger {
|
let hb = HoneyBadger {
|
||||||
netinfo: self.netinfo.clone(),
|
netinfo: self.netinfo.clone(),
|
||||||
epoch: 0,
|
epoch: 0,
|
||||||
|
@ -53,6 +63,7 @@ where
|
||||||
max_future_epochs: self.max_future_epochs as u64,
|
max_future_epochs: self.max_future_epochs as u64,
|
||||||
incoming_queue: BTreeMap::new(),
|
incoming_queue: BTreeMap::new(),
|
||||||
remote_epochs: BTreeMap::new(),
|
remote_epochs: BTreeMap::new(),
|
||||||
|
rng: Box::new(self.rng.sub_rng()),
|
||||||
};
|
};
|
||||||
let step = if self.netinfo.is_validator() {
|
let step = if self.netinfo.is_validator() {
|
||||||
// The first message in an epoch announces the epoch transition.
|
// The first message in an epoch announces the epoch transition.
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use std::collections::btree_map::Entry;
|
use std::collections::btree_map::Entry;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::fmt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use bincode;
|
use bincode;
|
||||||
use rand::Rand;
|
use rand::{Rand, Rng};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::epoch_state::EpochState;
|
use super::epoch_state::EpochState;
|
||||||
|
@ -12,7 +13,6 @@ use messaging::{self, DistAlgorithm, NetworkInfo, Target};
|
||||||
use traits::{Contribution, NodeIdT};
|
use traits::{Contribution, NodeIdT};
|
||||||
|
|
||||||
/// An instance of the Honey Badger Byzantine fault tolerant consensus algorithm.
|
/// An instance of the Honey Badger Byzantine fault tolerant consensus algorithm.
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct HoneyBadger<C, N: Rand> {
|
pub struct HoneyBadger<C, N: Rand> {
|
||||||
/// Shared network data.
|
/// Shared network data.
|
||||||
pub(super) netinfo: Arc<NetworkInfo<N>>,
|
pub(super) netinfo: Arc<NetworkInfo<N>>,
|
||||||
|
@ -28,6 +28,28 @@ pub struct HoneyBadger<C, N: Rand> {
|
||||||
pub(super) incoming_queue: BTreeMap<u64, Vec<(N, MessageContent<N>)>>,
|
pub(super) incoming_queue: BTreeMap<u64, Vec<(N, MessageContent<N>)>>,
|
||||||
/// Known current epochs of remote nodes.
|
/// Known current epochs of remote nodes.
|
||||||
pub(super) remote_epochs: BTreeMap<N, u64>,
|
pub(super) remote_epochs: BTreeMap<N, u64>,
|
||||||
|
/// A random number generator used for secret key generation.
|
||||||
|
// Boxed to avoid overloading the algorithm's type with more generics.
|
||||||
|
pub(super) rng: Box<dyn Rng>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, N> fmt::Debug for HoneyBadger<C, N>
|
||||||
|
where
|
||||||
|
N: Rand + fmt::Debug,
|
||||||
|
C: fmt::Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt.debug_struct("HoneyBadger")
|
||||||
|
.field("netinfo", &self.netinfo)
|
||||||
|
.field("epoch", &self.epoch)
|
||||||
|
.field("has_input", &self.has_input)
|
||||||
|
.field("epochs", &self.epochs)
|
||||||
|
.field("max_future_epochs", &self.max_future_epochs)
|
||||||
|
.field("incoming_queue", &self.incoming_queue)
|
||||||
|
.field("remote_epochs", &self.remote_epochs)
|
||||||
|
.field("rng", &"<RNG>")
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Step<C, N> = messaging::Step<HoneyBadger<C, N>>;
|
pub type Step<C, N> = messaging::Step<HoneyBadger<C, N>>;
|
||||||
|
@ -79,7 +101,11 @@ where
|
||||||
self.has_input = true;
|
self.has_input = true;
|
||||||
let ser_prop =
|
let ser_prop =
|
||||||
bincode::serialize(&proposal).map_err(|err| ErrorKind::ProposeBincode(*err))?;
|
bincode::serialize(&proposal).map_err(|err| ErrorKind::ProposeBincode(*err))?;
|
||||||
let ciphertext = self.netinfo.public_key_set().public_key().encrypt(ser_prop);
|
let ciphertext = self
|
||||||
|
.netinfo
|
||||||
|
.public_key_set()
|
||||||
|
.public_key()
|
||||||
|
.encrypt_with_rng(&mut self.rng, ser_prop);
|
||||||
let epoch = self.epoch;
|
let epoch = self.epoch;
|
||||||
let mut step = self.epoch_state_mut(epoch)?.propose(&ciphertext)?;
|
let mut step = self.epoch_state_mut(epoch)?.propose(&ciphertext)?;
|
||||||
step.extend(self.try_output_batches()?);
|
step.extend(self.try_output_batches()?);
|
||||||
|
|
|
@ -149,6 +149,7 @@ pub mod subset;
|
||||||
pub mod sync_key_gen;
|
pub mod sync_key_gen;
|
||||||
pub mod threshold_decryption;
|
pub mod threshold_decryption;
|
||||||
pub mod transaction_queue;
|
pub mod transaction_queue;
|
||||||
|
pub mod util;
|
||||||
|
|
||||||
/// Common supertraits.
|
/// Common supertraits.
|
||||||
pub mod traits {
|
pub mod traits {
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||||
use std::iter::once;
|
use std::iter::once;
|
||||||
|
|
||||||
use failure::Fail;
|
use failure::Fail;
|
||||||
|
use rand;
|
||||||
|
|
||||||
use crypto::{self, PublicKey, PublicKeySet, PublicKeyShare, SecretKey, SecretKeyShare};
|
use crypto::{self, PublicKey, PublicKeySet, PublicKeyShare, SecretKey, SecretKeyShare};
|
||||||
use fault_log::{Fault, FaultLog};
|
use fault_log::{Fault, FaultLog};
|
||||||
|
@ -357,21 +358,21 @@ impl<N: NodeIdT> NetworkInfo<N> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a map of matching `NetworkInfo`s for testing.
|
/// Generates a map of matching `NetworkInfo`s for testing.
|
||||||
pub fn generate_map<I>(ids: I) -> Result<BTreeMap<N, NetworkInfo<N>>, crypto::error::Error>
|
pub fn generate_map<I, R>(
|
||||||
|
ids: I,
|
||||||
|
rng: &mut R,
|
||||||
|
) -> Result<BTreeMap<N, NetworkInfo<N>>, crypto::error::Error>
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = N>,
|
I: IntoIterator<Item = N>,
|
||||||
|
R: rand::Rng,
|
||||||
{
|
{
|
||||||
use rand::{self, Rng};
|
|
||||||
|
|
||||||
use crypto::SecretKeySet;
|
use crypto::SecretKeySet;
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
|
|
||||||
let all_ids: BTreeSet<N> = ids.into_iter().collect();
|
let all_ids: BTreeSet<N> = ids.into_iter().collect();
|
||||||
let num_faulty = (all_ids.len() - 1) / 3;
|
let num_faulty = (all_ids.len() - 1) / 3;
|
||||||
|
|
||||||
// Generate the keys for threshold cryptography.
|
// Generate the keys for threshold cryptography.
|
||||||
let sk_set = SecretKeySet::random(num_faulty, &mut rng)?;
|
let sk_set = SecretKeySet::random(num_faulty, rng)?;
|
||||||
let pk_set = sk_set.public_keys();
|
let pk_set = sk_set.public_keys();
|
||||||
|
|
||||||
// Generate keys for individually signing and encrypting messages.
|
// Generate keys for individually signing and encrypting messages.
|
||||||
|
|
|
@ -59,6 +59,9 @@
|
||||||
//! use threshold_crypto::{PublicKey, SecretKey, SignatureShare};
|
//! use threshold_crypto::{PublicKey, SecretKey, SignatureShare};
|
||||||
//! use hbbft::sync_key_gen::{PartOutcome, SyncKeyGen};
|
//! use hbbft::sync_key_gen::{PartOutcome, SyncKeyGen};
|
||||||
//!
|
//!
|
||||||
|
//! // Use a default random number generator for any randomness:
|
||||||
|
//! let mut rng = rand::thread_rng();
|
||||||
|
//!
|
||||||
//! // Two out of four shares will suffice to sign or encrypt something.
|
//! // Two out of four shares will suffice to sign or encrypt something.
|
||||||
//! let (threshold, node_num) = (1, 4);
|
//! let (threshold, node_num) = (1, 4);
|
||||||
//!
|
//!
|
||||||
|
@ -75,7 +78,7 @@
|
||||||
//! let mut nodes = BTreeMap::new();
|
//! let mut nodes = BTreeMap::new();
|
||||||
//! let mut parts = Vec::new();
|
//! let mut parts = Vec::new();
|
||||||
//! for (id, sk) in sec_keys.into_iter().enumerate() {
|
//! for (id, sk) in sec_keys.into_iter().enumerate() {
|
||||||
//! let (sync_key_gen, opt_part) = SyncKeyGen::new(id, sk, pub_keys.clone(), threshold)
|
//! let (sync_key_gen, opt_part) = SyncKeyGen::new(&mut rng, id, sk, pub_keys.clone(), threshold)
|
||||||
//! .unwrap_or_else(|_| panic!("Failed to create `SyncKeyGen` instance for node #{}", id));
|
//! .unwrap_or_else(|_| panic!("Failed to create `SyncKeyGen` instance for node #{}", id));
|
||||||
//! nodes.insert(id, sync_key_gen);
|
//! nodes.insert(id, sync_key_gen);
|
||||||
//! parts.push((id, opt_part.unwrap())); // Would be `None` for observer nodes.
|
//! parts.push((id, opt_part.unwrap())); // Would be `None` for observer nodes.
|
||||||
|
@ -85,7 +88,7 @@
|
||||||
//! let mut acks = Vec::new();
|
//! let mut acks = Vec::new();
|
||||||
//! for (sender_id, part) in parts {
|
//! for (sender_id, part) in parts {
|
||||||
//! for (&id, node) in &mut nodes {
|
//! for (&id, node) in &mut nodes {
|
||||||
//! match node.handle_part(&sender_id, part.clone()) {
|
//! match node.handle_part(&mut rng, &sender_id, part.clone()) {
|
||||||
//! Some(PartOutcome::Valid(ack)) => acks.push((id, ack)),
|
//! Some(PartOutcome::Valid(ack)) => acks.push((id, ack)),
|
||||||
//! Some(PartOutcome::Invalid(faults)) => panic!("Invalid part: {:?}", faults),
|
//! Some(PartOutcome::Invalid(faults)) => panic!("Invalid part: {:?}", faults),
|
||||||
//! None => panic!("We are not an observer, so we should send Ack."),
|
//! None => panic!("We are not an observer, so we should send Ack."),
|
||||||
|
@ -169,7 +172,7 @@ use crypto::{
|
||||||
};
|
};
|
||||||
use pairing::bls12_381::{Fr, G1Affine};
|
use pairing::bls12_381::{Fr, G1Affine};
|
||||||
use pairing::{CurveAffine, Field};
|
use pairing::{CurveAffine, Field};
|
||||||
use rand::OsRng;
|
use rand;
|
||||||
|
|
||||||
use fault_log::{AckMessageFault as Fault, FaultKind, FaultLog};
|
use fault_log::{AckMessageFault as Fault, FaultKind, FaultLog};
|
||||||
use messaging::NetworkInfo;
|
use messaging::NetworkInfo;
|
||||||
|
@ -287,7 +290,8 @@ impl<N: NodeIdT> SyncKeyGen<N> {
|
||||||
///
|
///
|
||||||
/// If we are not a validator but only an observer, no `Part` message is produced and no
|
/// If we are not a validator but only an observer, no `Part` message is produced and no
|
||||||
/// messages need to be sent.
|
/// messages need to be sent.
|
||||||
pub fn new(
|
pub fn new<R: rand::Rng>(
|
||||||
|
rng: &mut R,
|
||||||
our_id: N,
|
our_id: N,
|
||||||
sec_key: SecretKey,
|
sec_key: SecretKey,
|
||||||
pub_keys: BTreeMap<N, PublicKey>,
|
pub_keys: BTreeMap<N, PublicKey>,
|
||||||
|
@ -308,13 +312,13 @@ impl<N: NodeIdT> SyncKeyGen<N> {
|
||||||
if our_idx.is_none() {
|
if our_idx.is_none() {
|
||||||
return Ok((key_gen, None)); // No part: we are an observer.
|
return Ok((key_gen, None)); // No part: we are an observer.
|
||||||
}
|
}
|
||||||
let mut rng = OsRng::new().expect("OS random number generator");
|
|
||||||
let our_part = BivarPoly::random(threshold, &mut rng).map_err(Error::Creation)?;
|
let our_part = BivarPoly::random(threshold, rng).map_err(Error::Creation)?;
|
||||||
let commit = our_part.commitment();
|
let commit = our_part.commitment();
|
||||||
let encrypt = |(i, pk): (usize, &PublicKey)| {
|
let encrypt = |(i, pk): (usize, &PublicKey)| {
|
||||||
let row = our_part.row(i + 1).map_err(Error::Creation)?;
|
let row = our_part.row(i + 1).map_err(Error::Creation)?;
|
||||||
let bytes = bincode::serialize(&row).expect("failed to serialize row");
|
let bytes = bincode::serialize(&row).expect("failed to serialize row");
|
||||||
Ok(pk.encrypt(&bytes))
|
Ok(pk.encrypt_with_rng(rng, &bytes))
|
||||||
};
|
};
|
||||||
let rows = key_gen
|
let rows = key_gen
|
||||||
.pub_keys
|
.pub_keys
|
||||||
|
@ -331,8 +335,9 @@ impl<N: NodeIdT> SyncKeyGen<N> {
|
||||||
///
|
///
|
||||||
/// All participating nodes must handle the exact same sequence of messages.
|
/// All participating nodes must handle the exact same sequence of messages.
|
||||||
/// Note that `handle_part` also needs to explicitly be called with this instance's own `Part`.
|
/// Note that `handle_part` also needs to explicitly be called with this instance's own `Part`.
|
||||||
pub fn handle_part(
|
pub fn handle_part<R: rand::Rng>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
rng: &mut R,
|
||||||
sender_id: &N,
|
sender_id: &N,
|
||||||
Part(commit, rows): Part,
|
Part(commit, rows): Part,
|
||||||
) -> Option<PartOutcome<N>> {
|
) -> Option<PartOutcome<N>> {
|
||||||
|
@ -369,7 +374,7 @@ impl<N: NodeIdT> SyncKeyGen<N> {
|
||||||
let wrap = FieldWrap::new(val);
|
let wrap = FieldWrap::new(val);
|
||||||
// TODO: Handle errors.
|
// TODO: Handle errors.
|
||||||
let ser_val = bincode::serialize(&wrap).expect("failed to serialize value");
|
let ser_val = bincode::serialize(&wrap).expect("failed to serialize value");
|
||||||
pk.encrypt(ser_val)
|
pk.encrypt_with_rng(rng, ser_val)
|
||||||
};
|
};
|
||||||
let values = self.pub_keys.values().enumerate().map(encrypt).collect();
|
let values = self.pub_keys.values().enumerate().map(encrypt).collect();
|
||||||
Some(PartOutcome::Valid(Ack(sender_idx, values)))
|
Some(PartOutcome::Valid(Ack(sender_idx, values)))
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
//! Utility functions
|
||||||
|
//!
|
||||||
|
//! Functions not large enough to warrant their own crate or module, but flexible enough to be used
|
||||||
|
//! in multiple disjunct places in the library. May also contain backports, workarounds.
|
||||||
|
|
||||||
|
use rand;
|
||||||
|
|
||||||
|
/// Workaround trait for creating new random number generators
|
||||||
|
pub trait SubRng {
|
||||||
|
fn sub_rng(&mut self) -> Box<dyn rand::Rng>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> SubRng for R
|
||||||
|
where
|
||||||
|
R: rand::Rng,
|
||||||
|
{
|
||||||
|
fn sub_rng(&mut self) -> Box<dyn rand::Rng> {
|
||||||
|
// Currently hard-coded to be an `Isaac64Rng`, until better options emerge. This is either
|
||||||
|
// dependant on `rand` 0.5 support or an API re-design of parts of `threshold_crypto` and
|
||||||
|
// `hbbft`.
|
||||||
|
let rng = self.gen::<rand::isaac::Isaac64Rng>();
|
||||||
|
Box::new(rng)
|
||||||
|
}
|
||||||
|
}
|
|
@ -140,6 +140,32 @@ If the time limit has been reached, `crank` will return a `TimeLimitHit` error.
|
||||||
|
|
||||||
It's also possible to run tests without a time-limit on a per-run basis by setting the `HBBFT_NO_TIME_LIMIT` environment variable to "true".
|
It's also possible to run tests without a time-limit on a per-run basis by setting the `HBBFT_NO_TIME_LIMIT` environment variable to "true".
|
||||||
|
|
||||||
|
### Randomness
|
||||||
|
|
||||||
|
The test framework supports deterministic randomness by allowing all random values to be dervied from a single random generator. By seeding this generator with a fixed value, tests can be reproduced precisely:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[test]
|
||||||
|
fn do_example_test(seed: TestRngSeed, dimension: NetworkDimension) {
|
||||||
|
// Instantiates a new random number generator for the test.
|
||||||
|
let mut rng: TestRng = TestRng::from_seed(cfg.seed);
|
||||||
|
|
||||||
|
let mut net = NetBuilder::new(0..dimension.size)
|
||||||
|
.num_faulty(cfg.dimension.faulty)
|
||||||
|
// Setting `rng` ensures that randomness will only be retrieved by
|
||||||
|
// `VirtualNet` from the `TestRng` instantiated above.
|
||||||
|
.rng(rng)
|
||||||
|
.using_step(move |node: NewNodeInfo<_>| {
|
||||||
|
DynamicHoneyBadger::builder()
|
||||||
|
// `DynamicHoneyBadger` can also be configured with a random
|
||||||
|
// number generator. `NewNodeInfo` includes a convenience
|
||||||
|
// field that contains a ready-to-use random number generator:
|
||||||
|
.rng(node.rng)
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
### Property based testing
|
### Property based testing
|
||||||
|
|
||||||
Many higher-level tests allow for a variety of different input parameters like the number of nodes in a network or the amount of faulty ones among them. Other possible parameters include transaction, batch or contribution sizes. To test a variety of randomized combinations of these, the [proptest](https://docs.rs/proptest) crate should be used.
|
Many higher-level tests allow for a variety of different input parameters like the number of nodes in a network or the amount of faulty ones among them. Other possible parameters include transaction, batch or contribution sizes. To test a variety of randomized combinations of these, the [proptest](https://docs.rs/proptest) crate should be used.
|
||||||
|
|
|
@ -79,7 +79,7 @@ impl Adversary<Broadcast<NodeId>> for ProposeAdversary {
|
||||||
|
|
||||||
// FIXME: Take the correct, known keys from the network.
|
// FIXME: Take the correct, known keys from the network.
|
||||||
let netinfo = Arc::new(
|
let netinfo = Arc::new(
|
||||||
NetworkInfo::generate_map(node_ids)
|
NetworkInfo::generate_map(node_ids, &mut rand::thread_rng())
|
||||||
.expect("Failed to create `NetworkInfo` map")
|
.expect("Failed to create `NetworkInfo` map")
|
||||||
.remove(&id)
|
.remove(&id)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
|
|
@ -22,10 +22,11 @@ use std::io::Write;
|
||||||
use std::{cmp, collections, env, fmt, fs, io, ops, process, time};
|
use std::{cmp, collections, env, fmt, fs, io, ops, process, time};
|
||||||
|
|
||||||
use rand;
|
use rand;
|
||||||
use rand::Rand;
|
use rand::{Rand, Rng};
|
||||||
use threshold_crypto as crypto;
|
use threshold_crypto as crypto;
|
||||||
|
|
||||||
use hbbft::messaging::{self, DistAlgorithm, NetworkInfo, Step};
|
use hbbft::messaging::{self, DistAlgorithm, NetworkInfo, Step};
|
||||||
|
use hbbft::util::SubRng;
|
||||||
|
|
||||||
pub use self::adversary::Adversary;
|
pub use self::adversary::Adversary;
|
||||||
pub use self::err::CrankError;
|
pub use self::err::CrankError;
|
||||||
|
@ -234,7 +235,6 @@ where
|
||||||
/// New network node construction information.
|
/// New network node construction information.
|
||||||
///
|
///
|
||||||
/// Helper structure passed to node constructors when building virtual networks.
|
/// Helper structure passed to node constructors when building virtual networks.
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct NewNodeInfo<D>
|
pub struct NewNodeInfo<D>
|
||||||
where
|
where
|
||||||
D: DistAlgorithm,
|
D: DistAlgorithm,
|
||||||
|
@ -245,6 +245,28 @@ where
|
||||||
pub netinfo: NetworkInfo<D::NodeId>,
|
pub netinfo: NetworkInfo<D::NodeId>,
|
||||||
/// Whether or not the node is marked faulty.
|
/// Whether or not the node is marked faulty.
|
||||||
pub faulty: bool,
|
pub faulty: bool,
|
||||||
|
/// An initialized random number generated for exclusive use by the node.
|
||||||
|
///
|
||||||
|
/// Can be ignored, but usually comes in handy with algorithms that require additional
|
||||||
|
/// randomness for instantiation or operation.
|
||||||
|
///
|
||||||
|
/// Note that the random number generator type may differ from the one set for generation on
|
||||||
|
/// the `VirtualNet`, due to limitations of the `rand` crates API.
|
||||||
|
pub rng: Box<dyn rand::Rng>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D> fmt::Debug for NewNodeInfo<D>
|
||||||
|
where
|
||||||
|
D: DistAlgorithm,
|
||||||
|
{
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt.debug_struct("NewNodeInfo")
|
||||||
|
.field("id", &self.id)
|
||||||
|
.field("netinfo", &self.netinfo)
|
||||||
|
.field("faulty", &self.faulty)
|
||||||
|
.field("rng", &"<RNG>")
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Virtual network builder.
|
/// Virtual network builder.
|
||||||
|
@ -274,6 +296,8 @@ where
|
||||||
message_limit: Option<usize>,
|
message_limit: Option<usize>,
|
||||||
/// Optional time limit.
|
/// Optional time limit.
|
||||||
time_limit: Option<time::Duration>,
|
time_limit: Option<time::Duration>,
|
||||||
|
/// Random number generator used to generate keys.
|
||||||
|
rng: Option<Box<dyn Rng>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D, I> fmt::Debug for NetBuilder<D, I>
|
impl<D, I> fmt::Debug for NetBuilder<D, I>
|
||||||
|
@ -289,6 +313,8 @@ where
|
||||||
.field("trace", &self.trace)
|
.field("trace", &self.trace)
|
||||||
.field("crank_limit", &self.crank_limit)
|
.field("crank_limit", &self.crank_limit)
|
||||||
.field("message_limit", &self.message_limit)
|
.field("message_limit", &self.message_limit)
|
||||||
|
.field("time_limit", &self.time_limit)
|
||||||
|
.field("rng", &"<RNG>")
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -319,6 +345,7 @@ where
|
||||||
crank_limit: None,
|
crank_limit: None,
|
||||||
message_limit: None,
|
message_limit: None,
|
||||||
time_limit: DEFAULT_TIME_LIMIT,
|
time_limit: DEFAULT_TIME_LIMIT,
|
||||||
|
rng: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,6 +401,20 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Random number generator.
|
||||||
|
///
|
||||||
|
/// Overrides the random number generator used. If not specified, a `thread_rng` will be
|
||||||
|
/// used on construction.
|
||||||
|
///
|
||||||
|
/// The passed in generator is used for key generation.
|
||||||
|
pub fn rng<R>(mut self, rng: R) -> Self
|
||||||
|
where
|
||||||
|
R: Rng + 'static,
|
||||||
|
{
|
||||||
|
self.rng = Some(Box::new(rng));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Time limit.
|
/// Time limit.
|
||||||
///
|
///
|
||||||
/// Sets the time limit; `crank` will fail if called after this much time as elapsed since
|
/// Sets the time limit; `crank` will fail if called after this much time as elapsed since
|
||||||
|
@ -427,6 +468,8 @@ where
|
||||||
/// If the total number of nodes is not `> 3 * num_faulty`, construction will panic.
|
/// If the total number of nodes is not `> 3 * num_faulty`, construction will panic.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn build(self) -> Result<VirtualNet<D>, crypto::error::Error> {
|
pub fn build(self) -> Result<VirtualNet<D>, crypto::error::Error> {
|
||||||
|
let rng: Box<dyn Rng> = self.rng.unwrap_or_else(|| Box::new(rand::thread_rng()));
|
||||||
|
|
||||||
// The time limit can be overriden through environment variables:
|
// The time limit can be overriden through environment variables:
|
||||||
let override_time_limit = env::var("HBBFT_NO_TIME_LIMIT")
|
let override_time_limit = env::var("HBBFT_NO_TIME_LIMIT")
|
||||||
// We fail early, to avoid tricking the user into thinking that they have set the time
|
// We fail early, to avoid tricking the user into thinking that they have set the time
|
||||||
|
@ -448,7 +491,7 @@ where
|
||||||
|
|
||||||
// Note: Closure is not redundant, won't compile without it.
|
// Note: Closure is not redundant, won't compile without it.
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(redundant_closure))]
|
#[cfg_attr(feature = "cargo-clippy", allow(redundant_closure))]
|
||||||
let mut net = VirtualNet::new(self.node_ids, self.num_faulty, move |node| cons(node))?;
|
let mut net = VirtualNet::new(self.node_ids, self.num_faulty, rng, move |node| cons(node))?;
|
||||||
|
|
||||||
if self.adversary.is_some() {
|
if self.adversary.is_some() {
|
||||||
net.adversary = self.adversary;
|
net.adversary = self.adversary;
|
||||||
|
@ -647,13 +690,19 @@ where
|
||||||
///
|
///
|
||||||
/// The total number of nodes, that is `node_ids.count()` must be `> 3 * faulty`, otherwise
|
/// The total number of nodes, that is `node_ids.count()` must be `> 3 * faulty`, otherwise
|
||||||
/// the construction function will panic.
|
/// the construction function will panic.
|
||||||
fn new<F, I>(node_ids: I, faulty: usize, cons: F) -> Result<Self, crypto::error::Error>
|
fn new<F, I, R>(
|
||||||
|
node_ids: I,
|
||||||
|
faulty: usize,
|
||||||
|
mut rng: R,
|
||||||
|
cons: F,
|
||||||
|
) -> Result<Self, crypto::error::Error>
|
||||||
where
|
where
|
||||||
F: Fn(NewNodeInfo<D>) -> (D, Step<D>),
|
F: Fn(NewNodeInfo<D>) -> (D, Step<D>),
|
||||||
I: IntoIterator<Item = D::NodeId>,
|
I: IntoIterator<Item = D::NodeId>,
|
||||||
|
R: rand::Rng,
|
||||||
{
|
{
|
||||||
// Generate a new set of cryptographic keys for threshold cryptography.
|
// Generate a new set of cryptographic keys for threshold cryptography.
|
||||||
let net_infos = messaging::NetworkInfo::generate_map(node_ids)?;
|
let net_infos = messaging::NetworkInfo::generate_map(node_ids, &mut rng)?;
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
faulty * 3 < net_infos.len(),
|
faulty * 3 < net_infos.len(),
|
||||||
|
@ -668,10 +717,12 @@ where
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(idx, (id, netinfo))| {
|
.map(|(idx, (id, netinfo))| {
|
||||||
let is_faulty = idx < faulty;
|
let is_faulty = idx < faulty;
|
||||||
|
|
||||||
let (algorithm, step) = cons(NewNodeInfo {
|
let (algorithm, step) = cons(NewNodeInfo {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
netinfo,
|
netinfo,
|
||||||
faulty: is_faulty,
|
faulty: is_faulty,
|
||||||
|
rng: rng.sub_rng(),
|
||||||
});
|
});
|
||||||
steps.insert(id.clone(), step);
|
steps.insert(id.clone(), step);
|
||||||
(id, Node::new(algorithm, is_faulty))
|
(id, Node::new(algorithm, is_faulty))
|
||||||
|
|
|
@ -3,9 +3,31 @@
|
||||||
//! This module houses strategies to generate (and reduce/expand) various `hbbft` and `net` related
|
//! This module houses strategies to generate (and reduce/expand) various `hbbft` and `net` related
|
||||||
//! structures.
|
//! structures.
|
||||||
|
|
||||||
|
use proptest::arbitrary::any;
|
||||||
use proptest::prelude::Rng;
|
use proptest::prelude::Rng;
|
||||||
use proptest::strategy::{Strategy, ValueTree};
|
use proptest::strategy::{Strategy, ValueTree};
|
||||||
use proptest::test_runner::{Reason, TestRunner};
|
use proptest::test_runner::{Reason, TestRunner};
|
||||||
|
use rand::{self, SeedableRng};
|
||||||
|
|
||||||
|
/// Random number generator type used in testing.
|
||||||
|
pub type TestRng = rand::XorShiftRng;
|
||||||
|
|
||||||
|
/// Seed type of the random number generator used in testing.
|
||||||
|
// Note: In `rand` 0.5, this is an associated type of the `SeedableRng` trait, but for 0.4 and below
|
||||||
|
// we still need to alias this type.
|
||||||
|
pub type TestRngSeed = [u32; 4];
|
||||||
|
|
||||||
|
/// Generates a random instance of a random number generator.
|
||||||
|
pub fn gen_rng() -> impl Strategy<Value = TestRng> {
|
||||||
|
gen_seed().prop_map(TestRng::from_seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a random seed to instantiate a `TestRng`.
|
||||||
|
///
|
||||||
|
/// The random seed is non-shrinkable, to avoid meaningless shrinking in case of failed tests.
|
||||||
|
pub fn gen_seed() -> impl Strategy<Value = TestRngSeed> {
|
||||||
|
any::<TestRngSeed>().no_shrink()
|
||||||
|
}
|
||||||
|
|
||||||
/// Node network dimension.
|
/// Node network dimension.
|
||||||
///
|
///
|
||||||
|
|
|
@ -11,9 +11,10 @@ use std::collections;
|
||||||
|
|
||||||
use hbbft::dynamic_honey_badger::{Change, ChangeState, DynamicHoneyBadger, Input};
|
use hbbft::dynamic_honey_badger::{Change, ChangeState, DynamicHoneyBadger, Input};
|
||||||
use hbbft::messaging::DistAlgorithm;
|
use hbbft::messaging::DistAlgorithm;
|
||||||
use net::proptest::NetworkDimension;
|
use net::proptest::{gen_seed, NetworkDimension, TestRng, TestRngSeed};
|
||||||
use net::NetBuilder;
|
use net::NetBuilder;
|
||||||
use proptest::prelude::ProptestConfig;
|
use proptest::prelude::ProptestConfig;
|
||||||
|
use rand::{Rng, SeedableRng};
|
||||||
|
|
||||||
/// Choose a node's contribution for an epoch.
|
/// Choose a node's contribution for an epoch.
|
||||||
///
|
///
|
||||||
|
@ -53,6 +54,8 @@ struct TestConfig {
|
||||||
batch_size: usize,
|
batch_size: usize,
|
||||||
/// Individual nodes contribution size.
|
/// Individual nodes contribution size.
|
||||||
contribution_size: usize,
|
contribution_size: usize,
|
||||||
|
/// Random number generator to be passed to subsystems.
|
||||||
|
seed: TestRngSeed,
|
||||||
}
|
}
|
||||||
|
|
||||||
prop_compose! {
|
prop_compose! {
|
||||||
|
@ -61,10 +64,11 @@ prop_compose! {
|
||||||
(dimension in NetworkDimension::range(3, 15),
|
(dimension in NetworkDimension::range(3, 15),
|
||||||
total_txs in 20..60usize,
|
total_txs in 20..60usize,
|
||||||
batch_size in 10..20usize,
|
batch_size in 10..20usize,
|
||||||
contribution_size in 1..10usize)
|
contribution_size in 1..10usize,
|
||||||
|
seed in gen_seed())
|
||||||
-> TestConfig {
|
-> TestConfig {
|
||||||
TestConfig{
|
TestConfig{
|
||||||
dimension, total_txs, batch_size, contribution_size,
|
dimension, total_txs, batch_size, contribution_size, seed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,15 +89,17 @@ proptest!{
|
||||||
/// running a regular honey badger network.
|
/// running a regular honey badger network.
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
|
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
|
||||||
fn do_drop_and_readd(cfg: TestConfig) {
|
fn do_drop_and_readd(cfg: TestConfig) {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng: TestRng = TestRng::from_seed(cfg.seed);
|
||||||
|
|
||||||
// First, we create a new test network with Honey Badger instances.
|
// First, we create a new test network with Honey Badger instances.
|
||||||
let mut net = NetBuilder::new(0..cfg.dimension.size)
|
let mut net = NetBuilder::new(0..cfg.dimension.size)
|
||||||
.num_faulty(cfg.dimension.faulty)
|
.num_faulty(cfg.dimension.faulty)
|
||||||
.message_limit(200_000) // Limited to 200k messages for now.
|
.message_limit(200_000) // Limited to 200k messages for now.
|
||||||
|
.rng(rng.gen::<TestRng>()) // Ensure runs are reproducible.
|
||||||
.using_step(move |node| {
|
.using_step(move |node| {
|
||||||
println!("Constructing new dynamic honey badger node #{}", node.id);
|
println!("Constructing new dynamic honey badger node #{}", node.id);
|
||||||
DynamicHoneyBadger::builder()
|
DynamicHoneyBadger::builder()
|
||||||
|
.rng(node.rng)
|
||||||
.build(node.netinfo)
|
.build(node.netinfo)
|
||||||
.expect("cannot build instance")
|
.expect("cannot build instance")
|
||||||
}).build()
|
}).build()
|
||||||
|
|
|
@ -399,8 +399,8 @@ where
|
||||||
{
|
{
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let node_ids = (0..(good_num + adv_num)).map(NodeId);
|
let node_ids = (0..(good_num + adv_num)).map(NodeId);
|
||||||
let mut netinfos =
|
let mut netinfos = NetworkInfo::generate_map(node_ids, &mut rng)
|
||||||
NetworkInfo::generate_map(node_ids).expect("Failed to generate `NetworkInfo` map");
|
.expect("Failed to generate `NetworkInfo` map");
|
||||||
let obs_netinfo = {
|
let obs_netinfo = {
|
||||||
let node_ni = netinfos.values().next().unwrap();
|
let node_ni = netinfos.values().next().unwrap();
|
||||||
NetworkInfo::new(
|
NetworkInfo::new(
|
||||||
|
|
|
@ -28,8 +28,11 @@ fn test_sync_key_gen_with(threshold: usize, node_num: usize) {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(id, sk)| {
|
.map(|(id, sk)| {
|
||||||
let (sync_key_gen, proposal) = SyncKeyGen::new(id, sk, pub_keys.clone(), threshold)
|
let (sync_key_gen, proposal) =
|
||||||
.unwrap_or_else(|_err| panic!("Failed to create `SyncKeyGen` instance #{}", id));
|
SyncKeyGen::new(&mut rand::thread_rng(), id, sk, pub_keys.clone(), threshold)
|
||||||
|
.unwrap_or_else(|_err| {
|
||||||
|
panic!("Failed to create `SyncKeyGen` instance #{}", id)
|
||||||
|
});
|
||||||
nodes.push(sync_key_gen);
|
nodes.push(sync_key_gen);
|
||||||
proposal
|
proposal
|
||||||
}).collect();
|
}).collect();
|
||||||
|
@ -39,7 +42,7 @@ fn test_sync_key_gen_with(threshold: usize, node_num: usize) {
|
||||||
for (sender_id, proposal) in proposals[..=threshold].iter().enumerate() {
|
for (sender_id, proposal) in proposals[..=threshold].iter().enumerate() {
|
||||||
for (node_id, node) in nodes.iter_mut().enumerate() {
|
for (node_id, node) in nodes.iter_mut().enumerate() {
|
||||||
let proposal = proposal.clone().expect("proposal");
|
let proposal = proposal.clone().expect("proposal");
|
||||||
let ack = match node.handle_part(&sender_id, proposal) {
|
let ack = match node.handle_part(&mut rand::thread_rng(), &sender_id, proposal) {
|
||||||
Some(PartOutcome::Valid(ack)) => ack,
|
Some(PartOutcome::Valid(ack)) => ack,
|
||||||
_ => panic!("invalid proposal"),
|
_ => panic!("invalid proposal"),
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue