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"
|
||||
serde = "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"
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -41,7 +41,7 @@ docopt = "1.0"
|
|||
itertools = "0.7"
|
||||
serde_derive = "1.0.55"
|
||||
signifix = "0.9"
|
||||
proptest = "0.8.6"
|
||||
proptest = "0.8.7"
|
||||
|
||||
[[example]]
|
||||
name = "consensus-node"
|
||||
|
|
|
@ -267,8 +267,8 @@ where
|
|||
F: Fn(NetworkInfo<NodeId>) -> (D, Step<D>),
|
||||
{
|
||||
let node_ids = (0..(good_num + adv_num)).map(NodeId);
|
||||
let netinfos =
|
||||
NetworkInfo::generate_map(node_ids).expect("Failed to create `NetworkInfo` map");
|
||||
let netinfos = NetworkInfo::generate_map(node_ids, &mut rand::thread_rng())
|
||||
.expect("Failed to create `NetworkInfo` map");
|
||||
let new_node = |(id, netinfo): (NodeId, NetworkInfo<_>)| {
|
||||
(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();
|
||||
|
||||
match message_type {
|
||||
"sbvb" => MessageContent::SbvBroadcast(rand::random()),
|
||||
"conf" => MessageContent::Conf(rand::random()),
|
||||
"term" => MessageContent::Term(rand::random()),
|
||||
"coin" => MessageContent::Coin(Box::new(rand::random())),
|
||||
"sbvb" => MessageContent::SbvBroadcast(rng.gen()),
|
||||
"conf" => MessageContent::Conf(rng.gen()),
|
||||
"term" => MessageContent::Term(rng.gen()),
|
||||
"coin" => MessageContent::Coin(Box::new(rng.gen())),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,8 +35,8 @@ impl rand::Rand for Message {
|
|||
let message_type = *rng.choose(&["bval", "aux"]).unwrap();
|
||||
|
||||
match message_type {
|
||||
"bval" => Message::BVal(rand::random()),
|
||||
"aux" => Message::Aux(rand::random()),
|
||||
"bval" => Message::BVal(rng.gen()),
|
||||
"aux" => Message::Aux(rng.gen()),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
//! let mut rng = thread_rng();
|
||||
//!
|
||||
//! // 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");
|
||||
//!
|
||||
//! // 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 messaging::NetworkInfo;
|
||||
use traits::{Contribution, NodeIdT};
|
||||
use util::SubRng;
|
||||
|
||||
/// A Dynamic Honey Badger builder, to configure the parameters and create new instances of
|
||||
/// `DynamicHoneyBadger`.
|
||||
pub struct DynamicHoneyBadgerBuilder<C, N> {
|
||||
/// The maximum number of future epochs for which we handle messages simultaneously.
|
||||
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)>,
|
||||
}
|
||||
|
||||
|
@ -25,6 +29,7 @@ impl<C, N> Default for DynamicHoneyBadgerBuilder<C, N> {
|
|||
// TODO: Use the defaults from `HoneyBadgerBuilder`.
|
||||
DynamicHoneyBadgerBuilder {
|
||||
max_future_epochs: 3,
|
||||
rng: Box::new(rand::thread_rng()),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
@ -47,11 +52,21 @@ where
|
|||
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.
|
||||
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 (honey_badger, hb_step) = HoneyBadger::builder(arc_netinfo.clone())
|
||||
.max_future_epochs(self.max_future_epochs)
|
||||
.rng(self.rng.sub_rng())
|
||||
.build();
|
||||
let mut dhb = DynamicHoneyBadger {
|
||||
netinfo,
|
||||
|
@ -62,18 +77,21 @@ where
|
|||
honey_badger,
|
||||
key_gen_state: None,
|
||||
incoming_queue: Vec::new(),
|
||||
rng: Box::new(self.rng.sub_rng()),
|
||||
};
|
||||
let step = dhb.process_output(hb_step)?;
|
||||
Ok((dhb, step))
|
||||
}
|
||||
|
||||
/// 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>)> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let sk_set = SecretKeySet::random(0, &mut rng)?;
|
||||
pub fn build_first_node(
|
||||
&mut self,
|
||||
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 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 netinfo = NetworkInfo::new(our_id, sks, pk_set, sk, pub_keys);
|
||||
self.build(netinfo)
|
||||
|
@ -82,7 +100,7 @@ where
|
|||
/// Creates a new `DynamicHoneyBadger` configured to join the network at the epoch specified in
|
||||
/// the `JoinPlan`.
|
||||
pub fn build_joining(
|
||||
&self,
|
||||
&mut self,
|
||||
our_id: N,
|
||||
secret_key: SecretKey,
|
||||
join_plan: JoinPlan<N>,
|
||||
|
@ -108,6 +126,7 @@ where
|
|||
honey_badger,
|
||||
key_gen_state: None,
|
||||
incoming_queue: Vec::new(),
|
||||
rng: Box::new(self.rng.sub_rng()),
|
||||
};
|
||||
let mut step = dhb.process_output(hb_step)?;
|
||||
match join_plan.change {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use rand::Rand;
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, mem};
|
||||
|
||||
use bincode;
|
||||
use crypto::Signature;
|
||||
use rand;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::votes::{SignedVote, VoteCounter};
|
||||
|
@ -16,9 +17,9 @@ use honey_badger::{self, HoneyBadger, Message as HbMessage};
|
|||
use messaging::{DistAlgorithm, NetworkInfo, Target};
|
||||
use sync_key_gen::{Ack, Part, PartOutcome, SyncKeyGen};
|
||||
use traits::{Contribution, NodeIdT};
|
||||
use util::SubRng;
|
||||
|
||||
/// A Honey Badger instance that can handle adding and removing nodes.
|
||||
#[derive(Debug)]
|
||||
pub struct DynamicHoneyBadger<C, N: Rand> {
|
||||
/// Shared network data.
|
||||
pub(super) netinfo: NetworkInfo<N>,
|
||||
|
@ -36,6 +37,29 @@ pub struct DynamicHoneyBadger<C, N: Rand> {
|
|||
pub(super) key_gen_state: Option<KeyGenState<N>>,
|
||||
/// A queue for messages from future epochs that cannot be handled yet.
|
||||
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>
|
||||
|
@ -312,7 +336,7 @@ where
|
|||
let threshold = (pub_keys.len() - 1) / 3;
|
||||
let sk = self.netinfo.secret_key().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()));
|
||||
if let Some(part) = part {
|
||||
let step_on_send = self.send_transaction(KeyGenMessage::Part(part))?;
|
||||
|
@ -330,6 +354,7 @@ where
|
|||
mem::replace(&mut self.vote_counter, counter);
|
||||
let (hb, hb_step) = HoneyBadger::builder(netinfo)
|
||||
.max_future_epochs(self.max_future_epochs)
|
||||
.rng(self.rng.sub_rng())
|
||||
.build();
|
||||
self.honey_badger = hb;
|
||||
self.process_output(hb_step)
|
||||
|
@ -337,8 +362,17 @@ where
|
|||
|
||||
/// Handles a `Part` message that was output by Honey Badger.
|
||||
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);
|
||||
match self.key_gen_state.as_mut().and_then(handle) {
|
||||
// Awkward construction due to borrowck shortcomings; we need to borrow two struct fields
|
||||
// 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::Invalid(fault_log)) => Ok(fault_log.into()),
|
||||
None => Ok(Step::default()),
|
||||
|
|
|
@ -188,6 +188,8 @@ impl<N> SignedVote<N> {
|
|||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use rand;
|
||||
|
||||
use super::{Change, SignedVote, VoteCounter};
|
||||
use fault_log::{FaultKind, FaultLog};
|
||||
use messaging::NetworkInfo;
|
||||
|
@ -199,8 +201,8 @@ mod tests {
|
|||
/// order.
|
||||
fn setup(node_num: usize, era: u64) -> (Vec<VoteCounter<usize>>, Vec<Vec<SignedVote<usize>>>) {
|
||||
// Create keys for threshold cryptography.
|
||||
let netinfos =
|
||||
NetworkInfo::generate_map(0..node_num).expect("Failed to generate `NetworkInfo` map");
|
||||
let netinfos = NetworkInfo::generate_map(0..node_num, &mut rand::thread_rng())
|
||||
.expect("Failed to generate `NetworkInfo` map");
|
||||
|
||||
// Create a `VoteCounter` instance for each node.
|
||||
let create_counter =
|
||||
|
|
|
@ -2,12 +2,13 @@ use std::collections::BTreeMap;
|
|||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
use rand::Rand;
|
||||
use rand::{self, Rand, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{HoneyBadger, Message, Step};
|
||||
use messaging::{NetworkInfo, Target};
|
||||
use traits::{Contribution, NodeIdT};
|
||||
use util::SubRng;
|
||||
|
||||
/// A Honey Badger builder, to configure the parameters and create new instances of `HoneyBadger`.
|
||||
pub struct HoneyBadgerBuilder<C, N>
|
||||
|
@ -18,6 +19,8 @@ where
|
|||
netinfo: Arc<NetworkInfo<N>>,
|
||||
/// The maximum number of future epochs for which we handle messages simultaneously.
|
||||
max_future_epochs: usize,
|
||||
/// Random number generator passed on to algorithm instance for signing and encrypting.
|
||||
rng: Box<dyn Rng>,
|
||||
_phantom: PhantomData<C>,
|
||||
}
|
||||
|
||||
|
@ -32,10 +35,17 @@ where
|
|||
HoneyBadgerBuilder {
|
||||
netinfo,
|
||||
max_future_epochs: 3,
|
||||
rng: Box::new(rand::thread_rng()),
|
||||
_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.
|
||||
pub fn max_future_epochs(&mut self, max_future_epochs: usize) -> &mut Self {
|
||||
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
|
||||
/// 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 {
|
||||
netinfo: self.netinfo.clone(),
|
||||
epoch: 0,
|
||||
|
@ -53,6 +63,7 @@ where
|
|||
max_future_epochs: self.max_future_epochs as u64,
|
||||
incoming_queue: BTreeMap::new(),
|
||||
remote_epochs: BTreeMap::new(),
|
||||
rng: Box::new(self.rng.sub_rng()),
|
||||
};
|
||||
let step = if self.netinfo.is_validator() {
|
||||
// The first message in an epoch announces the epoch transition.
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use std::collections::btree_map::Entry;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bincode;
|
||||
use rand::Rand;
|
||||
use rand::{Rand, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::epoch_state::EpochState;
|
||||
|
@ -12,7 +13,6 @@ use messaging::{self, DistAlgorithm, NetworkInfo, Target};
|
|||
use traits::{Contribution, NodeIdT};
|
||||
|
||||
/// An instance of the Honey Badger Byzantine fault tolerant consensus algorithm.
|
||||
#[derive(Debug)]
|
||||
pub struct HoneyBadger<C, N: Rand> {
|
||||
/// Shared network data.
|
||||
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>)>>,
|
||||
/// Known current epochs of remote nodes.
|
||||
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>>;
|
||||
|
@ -79,7 +101,11 @@ where
|
|||
self.has_input = true;
|
||||
let ser_prop =
|
||||
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 mut step = self.epoch_state_mut(epoch)?.propose(&ciphertext)?;
|
||||
step.extend(self.try_output_batches()?);
|
||||
|
|
|
@ -149,6 +149,7 @@ pub mod subset;
|
|||
pub mod sync_key_gen;
|
||||
pub mod threshold_decryption;
|
||||
pub mod transaction_queue;
|
||||
pub mod util;
|
||||
|
||||
/// Common supertraits.
|
||||
pub mod traits {
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
|||
use std::iter::once;
|
||||
|
||||
use failure::Fail;
|
||||
use rand;
|
||||
|
||||
use crypto::{self, PublicKey, PublicKeySet, PublicKeyShare, SecretKey, SecretKeyShare};
|
||||
use fault_log::{Fault, FaultLog};
|
||||
|
@ -357,21 +358,21 @@ impl<N: NodeIdT> NetworkInfo<N> {
|
|||
}
|
||||
|
||||
/// 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
|
||||
I: IntoIterator<Item = N>,
|
||||
R: rand::Rng,
|
||||
{
|
||||
use rand::{self, Rng};
|
||||
|
||||
use crypto::SecretKeySet;
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let all_ids: BTreeSet<N> = ids.into_iter().collect();
|
||||
let num_faulty = (all_ids.len() - 1) / 3;
|
||||
|
||||
// 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();
|
||||
|
||||
// Generate keys for individually signing and encrypting messages.
|
||||
|
|
|
@ -59,6 +59,9 @@
|
|||
//! use threshold_crypto::{PublicKey, SecretKey, SignatureShare};
|
||||
//! 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.
|
||||
//! let (threshold, node_num) = (1, 4);
|
||||
//!
|
||||
|
@ -75,7 +78,7 @@
|
|||
//! let mut nodes = BTreeMap::new();
|
||||
//! let mut parts = Vec::new();
|
||||
//! 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));
|
||||
//! nodes.insert(id, sync_key_gen);
|
||||
//! parts.push((id, opt_part.unwrap())); // Would be `None` for observer nodes.
|
||||
|
@ -85,7 +88,7 @@
|
|||
//! let mut acks = Vec::new();
|
||||
//! for (sender_id, part) in parts {
|
||||
//! 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::Invalid(faults)) => panic!("Invalid part: {:?}", faults),
|
||||
//! 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::{CurveAffine, Field};
|
||||
use rand::OsRng;
|
||||
use rand;
|
||||
|
||||
use fault_log::{AckMessageFault as Fault, FaultKind, FaultLog};
|
||||
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
|
||||
/// messages need to be sent.
|
||||
pub fn new(
|
||||
pub fn new<R: rand::Rng>(
|
||||
rng: &mut R,
|
||||
our_id: N,
|
||||
sec_key: SecretKey,
|
||||
pub_keys: BTreeMap<N, PublicKey>,
|
||||
|
@ -308,13 +312,13 @@ impl<N: NodeIdT> SyncKeyGen<N> {
|
|||
if our_idx.is_none() {
|
||||
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 encrypt = |(i, pk): (usize, &PublicKey)| {
|
||||
let row = our_part.row(i + 1).map_err(Error::Creation)?;
|
||||
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
|
||||
.pub_keys
|
||||
|
@ -331,8 +335,9 @@ impl<N: NodeIdT> SyncKeyGen<N> {
|
|||
///
|
||||
/// 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`.
|
||||
pub fn handle_part(
|
||||
pub fn handle_part<R: rand::Rng>(
|
||||
&mut self,
|
||||
rng: &mut R,
|
||||
sender_id: &N,
|
||||
Part(commit, rows): Part,
|
||||
) -> Option<PartOutcome<N>> {
|
||||
|
@ -369,7 +374,7 @@ impl<N: NodeIdT> SyncKeyGen<N> {
|
|||
let wrap = FieldWrap::new(val);
|
||||
// TODO: Handle errors.
|
||||
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();
|
||||
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".
|
||||
|
||||
### 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
|
||||
|
||||
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.
|
||||
let netinfo = Arc::new(
|
||||
NetworkInfo::generate_map(node_ids)
|
||||
NetworkInfo::generate_map(node_ids, &mut rand::thread_rng())
|
||||
.expect("Failed to create `NetworkInfo` map")
|
||||
.remove(&id)
|
||||
.unwrap(),
|
||||
|
|
|
@ -22,10 +22,11 @@ use std::io::Write;
|
|||
use std::{cmp, collections, env, fmt, fs, io, ops, process, time};
|
||||
|
||||
use rand;
|
||||
use rand::Rand;
|
||||
use rand::{Rand, Rng};
|
||||
use threshold_crypto as crypto;
|
||||
|
||||
use hbbft::messaging::{self, DistAlgorithm, NetworkInfo, Step};
|
||||
use hbbft::util::SubRng;
|
||||
|
||||
pub use self::adversary::Adversary;
|
||||
pub use self::err::CrankError;
|
||||
|
@ -234,7 +235,6 @@ where
|
|||
/// New network node construction information.
|
||||
///
|
||||
/// Helper structure passed to node constructors when building virtual networks.
|
||||
#[derive(Debug)]
|
||||
pub struct NewNodeInfo<D>
|
||||
where
|
||||
D: DistAlgorithm,
|
||||
|
@ -245,6 +245,28 @@ where
|
|||
pub netinfo: NetworkInfo<D::NodeId>,
|
||||
/// Whether or not the node is marked faulty.
|
||||
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.
|
||||
|
@ -274,6 +296,8 @@ where
|
|||
message_limit: Option<usize>,
|
||||
/// Optional time limit.
|
||||
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>
|
||||
|
@ -289,6 +313,8 @@ where
|
|||
.field("trace", &self.trace)
|
||||
.field("crank_limit", &self.crank_limit)
|
||||
.field("message_limit", &self.message_limit)
|
||||
.field("time_limit", &self.time_limit)
|
||||
.field("rng", &"<RNG>")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
@ -319,6 +345,7 @@ where
|
|||
crank_limit: None,
|
||||
message_limit: None,
|
||||
time_limit: DEFAULT_TIME_LIMIT,
|
||||
rng: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -374,6 +401,20 @@ where
|
|||
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.
|
||||
///
|
||||
/// 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.
|
||||
#[inline]
|
||||
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:
|
||||
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
|
||||
|
@ -448,7 +491,7 @@ where
|
|||
|
||||
// Note: Closure is not redundant, won't compile without it.
|
||||
#[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() {
|
||||
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 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
|
||||
F: Fn(NewNodeInfo<D>) -> (D, Step<D>),
|
||||
I: IntoIterator<Item = D::NodeId>,
|
||||
R: rand::Rng,
|
||||
{
|
||||
// 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!(
|
||||
faulty * 3 < net_infos.len(),
|
||||
|
@ -668,10 +717,12 @@ where
|
|||
.enumerate()
|
||||
.map(|(idx, (id, netinfo))| {
|
||||
let is_faulty = idx < faulty;
|
||||
|
||||
let (algorithm, step) = cons(NewNodeInfo {
|
||||
id: id.clone(),
|
||||
netinfo,
|
||||
faulty: is_faulty,
|
||||
rng: rng.sub_rng(),
|
||||
});
|
||||
steps.insert(id.clone(), step);
|
||||
(id, Node::new(algorithm, is_faulty))
|
||||
|
|
|
@ -3,9 +3,31 @@
|
|||
//! This module houses strategies to generate (and reduce/expand) various `hbbft` and `net` related
|
||||
//! structures.
|
||||
|
||||
use proptest::arbitrary::any;
|
||||
use proptest::prelude::Rng;
|
||||
use proptest::strategy::{Strategy, ValueTree};
|
||||
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.
|
||||
///
|
||||
|
|
|
@ -11,9 +11,10 @@ use std::collections;
|
|||
|
||||
use hbbft::dynamic_honey_badger::{Change, ChangeState, DynamicHoneyBadger, Input};
|
||||
use hbbft::messaging::DistAlgorithm;
|
||||
use net::proptest::NetworkDimension;
|
||||
use net::proptest::{gen_seed, NetworkDimension, TestRng, TestRngSeed};
|
||||
use net::NetBuilder;
|
||||
use proptest::prelude::ProptestConfig;
|
||||
use rand::{Rng, SeedableRng};
|
||||
|
||||
/// Choose a node's contribution for an epoch.
|
||||
///
|
||||
|
@ -53,6 +54,8 @@ struct TestConfig {
|
|||
batch_size: usize,
|
||||
/// Individual nodes contribution size.
|
||||
contribution_size: usize,
|
||||
/// Random number generator to be passed to subsystems.
|
||||
seed: TestRngSeed,
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
|
@ -61,10 +64,11 @@ prop_compose! {
|
|||
(dimension in NetworkDimension::range(3, 15),
|
||||
total_txs in 20..60usize,
|
||||
batch_size in 10..20usize,
|
||||
contribution_size in 1..10usize)
|
||||
contribution_size in 1..10usize,
|
||||
seed in gen_seed())
|
||||
-> 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.
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
|
||||
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.
|
||||
let mut net = NetBuilder::new(0..cfg.dimension.size)
|
||||
.num_faulty(cfg.dimension.faulty)
|
||||
.message_limit(200_000) // Limited to 200k messages for now.
|
||||
.rng(rng.gen::<TestRng>()) // Ensure runs are reproducible.
|
||||
.using_step(move |node| {
|
||||
println!("Constructing new dynamic honey badger node #{}", node.id);
|
||||
DynamicHoneyBadger::builder()
|
||||
.rng(node.rng)
|
||||
.build(node.netinfo)
|
||||
.expect("cannot build instance")
|
||||
}).build()
|
||||
|
|
|
@ -399,8 +399,8 @@ where
|
|||
{
|
||||
let mut rng = rand::thread_rng();
|
||||
let node_ids = (0..(good_num + adv_num)).map(NodeId);
|
||||
let mut netinfos =
|
||||
NetworkInfo::generate_map(node_ids).expect("Failed to generate `NetworkInfo` map");
|
||||
let mut netinfos = NetworkInfo::generate_map(node_ids, &mut rng)
|
||||
.expect("Failed to generate `NetworkInfo` map");
|
||||
let obs_netinfo = {
|
||||
let node_ni = netinfos.values().next().unwrap();
|
||||
NetworkInfo::new(
|
||||
|
|
|
@ -28,8 +28,11 @@ fn test_sync_key_gen_with(threshold: usize, node_num: usize) {
|
|||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, sk)| {
|
||||
let (sync_key_gen, proposal) = SyncKeyGen::new(id, sk, pub_keys.clone(), threshold)
|
||||
.unwrap_or_else(|_err| panic!("Failed to create `SyncKeyGen` instance #{}", id));
|
||||
let (sync_key_gen, proposal) =
|
||||
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);
|
||||
proposal
|
||||
}).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 (node_id, node) in nodes.iter_mut().enumerate() {
|
||||
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,
|
||||
_ => panic!("invalid proposal"),
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue