mirror of https://github.com/poanetwork/hbbft.git
Merge pull request #179 from poanetwork/afck-qhb-single
Change the QHB criterion for moving on to the next epoch.
This commit is contained in:
commit
071bfed00b
|
@ -307,14 +307,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles a queued message in one of the nodes with the earliest timestamp.
|
/// Handles a queued message in one of the nodes with the earliest timestamp, if any. Returns
|
||||||
pub fn step(&mut self) -> NodeUid {
|
/// the recipient's ID.
|
||||||
|
pub fn step(&mut self) -> Option<NodeUid> {
|
||||||
let min_time = self
|
let min_time = self
|
||||||
.nodes
|
.nodes
|
||||||
.values()
|
.values()
|
||||||
.filter_map(TestNode::next_event_time)
|
.filter_map(TestNode::next_event_time)
|
||||||
.min()
|
.min()?;
|
||||||
.expect("no more messages in queue");
|
|
||||||
let min_ids: Vec<NodeUid> = self
|
let min_ids: Vec<NodeUid> = self
|
||||||
.nodes
|
.nodes
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -328,7 +328,7 @@ where
|
||||||
node.out_queue.drain(..).collect()
|
node.out_queue.drain(..).collect()
|
||||||
};
|
};
|
||||||
self.dispatch_messages(msgs);
|
self.dispatch_messages(msgs);
|
||||||
next_id
|
Some(next_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of messages that have been handled so far.
|
/// Returns the number of messages that have been handled so far.
|
||||||
|
@ -371,7 +371,7 @@ impl EpochInfo {
|
||||||
.minmax()
|
.minmax()
|
||||||
.into_option()
|
.into_option()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let txs = batch.len();
|
let txs = batch.iter().unique().count();
|
||||||
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(),
|
||||||
|
@ -386,27 +386,14 @@ impl EpochInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Proposes `num_txs` values and expects nodes to output and order them.
|
/// Proposes `num_txs` values and expects nodes to output and order them.
|
||||||
fn simulate_honey_badger(
|
fn simulate_honey_badger(mut network: TestNetwork<QueueingHoneyBadger<Transaction, NodeUid>>) {
|
||||||
mut network: TestNetwork<QueueingHoneyBadger<Transaction, NodeUid>>,
|
|
||||||
num_txs: usize,
|
|
||||||
) {
|
|
||||||
// Returns `true` if the node has not output all transactions yet.
|
|
||||||
// If it has, and has advanced another epoch, it clears all messages for later epochs.
|
|
||||||
let node_busy = |node: &mut TestNode<QueueingHoneyBadger<Transaction, NodeUid>>| {
|
|
||||||
node.outputs
|
|
||||||
.iter()
|
|
||||||
.map(|&(_, ref batch)| batch.len())
|
|
||||||
.sum::<usize>() < num_txs
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle messages until all nodes have output all transactions.
|
// Handle messages until all nodes have output all transactions.
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
"Epoch Min/Max Time Txs Msgs/Node Size/Node".bold()
|
"Epoch Min/Max Time Txs Msgs/Node Size/Node".bold()
|
||||||
);
|
);
|
||||||
let mut epochs = Vec::new();
|
let mut epochs = Vec::new();
|
||||||
while network.nodes.values_mut().any(node_busy) {
|
while let Some(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 {
|
||||||
let epoch = batch.epoch() as usize;
|
let epoch = batch.epoch() as usize;
|
||||||
if epochs.len() <= epoch {
|
if epochs.len() <= epoch {
|
||||||
|
@ -444,7 +431,9 @@ fn main() {
|
||||||
);
|
);
|
||||||
println!();
|
println!();
|
||||||
let num_good_nodes = args.flag_n - args.flag_f;
|
let num_good_nodes = args.flag_n - args.flag_f;
|
||||||
let txs = (0..args.flag_txs).map(|_| Transaction::new(args.flag_tx_size));
|
let txs: Vec<_> = (0..args.flag_txs)
|
||||||
|
.map(|_| Transaction::new(args.flag_tx_size))
|
||||||
|
.collect();
|
||||||
let new_honey_badger = |netinfo: NetworkInfo<NodeUid>| {
|
let new_honey_badger = |netinfo: NetworkInfo<NodeUid>| {
|
||||||
let dyn_hb = DynamicHoneyBadger::builder().build(netinfo);
|
let dyn_hb = DynamicHoneyBadger::builder().build(netinfo);
|
||||||
QueueingHoneyBadger::builder(dyn_hb)
|
QueueingHoneyBadger::builder(dyn_hb)
|
||||||
|
@ -458,5 +447,5 @@ fn main() {
|
||||||
cpu_factor: (10_000f32 / args.flag_cpu) as u32,
|
cpu_factor: (10_000f32 / args.flag_cpu) as u32,
|
||||||
};
|
};
|
||||||
let network = TestNetwork::new(num_good_nodes, args.flag_f, new_honey_badger, hw_quality);
|
let network = TestNetwork::new(num_good_nodes, args.flag_f, new_honey_badger, hw_quality);
|
||||||
simulate_honey_badger(network, args.flag_txs);
|
simulate_honey_badger(network);
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,6 +164,11 @@ impl<NodeUid: Clone + Debug + Ord + Rand> CommonSubset<NodeUid> {
|
||||||
self.process_broadcast(&uid, |bc| bc.input(value))
|
self.process_broadcast(&uid, |bc| bc.input(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number of validators from which we have already received a proposal.
|
||||||
|
pub(crate) fn received_proposals(&self) -> usize {
|
||||||
|
self.broadcast_results.len()
|
||||||
|
}
|
||||||
|
|
||||||
/// Receives a broadcast message from a remote node `sender_id` concerning a
|
/// Receives a broadcast message from a remote node `sender_id` concerning a
|
||||||
/// value proposed by the node `proposer_id`.
|
/// value proposed by the node `proposer_id`.
|
||||||
fn handle_broadcast(
|
fn handle_broadcast(
|
||||||
|
|
|
@ -61,7 +61,7 @@ where
|
||||||
vote_counter: VoteCounter::new(arc_netinfo, 0),
|
vote_counter: VoteCounter::new(arc_netinfo, 0),
|
||||||
key_gen_msg_buffer: Vec::new(),
|
key_gen_msg_buffer: Vec::new(),
|
||||||
honey_badger,
|
honey_badger,
|
||||||
key_gen: None,
|
key_gen_state: None,
|
||||||
incoming_queue: Vec::new(),
|
incoming_queue: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ where
|
||||||
vote_counter: VoteCounter::new(arc_netinfo, join_plan.epoch),
|
vote_counter: VoteCounter::new(arc_netinfo, join_plan.epoch),
|
||||||
key_gen_msg_buffer: Vec::new(),
|
key_gen_msg_buffer: Vec::new(),
|
||||||
honey_badger,
|
honey_badger,
|
||||||
key_gen: None,
|
key_gen_state: None,
|
||||||
incoming_queue: Vec::new(),
|
incoming_queue: Vec::new(),
|
||||||
};
|
};
|
||||||
let step = match join_plan.change {
|
let step = match join_plan.change {
|
||||||
|
|
|
@ -13,8 +13,8 @@
|
||||||
//! `Change` input variant, which contains either a vote `Add(node_id, public_key)`, to add an
|
//! `Change` input variant, which contains either a vote `Add(node_id, public_key)`, to add an
|
||||||
//! existing observer to the set of validators, or `Remove(node_id)` to remove it. Each
|
//! existing observer to the set of validators, or `Remove(node_id)` to remove it. Each
|
||||||
//! validator can have at most one active vote, and casting another vote revokes the previous one.
|
//! validator can have at most one active vote, and casting another vote revokes the previous one.
|
||||||
//! Once a simple majority of validators has the same active vote, a reconfiguration process
|
//! Once _f + 1_ validators have the same active vote, a reconfiguration process begins: They
|
||||||
//! begins: They create new cryptographic key shares for the new group of validators.
|
//! create new cryptographic key shares for the new group of validators.
|
||||||
//!
|
//!
|
||||||
//! The state of that process after each epoch is communicated via the `change` field in `Batch`.
|
//! The state of that process after each epoch is communicated via the `change` field in `Batch`.
|
||||||
//! When this contains an `InProgress(..)` value, key generation begins. The joining validator (in
|
//! When this contains an `InProgress(..)` value, key generation begins. The joining validator (in
|
||||||
|
@ -50,10 +50,10 @@
|
||||||
//! contributions in its own batch. The other transactions are processed: votes are counted and key
|
//! contributions in its own batch. The other transactions are processed: votes are counted and key
|
||||||
//! generation messages are passed into a `SyncKeyGen` instance.
|
//! generation messages are passed into a `SyncKeyGen` instance.
|
||||||
//!
|
//!
|
||||||
//! Whenever a change receives a majority of votes, the votes are reset and key generation for that
|
//! Whenever a change receives _f + 1_ votes, the votes are reset and key generation for that
|
||||||
//! change begins. If key generation completes successfully, the Honey Badger instance is dropped,
|
//! change begins. If key generation completes successfully, the Honey Badger instance is dropped,
|
||||||
//! and replaced by a new one with the new set of participants. If a different change gains a
|
//! and replaced by a new one with the new set of participants. If a different change wins a
|
||||||
//! majority before that happens, key generation resets again, and is attempted for the new change.
|
//! vote before that happens, key generation resets again, and is attempted for the new change.
|
||||||
|
|
||||||
use rand::Rand;
|
use rand::Rand;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
@ -67,7 +67,7 @@ use crypto::{PublicKey, PublicKeySet, Signature};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use self::votes::{SignedVote, VoteCounter};
|
use self::votes::{SignedVote, VoteCounter};
|
||||||
use fault_log::{FaultKind, FaultLog};
|
use fault_log::{Fault, FaultKind, FaultLog};
|
||||||
use honey_badger::{self, HoneyBadger, Message as HbMessage};
|
use honey_badger::{self, HoneyBadger, Message as HbMessage};
|
||||||
use messaging::{self, DistAlgorithm, NetworkInfo, Target};
|
use messaging::{self, DistAlgorithm, NetworkInfo, Target};
|
||||||
use sync_key_gen::{Ack, Part, PartOutcome, SyncKeyGen};
|
use sync_key_gen::{Ack, Part, PartOutcome, SyncKeyGen};
|
||||||
|
@ -108,7 +108,7 @@ pub struct DynamicHoneyBadger<C, NodeUid: Rand> {
|
||||||
/// The `HoneyBadger` instance with the current set of nodes.
|
/// The `HoneyBadger` instance with the current set of nodes.
|
||||||
honey_badger: HoneyBadger<InternalContrib<C, NodeUid>, NodeUid>,
|
honey_badger: HoneyBadger<InternalContrib<C, NodeUid>, NodeUid>,
|
||||||
/// The current key generation process, and the change it applies to.
|
/// The current key generation process, and the change it applies to.
|
||||||
key_gen: Option<(SyncKeyGen<NodeUid>, Change<NodeUid>)>,
|
key_gen_state: Option<KeyGenState<NodeUid>>,
|
||||||
/// A queue for messages from future epochs that cannot be handled yet.
|
/// A queue for messages from future epochs that cannot be handled yet.
|
||||||
incoming_queue: Vec<(NodeUid, Message<NodeUid>)>,
|
incoming_queue: Vec<(NodeUid, Message<NodeUid>)>,
|
||||||
}
|
}
|
||||||
|
@ -154,10 +154,9 @@ where
|
||||||
Message::HoneyBadger(_, hb_msg) => {
|
Message::HoneyBadger(_, hb_msg) => {
|
||||||
self.handle_honey_badger_message(sender_id, hb_msg)
|
self.handle_honey_badger_message(sender_id, hb_msg)
|
||||||
}
|
}
|
||||||
Message::KeyGen(_, kg_msg, sig) => {
|
Message::KeyGen(_, kg_msg, sig) => self
|
||||||
self.handle_key_gen_message(sender_id, kg_msg, *sig)?;
|
.handle_key_gen_message(sender_id, kg_msg, *sig)
|
||||||
Ok(Step::default())
|
.map(FaultLog::into),
|
||||||
}
|
|
||||||
Message::SignedVote(signed_vote) => self
|
Message::SignedVote(signed_vote) => self
|
||||||
.vote_counter
|
.vote_counter
|
||||||
.add_pending_vote(sender_id, signed_vote)
|
.add_pending_vote(sender_id, signed_vote)
|
||||||
|
@ -218,6 +217,34 @@ where
|
||||||
&self.netinfo
|
&self.netinfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if we should make our contribution for the next epoch, even if we don't have
|
||||||
|
/// content ourselves, to avoid stalling the network.
|
||||||
|
///
|
||||||
|
/// By proposing only if this returns `true`, you can prevent an adversary from making the
|
||||||
|
/// network output empty baches indefinitely, but it also means that the network won't advance
|
||||||
|
/// if fewer than _f + 1_ nodes have pending contributions.
|
||||||
|
pub fn should_propose(&self) -> bool {
|
||||||
|
if self.has_input() {
|
||||||
|
return false; // We have already proposed.
|
||||||
|
}
|
||||||
|
if self.honey_badger.received_proposals() > self.netinfo.num_faulty() {
|
||||||
|
return true; // At least one correct node wants to move on to the next epoch.
|
||||||
|
}
|
||||||
|
let is_our_vote = |signed_vote: &SignedVote<_>| signed_vote.voter() == self.our_id();
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
/// Handles a message for the `HoneyBadger` instance.
|
/// Handles a message for the `HoneyBadger` instance.
|
||||||
fn handle_honey_badger_message(
|
fn handle_honey_badger_message(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -243,11 +270,41 @@ where
|
||||||
sender_id: &NodeUid,
|
sender_id: &NodeUid,
|
||||||
kg_msg: KeyGenMessage,
|
kg_msg: KeyGenMessage,
|
||||||
sig: Signature,
|
sig: Signature,
|
||||||
) -> Result<()> {
|
) -> Result<FaultLog<NodeUid>> {
|
||||||
self.verify_signature(sender_id, &sig, &kg_msg)?;
|
if !self.verify_signature(sender_id, &sig, &kg_msg)? {
|
||||||
|
info!("Invalid signature from {:?} for: {:?}.", sender_id, kg_msg);
|
||||||
|
let fault_kind = FaultKind::InvalidKeyGenMessageSignature;
|
||||||
|
return Ok(Fault::new(sender_id.clone(), fault_kind).into());
|
||||||
|
}
|
||||||
|
let kgs = match self.key_gen_state {
|
||||||
|
Some(ref mut kgs) => kgs,
|
||||||
|
None => {
|
||||||
|
info!(
|
||||||
|
"Unexpected key gen message from {:?}: {:?}.",
|
||||||
|
sender_id, kg_msg
|
||||||
|
);
|
||||||
|
return Ok(Fault::new(sender_id.clone(), FaultKind::UnexpectedKeyGenMessage).into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the joining node is correct, it will send at most (N + 1)² + 1 key generation
|
||||||
|
// messages.
|
||||||
|
if Some(sender_id) == kgs.change.candidate() {
|
||||||
|
let n = self.netinfo.num_nodes() + 1;
|
||||||
|
if kgs.candidate_msg_count > n * n {
|
||||||
|
info!(
|
||||||
|
"Too many key gen messages from candidate {:?}: {:?}.",
|
||||||
|
sender_id, kg_msg
|
||||||
|
);
|
||||||
|
let fault_kind = FaultKind::TooManyCandidateKeyGenMessages;
|
||||||
|
return Ok(Fault::new(sender_id.clone(), fault_kind).into());
|
||||||
|
}
|
||||||
|
kgs.candidate_msg_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
let tx = SignedKeyGenMsg(self.start_epoch, sender_id.clone(), kg_msg, sig);
|
let tx = SignedKeyGenMsg(self.start_epoch, sender_id.clone(), kg_msg, sig);
|
||||||
self.key_gen_msg_buffer.push(tx);
|
self.key_gen_msg_buffer.push(tx);
|
||||||
Ok(())
|
Ok(FaultLog::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes all pending batches output by Honey Badger.
|
/// Processes all pending batches output by Honey Badger.
|
||||||
|
@ -272,7 +329,7 @@ where
|
||||||
} = int_contrib;
|
} = int_contrib;
|
||||||
step.fault_log
|
step.fault_log
|
||||||
.extend(self.vote_counter.add_committed_votes(&id, votes)?);
|
.extend(self.vote_counter.add_committed_votes(&id, votes)?);
|
||||||
batch.contributions.insert(id, contrib);
|
batch.contributions.insert(id.clone(), contrib);
|
||||||
self.key_gen_msg_buffer
|
self.key_gen_msg_buffer
|
||||||
.retain(|skgm| !key_gen_messages.contains(skgm));
|
.retain(|skgm| !key_gen_messages.contains(skgm));
|
||||||
for SignedKeyGenMsg(epoch, s_id, kg_msg, sig) in key_gen_messages {
|
for SignedKeyGenMsg(epoch, s_id, kg_msg, sig) in key_gen_messages {
|
||||||
|
@ -281,9 +338,12 @@ where
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if !self.verify_signature(&s_id, &sig, &kg_msg)? {
|
if !self.verify_signature(&s_id, &sig, &kg_msg)? {
|
||||||
info!("Invalid signature from {:?} for: {:?}.", s_id, kg_msg);
|
info!(
|
||||||
|
"Invalid signature in {:?}'s batch from {:?} for: {:?}.",
|
||||||
|
id, s_id, kg_msg
|
||||||
|
);
|
||||||
let fault_kind = FaultKind::InvalidKeyGenMessageSignature;
|
let fault_kind = FaultKind::InvalidKeyGenMessageSignature;
|
||||||
step.fault_log.append(s_id.clone(), fault_kind);
|
step.fault_log.append(id.clone(), fault_kind);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
step.extend(match kg_msg {
|
step.extend(match kg_msg {
|
||||||
|
@ -293,14 +353,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((key_gen, change)) = self.take_ready_key_gen() {
|
if let Some(kgs) = self.take_ready_key_gen() {
|
||||||
// If DKG completed, apply the change, restart Honey Badger, and inform the user.
|
// If DKG completed, apply the change, restart Honey Badger, and inform the user.
|
||||||
debug!("{:?} DKG for {:?} complete!", self.our_id(), change);
|
debug!("{:?} DKG for {:?} complete!", self.our_id(), kgs.change);
|
||||||
self.netinfo = key_gen.into_network_info();
|
self.netinfo = kgs.key_gen.into_network_info();
|
||||||
self.restart_honey_badger(batch.epoch + 1);
|
self.restart_honey_badger(batch.epoch + 1);
|
||||||
batch.set_change(ChangeState::Complete(change), &self.netinfo);
|
batch.set_change(ChangeState::Complete(kgs.change), &self.netinfo);
|
||||||
} else if let Some(change) = self.vote_counter.compute_majority().cloned() {
|
} else if let Some(change) = self.vote_counter.compute_winner().cloned() {
|
||||||
// If there is a majority, restart DKG. Inform the user about the current change.
|
// If there is a new change, restart DKG. Inform the user about the current change.
|
||||||
step.extend(self.update_key_gen(batch.epoch + 1, &change)?);
|
step.extend(self.update_key_gen(batch.epoch + 1, &change)?);
|
||||||
batch.set_change(ChangeState::InProgress(change), &self.netinfo);
|
batch.set_change(ChangeState::InProgress(change), &self.netinfo);
|
||||||
}
|
}
|
||||||
|
@ -316,10 +376,10 @@ where
|
||||||
Ok(step)
|
Ok(step)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If the majority of votes has changed, restarts Key Generation for the set of nodes implied
|
/// If the winner of the vote has changed, restarts Key Generation for the set of nodes implied
|
||||||
/// by the current change.
|
/// by the current change.
|
||||||
fn update_key_gen(&mut self, epoch: u64, change: &Change<NodeUid>) -> Result<Step<C, NodeUid>> {
|
fn update_key_gen(&mut self, epoch: u64, change: &Change<NodeUid>) -> Result<Step<C, NodeUid>> {
|
||||||
if self.key_gen.as_ref().map(|&(_, ref ch)| ch) == Some(change) {
|
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.
|
return Ok(Step::default()); // The change is the same as before. Continue DKG as is.
|
||||||
}
|
}
|
||||||
debug!("{:?} Restarting DKG for {:?}.", self.our_id(), change);
|
debug!("{:?} Restarting DKG for {:?}.", self.our_id(), change);
|
||||||
|
@ -338,7 +398,7 @@ where
|
||||||
let sk = self.netinfo.secret_key().clone();
|
let sk = self.netinfo.secret_key().clone();
|
||||||
let our_uid = self.our_id().clone();
|
let our_uid = self.our_id().clone();
|
||||||
let (key_gen, part) = SyncKeyGen::new(our_uid, sk, pub_keys, threshold);
|
let (key_gen, part) = SyncKeyGen::new(our_uid, sk, pub_keys, threshold);
|
||||||
self.key_gen = Some((key_gen, change.clone()));
|
self.key_gen_state = Some(KeyGenState::new(key_gen, change.clone()));
|
||||||
if let Some(part) = part {
|
if let Some(part) = part {
|
||||||
self.send_transaction(KeyGenMessage::Part(part))
|
self.send_transaction(KeyGenMessage::Part(part))
|
||||||
} else {
|
} else {
|
||||||
|
@ -360,10 +420,8 @@ where
|
||||||
|
|
||||||
/// Handles a `Part` message that was output by Honey Badger.
|
/// Handles a `Part` message that was output by Honey Badger.
|
||||||
fn handle_part(&mut self, sender_id: &NodeUid, part: Part) -> Result<Step<C, NodeUid>> {
|
fn handle_part(&mut self, sender_id: &NodeUid, part: Part) -> Result<Step<C, NodeUid>> {
|
||||||
let handle = |&mut (ref mut key_gen, _): &mut (SyncKeyGen<NodeUid>, _)| {
|
let handle = |kgs: &mut KeyGenState<NodeUid>| kgs.key_gen.handle_part(&sender_id, part);
|
||||||
key_gen.handle_part(&sender_id, part)
|
match self.key_gen_state.as_mut().and_then(handle) {
|
||||||
};
|
|
||||||
match self.key_gen.as_mut().and_then(handle) {
|
|
||||||
Some(PartOutcome::Valid(ack)) => self.send_transaction(KeyGenMessage::Ack(ack)),
|
Some(PartOutcome::Valid(ack)) => self.send_transaction(KeyGenMessage::Ack(ack)),
|
||||||
Some(PartOutcome::Invalid(fault_log)) => Ok(fault_log.into()),
|
Some(PartOutcome::Invalid(fault_log)) => Ok(fault_log.into()),
|
||||||
None => Ok(Step::default()),
|
None => Ok(Step::default()),
|
||||||
|
@ -372,8 +430,8 @@ where
|
||||||
|
|
||||||
/// Handles an `Ack` message that was output by Honey Badger.
|
/// Handles an `Ack` message that was output by Honey Badger.
|
||||||
fn handle_ack(&mut self, sender_id: &NodeUid, ack: Ack) -> Result<FaultLog<NodeUid>> {
|
fn handle_ack(&mut self, sender_id: &NodeUid, ack: Ack) -> Result<FaultLog<NodeUid>> {
|
||||||
if let Some(&mut (ref mut key_gen, _)) = self.key_gen.as_mut() {
|
if let Some(kgs) = self.key_gen_state.as_mut() {
|
||||||
Ok(key_gen.handle_ack(&sender_id, ack))
|
Ok(kgs.key_gen.handle_ack(sender_id, ack))
|
||||||
} else {
|
} else {
|
||||||
Ok(FaultLog::new())
|
Ok(FaultLog::new())
|
||||||
}
|
}
|
||||||
|
@ -394,18 +452,18 @@ where
|
||||||
Ok(Target::All.message(msg).into())
|
Ok(Target::All.message(msg).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If the current Key Generation process is ready, returns the `SyncKeyGen`.
|
/// If the current Key Generation process is ready, returns the `KeyGenState`.
|
||||||
///
|
///
|
||||||
/// We require the minimum number of completed proposals (`SyncKeyGen::is_ready`) and if a new
|
/// We require the minimum number of completed proposals (`SyncKeyGen::is_ready`) and if a new
|
||||||
/// node is joining, we require in addition that the new node's proposal is complete. That way
|
/// node is joining, we require in addition that the new node's proposal is complete. That way
|
||||||
/// the new node knows that it's key is secret, without having to trust any number of nodes.
|
/// the new node knows that it's key is secret, without having to trust any number of nodes.
|
||||||
fn take_ready_key_gen(&mut self) -> Option<(SyncKeyGen<NodeUid>, Change<NodeUid>)> {
|
fn take_ready_key_gen(&mut self) -> Option<KeyGenState<NodeUid>> {
|
||||||
let is_ready = |&(ref key_gen, ref change): &(SyncKeyGen<_>, Change<_>)| {
|
if self
|
||||||
let candidate_ready = |id: &NodeUid| key_gen.is_node_ready(id);
|
.key_gen_state
|
||||||
key_gen.is_ready() && change.candidate().map_or(true, candidate_ready)
|
.as_ref()
|
||||||
};
|
.map_or(false, KeyGenState::is_ready)
|
||||||
if self.key_gen.as_ref().map_or(false, is_ready) {
|
{
|
||||||
self.key_gen.take()
|
self.key_gen_state.take()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -413,6 +471,8 @@ where
|
||||||
|
|
||||||
/// Returns `true` if the signature of `kg_msg` by the node with the specified ID is valid.
|
/// 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.
|
/// Returns an error if the payload fails to serialize.
|
||||||
|
///
|
||||||
|
/// This accepts signatures from both validators and the currently joining candidate, if any.
|
||||||
fn verify_signature(
|
fn verify_signature(
|
||||||
&self,
|
&self,
|
||||||
node_id: &NodeUid,
|
node_id: &NodeUid,
|
||||||
|
@ -421,15 +481,12 @@ where
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
let ser =
|
let ser =
|
||||||
bincode::serialize(kg_msg).map_err(|err| ErrorKind::VerifySignatureBincode(*err))?;
|
bincode::serialize(kg_msg).map_err(|err| ErrorKind::VerifySignatureBincode(*err))?;
|
||||||
let pk_opt = (self.netinfo.public_key(node_id)).or_else(|| {
|
let get_candidate_key = || {
|
||||||
self.key_gen
|
self.key_gen_state
|
||||||
.iter()
|
.as_ref()
|
||||||
.filter_map(|&(_, ref change): &(_, Change<_>)| match *change {
|
.and_then(|kgs| kgs.candidate_key(node_id))
|
||||||
Change::Add(ref id, ref pk) if id == node_id => Some(pk),
|
};
|
||||||
Change::Add(_, _) | Change::Remove(_) => None,
|
let pk_opt = self.netinfo.public_key(node_id).or_else(get_candidate_key);
|
||||||
})
|
|
||||||
.next()
|
|
||||||
});
|
|
||||||
Ok(pk_opt.map_or(false, |pk| pk.verify(&sig, ser)))
|
Ok(pk_opt.map_or(false, |pk| pk.verify(&sig, ser)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -504,3 +561,40 @@ pub struct JoinPlan<NodeUid: Ord> {
|
||||||
/// The public keys of the nodes taking part in key generation.
|
/// The public keys of the nodes taking part in key generation.
|
||||||
pub_keys: BTreeMap<NodeUid, PublicKey>,
|
pub_keys: BTreeMap<NodeUid, PublicKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The ongoing key generation, together with information about the validator change.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct KeyGenState<NodeUid> {
|
||||||
|
/// The key generation instance.
|
||||||
|
key_gen: SyncKeyGen<NodeUid>,
|
||||||
|
/// The change for which key generation is performed.
|
||||||
|
change: Change<NodeUid>,
|
||||||
|
/// The number of key generation messages received from the candidate. At most _N² + 1_ are
|
||||||
|
/// accepted.
|
||||||
|
candidate_msg_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<NodeUid: Ord + Clone + Debug> KeyGenState<NodeUid> {
|
||||||
|
fn new(key_gen: SyncKeyGen<NodeUid>, change: Change<NodeUid>) -> Self {
|
||||||
|
KeyGenState {
|
||||||
|
key_gen,
|
||||||
|
change,
|
||||||
|
candidate_msg_count: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the candidate's, if any, as well as enough validators' key generation
|
||||||
|
/// parts have been completed.
|
||||||
|
fn is_ready(&self) -> bool {
|
||||||
|
let candidate_ready = |id: &NodeUid| self.key_gen.is_node_ready(id);
|
||||||
|
self.key_gen.is_ready() && self.change.candidate().map_or(true, candidate_ready)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the node `node_id` is the currently joining candidate, returns its public key.
|
||||||
|
fn candidate_key(&self, node_id: &NodeUid) -> Option<&PublicKey> {
|
||||||
|
match self.change {
|
||||||
|
Change::Add(ref id, ref pk) if id == node_id => Some(pk),
|
||||||
|
Change::Add(_, _) | Change::Remove(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use messaging::NetworkInfo;
|
||||||
|
|
||||||
/// A buffer and counter collecting pending and committed votes for validator set changes.
|
/// A buffer and counter collecting pending and committed votes for validator set changes.
|
||||||
///
|
///
|
||||||
/// This is reset whenever the set of validators changes or a change reaches a majority. We call
|
/// This is reset whenever the set of validators changes or a change reaches _f + 1_ votes. We call
|
||||||
/// the epochs since the last reset the current _era_.
|
/// the epochs since the last reset the current _era_.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct VoteCounter<NodeUid> {
|
pub struct VoteCounter<NodeUid> {
|
||||||
|
@ -134,14 +134,14 @@ where
|
||||||
Ok(FaultLog::new())
|
Ok(FaultLog::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the change that has a majority of votes, if any.
|
/// Returns the change that has at least _f + 1_ votes, if any.
|
||||||
pub fn compute_majority(&self) -> Option<&Change<NodeUid>> {
|
pub fn compute_winner(&self) -> Option<&Change<NodeUid>> {
|
||||||
let mut vote_counts: HashMap<&Change<NodeUid>, usize> = HashMap::new();
|
let mut vote_counts: HashMap<&Change<NodeUid>, usize> = HashMap::new();
|
||||||
for vote in self.committed.values() {
|
for vote in self.committed.values() {
|
||||||
let change = &vote.change;
|
let change = &vote.change;
|
||||||
let entry = vote_counts.entry(change).or_insert(0);
|
let entry = vote_counts.entry(change).or_insert(0);
|
||||||
*entry += 1;
|
*entry += 1;
|
||||||
if *entry * 2 > self.netinfo.num_nodes() {
|
if *entry > self.netinfo.num_faulty() {
|
||||||
return Some(change);
|
return Some(change);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,6 +180,10 @@ impl<NodeUid> SignedVote<NodeUid> {
|
||||||
pub fn era(&self) -> u64 {
|
pub fn era(&self) -> u64 {
|
||||||
self.vote.era
|
self.vote.era
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn voter(&self) -> &NodeUid {
|
||||||
|
&self.voter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -270,14 +274,14 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_committed_votes() {
|
fn test_committed_votes() {
|
||||||
let node_num = 4;
|
let node_num = 4; // At most one faulty node.
|
||||||
let era = 5;
|
let era = 5;
|
||||||
// Create the counter instances and the matrix of signed votes.
|
// Create the counter instances and the matrix of signed votes.
|
||||||
let (mut counters, sv) = setup(node_num, era);
|
let (mut counters, sv) = setup(node_num, era);
|
||||||
// We will only use counter number 0.
|
// We will only use counter number 0.
|
||||||
let ct = &mut counters[0];
|
let ct = &mut counters[0];
|
||||||
|
|
||||||
let mut vote_batch = vec![sv[1][1].clone(), sv[2][1].clone()];
|
let mut vote_batch = vec![sv[1][1].clone()];
|
||||||
// Include a vote with a wrong signature.
|
// Include a vote with a wrong signature.
|
||||||
vote_batch.push(SignedVote {
|
vote_batch.push(SignedVote {
|
||||||
sig: sv[2][1].sig.clone(),
|
sig: sv[2][1].sig.clone(),
|
||||||
|
@ -288,13 +292,13 @@ mod tests {
|
||||||
.expect("add committed");
|
.expect("add committed");
|
||||||
let expected_faults = FaultLog::init(1, FaultKind::InvalidCommittedVote);
|
let expected_faults = FaultLog::init(1, FaultKind::InvalidCommittedVote);
|
||||||
assert_eq!(faults, expected_faults);
|
assert_eq!(faults, expected_faults);
|
||||||
assert_eq!(ct.compute_majority(), None);
|
assert_eq!(ct.compute_winner(), None);
|
||||||
|
|
||||||
// Adding the third vote for `Remove(1)` should return the change: It has the majority.
|
// Adding the second vote for `Remove(1)` should return the change: It has f + 1 votes.
|
||||||
let faults = ct
|
let faults = ct
|
||||||
.add_committed_vote(&1, sv[3][1].clone())
|
.add_committed_vote(&1, sv[2][1].clone())
|
||||||
.expect("add committed");
|
.expect("add committed");
|
||||||
assert!(faults.is_empty());
|
assert!(faults.is_empty());
|
||||||
assert_eq!(ct.compute_majority(), Some(&Change::Remove(1)));
|
assert_eq!(ct.compute_winner(), Some(&Change::Remove(1)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,11 @@ pub enum FaultKind {
|
||||||
/// `DynamicHoneyBadger` received a key generation message with an invalid
|
/// `DynamicHoneyBadger` received a key generation message with an invalid
|
||||||
/// signature.
|
/// signature.
|
||||||
InvalidKeyGenMessageSignature,
|
InvalidKeyGenMessageSignature,
|
||||||
|
/// `DynamicHoneyBadger` received a key generation message when there was no key generation in
|
||||||
|
/// progress.
|
||||||
|
UnexpectedKeyGenMessage,
|
||||||
|
/// `DynamicHoneyBadger` received more key generation messages from the candidate than expected.
|
||||||
|
TooManyCandidateKeyGenMessages,
|
||||||
/// `DynamicHoneyBadger` received a message (Accept, Propose, or Change)
|
/// `DynamicHoneyBadger` received a message (Accept, Propose, or Change)
|
||||||
/// with an invalid signature.
|
/// with an invalid signature.
|
||||||
IncorrectPayloadSignature,
|
IncorrectPayloadSignature,
|
||||||
|
|
|
@ -132,6 +132,14 @@ where
|
||||||
!self.netinfo.is_validator() || self.has_input
|
!self.netinfo.is_validator() || self.has_input
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number of validators from which we have already received a proposal for the
|
||||||
|
/// current epoch.
|
||||||
|
pub(crate) fn received_proposals(&self) -> usize {
|
||||||
|
self.common_subsets
|
||||||
|
.get(&self.epoch)
|
||||||
|
.map_or(0, CommonSubset::received_proposals)
|
||||||
|
}
|
||||||
|
|
||||||
/// Handles a message for the given epoch.
|
/// Handles a message for the given epoch.
|
||||||
fn handle_message_content(
|
fn handle_message_content(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -4,7 +4,9 @@
|
||||||
//! an epoch is output, it will automatically select a list of pending transactions and propose it
|
//! an epoch is output, it will automatically select a list of pending transactions and propose it
|
||||||
//! for the next one. The user can continuously add more pending transactions to the queue.
|
//! for the next one. The user can continuously add more pending transactions to the queue.
|
||||||
//!
|
//!
|
||||||
//! **Note**: `QueueingHoneyBadger` currently requires at least two validators.
|
//! If there are no pending transactions, no validators in the process of being added or
|
||||||
|
//! removed and not enough other nodes have proposed yet, no automatic proposal will be made: The
|
||||||
|
//! network then waits until at least _f + 1_ have any content for the next epoch.
|
||||||
//!
|
//!
|
||||||
//! ## How it works
|
//! ## How it works
|
||||||
//!
|
//!
|
||||||
|
@ -187,17 +189,19 @@ where
|
||||||
fn input(&mut self, input: Self::Input) -> Result<Step<Tx, NodeUid>> {
|
fn input(&mut self, input: Self::Input) -> Result<Step<Tx, NodeUid>> {
|
||||||
// User transactions are forwarded to `HoneyBadger` right away. Internal messages are
|
// User transactions are forwarded to `HoneyBadger` right away. Internal messages are
|
||||||
// in addition signed and broadcast.
|
// in addition signed and broadcast.
|
||||||
match input {
|
let mut step = match input {
|
||||||
Input::User(tx) => {
|
Input::User(tx) => {
|
||||||
self.queue.0.push_back(tx);
|
self.queue.0.push_back(tx);
|
||||||
Ok(Step::default())
|
Step::default()
|
||||||
}
|
}
|
||||||
Input::Change(change) => Ok(self
|
Input::Change(change) => self
|
||||||
.dyn_hb
|
.dyn_hb
|
||||||
.input(Input::Change(change))
|
.input(Input::Change(change))
|
||||||
.map_err(ErrorKind::Input)?
|
.map_err(ErrorKind::Input)?
|
||||||
.convert()),
|
.convert(),
|
||||||
}
|
};
|
||||||
|
step.extend(self.propose()?);
|
||||||
|
Ok(step)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_message(
|
fn handle_message(
|
||||||
|
@ -244,12 +248,21 @@ where
|
||||||
&self.dyn_hb
|
&self.dyn_hb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if we are ready to propose our contribution for the next epoch, i.e. if the
|
||||||
|
/// previous epoch has completed and we have either pending transactions or we are required to
|
||||||
|
/// make a proposal to avoid stalling the network.
|
||||||
|
fn can_propose(&self) -> bool {
|
||||||
|
if self.dyn_hb.has_input() {
|
||||||
|
return false; // Previous epoch is still in progress.
|
||||||
|
}
|
||||||
|
!self.queue.0.is_empty() || self.dyn_hb.should_propose()
|
||||||
|
}
|
||||||
|
|
||||||
/// Initiates the next epoch by proposing a batch from the queue.
|
/// Initiates the next epoch by proposing a batch from the queue.
|
||||||
fn propose(&mut self) -> Result<Step<Tx, NodeUid>> {
|
fn propose(&mut self) -> Result<Step<Tx, NodeUid>> {
|
||||||
let amount = cmp::max(1, self.batch_size / self.dyn_hb.netinfo().num_nodes());
|
|
||||||
// TODO: This will loop indefinitely if we are the only validator.
|
|
||||||
let mut step = Step::default();
|
let mut step = Step::default();
|
||||||
while !self.dyn_hb.has_input() {
|
while self.can_propose() {
|
||||||
|
let amount = cmp::max(1, self.batch_size / self.dyn_hb.netinfo().num_nodes());
|
||||||
let proposal = self.queue.choose(amount, self.batch_size);
|
let proposal = self.queue.choose(amount, self.batch_size);
|
||||||
step.extend(
|
step.extend(
|
||||||
self.dyn_hb
|
self.dyn_hb
|
||||||
|
|
|
@ -70,12 +70,6 @@ where
|
||||||
|
|
||||||
// Handle messages in random order until all nodes have output all transactions.
|
// Handle messages in random order until all nodes have output all transactions.
|
||||||
while network.nodes.values().any(node_busy) {
|
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(Batch::epoch) {
|
|
||||||
node.queue.retain(|(_, ref msg)| msg.epoch() < last);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If a node is expecting input, take it from the queue. Otherwise handle a message.
|
// If a node is expecting input, take it from the queue. Otherwise handle a message.
|
||||||
let input_ids: Vec<_> = network
|
let input_ids: Vec<_> = network
|
||||||
.nodes
|
.nodes
|
||||||
|
|
|
@ -134,14 +134,7 @@ where
|
||||||
// Returns `true` if the node has not output all transactions yet.
|
// Returns `true` if the node has not output all transactions yet.
|
||||||
// If it has, and has advanced another epoch, it clears all messages for later epochs.
|
// If it has, and has advanced another epoch, it clears all messages for later epochs.
|
||||||
let node_busy = |node: &mut TestNode<UsizeHoneyBadger>| {
|
let node_busy = |node: &mut TestNode<UsizeHoneyBadger>| {
|
||||||
if node.outputs().iter().flat_map(Batch::iter).unique().count() < num_txs {
|
node.outputs().iter().flat_map(Batch::iter).unique().count() < num_txs
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if node.outputs().last().unwrap().is_empty() {
|
|
||||||
let last = node.outputs().last().unwrap().epoch;
|
|
||||||
node.queue.retain(|(_, ref msg)| msg.epoch() < last);
|
|
||||||
}
|
|
||||||
false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
|
@ -63,10 +63,6 @@ fn test_queueing_honey_badger<A>(
|
||||||
if node.outputs().iter().flat_map(Batch::iter).unique().count() < num_txs {
|
if node.outputs().iter().flat_map(Batch::iter).unique().count() < num_txs {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if node.outputs().last().unwrap().is_empty() {
|
|
||||||
let last = node.outputs().last().unwrap().epoch();
|
|
||||||
node.queue.retain(|(_, ref msg)| msg.epoch() < last);
|
|
||||||
}
|
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue