hbbft/src/dynamic_honey_badger/votes.rs

319 lines
11 KiB
Rust

use std::collections::{BTreeMap, HashMap};
use crate::crypto::{SecretKey, Signature};
use bincode;
use serde::{Deserialize, Serialize};
use super::{Change, Error, FaultKind, Result};
use crate::{fault_log, util, NodeIdT, PubKeyMap};
pub type FaultLog<N> = fault_log::FaultLog<N, FaultKind>;
/// 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 _f + 1_ votes. We call
/// the epochs since the last reset the current _era_.
#[derive(Debug)]
pub struct VoteCounter<N: Ord> {
/// This node's ID.
// TODO: Make optional for observers?
our_id: N,
/// This node's secret key,
// TODO: Make optional for observers?
secret_key: SecretKey,
/// The map of public keys.
pub_keys: PubKeyMap<N>,
/// The epoch when voting was reset.
era: u64,
/// Pending node transactions that we will propose in the next epoch.
pending: BTreeMap<N, SignedVote<N>>,
/// Collected votes for adding or removing nodes. Each node has one vote, and casting another
/// vote revokes the previous one.
committed: BTreeMap<N, Vote<N>>,
}
impl<N> VoteCounter<N>
where
N: NodeIdT + Serialize,
{
/// Creates a new `VoteCounter` object with empty buffer and counter.
pub fn new(our_id: N, secret_key: SecretKey, pub_keys: PubKeyMap<N>, era: u64) -> Self {
VoteCounter {
our_id,
secret_key,
pub_keys: pub_keys.clone(),
era,
pending: BTreeMap::new(),
committed: BTreeMap::new(),
}
}
/// Creates a signed vote for the given change, and inserts it into the pending votes buffer.
pub fn sign_vote_for(&mut self, change: Change<N>) -> Result<&SignedVote<N>> {
let voter = self.our_id.clone();
let vote = Vote {
change,
era: self.era,
num: self.pending.get(&voter).map_or(0, |sv| sv.vote.num + 1),
};
let ser_vote = bincode::serialize(&vote).map_err(|err| Error::SerializeVote(*err))?;
let signed_vote = SignedVote {
vote,
voter: voter.clone(),
sig: self.secret_key.sign(ser_vote),
};
self.pending.remove(&voter);
Ok(self.pending.entry(voter).or_insert(signed_vote))
}
/// Inserts a pending vote into the buffer, if it has a higher number than the existing one.
pub fn add_pending_vote(
&mut self,
sender_id: &N,
signed_vote: SignedVote<N>,
) -> Result<FaultLog<N>> {
if signed_vote.vote.era != self.era
|| self
.pending
.get(&signed_vote.voter)
.map_or(false, |sv| sv.vote.num >= signed_vote.vote.num)
{
return Ok(FaultLog::new()); // The vote is obsolete or already exists.
}
if !self.validate(&signed_vote)? {
return Ok(FaultLog::init(
sender_id.clone(),
FaultKind::InvalidVoteSignature,
));
}
self.pending.insert(signed_vote.voter.clone(), signed_vote);
Ok(FaultLog::new())
}
/// Returns an iterator over all pending votes that are newer than their voter's committed
/// vote.
pub fn pending_votes(&self) -> impl Iterator<Item = &SignedVote<N>> {
self.pending.values().filter(move |signed_vote| {
self.committed
.get(&signed_vote.voter)
.map_or(true, |vote| vote.num < signed_vote.vote.num)
})
}
/// Inserts committed votes into the counter, if they have higher numbers than the existing
/// ones.
pub fn add_committed_votes<I>(
&mut self,
proposer_id: &N,
signed_votes: I,
) -> Result<FaultLog<N>>
where
I: IntoIterator<Item = SignedVote<N>>,
{
let mut fault_log = FaultLog::new();
for signed_vote in signed_votes {
fault_log.extend(self.add_committed_vote(proposer_id, signed_vote)?);
}
Ok(fault_log)
}
/// Inserts a committed vote into the counter, if it has a higher number than the existing one.
pub fn add_committed_vote(
&mut self,
proposer_id: &N,
signed_vote: SignedVote<N>,
) -> Result<FaultLog<N>> {
if self
.committed
.get(&signed_vote.voter)
.map_or(false, |vote| vote.num >= signed_vote.vote.num)
{
return Ok(FaultLog::new()); // The vote is obsolete or already exists.
}
if signed_vote.vote.era != self.era || !self.validate(&signed_vote)? {
return Ok(FaultLog::init(
proposer_id.clone(),
FaultKind::InvalidCommittedVote,
));
}
self.committed.insert(signed_vote.voter, signed_vote.vote);
Ok(FaultLog::new())
}
/// Returns the change that has at least _f + 1_ votes, if any.
pub fn compute_winner(&self) -> Option<&Change<N>> {
let mut vote_counts: HashMap<&Change<N>, usize> = HashMap::new();
for vote in self.committed.values() {
let change = &vote.change;
let entry = vote_counts.entry(change).or_insert(0);
*entry += 1;
if *entry > util::max_faulty(self.pub_keys.len()) {
return Some(change);
}
}
None
}
/// Returns `true` if the signature is valid.
fn validate(&self, signed_vote: &SignedVote<N>) -> Result<bool> {
let ser_vote =
bincode::serialize(&signed_vote.vote).map_err(|err| Error::SerializeVote(*err))?;
let pk_opt = self.pub_keys.get(&signed_vote.voter);
Ok(pk_opt.map_or(false, |pk| pk.verify(&signed_vote.sig, ser_vote)))
}
}
/// A vote fore removing or adding a validator.
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Hash, Clone)]
struct Vote<N: Ord> {
/// The change this vote is for.
change: Change<N>,
/// The epoch in which the current era began.
era: u64,
/// The vote number: VoteCounter can be changed by casting another vote with a higher number.
num: u64,
}
/// A signed vote for removing or adding a validator.
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Hash, Clone)]
pub struct SignedVote<N: Ord> {
vote: Vote<N>,
voter: N,
sig: Signature,
}
impl<N: Ord> SignedVote<N> {
pub fn era(&self) -> u64 {
self.vote.era
}
pub fn voter(&self) -> &N {
&self.voter
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use std::iter;
use std::sync::Arc;
use rand::{rngs, Rng};
use super::{Change, FaultKind, SecretKey, SignedVote, VoteCounter};
use crate::{fault_log::FaultLog, to_pub_keys};
/// Returns a vector of `node_num` `VoteCounter`s, and some signed example votes.
///
/// If `signed_votes` is the second entry of the return value, then `signed_votes[i][j]` is the
/// the vote by node `i` for making `j` the only validator. Each node signed this for nodes
/// `0`, `1`, ... in order.
fn setup(node_num: usize, era: u64) -> (Vec<VoteCounter<usize>>, Vec<Vec<SignedVote<usize>>>) {
let mut rng = rngs::OsRng::new().expect("could not initialize OsRng");
// Generate keys for signing and encrypting messages.
let sec_keys: BTreeMap<_, SecretKey> = (0..node_num).map(|id| (id, rng.gen())).collect();
let pub_keys = to_pub_keys(&sec_keys);
// Create a `VoteCounter` instance for each node.
let create_counter = |(id, sk)| VoteCounter::new(id, sk, pub_keys.clone(), era);
let mut counters: Vec<_> = sec_keys.into_iter().map(create_counter).collect();
// Sign a few votes.
let sign_votes = |counter: &mut VoteCounter<usize>| {
(0..node_num)
.map(|j| Change::NodeChange(Arc::new(iter::once((j, pub_keys[&j])).collect())))
.map(|change| counter.sign_vote_for(change).expect("sign vote").clone())
.collect::<Vec<_>>()
};
let signed_votes: Vec<_> = counters.iter_mut().map(sign_votes).collect();
(counters, signed_votes)
}
#[test]
fn test_pending_votes() {
let node_num = 4;
let era = 5;
// Create the counter instances and the matrix of signed votes.
let (mut counters, sv) = setup(node_num, era);
// We will only use counter number 0.
let ct = &mut counters[0];
// Node 0 already contains its own vote for `Remove(3)`. Add two more.
let faults = ct
.add_pending_vote(&1, sv[1][2].clone())
.expect("add pending");
assert!(faults.is_empty());
let faults = ct
.add_pending_vote(&2, sv[2][1].clone())
.expect("add pending");
assert!(faults.is_empty());
// Include a vote with a wrong signature.
let fake_vote = SignedVote {
sig: sv[2][1].sig.clone(),
..sv[3][1].clone()
};
let faults = ct.add_pending_vote(&1, fake_vote).expect("add pending");
let expected_faults = FaultLog::init(1, FaultKind::InvalidVoteSignature);
assert_eq!(faults, expected_faults);
assert_eq!(
ct.pending_votes().collect::<Vec<_>>(),
vec![&sv[0][3], &sv[1][2], &sv[2][1]]
);
// Now add an older vote by node 1 and a newer one by node 2. Only the latter should be
// included.
let faults = ct
.add_pending_vote(&3, sv[1][1].clone())
.expect("add pending");
assert!(faults.is_empty());
let faults = ct
.add_pending_vote(&1, sv[2][2].clone())
.expect("add pending");
assert!(faults.is_empty());
assert_eq!(
ct.pending_votes().collect::<Vec<_>>(),
vec![&sv[0][3], &sv[1][2], &sv[2][2]]
);
// Adding a committed vote removes it from the pending ones, unless it is older.
let vote_batch = vec![sv[1][3].clone(), sv[2][1].clone(), sv[0][3].clone()];
ct.add_committed_votes(&1, vote_batch)
.expect("add committed");
assert_eq!(ct.pending_votes().collect::<Vec<_>>(), vec![&sv[2][2]]);
}
#[test]
fn test_committed_votes() {
let node_num = 4; // At most one faulty node.
let era = 5;
// Create the counter instances and the matrix of signed votes.
let (mut counters, sv) = setup(node_num, era);
// We will only use counter number 0.
let ct = &mut counters[0];
let mut vote_batch = vec![sv[1][1].clone()];
// Include a vote with a wrong signature.
vote_batch.push(SignedVote {
sig: sv[2][1].sig.clone(),
..sv[3][1].clone()
});
let faults = ct
.add_committed_votes(&1, vote_batch)
.expect("add committed");
let expected_faults = FaultLog::init(1, FaultKind::InvalidCommittedVote);
assert_eq!(faults, expected_faults);
assert_eq!(ct.compute_winner(), None);
// Adding the second vote for `Remove(1)` should return the change: It has f + 1 votes.
let faults = ct
.add_committed_vote(&1, sv[2][1].clone())
.expect("add committed");
assert!(faults.is_empty());
match ct.compute_winner() {
Some(Change::NodeChange(pub_keys)) => assert!(pub_keys.keys().eq(iter::once(&1))),
winner => panic!("Unexpected winner: {:?}", winner),
}
}
}