Merge pull request #250 from ErichDonGubler/subset-optimization-option

Make Subset optimization optional
This commit is contained in:
Vladimir Komendantskiy 2018-10-04 09:10:07 +01:00 committed by GitHub
commit 79e5ef19fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 153 additions and 28 deletions

View File

@ -8,7 +8,7 @@ use rand::{self, Rand, Rng};
use serde::{Deserialize, Serialize};
use super::{ChangeState, DynamicHoneyBadger, JoinPlan, Result, Step, VoteCounter};
use honey_badger::HoneyBadger;
use honey_badger::{HoneyBadger, SubsetHandlingStrategy};
use messaging::NetworkInfo;
use traits::{Contribution, NodeIdT};
use util::SubRng;
@ -21,6 +21,8 @@ pub struct DynamicHoneyBadgerBuilder<C, N> {
/// Random number generator passed on to algorithm instance for key generation. Also used to
/// instantiate `HoneyBadger`.
rng: Box<dyn rand::Rng>,
/// Strategy used to handle the output of the `Subset` algorithm.
subset_handling_strategy: SubsetHandlingStrategy,
_phantom: PhantomData<(C, N)>,
}
@ -30,6 +32,7 @@ impl<C, N> Default for DynamicHoneyBadgerBuilder<C, N> {
DynamicHoneyBadgerBuilder {
max_future_epochs: 3,
rng: Box::new(rand::thread_rng()),
subset_handling_strategy: SubsetHandlingStrategy::Incremental,
_phantom: PhantomData,
}
}
@ -58,26 +61,43 @@ where
self
}
/// Sets the strategy to use when handling `Subset` output.
pub fn subset_handling_strategy(
&mut self,
subset_handling_strategy: SubsetHandlingStrategy,
) -> &mut Self {
self.subset_handling_strategy = subset_handling_strategy;
self
}
/// Creates a new Dynamic Honey Badger instance with an empty buffer.
pub fn build(
&mut self,
netinfo: NetworkInfo<N>,
) -> Result<(DynamicHoneyBadger<C, N>, Step<C, N>)> {
let DynamicHoneyBadgerBuilder {
max_future_epochs,
rng,
subset_handling_strategy,
_phantom,
} = self;
let max_future_epochs = *max_future_epochs;
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())
.max_future_epochs(max_future_epochs)
.rng(rng.sub_rng())
.subset_handling_strategy(subset_handling_strategy.clone())
.build();
let mut dhb = DynamicHoneyBadger {
netinfo,
max_future_epochs: self.max_future_epochs,
max_future_epochs,
start_epoch: 0,
vote_counter: VoteCounter::new(arc_netinfo, 0),
key_gen_msg_buffer: Vec::new(),
honey_badger,
key_gen_state: None,
incoming_queue: Vec::new(),
rng: Box::new(self.rng.sub_rng()),
rng: Box::new(rng.sub_rng()),
};
let step = dhb.process_output(hb_step)?;
Ok((dhb, step))

View File

@ -6,6 +6,7 @@ use rand::{self, Rand, Rng};
use serde::{Deserialize, Serialize};
use super::{HoneyBadger, Message, Step};
use honey_badger::SubsetHandlingStrategy;
use messaging::{NetworkInfo, Target};
use traits::{Contribution, NodeIdT};
use util::SubRng;
@ -21,6 +22,8 @@ where
max_future_epochs: usize,
/// Random number generator passed on to algorithm instance for signing and encrypting.
rng: Box<dyn Rng>,
/// Strategy used to handle the output of the `Subset` algorithm.
subset_handling_strategy: SubsetHandlingStrategy,
_phantom: PhantomData<C>,
}
@ -36,6 +39,7 @@ where
netinfo,
max_future_epochs: 3,
rng: Box::new(rand::thread_rng()),
subset_handling_strategy: SubsetHandlingStrategy::Incremental,
_phantom: PhantomData,
}
}
@ -52,6 +56,15 @@ where
self
}
/// Sets the strategy to use when handling `Subset` output.
pub fn subset_handling_strategy(
&mut self,
subset_handling_strategy: SubsetHandlingStrategy,
) -> &mut Self {
self.subset_handling_strategy = subset_handling_strategy;
self
}
/// Creates a new Honey Badger instance in epoch 0 and makes the initial `Step` on that
/// instance.
pub fn build(&mut self) -> (HoneyBadger<C, N>, Step<C, N>) {
@ -64,6 +77,7 @@ where
incoming_queue: BTreeMap::new(),
remote_epochs: BTreeMap::new(),
rng: Box::new(self.rng.sub_rng()),
subset_handling_strategy: self.subset_handling_strategy.clone(),
};
let step = if self.netinfo.is_validator() {
// The first message in an epoch announces the epoch transition.

View File

@ -1,6 +1,7 @@
use std::collections::btree_map::Entry;
use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::marker::PhantomData;
use std::mem::replace;
use std::sync::Arc;
use bincode;
@ -105,6 +106,77 @@ where
}
}
/// A flag used when constructing an `EpochState` to determine which behavior to use when receiving
/// proposals from a `Subset` instance.
#[derive(Debug, Clone)]
pub enum SubsetHandlingStrategy {
/// Sets the `EpochState` to return proposals as they are contributed.
Incremental,
/// Sets the `EpochState` to return all received proposals once consensus has been finalized.
AllAtEnd,
}
/// Used in an `EpochState` to encapsulate the state necessary to maintain each
/// `SubsetHandlingStrategy`.
#[derive(Debug, Clone)]
enum SubsetHandler<N> {
Incremental,
AllAtEnd(Vec<(N, Vec<u8>)>),
}
/// The result of a call to `SubsetHandler::handle(...)`.
struct SubsetHandleData<N> {
/// The number of contributions propagated from the handler.
contributions: Vec<(N, Vec<u8>)>,
/// Indicates whether the underlying `Subset` algorithm has achieved consensus and whether
/// there may be more contributions or not.
is_done: bool,
}
impl<N> SubsetHandler<N> {
fn handle(&mut self, o: SubsetOutput<N>) -> SubsetHandleData<N> {
use self::SubsetHandler::*;
use self::SubsetOutput::*;
let contributions;
let is_done;
match o {
Contribution(proposer_id, data) => {
let proposal = (proposer_id, data);
contributions = match self {
Incremental => vec![proposal],
AllAtEnd(cs) => {
cs.push(proposal);
vec![]
}
};
is_done = false;
}
Done => {
contributions = match self {
Incremental => vec![],
AllAtEnd(cs) => replace(cs, vec![]),
};
is_done = true;
}
}
SubsetHandleData {
contributions,
is_done,
}
}
}
impl<N> From<SubsetHandlingStrategy> for SubsetHandler<N> {
fn from(s: SubsetHandlingStrategy) -> Self {
use self::SubsetHandlingStrategy::*;
match s {
Incremental => SubsetHandler::Incremental,
AllAtEnd => SubsetHandler::AllAtEnd(Vec::new()),
}
}
}
/// The sub-algorithms and their intermediate results for a single epoch.
#[derive(Debug)]
pub struct EpochState<C, N: Rand> {
@ -118,6 +190,8 @@ pub struct EpochState<C, N: Rand> {
decryption: BTreeMap<N, DecryptionState<N>>,
/// Nodes found so far in `Subset` output.
accepted_proposers: BTreeSet<N>,
/// Determines the behavior upon receiving proposals from `subset`.
subset_handler: SubsetHandler<N>,
_phantom: PhantomData<C>,
}
@ -127,7 +201,11 @@ where
N: NodeIdT + Rand,
{
/// Creates a new `Subset` instance.
pub fn new(netinfo: Arc<NetworkInfo<N>>, epoch: u64) -> Result<Self> {
pub fn new(
netinfo: Arc<NetworkInfo<N>>,
epoch: u64,
subset_handling_strategy: SubsetHandlingStrategy,
) -> Result<Self> {
let cs = Subset::new(netinfo.clone(), epoch).map_err(ErrorKind::CreateSubset)?;
Ok(EpochState {
epoch,
@ -135,6 +213,7 @@ where
subset: SubsetState::Ongoing(cs),
decryption: BTreeMap::default(),
accepted_proposers: Default::default(),
subset_handler: subset_handling_strategy.into(),
_phantom: PhantomData,
})
}
@ -230,30 +309,34 @@ where
if has_seen_done {
error!("`SubsetOutput::Done` was not the last `SubsetOutput`");
}
match cs_output {
SubsetOutput::Contribution(k, v) => {
step.extend(self.send_decryption_share(k.clone(), &v)?);
self.accepted_proposers.insert(k);
}
SubsetOutput::Done => {
self.subset = SubsetState::Complete(self.accepted_proposers.clone());
let faulty_shares: Vec<_> = self
.decryption
.keys()
.filter(|id| !self.accepted_proposers.contains(id))
.cloned()
.collect();
for id in faulty_shares {
if let Some(DecryptionState::Ongoing(td)) = self.decryption.remove(&id) {
for id in td.sender_ids() {
let fault_kind = FaultKind::UnexpectedDecryptionShare;
step.fault_log.append(id.clone(), fault_kind);
}
let SubsetHandleData {
contributions,
is_done,
} = self.subset_handler.handle(cs_output);
for (k, v) in contributions {
step.extend(self.send_decryption_share(k.clone(), &v)?);
self.accepted_proposers.insert(k);
}
if is_done {
self.subset = SubsetState::Complete(self.accepted_proposers.clone());
let faulty_shares: Vec<_> = self
.decryption
.keys()
.filter(|id| !self.accepted_proposers.contains(id))
.cloned()
.collect();
for id in faulty_shares {
if let Some(DecryptionState::Ongoing(td)) = self.decryption.remove(&id) {
for id in td.sender_ids() {
let fault_kind = FaultKind::UnexpectedDecryptionShare;
step.fault_log.append(id.clone(), fault_kind);
}
}
has_seen_done = true
}
has_seen_done = true;
}
}
Ok(step)

View File

@ -12,6 +12,8 @@ use super::{Batch, Error, ErrorKind, HoneyBadgerBuilder, Message, MessageContent
use messaging::{self, DistAlgorithm, NetworkInfo, Target};
use traits::{Contribution, NodeIdT};
pub use super::epoch_state::SubsetHandlingStrategy;
/// An instance of the Honey Badger Byzantine fault tolerant consensus algorithm.
pub struct HoneyBadger<C, N: Rand> {
/// Shared network data.
@ -31,6 +33,8 @@ pub struct HoneyBadger<C, N: Rand> {
/// 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>,
/// Represents the optimization strategy to use for output of the `Subset` algorithm.
pub(super) subset_handling_strategy: SubsetHandlingStrategy,
}
impl<C, N> fmt::Debug for HoneyBadger<C, N>
@ -216,7 +220,11 @@ where
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)?),
Entry::Vacant(entry) => entry.insert(EpochState::new(
self.netinfo.clone(),
epoch,
self.subset_handling_strategy.clone(),
)?),
})
}
}

View File

@ -32,5 +32,5 @@ mod message;
pub use self::batch::Batch;
pub use self::builder::HoneyBadgerBuilder;
pub use self::error::{Error, ErrorKind, Result};
pub use self::honey_badger::{HoneyBadger, Step};
pub use self::honey_badger::{HoneyBadger, Step, SubsetHandlingStrategy};
pub use self::message::{Message, MessageContent};