refactor change and make it possible for encryption to be optional

This commit is contained in:
Logan Collins 2018-10-23 23:21:59 -05:00
parent e65a470720
commit 85910d8d43
11 changed files with 79 additions and 71 deletions

View File

@ -149,6 +149,7 @@ where
let arc_netinfo = Arc::new(netinfo.clone());
let honey_badger = HoneyBadger::builder(arc_netinfo.clone())
.max_future_epochs(self.max_future_epochs)
.encryption_schedule(self.encryption_schedule)
.build();
let mut dhb = DynamicHoneyBadger {
netinfo,
@ -164,7 +165,7 @@ where
let step = match join_plan.change {
ChangeState::InProgress(ref change) => {
match change {
Change::Add(_,_) | Change::Remove(_) => dhb.update_key_gen(join_plan.epoch, change)?,
Change::NodeChange(change) => dhb.update_key_gen(join_plan.epoch, change)?,
_ => Step::default(),
}
},

View File

@ -1,14 +1,29 @@
use crypto::PublicKey;
use threshold_decryption::EncryptionSchedule;
/// A node change action: adding or removing a node.
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Hash, Debug)]
pub enum Change<N> {
// TODO: Refactor so that node changes are in a sub-enum of a Change.
#[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>),
/// Change the threshold encryption schedule. i.e., to increase frequency to prevent censorship or decrease for performance.
EncryptionSchedule(EncryptionSchedule),
}
@ -16,20 +31,19 @@ pub enum Change<N> {
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::Add(ref id, _) => Some(id),
Change::Remove(_) => None,
Change::EncryptionSchedule(_) => None,
match self {
Change::NodeChange(node_change) => node_change.candidate(),
_ => None,
}
}
}
/// A change status: whether a node addition or removal is currently in progress or completed.
/// 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> {
/// No node is currently being considered for addition or removal.
/// No change is currently being considered.
None,
/// A change is currently in progress. If it is an addition, all broadcast messages must be
/// A change is currently in progress. If it is a node addition, all broadcast messages must be
/// sent to the new node, too.
InProgress(Change<N>),
/// A change has been completed in this epoch. From the next epoch on, the new composition of

View File

@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
use super::votes::{SignedVote, VoteCounter};
use super::{
Batch, Change, ChangeState, DynamicHoneyBadgerBuilder, Error, ErrorKind, Input,
Batch, Change, NodeChange, ChangeState, DynamicHoneyBadgerBuilder, Error, ErrorKind, Input,
InternalContrib, KeyGenMessage, KeyGenState, Message, Result, SignedKeyGenMsg, Step,
};
use fault_log::{Fault, FaultKind, FaultLog};
@ -298,11 +298,11 @@ where
debug!("{:?} DKG for {:?} complete!", self.our_id(), kgs.change);
self.netinfo = kgs.key_gen.into_network_info()?;
self.restart_honey_badger(batch_epoch + 1);
ChangeState::Complete(kgs.change)
ChangeState::Complete(Change::NodeChange(kgs.change))
} 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::Add(_,_) | Change::Remove(_) => self.update_key_gen(batch_epoch + 1, &change)?,
step.extend(match &change {
Change::NodeChange(change) => self.update_key_gen(batch_epoch + 1, &change)?,
Change::EncryptionSchedule(_) => self.update_encryption_schedule(batch_epoch + 1, &change)?,
});
ChangeState::InProgress(change)
@ -327,22 +327,16 @@ where
}
pub(super) fn update_encryption_schedule(&mut self, epoch: u64, change: &Change<N>) -> Result<Step<C, N>> {
// TODO: verify this implementation
self.restart_honey_badger(epoch);
if let Change::EncryptionSchedule(schedule) = change {
self.honey_badger.encryption_schedule = *schedule;
}
if self.netinfo().is_validator() {
Ok(Step::default())
// self.send_transaction() TODO: commit a message about changing schedule
} else {
Ok(Step::default())
}
Ok(Step::default())
}
/// If the winner of the vote has changed, restarts Key Generation for the set of nodes implied
/// by the current change.
pub(super) fn update_key_gen(&mut self, epoch: u64, change: &Change<N>) -> Result<Step<C, N>> {
pub(super) fn update_key_gen(&mut self, epoch: u64, change: &NodeChange<N>) -> Result<Step<C, N>> {
if self.key_gen_state.as_ref().map(|kgs| &kgs.change) == Some(change) {
return Ok(Step::default()); // The change is the same as before. Continue DKG as is.
}
@ -350,9 +344,8 @@ where
// 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 {
Change::Remove(ref id) => pub_keys.remove(id).is_none(),
Change::Add(ref id, ref pk) => pub_keys.insert(id.clone(), pk.clone()).is_some(),
_ => unreachable!(), // Unreachable by construction. Will be more clear once Change is refactored to have `NodeChange` so there won't be other variants.
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(),
} {
info!("{:?} No-op change: {:?}", self.our_id(), change);
}

View File

@ -73,7 +73,7 @@ use NodeIdT;
pub use self::batch::Batch;
pub use self::builder::DynamicHoneyBadgerBuilder;
pub use self::change::{Change, ChangeState};
pub use self::change::{Change, NodeChange, ChangeState};
pub use self::dynamic_honey_badger::DynamicHoneyBadger;
pub use self::error::{Error, ErrorKind, Result};
@ -149,14 +149,14 @@ struct KeyGenState<N> {
/// The key generation instance.
key_gen: SyncKeyGen<N>,
/// The change for which key generation is performed.
change: Change<N>,
change: NodeChange<N>,
/// The number of key generation messages received from the candidate. At most _N² + 1_ are
/// accepted.
candidate_msg_count: usize,
}
impl<N: NodeIdT> KeyGenState<N> {
fn new(key_gen: SyncKeyGen<N>, change: Change<N>) -> Self {
fn new(key_gen: SyncKeyGen<N>, change: NodeChange<N>) -> Self {
KeyGenState {
key_gen,
change,
@ -174,9 +174,8 @@ impl<N: NodeIdT> KeyGenState<N> {
/// 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 {
Change::Add(ref id, ref pk) if id == node_id => Some(pk),
Change::Add(_, _) | Change::Remove(_) => None,
Change::EncryptionSchedule(_) => None,
NodeChange::Add(ref id, ref pk) if id == node_id => Some(pk),
NodeChange::Add(_, _) | NodeChange::Remove(_) => None,
}
}
}

View File

@ -5,7 +5,7 @@ use bincode;
use crypto::Signature;
use serde::{Deserialize, Serialize};
use super::{Change, ErrorKind, Result};
use super::{Change, NodeChange, ErrorKind, Result};
use fault_log::{FaultKind, FaultLog};
use {NetworkInfo, NodeIdT};
@ -189,7 +189,7 @@ mod tests {
use rand;
use super::{Change, SignedVote, VoteCounter};
use super::{Change, NodeChange, SignedVote, VoteCounter};
use fault_log::{FaultKind, FaultLog};
use NetworkInfo;
@ -211,8 +211,8 @@ mod tests {
// Sign a few votes.
let sign_votes = |counter: &mut VoteCounter<usize>| {
(0..node_num)
.map(Change::Remove)
.map(|change| counter.sign_vote_for(change).expect("sign vote").clone())
.map(NodeChange::Remove)
.map(|change| counter.sign_vote_for(Change::NodeChange(change)).expect("sign vote").clone())
.collect::<Vec<_>>()
};
let signed_votes: Vec<_> = counters.iter_mut().map(sign_votes).collect();
@ -299,6 +299,6 @@ mod tests {
.add_committed_vote(&1, sv[2][1].clone())
.expect("add committed");
assert!(faults.is_empty());
assert_eq!(ct.compute_winner(), Some(&Change::Remove(1)));
assert_eq!(ct.compute_winner(), Some(&Change::NodeChange(NodeChange::Remove(1))));
}
}

View File

@ -191,6 +191,7 @@ pub struct EpochState<C, N: Rand> {
accepted_proposers: BTreeSet<N>,
/// Determines the behavior upon receiving proposals from `subset`.
subset_handler: SubsetHandler<N>,
require_decryption: bool,
_phantom: PhantomData<C>,
}
@ -204,6 +205,7 @@ where
netinfo: Arc<NetworkInfo<N>>,
epoch: u64,
subset_handling_strategy: SubsetHandlingStrategy,
require_decryption: bool,
) -> Result<Self> {
let cs = Subset::new(netinfo.clone(), epoch).map_err(ErrorKind::CreateSubset)?;
Ok(EpochState {
@ -213,26 +215,17 @@ where
decryption: BTreeMap::default(),
accepted_proposers: Default::default(),
subset_handler: subset_handling_strategy.into(),
require_decryption: require_decryption,
_phantom: PhantomData,
})
}
/// If the instance hasn't terminated yet, inputs our encrypted contribution.
pub fn propose(&mut self, ciphertext: &Ciphertext) -> Result<Step<C, N>> {
let ser_ct =
bincode::serialize(ciphertext).map_err(|err| ErrorKind::ProposeBincode(*err))?;
let cs_step = self.subset.handle_input(ser_ct)?;
pub fn propose(&mut self, proposal: Vec<u8>) -> Result<Step<C, N>> {
let cs_step = self.subset.handle_input(proposal)?;
self.process_subset(cs_step)
}
pub fn propose_plain(&mut self, proposal: Vec<u8>) -> Result<Step<C, N>> {
let our_id = self.netinfo.our_id().clone();
self.decryption.insert(our_id, DecryptionState::Complete(proposal));
Ok(Step::default()) // TODO: this is obviously wrong, I need to send the proposal still.
// let cs_step = self.subset.handle_input(proposal)?;
// self.process_subset(cs_step)
}
/// Returns the number of contributions that we have already received or, after completion, how
/// many have been accepted.
pub fn received_proposals(&self) -> usize {
@ -323,7 +316,11 @@ where
} = self.subset_handler.handle(cs_output);
for (k, v) in contributions {
step.extend(self.send_decryption_share(k.clone(), &v)?);
step.extend(if self.require_decryption {
self.send_decryption_share(k.clone(), &v)?
} else {
self.process_decryption(k.clone(), td::Step::default().with_output(v))?
});
self.accepted_proposers.insert(k);
}

View File

@ -103,18 +103,21 @@ where
return Ok(Step::default());
}
self.has_input = true;
let ser_prop =
bincode::serialize(&proposal).map_err(|err| ErrorKind::ProposeBincode(*err))?;
let epoch = self.epoch;
let ser_prop = bincode::serialize(&proposal).map_err(|err| ErrorKind::ProposeBincode(*err))?;
let mut step = if self.encryption_schedule.use_on_epoch(self.epoch) {
let require_decryption = self.encryption_schedule.use_on_epoch(epoch);
let prop = if require_decryption {
let ciphertext = self
.netinfo
.public_key_set()
.public_key()
.encrypt_with_rng(&mut self.rng, ser_prop);
self.epoch_state_mut(epoch)?.propose(&ciphertext)?
bincode::serialize(&ciphertext).map_err(|err| ErrorKind::ProposeBincode(*err))?
} else {
self.epoch_state_mut(epoch)?.propose_plain(ser_prop)?
ser_prop
};
let mut step = self.epoch_state_mut(epoch)?.propose(prop)?;
step.extend(self.try_output_batches()?);
Ok(step)
}
@ -196,6 +199,7 @@ where
self.netinfo.clone(),
epoch,
self.subset_handling_strategy.clone(),
self.encryption_schedule.use_on_epoch(epoch),
)?),
})
}

View File

@ -34,7 +34,7 @@ use dynamic_honey_badger::{self, Batch as DhbBatch, DynamicHoneyBadger, Message}
use transaction_queue::TransactionQueue;
use {Contribution, DistAlgorithm, NodeIdT};
pub use dynamic_honey_badger::{Change, ChangeState, Input};
pub use dynamic_honey_badger::{Change, NodeChange, ChangeState, Input};
/// Queueing honey badger error variants.
#[derive(Debug, Fail)]

View File

@ -21,7 +21,7 @@ use std::sync::Arc;
use itertools::Itertools;
use rand::{Isaac64Rng, Rng};
use hbbft::dynamic_honey_badger::{Batch, Change, ChangeState, DynamicHoneyBadger, Input};
use hbbft::dynamic_honey_badger::{Batch, Change, NodeChange, ChangeState, DynamicHoneyBadger, Input};
use hbbft::transaction_queue::TransactionQueue;
use hbbft::NetworkInfo;
@ -41,17 +41,17 @@ where
network.input(*id, Input::User(queue.choose(&mut rng, 3, 10)));
}
network.input_all(Input::Change(Change::Remove(NodeId(0))));
network.input_all(Input::Change(Change::NodeChange(NodeChange::Remove(NodeId(0)))));
fn has_remove(node: &TestNode<UsizeDhb>) -> bool {
node.outputs()
.iter()
.any(|batch| *batch.change() == ChangeState::Complete(Change::Remove(NodeId(0))))
.any(|batch| *batch.change() == ChangeState::Complete(Change::NodeChange(NodeChange::Remove(NodeId(0)))))
}
fn has_add(node: &TestNode<UsizeDhb>) -> bool {
node.outputs().iter().any(|batch| match *batch.change() {
ChangeState::Complete(Change::Add(ref id, _)) => *id == NodeId(0),
ChangeState::Complete(Change::NodeChange(NodeChange::Add(ref id, _))) => *id == NodeId(0),
_ => false,
})
}
@ -97,7 +97,7 @@ where
.netinfo()
.secret_key()
.public_key();
network.input_all(Input::Change(Change::Add(NodeId(0), pk)));
network.input_all(Input::Change(Change::NodeChange(NodeChange::Add(NodeId(0), pk))));
input_add = true;
}
}

View File

@ -10,7 +10,7 @@ pub mod net;
use std::{collections, time};
use hbbft::dynamic_honey_badger::{Change, ChangeState, DynamicHoneyBadger, Input};
use hbbft::dynamic_honey_badger::{Change, NodeChange, ChangeState, DynamicHoneyBadger, Input};
use hbbft::DistAlgorithm;
use net::proptest::{gen_seed, NetworkDimension, TestRng, TestRngSeed};
use net::NetBuilder;
@ -137,7 +137,7 @@ fn do_drop_and_readd(cfg: TestConfig) {
}
// Afterwards, remove a specific node from the dynamic honey badger network.
net.broadcast_input(&Input::Change(Change::Remove(pivot_node_id)))
net.broadcast_input(&Input::Change(Change::NodeChange(NodeChange::Remove(pivot_node_id))))
.expect("broadcasting failed");
// We are tracking (correct) nodes' state through the process by ticking them off individually.
@ -156,7 +156,7 @@ fn do_drop_and_readd(cfg: TestConfig) {
for change in step.output.iter().map(|output| output.change()) {
match change {
ChangeState::Complete(Change::Remove(pivot_node_id)) => {
ChangeState::Complete(Change::NodeChange(NodeChange::Remove(pivot_node_id))) => {
println!("Node {:?} done removing.", node_id);
// Removal complete, tally:
awaiting_removal.remove(&node_id);
@ -169,11 +169,11 @@ fn do_drop_and_readd(cfg: TestConfig) {
.public_key();
let _ = net[node_id]
.algorithm_mut()
.handle_input(Input::Change(Change::Add(*pivot_node_id, pk)))
.handle_input(Input::Change(Change::NodeChange(NodeChange::Add(*pivot_node_id, pk))))
.expect("failed to send `Add` input");
}
ChangeState::Complete(Change::Add(pivot_node_id, _)) => {
ChangeState::Complete(Change::NodeChange(NodeChange::Add(pivot_node_id, _))) => {
println!("Node {:?} done adding.", node_id);
// Node added, ensure it has been removed first.
if awaiting_removal.contains(&node_id) {

View File

@ -22,7 +22,7 @@ use itertools::Itertools;
use rand::{Isaac64Rng, Rng};
use hbbft::dynamic_honey_badger::DynamicHoneyBadger;
use hbbft::queueing_honey_badger::{Batch, Change, ChangeState, Input, QueueingHoneyBadger, Step};
use hbbft::queueing_honey_badger::{Batch, Change, NodeChange, ChangeState, Input, QueueingHoneyBadger, Step};
use hbbft::NetworkInfo;
use network::{Adversary, MessageScheduler, NodeId, SilentAdversary, TestNetwork, TestNode};
@ -35,7 +35,7 @@ where
A: Adversary<QHB>,
{
// The second half of the transactions will be input only after a node has been removed.
network.input_all(Input::Change(Change::Remove(NodeId(0))));
network.input_all(Input::Change(Change::NodeChange(NodeChange::Remove(NodeId(0)))));
for tx in 0..(num_txs / 2) {
network.input_all(Input::User(tx));
}
@ -43,12 +43,12 @@ where
fn has_remove(node: &TestNode<QHB>) -> bool {
node.outputs()
.iter()
.any(|batch| *batch.change() == ChangeState::Complete(Change::Remove(NodeId(0))))
.any(|batch| *batch.change() == ChangeState::Complete(Change::NodeChange(NodeChange::Remove(NodeId(0)))))
}
fn has_add(node: &TestNode<QHB>) -> bool {
node.outputs().iter().any(|batch| match *batch.change() {
ChangeState::Complete(Change::Add(ref id, _)) => *id == NodeId(0),
ChangeState::Complete(Change::NodeChange(NodeChange::Add(ref id, _))) => *id == NodeId(0),
_ => false,
})
}
@ -79,7 +79,7 @@ where
.netinfo()
.secret_key()
.public_key();
network.input_all(Input::Change(Change::Add(NodeId(0), pk)));
network.input_all(Input::Change(Change::NodeChange(NodeChange::Add(NodeId(0), pk))));
input_add = true;
}
}