Merge pull request #138 from poanetwork/afck-builder-change

Add a `JoinPlan` to facilitate joining a running DHB network.
This commit is contained in:
Vladimir Komendantskiy 2018-07-16 15:36:35 +01:00 committed by GitHub
commit a887d82e6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 143 additions and 32 deletions

View File

@ -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);
}
}
}

View File

@ -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<C, NodeUid> {
/// 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<NodeUid, C>,
pub(super) contributions: BTreeMap<NodeUid, C>,
/// The current state of adding or removing a node: whether any is in progress, or completed
/// this epoch.
pub change: ChangeState<NodeUid>,
change: ChangeState<NodeUid>,
/// The public network info, if `change` is not `None`.
pub_netinfo: Option<(BTreeSet<NodeUid>, PublicKeySet)>,
}
impl<C, NodeUid: Ord + Rand> Batch<C, NodeUid> {
impl<C, NodeUid: Ord + Rand + Clone + Debug> Batch<C, NodeUid> {
/// 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<NodeUid> {
@ -68,4 +81,33 @@ impl<C, NodeUid: Ord + Rand> Batch<C, NodeUid> {
.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<JoinPlan<NodeUid>>
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<NodeUid>,
netinfo: &NetworkInfo<NodeUid>,
) {
self.change = change;
if self.change != ChangeState::None {
self.pub_netinfo = Some((netinfo.all_uids().clone(), netinfo.public_key_set().clone()));
}
}
}

View File

@ -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<C, NodeUid> {
netinfo: NetworkInfo<NodeUid>,
/// 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<NodeUid>,
/// The maximum number of future epochs for which we handle messages simultaneously.
max_future_epochs: usize,
_phantom: PhantomData<C>,
@ -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<NodeUid>,
) -> 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<C, NodeUid> {
pub fn build(&self) -> Result<DynamicHoneyBadger<C, NodeUid>> {
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)
}
}

View File

@ -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<C, NodeUid> {
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<NodeUid>,
) -> DynamicHoneyBadgerBuilder<C, NodeUid> {
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<NodeUid: Ord> {
/// 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<NodeUid>,
/// The set of all validators' node IDs.
all_uids: BTreeSet<NodeUid>,
/// The current public key set for threshold cryptography.
pub_key_set: PublicKeySet,
}

View File

@ -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,

View File

@ -45,11 +45,11 @@ where
fn has_remove(node: &TestNode<UsizeDhb>) -> 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<UsizeDhb>) -> 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<NetworkInfo<NodeUid>>) -> UsizeDhb {
DynamicHoneyBadger::builder((*netinfo).clone()).build()
DynamicHoneyBadger::builder((*netinfo).clone())
.build()
.expect("instantiate DHB")
}
fn test_dynamic_honey_badger_different_sizes<A, F>(new_adversary: F, num_txs: usize)

View File

@ -41,11 +41,11 @@ fn test_queueing_honey_badger<A>(
fn has_remove(node: &TestNode<QueueingHoneyBadger<usize, NodeUid>>) -> 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<QueueingHoneyBadger<usize, NodeUid>>) -> 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<A>(
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