mirror of https://github.com/poanetwork/hbbft.git
Merge pull request #138 from poanetwork/afck-builder-change
Add a `JoinPlan` to facilitate joining a running DHB network.
This commit is contained in:
commit
a887d82e6b
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue