Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow arbitrary validator set changes in DHB. #339

Merged
merged 3 commits into from
Nov 18, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/dynamic_honey_badger/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use {NetworkInfo, NodeIdT};

/// A batch of transactions the algorithm has output.
#[derive(Clone, Debug)]
pub struct Batch<C, N> {
pub struct Batch<C, N: Ord> {
/// The sequence number: there is exactly one batch in each epoch.
pub(super) epoch: u64,
/// The current `DynamicHoneyBadger` era.
Expand Down
40 changes: 8 additions & 32 deletions src/dynamic_honey_badger/change.rs
Original file line number Diff line number Diff line change
@@ -1,49 +1,25 @@
use std::collections::BTreeMap;

use crypto::PublicKey;
use serde_derive::{Deserialize, Serialize};

use super::EncryptionSchedule;

#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash, Debug)]
pub enum NodeChange<N> {
/// Add a node. The public key is used only temporarily, for key generation.
Add(N, PublicKey),
/// Remove a node.
Remove(N),
}

impl<N> NodeChange<N> {
/// Returns the ID of the current candidate for being added, if any.
pub fn candidate(&self) -> Option<&N> {
match *self {
NodeChange::Add(ref id, _) => Some(id),
NodeChange::Remove(_) => None,
}
}
}

/// A node change action: adding or removing a node.
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Hash, Debug)]
pub enum Change<N> {
// Add or Remove a node from the set of validators
NodeChange(NodeChange<N>),
pub enum Change<N: Ord> {
/// Change the set of validators to the one in the provided map. There are no restrictions on
/// the new set of validators. In particular, it can be disjoint with the current set of
/// validators.
NodeChange(BTreeMap<N, PublicKey>),
/// Change the threshold encryption schedule.
/// Increase frequency to prevent censorship or decrease frequency for increased throughput.
EncryptionSchedule(EncryptionSchedule),
}

impl<N> Change<N> {
/// Returns the ID of the current candidate for being added, if any.
pub fn candidate(&self) -> Option<&N> {
match self {
Change::NodeChange(node_change) => node_change.candidate(),
_ => None,
}
}
}

/// A change status: whether a change to the network is currently in progress or completed.
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Hash, Debug)]
pub enum ChangeState<N> {
pub enum ChangeState<N: Ord> {
/// No change is currently being considered.
None,
/// A change is currently in progress. If it is a node addition, all broadcast messages must be
Expand Down
73 changes: 31 additions & 42 deletions src/dynamic_honey_badger/dynamic_honey_badger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ use std::{fmt, result};
use bincode;
use crypto::{PublicKey, Signature};
use derivative::Derivative;
use log::{debug, warn};
use log::debug;
use rand::{self, Rand};
use serde::{de::DeserializeOwned, Serialize};

use super::votes::{SignedVote, VoteCounter};
use super::{
Batch, Change, ChangeState, DynamicHoneyBadgerBuilder, EncryptionSchedule, Error, ErrorKind,
Input, InternalContrib, KeyGenMessage, KeyGenState, Message, NodeChange, Result,
SignedKeyGenMsg, Step,
Input, InternalContrib, KeyGenMessage, KeyGenState, Message, Result, SignedKeyGenMsg, Step,
};
use fault_log::{Fault, FaultKind, FaultLog};
use honey_badger::{self, HoneyBadger, Message as HbMessage};
Expand All @@ -25,7 +24,7 @@ use {Contribution, DistAlgorithm, Epoched, NetworkInfo, NodeIdT, Target};
/// A Honey Badger instance that can handle adding and removing nodes.
#[derive(Derivative)]
#[derivative(Debug)]
pub struct DynamicHoneyBadger<C, N: Rand> {
pub struct DynamicHoneyBadger<C, N: Rand + Ord> {
/// Shared network data.
pub(super) netinfo: NetworkInfo<N>,
/// The maximum number of future epochs for which we handle messages simultaneously.
Expand Down Expand Up @@ -135,15 +134,19 @@ where
/// This stores a pending vote for the change. It will be included in some future batch, and
/// once enough validators have been voted for the same change, it will take effect.
pub fn vote_to_add(&mut self, node_id: N, pub_key: PublicKey) -> Result<Step<C, N>> {
self.vote_for(Change::NodeChange(NodeChange::Add(node_id, pub_key)))
let mut pub_keys = self.netinfo.public_key_map().clone();
pub_keys.insert(node_id, pub_key);
self.vote_for(Change::NodeChange(pub_keys))
}

/// Casts a vote to demote a validator to observer.
///
/// This stores a pending vote for the change. It will be included in some future batch, and
/// once enough validators have been voted for the same change, it will take effect.
pub fn vote_to_remove(&mut self, node_id: N) -> Result<Step<C, N>> {
self.vote_for(Change::NodeChange(NodeChange::Remove(node_id)))
pub fn vote_to_remove(&mut self, node_id: &N) -> Result<Step<C, N>> {
let mut pub_keys = self.netinfo.public_key_map().clone();
pub_keys.remove(node_id);
self.vote_for(Change::NodeChange(pub_keys))
}

/// Handles a message received from `sender_id`.
Expand Down Expand Up @@ -193,15 +196,8 @@ where
if self.vote_counter.pending_votes().any(is_our_vote) {
return true; // We have pending input to vote for a validator change.
}
let kgs = match self.key_gen_state {
None => return false, // No ongoing key generation.
Some(ref kgs) => kgs,
};
// If either we or the candidate have a pending key gen message, we should propose.
let ours_or_candidates = |msg: &SignedKeyGenMsg<_>| {
msg.1 == *self.our_id() || Some(&msg.1) == kgs.change.candidate()
};
self.key_gen_msg_buffer.iter().any(ours_or_candidates)
// If we have a pending key gen message, we should propose.
!self.key_gen_msg_buffer.is_empty()
}

/// Handles a message for the `HoneyBadger` instance.
Expand Down Expand Up @@ -293,16 +289,18 @@ where
}
let change = if let Some(kgs) = self.take_ready_key_gen() {
// If DKG completed, apply the change, restart Honey Badger, and inform the user.
debug!("{}: DKG for {:?} complete!", self, kgs.change);
debug!("{}: DKG for complete for: {:?}", self, kgs.public_keys());
self.netinfo = kgs.key_gen.into_network_info()?;
self.restart_honey_badger(batch_epoch + 1, None);
ChangeState::Complete(Change::NodeChange(kgs.change))
ChangeState::Complete(Change::NodeChange(self.netinfo.public_key_map().clone()))
} else if let Some(change) = self.vote_counter.compute_winner().cloned() {
// If there is a new change, restart DKG. Inform the user about the current change.
step.extend(match &change {
Change::NodeChange(change) => self.update_key_gen(batch_epoch + 1, &change)?,
step.extend(match change {
Change::NodeChange(ref pub_keys) => {
self.update_key_gen(batch_epoch + 1, pub_keys)?
}
Change::EncryptionSchedule(schedule) => {
self.update_encryption_schedule(batch_epoch + 1, *schedule)?
self.update_encryption_schedule(batch_epoch + 1, schedule)?
}
});
match change {
Expand Down Expand Up @@ -339,28 +337,21 @@ where
pub(super) fn update_key_gen(
&mut self,
era: u64,
change: &NodeChange<N>,
pub_keys: &BTreeMap<N, PublicKey>,
) -> Result<Step<C, N>> {
if self.key_gen_state.as_ref().map(|kgs| &kgs.change) == Some(change) {
if self.key_gen_state.as_ref().map(KeyGenState::public_keys) == Some(pub_keys) {
return Ok(Step::default()); // The change is the same as before. Continue DKG as is.
}
debug!("{}: Restarting DKG for {:?}.", self, change);
// Use the existing key shares - with the change applied - as keys for DKG.
let mut pub_keys = self.netinfo.public_key_map().clone();
if match *change {
NodeChange::Remove(ref id) => pub_keys.remove(id).is_none(),
NodeChange::Add(ref id, ref pk) => pub_keys.insert(id.clone(), pk.clone()).is_some(),
} {
warn!("{}: No-op change: {:?}", self, change);
}
debug!("{}: Restarting DKG for {:?}.", self, pub_keys);
self.restart_honey_badger(era, None);
// TODO: This needs to be the same as `num_faulty` will be in the _new_
// `NetworkInfo` if the change goes through. It would be safer to deduplicate.
let threshold = (pub_keys.len() - 1) / 3;
let sk = self.netinfo.secret_key().clone();
let our_id = self.our_id().clone();
let (key_gen, part) = SyncKeyGen::new(&mut self.rng, our_id, sk, pub_keys, threshold)?;
self.key_gen_state = Some(KeyGenState::new(key_gen, change.clone()));
let (key_gen, part) =
SyncKeyGen::new(&mut self.rng, our_id, sk, pub_keys.clone(), threshold)?;
self.key_gen_state = Some(KeyGenState::new(key_gen));
if let Some(part) = part {
self.send_transaction(KeyGenMessage::Part(part))
} else {
Expand Down Expand Up @@ -460,7 +451,7 @@ where
/// Returns `true` if the signature of `kg_msg` by the node with the specified ID is valid.
/// Returns an error if the payload fails to serialize.
///
/// This accepts signatures from both validators and the currently joining candidate, if any.
/// This accepts signatures from both validators and currently joining candidates, if any.
fn verify_signature(
&self,
node_id: &N,
Expand All @@ -469,13 +460,11 @@ where
) -> Result<bool> {
let ser =
bincode::serialize(kg_msg).map_err(|err| ErrorKind::VerifySignatureBincode(*err))?;
let get_candidate_key = || {
self.key_gen_state
.as_ref()
.and_then(|kgs| kgs.candidate_key(node_id))
};
let pk_opt = self.netinfo.public_key(node_id).or_else(get_candidate_key);
Ok(pk_opt.map_or(false, |pk| pk.verify(&sig, ser)))
let verify = |opt_pk: Option<&PublicKey>| opt_pk.map_or(false, |pk| pk.verify(&sig, &ser));
let kgs = self.key_gen_state.as_ref();
let current_key = self.netinfo.public_key(node_id);
let candidate_key = kgs.and_then(|kgs| kgs.public_keys().get(node_id));
Ok(verify(current_key) || verify(candidate_key))
}

/// Returns the maximum future epochs of the Honey Badger algorithm instance.
Expand Down
33 changes: 13 additions & 20 deletions src/dynamic_honey_badger/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,15 @@ use NodeIdT;

pub use self::batch::Batch;
pub use self::builder::DynamicHoneyBadgerBuilder;
pub use self::change::{Change, ChangeState, NodeChange};
pub use self::change::{Change, ChangeState};
pub use self::dynamic_honey_badger::DynamicHoneyBadger;
pub use self::error::{Error, ErrorKind, Result};

pub type Step<C, N> = ::DaStep<DynamicHoneyBadger<C, N>>;

/// The user input for `DynamicHoneyBadger`.
#[derive(Clone, Debug)]
pub enum Input<C, N> {
pub enum Input<C, N: Ord> {
/// A user-defined contribution for the next epoch.
User(C),
/// A vote to change the set of validators.
Expand All @@ -113,7 +113,7 @@ pub enum KeyGenMessage {

/// A message sent to or received from another node's Honey Badger instance.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Message<N: Rand> {
pub enum Message<N: Rand + Ord> {
/// A message belonging to the `HoneyBadger` algorithm started in the given epoch.
HoneyBadger(u64, HbMessage<N>),
/// A transaction to be committed, signed by a node.
Expand All @@ -122,7 +122,7 @@ pub enum Message<N: Rand> {
SignedVote(SignedVote<N>),
}

impl<N: Rand> Message<N> {
impl<N: Rand + Ord> Message<N> {
fn era(&self) -> u64 {
match *self {
Message::HoneyBadger(era, _) => era,
Expand Down Expand Up @@ -153,38 +153,31 @@ pub struct JoinPlan<N: Ord> {

/// The ongoing key generation, together with information about the validator change.
#[derive(Debug)]
struct KeyGenState<N> {
struct KeyGenState<N: Ord> {
/// The key generation instance.
key_gen: SyncKeyGen<N>,
/// The change for which key generation is performed.
change: NodeChange<N>,
/// The number of key generation messages received from each peer. At most _N + 1_ are
/// accepted.
msg_count: BTreeMap<N, usize>,
}

impl<N: NodeIdT> KeyGenState<N> {
fn new(key_gen: SyncKeyGen<N>, change: NodeChange<N>) -> Self {
fn new(key_gen: SyncKeyGen<N>) -> Self {
KeyGenState {
key_gen,
change,
msg_count: BTreeMap::new(),
}
}

/// Returns `true` if the candidate's, if any, as well as enough validators' key generation
/// parts have been completed.
/// Returns `true` if enough validators' key generation parts have been completed.
fn is_ready(&self) -> bool {
let candidate_ready = |id: &N| self.key_gen.is_node_ready(id);
self.key_gen.is_ready() && self.change.candidate().map_or(true, candidate_ready)
let kg = &self.key_gen;
kg.is_ready() && kg.count_complete() * 3 > 2 * kg.public_keys().len()
}

/// If the node `node_id` is the currently joining candidate, returns its public key.
fn candidate_key(&self, node_id: &N) -> Option<&PublicKey> {
match self.change {
NodeChange::Add(ref id, ref pk) if id == node_id => Some(pk),
NodeChange::Add(_, _) | NodeChange::Remove(_) => None,
}
/// Returns the map of new validators and their public keys.
fn public_keys(&self) -> &BTreeMap<N, PublicKey> {
self.key_gen.public_keys()
}

/// Increments the message count for the given node, and returns the new count.
Expand All @@ -198,7 +191,7 @@ impl<N: NodeIdT> KeyGenState<N> {
/// The contribution for the internal `HoneyBadger` instance: this includes a user-defined
/// application-level contribution as well as internal signed messages.
#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize, Hash)]
struct InternalContrib<C, N> {
struct InternalContrib<C, N: Ord> {
/// A user-defined contribution.
contrib: C,
/// Key generation messages that get committed via Honey Badger to communicate synchronously.
Expand Down
Loading