diff --git a/examples/simulation.rs b/examples/simulation.rs index 5fb8efc..7c9f754 100644 --- a/examples/simulation.rs +++ b/examples/simulation.rs @@ -358,7 +358,7 @@ impl EpochInfo { let txs = batch.len(); println!( "{:>5} {:6} {:6} {:5} {:9} {:>9}B", - batch.epoch.to_string().cyan(), + batch.epoch().to_string().cyan(), min_t.as_secs() * 1000 + max_t.subsec_nanos() as u64 / 1_000_000, max_t.as_secs() * 1000 + max_t.subsec_nanos() as u64 / 1_000_000, txs, @@ -392,10 +392,11 @@ fn simulate_honey_badger( while network.nodes.values_mut().any(node_busy) { let id = network.step(); for &(time, ref batch) in &network.nodes[&id].outputs { - if epochs.len() <= batch.epoch as usize { - epochs.resize(batch.epoch as usize + 1, EpochInfo::default()); + let epoch = batch.epoch() as usize; + if epochs.len() <= epoch { + epochs.resize(epoch + 1, EpochInfo::default()); } - epochs[batch.epoch as usize].add(id, time, batch, &network); + epochs[epoch].add(id, time, batch, &network); } } } diff --git a/src/dynamic_honey_badger/batch.rs b/src/dynamic_honey_badger/batch.rs index 4271948..f047396 100644 --- a/src/dynamic_honey_badger/batch.rs +++ b/src/dynamic_honey_badger/batch.rs @@ -1,29 +1,42 @@ -use super::ChangeState; +use std::collections::{BTreeMap, BTreeSet}; +use std::fmt::Debug; + use rand::Rand; -use std::collections::BTreeMap; +use serde::{Deserialize, Serialize}; + +use super::{ChangeState, JoinPlan}; +use crypto::PublicKeySet; +use messaging::NetworkInfo; /// A batch of transactions the algorithm has output. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Batch { /// The sequence number: there is exactly one batch in each epoch. - pub epoch: u64, + pub(super) epoch: u64, /// The user contributions committed in this epoch. - pub contributions: BTreeMap, + pub(super) contributions: BTreeMap, /// The current state of adding or removing a node: whether any is in progress, or completed /// this epoch. - pub change: ChangeState, + change: ChangeState, + /// The public network info, if `change` is not `None`. + pub_netinfo: Option<(BTreeSet, PublicKeySet)>, } -impl Batch { +impl Batch { /// Returns a new, empty batch with the given epoch. pub fn new(epoch: u64) -> Self { Batch { epoch, contributions: BTreeMap::new(), change: ChangeState::None, + pub_netinfo: None, } } + pub fn epoch(&self) -> u64 { + self.epoch + } + /// Returns whether any change to the set of participating nodes is in progress or was /// completed in this epoch. pub fn change(&self) -> &ChangeState { @@ -68,4 +81,33 @@ impl Batch { .map(C::as_ref) .all(<[Tx]>::is_empty) } + + /// Returns the `JoinPlan` to be sent to new observer nodes, if it is possible to join in the + /// next epoch. + pub fn join_plan(&self) -> Option> + where + NodeUid: Serialize + for<'r> Deserialize<'r>, + { + self.pub_netinfo + .as_ref() + .map(|&(ref all_uids, ref pub_key_set)| JoinPlan { + epoch: self.epoch + 1, + change: self.change.clone(), + all_uids: all_uids.clone(), + pub_key_set: pub_key_set.clone(), + }) + } + + /// Sets the current change state, and if it is not `None`, inserts the network information so + /// that a `JoinPlan` can be generated for the next epoch. + pub(super) fn set_change( + &mut self, + change: ChangeState, + netinfo: &NetworkInfo, + ) { + self.change = change; + if self.change != ChangeState::None { + self.pub_netinfo = Some((netinfo.all_uids().clone(), netinfo.public_key_set().clone())); + } + } } diff --git a/src/dynamic_honey_badger/builder.rs b/src/dynamic_honey_badger/builder.rs index 2be5b1f..13db7c5 100644 --- a/src/dynamic_honey_badger/builder.rs +++ b/src/dynamic_honey_badger/builder.rs @@ -1,13 +1,15 @@ use std::collections::VecDeque; use std::fmt::Debug; use std::hash::Hash; +use std::iter::once; use std::marker::PhantomData; use std::sync::Arc; -use rand::Rand; +use rand::{self, Rand}; use serde::{Deserialize, Serialize}; -use super::{DynamicHoneyBadger, MessageQueue, VoteCounter}; +use super::{ChangeState, DynamicHoneyBadger, JoinPlan, MessageQueue, Result, VoteCounter}; +use crypto::{SecretKey, SecretKeySet}; use honey_badger::HoneyBadger; use messaging::NetworkInfo; @@ -18,6 +20,8 @@ pub struct DynamicHoneyBadgerBuilder { netinfo: NetworkInfo, /// The epoch at which to join the network. start_epoch: u64, + /// The current change, for which key generation is beginning at `start_epoch`. + change: ChangeState, /// The maximum number of future epochs for which we handle messages simultaneously. max_future_epochs: usize, _phantom: PhantomData, @@ -35,6 +39,40 @@ where DynamicHoneyBadgerBuilder { netinfo, start_epoch: 0, + change: ChangeState::None, + max_future_epochs: 3, + _phantom: PhantomData, + } + } + + /// Returns a new `DynamicHoneyBadgerBuilder` configured to start a new network as a single + /// validator. + pub fn new_first_node(our_uid: NodeUid) -> Self { + let mut rng = rand::thread_rng(); + let sk_set = SecretKeySet::random(0, &mut rng); + let pk_set = sk_set.public_keys(); + let sk = sk_set.secret_key_share(0); + let netinfo = NetworkInfo::new(our_uid.clone(), once(our_uid).collect(), sk, pk_set); + DynamicHoneyBadgerBuilder::new(netinfo) + } + + /// Returns a new `DynamicHoneyBadgerBuilder` configured to join the network at the epoch + /// specified in the `JoinPlan`. + pub fn new_joining( + our_uid: NodeUid, + secret_key: SecretKey, + join_plan: JoinPlan, + ) -> Self { + let netinfo = NetworkInfo::new( + our_uid, + join_plan.all_uids, + secret_key, + join_plan.pub_key_set, + ); + DynamicHoneyBadgerBuilder { + netinfo, + start_epoch: join_plan.epoch, + change: join_plan.change, max_future_epochs: 3, _phantom: PhantomData, } @@ -46,20 +84,13 @@ where self } - /// Sets the epoch at which to join the network as an observer. This requires the node to - /// receive all broadcast messages for `start_epoch` and later. - pub fn start_epoch(&mut self, start_epoch: u64) -> &mut Self { - self.start_epoch = start_epoch; - self - } - /// Creates a new Dynamic Honey Badger instance with an empty buffer. - pub fn build(&self) -> DynamicHoneyBadger { + pub fn build(&self) -> Result> { let netinfo = Arc::new(self.netinfo.clone()); let honey_badger = HoneyBadger::builder(netinfo.clone()) .max_future_epochs(self.max_future_epochs) .build(); - DynamicHoneyBadger { + let mut dhb = DynamicHoneyBadger { netinfo: self.netinfo.clone(), max_future_epochs: self.max_future_epochs, start_epoch: self.start_epoch, @@ -70,6 +101,10 @@ where incoming_queue: Vec::new(), messages: MessageQueue(VecDeque::new()), output: VecDeque::new(), + }; + if let ChangeState::InProgress(ref change) = self.change { + dhb.update_key_gen(self.start_epoch, change.clone())?; } + Ok(dhb) } } diff --git a/src/dynamic_honey_badger/mod.rs b/src/dynamic_honey_badger/mod.rs index 280c8b7..6b24c02 100644 --- a/src/dynamic_honey_badger/mod.rs +++ b/src/dynamic_honey_badger/mod.rs @@ -45,7 +45,7 @@ //! pending change. use rand::Rand; -use std::collections::VecDeque; +use std::collections::{BTreeSet, VecDeque}; use std::fmt::Debug; use std::hash::Hash; use std::mem; @@ -186,6 +186,22 @@ where DynamicHoneyBadgerBuilder::new(netinfo) } + /// Returns a new `DynamicHoneyBadgerBuilder` configured to start a new network as the first + /// node. + pub fn first_node_builder(our_uid: NodeUid) -> DynamicHoneyBadgerBuilder { + DynamicHoneyBadgerBuilder::new_first_node(our_uid) + } + + /// Returns a new `DynamicHoneyBadgerBuilder` configured to join the network at the epoch + /// specified in the `JoinPlan`. + pub fn joining_builder( + our_uid: NodeUid, + secret_key: SecretKey, + join_plan: JoinPlan, + ) -> DynamicHoneyBadgerBuilder { + DynamicHoneyBadgerBuilder::new_joining(our_uid, secret_key, join_plan) + } + /// Returns `true` if input for the current epoch has already been provided. pub fn has_input(&self) -> bool { self.honey_badger.has_input() @@ -291,12 +307,12 @@ where let sk = sk.unwrap_or_else(|| self.netinfo.secret_key().clone()); // Restart Honey Badger in the next epoch, and inform the user about the change. self.apply_change(&change, pub_key_set, sk, batch.epoch + 1)?; - batch.change = ChangeState::Complete(change); + batch.set_change(ChangeState::Complete(change), &self.netinfo); } else if let Some(change) = self.vote_counter.compute_majority().cloned() { // If there is a majority, restart DKG. Inform the user about the current change. self.update_key_gen(batch.epoch + 1, change)?; if let Some((_, ref change)) = self.key_gen { - batch.change = ChangeState::InProgress(change.clone()); + batch.set_change(ChangeState::InProgress(change.clone()), &self.netinfo); } } self.output.push_back(batch); @@ -537,3 +553,18 @@ where self.extend(hb.message_iter().map(convert)); } } + +/// The information a new node requires to join the network as an observer. It contains the state +/// of voting and key generation after a specific epoch, so that the new node will be in sync if it +/// joins in the next one. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct JoinPlan { + /// The first epoch the new node will observe. + epoch: u64, + /// The current change. If `InProgress`, key generation for it is beginning at `epoch`. + change: ChangeState, + /// The set of all validators' node IDs. + all_uids: BTreeSet, + /// The current public key set for threshold cryptography. + pub_key_set: PublicKeySet, +} diff --git a/src/queueing_honey_badger.rs b/src/queueing_honey_badger.rs index 84a6644..9104dc6 100644 --- a/src/queueing_honey_badger.rs +++ b/src/queueing_honey_badger.rs @@ -93,7 +93,7 @@ where { let dyn_hb = DynamicHoneyBadger::builder(self.netinfo.clone()) .max_future_epochs(self.max_future_epochs) - .build(); + .build()?; let queue = TransactionQueue(txs.into_iter().collect()); let mut qhb = QueueingHoneyBadger { dyn_hb, diff --git a/tests/dynamic_honey_badger.rs b/tests/dynamic_honey_badger.rs index 391e3ec..280f450 100644 --- a/tests/dynamic_honey_badger.rs +++ b/tests/dynamic_honey_badger.rs @@ -45,11 +45,11 @@ where fn has_remove(node: &TestNode) -> bool { node.outputs() .iter() - .any(|batch| batch.change == ChangeState::Complete(Change::Remove(NodeUid(0)))) + .any(|batch| *batch.change() == ChangeState::Complete(Change::Remove(NodeUid(0)))) } fn has_add(node: &TestNode) -> bool { - node.outputs().iter().any(|batch| match batch.change { + node.outputs().iter().any(|batch| match *batch.change() { ChangeState::Complete(Change::Add(ref id, _)) => *id == NodeUid(0), _ => false, }) @@ -70,7 +70,7 @@ where while network.nodes.values().any(node_busy) { // Remove all messages belonging to epochs after all expected outputs. for node in network.nodes.values_mut().filter(|node| !node_busy(node)) { - if let Some(last) = node.outputs().last().map(|out| out.epoch) { + if let Some(last) = node.outputs().last().map(Batch::epoch) { node.queue.retain(|(_, ref msg)| msg.epoch() < last); } } @@ -122,7 +122,9 @@ where // Allow passing `netinfo` by value. `TestNetwork` expects this function signature. #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] fn new_dynamic_hb(netinfo: Arc>) -> UsizeDhb { - DynamicHoneyBadger::builder((*netinfo).clone()).build() + DynamicHoneyBadger::builder((*netinfo).clone()) + .build() + .expect("instantiate DHB") } fn test_dynamic_honey_badger_different_sizes(new_adversary: F, num_txs: usize) diff --git a/tests/queueing_honey_badger.rs b/tests/queueing_honey_badger.rs index 13f987c..36b531b 100644 --- a/tests/queueing_honey_badger.rs +++ b/tests/queueing_honey_badger.rs @@ -41,11 +41,11 @@ fn test_queueing_honey_badger( fn has_remove(node: &TestNode>) -> bool { node.outputs() .iter() - .any(|batch| batch.change == ChangeState::Complete(Change::Remove(NodeUid(0)))) + .any(|batch| *batch.change() == ChangeState::Complete(Change::Remove(NodeUid(0)))) } fn has_add(node: &TestNode>) -> bool { - node.outputs().iter().any(|batch| match batch.change { + node.outputs().iter().any(|batch| match *batch.change() { ChangeState::Complete(Change::Add(ref id, _)) => *id == NodeUid(0), _ => false, }) @@ -61,7 +61,7 @@ fn test_queueing_honey_badger( return true; } if node.outputs().last().unwrap().is_empty() { - let last = node.outputs().last().unwrap().epoch; + let last = node.outputs().last().unwrap().epoch(); node.queue.retain(|(_, ref msg)| msg.epoch() < last); } false