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();
|
let txs = batch.len();
|
||||||
println!(
|
println!(
|
||||||
"{:>5} {:6} {:6} {:5} {:9} {:>9}B",
|
"{:>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,
|
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,
|
max_t.as_secs() * 1000 + max_t.subsec_nanos() as u64 / 1_000_000,
|
||||||
txs,
|
txs,
|
||||||
|
@ -392,10 +392,11 @@ fn simulate_honey_badger(
|
||||||
while network.nodes.values_mut().any(node_busy) {
|
while network.nodes.values_mut().any(node_busy) {
|
||||||
let id = network.step();
|
let id = network.step();
|
||||||
for &(time, ref batch) in &network.nodes[&id].outputs {
|
for &(time, ref batch) in &network.nodes[&id].outputs {
|
||||||
if epochs.len() <= batch.epoch as usize {
|
let epoch = batch.epoch() as usize;
|
||||||
epochs.resize(batch.epoch as usize + 1, EpochInfo::default());
|
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 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.
|
/// A batch of transactions the algorithm has output.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct Batch<C, NodeUid> {
|
pub struct Batch<C, NodeUid> {
|
||||||
/// The sequence number: there is exactly one batch in each epoch.
|
/// 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.
|
/// 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
|
/// The current state of adding or removing a node: whether any is in progress, or completed
|
||||||
/// this epoch.
|
/// 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.
|
/// Returns a new, empty batch with the given epoch.
|
||||||
pub fn new(epoch: u64) -> Self {
|
pub fn new(epoch: u64) -> Self {
|
||||||
Batch {
|
Batch {
|
||||||
epoch,
|
epoch,
|
||||||
contributions: BTreeMap::new(),
|
contributions: BTreeMap::new(),
|
||||||
change: ChangeState::None,
|
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
|
/// Returns whether any change to the set of participating nodes is in progress or was
|
||||||
/// completed in this epoch.
|
/// completed in this epoch.
|
||||||
pub fn change(&self) -> &ChangeState<NodeUid> {
|
pub fn change(&self) -> &ChangeState<NodeUid> {
|
||||||
|
@ -68,4 +81,33 @@ impl<C, NodeUid: Ord + Rand> Batch<C, NodeUid> {
|
||||||
.map(C::as_ref)
|
.map(C::as_ref)
|
||||||
.all(<[Tx]>::is_empty)
|
.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::collections::VecDeque;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
use std::iter::once;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use rand::Rand;
|
use rand::{self, Rand};
|
||||||
use serde::{Deserialize, Serialize};
|
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 honey_badger::HoneyBadger;
|
||||||
use messaging::NetworkInfo;
|
use messaging::NetworkInfo;
|
||||||
|
|
||||||
|
@ -18,6 +20,8 @@ pub struct DynamicHoneyBadgerBuilder<C, NodeUid> {
|
||||||
netinfo: NetworkInfo<NodeUid>,
|
netinfo: NetworkInfo<NodeUid>,
|
||||||
/// The epoch at which to join the network.
|
/// The epoch at which to join the network.
|
||||||
start_epoch: u64,
|
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.
|
/// The maximum number of future epochs for which we handle messages simultaneously.
|
||||||
max_future_epochs: usize,
|
max_future_epochs: usize,
|
||||||
_phantom: PhantomData<C>,
|
_phantom: PhantomData<C>,
|
||||||
|
@ -35,6 +39,40 @@ where
|
||||||
DynamicHoneyBadgerBuilder {
|
DynamicHoneyBadgerBuilder {
|
||||||
netinfo,
|
netinfo,
|
||||||
start_epoch: 0,
|
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,
|
max_future_epochs: 3,
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
|
@ -46,20 +84,13 @@ where
|
||||||
self
|
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.
|
/// 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 netinfo = Arc::new(self.netinfo.clone());
|
||||||
let honey_badger = HoneyBadger::builder(netinfo.clone())
|
let honey_badger = HoneyBadger::builder(netinfo.clone())
|
||||||
.max_future_epochs(self.max_future_epochs)
|
.max_future_epochs(self.max_future_epochs)
|
||||||
.build();
|
.build();
|
||||||
DynamicHoneyBadger {
|
let mut dhb = DynamicHoneyBadger {
|
||||||
netinfo: self.netinfo.clone(),
|
netinfo: self.netinfo.clone(),
|
||||||
max_future_epochs: self.max_future_epochs,
|
max_future_epochs: self.max_future_epochs,
|
||||||
start_epoch: self.start_epoch,
|
start_epoch: self.start_epoch,
|
||||||
|
@ -70,6 +101,10 @@ where
|
||||||
incoming_queue: Vec::new(),
|
incoming_queue: Vec::new(),
|
||||||
messages: MessageQueue(VecDeque::new()),
|
messages: MessageQueue(VecDeque::new()),
|
||||||
output: 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.
|
//! pending change.
|
||||||
|
|
||||||
use rand::Rand;
|
use rand::Rand;
|
||||||
use std::collections::VecDeque;
|
use std::collections::{BTreeSet, VecDeque};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
@ -186,6 +186,22 @@ where
|
||||||
DynamicHoneyBadgerBuilder::new(netinfo)
|
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.
|
/// Returns `true` if input for the current epoch has already been provided.
|
||||||
pub fn has_input(&self) -> bool {
|
pub fn has_input(&self) -> bool {
|
||||||
self.honey_badger.has_input()
|
self.honey_badger.has_input()
|
||||||
|
@ -291,12 +307,12 @@ where
|
||||||
let sk = sk.unwrap_or_else(|| self.netinfo.secret_key().clone());
|
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.
|
// 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)?;
|
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() {
|
} 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.
|
// If there is a majority, restart DKG. Inform the user about the current change.
|
||||||
self.update_key_gen(batch.epoch + 1, change)?;
|
self.update_key_gen(batch.epoch + 1, change)?;
|
||||||
if let Some((_, ref change)) = self.key_gen {
|
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);
|
self.output.push_back(batch);
|
||||||
|
@ -537,3 +553,18 @@ where
|
||||||
self.extend(hb.message_iter().map(convert));
|
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())
|
let dyn_hb = DynamicHoneyBadger::builder(self.netinfo.clone())
|
||||||
.max_future_epochs(self.max_future_epochs)
|
.max_future_epochs(self.max_future_epochs)
|
||||||
.build();
|
.build()?;
|
||||||
let queue = TransactionQueue(txs.into_iter().collect());
|
let queue = TransactionQueue(txs.into_iter().collect());
|
||||||
let mut qhb = QueueingHoneyBadger {
|
let mut qhb = QueueingHoneyBadger {
|
||||||
dyn_hb,
|
dyn_hb,
|
||||||
|
|
|
@ -45,11 +45,11 @@ where
|
||||||
fn has_remove(node: &TestNode<UsizeDhb>) -> bool {
|
fn has_remove(node: &TestNode<UsizeDhb>) -> bool {
|
||||||
node.outputs()
|
node.outputs()
|
||||||
.iter()
|
.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 {
|
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),
|
ChangeState::Complete(Change::Add(ref id, _)) => *id == NodeUid(0),
|
||||||
_ => false,
|
_ => false,
|
||||||
})
|
})
|
||||||
|
@ -70,7 +70,7 @@ where
|
||||||
while network.nodes.values().any(node_busy) {
|
while network.nodes.values().any(node_busy) {
|
||||||
// Remove all messages belonging to epochs after all expected outputs.
|
// Remove all messages belonging to epochs after all expected outputs.
|
||||||
for node in network.nodes.values_mut().filter(|node| !node_busy(node)) {
|
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);
|
node.queue.retain(|(_, ref msg)| msg.epoch() < last);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,9 @@ where
|
||||||
// Allow passing `netinfo` by value. `TestNetwork` expects this function signature.
|
// Allow passing `netinfo` by value. `TestNetwork` expects this function signature.
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
|
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
|
||||||
fn new_dynamic_hb(netinfo: Arc<NetworkInfo<NodeUid>>) -> UsizeDhb {
|
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)
|
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 {
|
fn has_remove(node: &TestNode<QueueingHoneyBadger<usize, NodeUid>>) -> bool {
|
||||||
node.outputs()
|
node.outputs()
|
||||||
.iter()
|
.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 {
|
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),
|
ChangeState::Complete(Change::Add(ref id, _)) => *id == NodeUid(0),
|
||||||
_ => false,
|
_ => false,
|
||||||
})
|
})
|
||||||
|
@ -61,7 +61,7 @@ fn test_queueing_honey_badger<A>(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if node.outputs().last().unwrap().is_empty() {
|
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);
|
node.queue.retain(|(_, ref msg)| msg.epoch() < last);
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
|
|
Loading…
Reference in New Issue