hbbft/src/honey_badger/honey_badger.rs

180 lines
6.4 KiB
Rust
Raw Normal View History

use std::collections::btree_map::Entry;
use std::collections::BTreeMap;
use std::sync::Arc;
use bincode;
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.
2018-10-02 07:24:51 -07:00
use rand::{Rand, Rng};
2018-10-24 02:38:14 -07:00
use serde::{de::DeserializeOwned, Serialize};
use super::epoch_state::EpochState;
2018-07-31 13:27:22 -07:00
use super::{Batch, Error, ErrorKind, HoneyBadgerBuilder, Message, MessageContent, Result};
use {util, Contribution, DistAlgorithm, NetworkInfo, NodeIdT};
pub use super::epoch_state::SubsetHandlingStrategy;
/// An instance of the Honey Badger Byzantine fault tolerant consensus algorithm.
#[derive(Derivative)]
#[derivative(Debug)]
pub struct HoneyBadger<C, N: Rand> {
/// Shared network data.
pub(super) netinfo: Arc<NetworkInfo<N>>,
/// The earliest epoch from which we have not yet received output.
2018-07-31 13:27:22 -07:00
pub(super) epoch: u64,
/// Whether we have already submitted a proposal for the current epoch.
2018-07-31 13:27:22 -07:00
pub(super) has_input: bool,
/// The subalgorithms for ongoing epochs.
pub(super) epochs: BTreeMap<u64, EpochState<C, N>>,
2018-08-14 06:06:51 -07:00
/// The maximum number of `Subset` instances that we run simultaneously.
2018-07-31 13:27:22 -07:00
pub(super) max_future_epochs: u64,
/// Messages for future epochs that couldn't be handled yet.
pub(super) incoming_queue: BTreeMap<u64, Vec<(N, MessageContent<N>)>>,
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.
2018-10-02 07:24:51 -07:00
/// A random number generator used for secret key generation.
// Boxed to avoid overloading the algorithm's type with more generics.
#[derivative(Debug(format_with = "util::fmt_rng"))]
2018-10-04 10:08:52 -07:00
pub(super) rng: Box<dyn Rng + Send + Sync>,
/// Represents the optimization strategy to use for output of the `Subset` algorithm.
pub(super) subset_handling_strategy: SubsetHandlingStrategy,
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.
2018-10-02 07:24:51 -07:00
}
2018-10-10 07:11:27 -07:00
pub type Step<C, N> = ::Step<HoneyBadger<C, N>>;
2018-07-09 04:35:26 -07:00
impl<C, N> DistAlgorithm for HoneyBadger<C, N>
where
2018-10-24 02:38:14 -07:00
C: Contribution + Serialize + DeserializeOwned,
2018-08-29 09:08:35 -07:00
N: NodeIdT + Rand,
{
2018-08-29 09:08:35 -07:00
type NodeId = N;
type Input = C;
type Output = Batch<C, N>;
type Message = Message<N>;
type Error = Error;
fn handle_input(&mut self, input: Self::Input) -> Result<Step<C, N>> {
self.propose(&input)
}
fn handle_message(&mut self, sender_id: &N, message: Self::Message) -> Result<Step<C, N>> {
self.handle_message(sender_id, message)
}
fn terminated(&self) -> bool {
false
}
fn our_id(&self) -> &N {
2018-08-29 09:08:35 -07:00
self.netinfo.our_id()
}
}
impl<C, N> HoneyBadger<C, N>
where
2018-10-24 02:38:14 -07:00
C: Contribution + Serialize + DeserializeOwned,
2018-08-29 09:08:35 -07:00
N: NodeIdT + Rand,
{
/// Returns a new `HoneyBadgerBuilder` configured to use the node IDs and cryptographic keys
2018-06-28 14:07:11 -07:00
/// specified by `netinfo`.
pub fn builder(netinfo: Arc<NetworkInfo<N>>) -> HoneyBadgerBuilder<C, N> {
2018-06-28 14:07:11 -07:00
HoneyBadgerBuilder::new(netinfo)
}
/// Proposes a new item in the current epoch.
pub fn propose(&mut self, proposal: &C) -> Result<Step<C, N>> {
if !self.netinfo.is_validator() {
return Ok(Step::default());
}
self.has_input = true;
let ser_prop =
bincode::serialize(&proposal).map_err(|err| ErrorKind::ProposeBincode(*err))?;
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.
2018-10-02 07:24:51 -07:00
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()?);
Ok(step)
}
/// Handles a message received from `sender_id`.
fn handle_message(&mut self, sender_id: &N, message: Message<N>) -> Result<Step<C, N>> {
if !self.netinfo.is_node_validator(sender_id) {
return Err(ErrorKind::UnknownSender.into());
}
let Message { epoch, content } = message;
if epoch > self.epoch + self.max_future_epochs {
// Postpone handling this message.
self.incoming_queue
.entry(epoch)
.or_insert_with(Vec::new)
.push((sender_id.clone(), content));
} else if self.epoch <= epoch {
let mut step = self
.epoch_state_mut(epoch)?
.handle_message_content(sender_id, content)?;
step.extend(self.try_output_batches()?);
return Ok(step);
} // And ignore all messages from past epochs.
Ok(Step::default())
}
/// Returns `true` if input for the current epoch has already been provided.
pub fn has_input(&self) -> bool {
!self.netinfo.is_validator() || self.has_input
}
/// Returns the number of validators from which we have already received a proposal for the
/// current epoch.
pub(crate) fn received_proposals(&self) -> usize {
self.epochs
.get(&self.epoch)
.map_or(0, EpochState::received_proposals)
}
/// Increments the epoch number and clears any state that is local to the finished epoch.
fn update_epoch(&mut self) -> Result<Step<C, N>> {
// Clear the state of the old epoch.
self.epochs.remove(&self.epoch);
self.epoch += 1;
self.has_input = false;
let max_epoch = self.epoch + self.max_future_epochs;
let mut step = Step::default();
if let Some(messages) = self.incoming_queue.remove(&max_epoch) {
let epoch_state = self.epoch_state_mut(max_epoch)?;
for (sender_id, content) in messages {
step.extend(epoch_state.handle_message_content(&sender_id, content)?);
}
}
Ok(step)
}
/// Tries to decrypt contributions from all proposers and output those in a batch.
fn try_output_batches(&mut self) -> Result<Step<C, N>> {
let mut step = Step::default();
while let Some((batch, fault_log)) = self
.epochs
.get(&self.epoch)
.and_then(EpochState::try_output_batch)
{
// Queue the output and advance the epoch.
step.output.push_back(batch);
step.fault_log.extend(fault_log);
step.extend(self.update_epoch()?);
}
Ok(step)
}
/// Returns a mutable reference to the state of the given `epoch`. Initializes a new one, if it
/// doesn't exist yet.
fn epoch_state_mut(&mut self, epoch: u64) -> Result<&mut EpochState<C, N>> {
Ok(match self.epochs.entry(epoch) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => entry.insert(EpochState::new(
self.netinfo.clone(),
epoch,
self.subset_handling_strategy.clone(),
)?),
})
}
}