diff --git a/src/bitfield.rs b/src/bitfield.rs index 47fd559d..7e90b881 100644 --- a/src/bitfield.rs +++ b/src/bitfield.rs @@ -14,85 +14,66 @@ // You should have received a copy of the GNU General Public License // along with finality-grandpa. If not, see . -//! Bitfield for handling equivocations. +//! Bitfields and tools for handling equivocations. //! -//! This is primarily a bitfield for tracking equivocating validators. +//! This is primarily a bitfield for tracking equivocating voters. //! It is necessary because there is a need to track vote-weight of equivocation //! on the vote-graph but to avoid double-counting. //! -//! Bitfields are either blank (in the general case) or live, in the case of -//! equivocations, with two bits per equivocator. The first is for equivocations -//! in prevote messages and the second for those in precommits. +//! We count equivocating voters as voting for everything. This makes any +//! further equivocations redundant with the first. //! -//! Each live bitfield carries a reference to a shared object that -//! provides lookups from bit indices to validator weight. Bitfields can be -//! merged, and queried for total weight in commits and precommits. +//! Bitfields are either blank or live, with two bits per equivocator. +//! The first is for equivocations in prevote messages and the second +//! for those in precommits. +//! +//! Bitfields on regular vote-nodes will tend to be live, but the equivocating +//! bitfield will be mostly empty. use std::fmt; use std::sync::Arc; -use std::sync::atomic::{AtomicUsize, Ordering}; use parking_lot::RwLock; -// global used to ensure that conflicting shared objects are not used. -static SHARED_IDX: AtomicUsize = AtomicUsize::new(0); +use crate::VoterInfo; /// Errors that can occur when using the equivocation weighting tools. #[derive(Clone, Debug, PartialEq, Eq)] pub enum Error { - /// Too many equivocating validators registered. (shared data IDX, n_validators). - TooManyEquivocators(usize, usize), - /// Mismatch in shared data IDX when merging bitfields. - ContextMismatch(usize, usize), + /// Attempted to index bitfield past its length. + IndexOutOfBounds(usize, usize), + /// Mismatch in bitfield length when merging bitfields. + LengthMismatch(usize, usize), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Error::TooManyEquivocators(ref idx, ref n) - => write!(f, "Registered too many equivocators for shared data with ID {}. Maximum specified was {}", idx, n), - Error::ContextMismatch(ref idx1, ref idx2) - => write!(f, "Attempted to merge bitfields with different contexts: {} vs {}", idx1, idx2), + Error::IndexOutOfBounds(ref idx, ref n) + => write!(f, "Attempted to set voter {}. Maximum specified was {}", idx, n), + Error::LengthMismatch(ref idx1, ref idx2) + => write!(f, "Attempted to merge bitfields with different lengths: {} vs {}", idx1, idx2), } } } impl ::std::error::Error for Error {} -/// Bitfield for equivocating validators. -/// -/// See module docs for more details. -#[derive(Debug)] -pub enum Bitfield { +/// Bitfield for tracking voters who have equivocated. +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum Bitfield { /// Blank bitfield, Blank, /// Live bitfield, - Live(LiveBitfield), + Live(LiveBitfield), } -impl Default for Bitfield { +impl Default for Bitfield { fn default() -> Self { Bitfield::Blank } } -impl Clone for Bitfield { - fn clone(&self) -> Self { - match *self { - Bitfield::Blank => Bitfield::Blank, - Bitfield::Live(ref l) => Bitfield::Live(l.clone()), - } - } -} - -impl Bitfield { - /// Find total equivocating weight (prevote, precommit). - pub fn total_weight(&self) -> (u64, u64) { - match *self { - Bitfield::Blank => (0, 0), - Bitfield::Live(ref live) => live.total_weight(), - } - } - +impl Bitfield { /// Combine two bitfields. Fails if they have conflicting shared data /// (i.e. they come from different contexts). pub fn merge(&self, other: &Self) -> Result { @@ -101,287 +82,257 @@ impl Bitfield { (&Bitfield::Live(ref live), &Bitfield::Blank) | (&Bitfield::Blank, &Bitfield::Live(ref live)) => Ok(Bitfield::Live(live.clone())), (&Bitfield::Live(ref a), &Bitfield::Live(ref b)) => { - if a.shared.idx != b.shared.idx { - // we can't merge two bitfields from different contexts. - Err(Error::ContextMismatch(a.shared.idx, b.shared.idx)) + if a.bits.len() != b.bits.len() { + // we can't merge two bitfields with different lengths. + Err(Error::LengthMismatch(a.bits.len(), b.bits.len())) } else { let bits = a.bits.iter().zip(&b.bits).map(|(a, b)| a | b).collect(); - Ok(Bitfield::Live(LiveBitfield { bits, shared: a.shared.clone() })) + Ok(Bitfield::Live(LiveBitfield { bits })) } } } } /// Find overlap weight (prevote, precommit) between this bitfield and another. - pub fn overlap(&self, other: &Self) -> Result<(u64, u64), Error> { + pub fn overlap(&self, other: &Self) -> Result { match (self, other) { (&Bitfield::Live(ref a), &Bitfield::Live(ref b)) => { - if a.shared.idx != b.shared.idx { - // we can't merge two bitfields from different contexts. - Err(Error::ContextMismatch(a.shared.idx, b.shared.idx)) + if a.bits.len() != b.bits.len() { + // we can't find overlap of two bitfields with different lengths. + Err(Error::LengthMismatch(a.bits.len(), b.bits.len())) } else { - Ok(total_weight( - a.bits.iter().zip(&b.bits).map(|(a, b)| a & b), - a.shared.validators.read().as_slice(), - )) + Ok(Bitfield::Live(LiveBitfield { + bits: a.bits.iter().zip(&b.bits).map(|(a, b)| a & b).collect(), + })) } } - _ => Ok((0, 0)) + _ => Ok(Bitfield::Blank) } } -} - -/// Live bitfield instance. -#[derive(Debug)] -pub struct LiveBitfield { - bits: Vec, - shared: Shared, -} -impl Clone for LiveBitfield { - fn clone(&self) -> Self { - LiveBitfield { - bits: self.bits.clone(), - shared: self.shared.clone(), + /// Find total equivocating weight (prevote, precommit). + /// Provide a function for looking up voter weight. + pub fn total_weight u64>(&self, lookup: F) -> (u64, u64) { + match *self { + Bitfield::Blank => (0, 0), + Bitfield::Live(ref live) => total_weight(live.bits.iter().cloned(), lookup), } } -} -impl LiveBitfield { - /// Create a new live bitfield. - pub fn new(shared: Shared) -> Self { - LiveBitfield { bits: shared.blank_bitfield(), shared } - } + /// Set a bit in the bitfield. + fn set_bit(&mut self, bit: usize, n_voters: usize) -> Result<(), Error> { + let mut live = match ::std::mem::replace(self, Bitfield::Blank) { + Bitfield::Blank => LiveBitfield::with_voters(n_voters), + Bitfield::Live(live) => live, + }; - /// Note a validator's equivocation in prevote. - /// Fails if more equivocators than the number of validators have - /// been registered. - pub fn equivocated_prevote(&mut self, id: Id, weight: u64) -> Result<(), Error> { - let val_off = self.shared.get_or_register_equivocator(id, weight)?; - self.set_bit(val_off * 2); + live.set_bit(bit, n_voters)?; + *self = Bitfield::Live(live); Ok(()) } +} - /// Note a validator's equivocation in precommit. - /// Fails if more equivocators than the number of validators have - /// been registered. - pub fn equivocated_precommit(&mut self, id: Id, weight: u64) -> Result<(), Error> { - let val_off = self.shared.get_or_register_equivocator(id, weight)?; - self.set_bit(val_off * 2 + 1); - Ok(()) +/// Live bitfield instance. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LiveBitfield { + bits: Vec, +} + +impl LiveBitfield { + fn with_voters(n_voters: usize) -> Self { + let n_bits = n_voters * 2; + let n_words = (n_bits + 63) / 64; + + LiveBitfield { bits: vec![0; n_words] } } - fn set_bit(&mut self, bit_idx: usize) { + fn set_bit(&mut self, bit_idx: usize, n_voters: usize) -> Result<(), Error> { let word_off = bit_idx / 64; let bit_off = bit_idx % 64; // If this isn't `Some`, something has gone really wrong. if let Some(word) = self.bits.get_mut(word_off) { // set bit starting from left. - *word |= 1 << (63 - bit_off) + *word |= 1 << (63 - bit_off); + Ok(()) } else { - warn!(target: "afg", "Could not set bit {}. Bitfield was meant to have 2 bits for each of {} validators.", - bit_idx, self.shared.n_validators); + Err(Error::IndexOutOfBounds(bit_idx / 2, n_voters)) } } - - // find total weight of this bitfield (prevote, precommit). - fn total_weight(&self) -> (u64, u64) { - total_weight(self.bits.iter().cloned(), self.shared.validators.read().as_slice()) - } } // find total weight of the given iterable of bits. assumes that there are enough -// validators in the given context to correspond to all bits. -fn total_weight(iterable: Iter, validators: &[ValidatorEntry]) -> (u64, u64) - where Iter: IntoIterator +// voters in the given context to correspond to all bits. +fn total_weight(iterable: Iter, lookup: Lookup) -> (u64, u64) where + Iter: IntoIterator, + Lookup: Fn(usize) -> u64, { - struct State { - word_idx: usize, - prevote: u64, - precommit: u64, - }; - - let state = State { - word_idx: 0, - prevote: 0, - precommit: 0, - }; - - let state = iterable.into_iter().fold(state, |mut state, mut word| { - for i in 0..32 { - if word == 0 { break } - - // prevote bit is set - if word & (1 << 63) == (1 << 63) { - state.prevote += validators[state.word_idx * 32 + i].weight; - } + struct State { + val_idx: usize, + prevote: u64, + precommit: u64, + }; + + let state = State { + val_idx: 0, + prevote: 0, + precommit: 0, + }; + + let state = iterable.into_iter().fold(state, |mut state, mut word| { + for i in 0..32 { + if word == 0 { break } - // precommit bit is set - if word & (1 << 62) == (1 << 62) { - state.precommit += validators[state.word_idx * 32 + i].weight; - } + // prevote bit is set + if word & (1 << 63) == (1 << 63) { + state.prevote += lookup(state.val_idx + i); + } - word <<= 2; + // precommit bit is set + if word & (1 << 62) == (1 << 62) { + state.precommit += lookup(state.val_idx + i); } - state.word_idx += 1; - state - }); + word <<= 2; + } - (state.prevote, state.precommit) - } + state.val_idx += 32; + state + }); + + (state.prevote, state.precommit) +} /// Shared data among all live bitfield instances. #[derive(Debug)] -pub struct Shared { - idx: usize, - n_validators: usize, - validators: Arc>>> +pub struct Shared { + n_voters: usize, + equivocators: Arc>, } -impl Clone for Shared { +impl Clone for Shared { fn clone(&self) -> Self { Shared { - idx: self.idx, - n_validators: self.n_validators, - validators: self.validators.clone(), + n_voters: self.n_voters, + equivocators: self.equivocators.clone(), } } } -impl Shared { - /// Create new shared equivocation detection data. Provide the number - /// of validators. - pub fn new(n_validators: usize) -> Self { - let idx = SHARED_IDX.fetch_add(1, Ordering::SeqCst); +impl Shared { + /// Create new shared equivocation detection data. Provide the number of voters. + pub fn new(n_voters: usize) -> Self { Shared { - idx, - n_validators, - validators: Arc::new(RwLock::new(Vec::new())), + n_voters, + equivocators: Arc::new(RwLock::new(Bitfield::Blank)), } } - fn blank_bitfield(&self) -> Vec { - let n_bits = self.n_validators * 2; - let n_words = (n_bits + 63) / 64; + /// Construct a new bitfield for a specific voter prevoting. + pub fn prevote_bitfield(&self, info: &VoterInfo) -> Result { + let mut bitfield = LiveBitfield::with_voters(self.n_voters); + bitfield.set_bit(info.canon_idx() * 2, self.n_voters)?; - vec![0; n_words] + Ok(Bitfield::Live(bitfield)) } - fn get_or_register_equivocator(&self, equivocator: Id, weight: u64) -> Result { - { - // linear search is probably fast enough until we have thousands of - // equivocators. finding the bit to set is slow but happens rarely. - let validators = self.validators.read(); - let maybe_found = validators.iter() - .enumerate() - .find(|&(_, ref e)| e.id == equivocator); - - if let Some((idx, _)) = maybe_found { - return Ok(idx); - } - } + /// Construct a new bitfield for a specific voter prevoting. + pub fn precommit_bitfield(&self, info: &VoterInfo) -> Result { + let mut bitfield = LiveBitfield::with_voters(self.n_voters); + bitfield.set_bit(info.canon_idx() * 2 + 1, self.n_voters)?; - let mut validators = self.validators.write(); - if validators.len() == self.n_validators { - return Err(Error::TooManyEquivocators(self.idx, self.n_validators) ) - } - validators.push(ValidatorEntry { id: equivocator, weight }); - Ok(validators.len() - 1) + Ok(Bitfield::Live(bitfield)) } -} -#[derive(Debug)] -struct ValidatorEntry { - id: Id, - weight: u64, + /// Get the equivocators bitfield. + pub fn equivocators(&self) -> &Arc> { + &self.equivocators + } + + /// Note a voter's equivocation in prevote. + pub fn equivocated_prevote(&self, info: &VoterInfo) -> Result<(), Error> { + self.equivocators.write().set_bit(info.canon_idx() * 2, self.n_voters)?; + Ok(()) + } + + /// Note a voter's equivocation in precommit. + pub fn equivocated_precommit(&self, info: &VoterInfo) -> Result<(), Error> { + self.equivocators.write().set_bit(info.canon_idx() * 2 + 1, self.n_voters)?; + Ok(()) + } } #[cfg(test)] mod tests { use super::*; - - #[test] - fn shared_fails_registering_too_many() { - let shared = Shared::new(0); - assert!(shared.get_or_register_equivocator(5, 1000).is_err()); - } - - #[test] - fn shared_register_same_many_times() { - let shared = Shared::new(1); - assert_eq!(shared.get_or_register_equivocator(5, 1000), Ok(0)); - assert_eq!(shared.get_or_register_equivocator(5, 1000), Ok(0)); - } + use crate::VoterSet; #[test] fn merge_live() { - let shared = Shared::new(10); - let mut live_a = LiveBitfield::new(shared.clone()); - let mut live_b = LiveBitfield::new(shared.clone()); - - live_a.equivocated_prevote(1, 5).unwrap(); - live_a.equivocated_precommit(2, 7).unwrap(); + let mut a = Bitfield::Live(LiveBitfield::with_voters(10)); + let mut b = Bitfield::Live(LiveBitfield::with_voters(10)); - live_b.equivocated_prevote(3, 9).unwrap(); - live_b.equivocated_precommit(3, 9).unwrap(); + let v: VoterSet = [ + (1, 5), + (4, 1), + (3, 9), + (5, 7), + (9, 9), + (2, 7), + ].iter().cloned().collect(); - assert_eq!(live_a.total_weight(), (5, 7)); - assert_eq!(live_b.total_weight(), (9, 9)); + a.set_bit(0, 10).unwrap(); // prevote 1 + a.set_bit(11, 10).unwrap(); // precommit 2 - let (a, b) = (Bitfield::Live(live_a), Bitfield::Live(live_b)); + b.set_bit(4, 10).unwrap(); // prevote 3 + b.set_bit(5, 10).unwrap(); // precommit 3 let c = a.merge(&b).unwrap(); - assert_eq!(c.total_weight(), (14, 16)); - } - - #[test] - fn merge_with_different_shared_is_error() { - let shared_a: Shared = Shared::new(1); - let shared_b = Shared::new(1); - - let bitfield_a = Bitfield::Live(LiveBitfield::new(shared_a)); - let bitfield_b = Bitfield::Live(LiveBitfield::new(shared_b)); - - assert!(bitfield_a.merge(&bitfield_b).is_err()); + assert_eq!(c.total_weight(|i| v.weight_by_index(i).unwrap()), (14, 16)); } #[test] fn set_first_and_last_bits() { - let shared = Shared::new(32); - assert_eq!(shared.blank_bitfield().len(), 1); + let v: VoterSet = (0..32).map(|i| (i, (i + 1) as u64)).collect(); - for i in 0..32 { - shared.get_or_register_equivocator(i, i + 1).unwrap(); - } + let mut live_bitfield = Bitfield::Live(LiveBitfield::with_voters(32)); - let mut live_bitfield = LiveBitfield::new(shared); - live_bitfield.equivocated_prevote(0, 1).unwrap(); - live_bitfield.equivocated_precommit(31, 32).unwrap(); + live_bitfield.set_bit(0, 32).unwrap(); + live_bitfield.set_bit(63, 32).unwrap(); - assert_eq!(live_bitfield.total_weight(), (1, 32)); + assert_eq!(live_bitfield.total_weight(|i| v.weight_by_index(i).unwrap()), (1, 32)); } #[test] fn weight_overlap() { - let shared = Shared::new(10); - let mut live_a = LiveBitfield::new(shared.clone()); - let mut live_b = LiveBitfield::new(shared.clone()); + let mut a = Bitfield::Live(LiveBitfield::with_voters(10)); + let mut b = Bitfield::Live(LiveBitfield::with_voters(10)); + + let v: VoterSet = [ + (1, 5), + (4, 1), + (3, 9), + (5, 7), + (9, 9), + (2, 7), + ].iter().cloned().collect(); + + a.set_bit(0, 10).unwrap(); // prevote 1 + a.set_bit(11, 10).unwrap(); // precommit 2 + a.set_bit(4, 10).unwrap(); // prevote 3 - live_a.equivocated_prevote(1, 5).unwrap(); - live_a.equivocated_precommit(2, 7).unwrap(); - live_a.equivocated_prevote(3, 9).unwrap(); + b.set_bit(0, 10).unwrap(); // prevote 1 + b.set_bit(11, 10).unwrap(); // precommit 2 + b.set_bit(5, 10).unwrap(); // precommit 3 - live_b.equivocated_prevote(1, 5).unwrap(); - live_b.equivocated_precommit(2, 7).unwrap(); - live_b.equivocated_precommit(3, 9).unwrap(); + assert_eq!(a.total_weight(|i| v.weight_by_index(i).unwrap()), (14, 7)); + assert_eq!(b.total_weight(|i| v.weight_by_index(i).unwrap()), (5, 16)); - assert_eq!(live_a.total_weight(), (14, 7)); - assert_eq!(live_b.total_weight(), (5, 16)); + let mut c = Bitfield::Live(LiveBitfield::with_voters(10)); - let (a, b) = (Bitfield::Live(live_a), Bitfield::Live(live_b)); + c.set_bit(0, 10).unwrap(); // prevote 1 + c.set_bit(11, 10).unwrap(); // precommit 2 - assert_eq!(a.overlap(&b).unwrap(), (5, 7)); + assert_eq!(a.overlap(&b).unwrap(), c); } } diff --git a/src/lib.rs b/src/lib.rs index 35a90529..6dcbdae3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,7 +59,9 @@ mod bridge_state; #[cfg(test)] mod testing; +use std::collections::HashMap; use std::fmt; +use std::hash::Hash; /// A prevote for a block and its ancestors. #[derive(Debug, Clone, PartialEq, Eq)] @@ -255,8 +257,8 @@ pub struct CompactCommit { pub auth_data: CommitAuthData, } -// Authentication data for a commit, currently a set of precommit signatures but -// in the future could be optimized with BLS signature aggregation. +/// Authentication data for a commit, currently a set of precommit signatures but +/// in the future could be optimized with BLS signature aggregation. pub type CommitAuthData = Vec<(S, Id)>; impl From> for Commit { @@ -283,21 +285,90 @@ impl From> for CompactCommit usize { self.canon_idx } + + /// Get the weight of the voter. + pub fn weight(&self) -> u64 { self.weight } +} + +/// A voter set, with accompanying indices. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VoterSet { + weights: HashMap, + voters: Vec<(Id, u64)>, + threshold: u64, +} + +impl VoterSet { + /// Get the voter info for a voter. + pub fn info<'a>(&'a self, id: &Id) -> Option<&'a VoterInfo> { + self.weights.get(id) + } + + /// Get the length of the set. + pub fn len(&self) -> usize { self.voters.len() } + + /// Whether the set contains the key. + pub fn contains_key(&self, id: &Id) -> bool { + self.weights.contains_key(id) + } + + /// Get voter info by index. + pub fn weight_by_index<'a>(&'a self, idx: usize) -> Option { + self.voters.get(idx).map(|&(_, weight)| weight) + } + + /// Get the threshold weight. + pub fn threshold(&self) -> u64 { self.threshold } + + /// Get the total weight. + pub fn total_weight(&self) -> u64 { + self.voters.iter().map(|&(_, weight)| weight).sum() + } +} + +impl std::iter::FromIterator<(Id, u64)> for VoterSet { + fn from_iter>(iterable: I) -> Self { + let iter = iterable.into_iter(); + let (lower, _) = iter.size_hint(); + + let mut voters = Vec::with_capacity(lower); + let mut weights = HashMap::with_capacity(lower); + + let mut total_weight = 0; + for (idx, (id, weight)) in iter.enumerate() { + voters.push((id.clone(), weight)); + weights.insert(id, VoterInfo { canon_idx: idx, weight }); + total_weight += weight; + } + + let threshold = threshold(total_weight); + VoterSet { weights, voters, threshold } + } +} + /// Validates a GRANDPA commit message and returns the ghost calculated using /// the precommits in the commit message and using the commit target as a /// base. If no threshold is given it is calculated using `::threshold` and the /// provided voters. pub fn validate_commit>( commit: &Commit, - voters: &::std::collections::HashMap, - threshold: Option, + voters: &VoterSet, chain: &C, ) -> Result, crate::Error> where H: ::std::hash::Hash + Clone + Eq + Ord + ::std::fmt::Debug, N: Copy + BlockNumberOps + ::std::fmt::Debug, I: Clone + ::std::hash::Hash + ::std::cmp::Eq, { - let threshold = threshold.unwrap_or_else(|| crate::threshold(voters.values().sum())); + let threshold = voters.threshold(); // check that all precommits are for blocks higher than the target // commit block, and that they're its descendents @@ -325,7 +396,7 @@ pub fn validate_commit>( // make sure weight of all precommits surpasses threshold // (this is needed to avoid a possible DoS vector) let commit_weight = commit.precommits.iter().fold(0, |total_weight, precommit| { - total_weight + voters.get(&precommit.id).unwrap_or(&0) + total_weight + voters.info(&precommit.id).map(|info| info.weight()).unwrap_or(0) }); if commit_weight < threshold { @@ -335,8 +406,10 @@ pub fn validate_commit>( // add all precommits to an empty vote graph with the commit target as the base let mut vote_graph = vote_graph::VoteGraph::new(commit.target_hash.clone(), commit.target_number.clone()); for SignedPrecommit { precommit, id, .. } in commit.precommits.iter() { - let weight = voters.get(id).expect("previously verified that all ids are voters; qed"); - vote_graph.insert(precommit.target_hash.clone(), precommit.target_number.clone(), *weight, chain)?; + let weight = voters.info(id) + .expect("previously verified that all ids are voters; qed") + .weight(); + vote_graph.insert(precommit.target_hash.clone(), precommit.target_number.clone(), weight, chain)?; } // find ghost using commit target as current best @@ -357,6 +430,22 @@ fn threshold(total_weight: u64) -> u64 { #[cfg(test)] mod tests { + use super::threshold; + + #[test] + fn threshold_is_right() { + assert_eq!(threshold(3), 3); + assert_eq!(threshold(4), 3); + assert_eq!(threshold(5), 4); + assert_eq!(threshold(6), 5); + assert_eq!(threshold(7), 5); + assert_eq!(threshold(10), 7); + assert_eq!(threshold(100), 67); + assert_eq!(threshold(101), 68); + assert_eq!(threshold(102), 69); + assert_eq!(threshold(103), 69); + } + #[cfg(feature = "derive-codec")] #[test] fn codec_was_derived() { diff --git a/src/round.rs b/src/round.rs index 7dfc279f..d1f107e0 100644 --- a/src/round.rs +++ b/src/round.rs @@ -22,44 +22,57 @@ use std::collections::hash_map::{HashMap, Entry}; use std::hash::Hash; use std::ops::AddAssign; -use crate::bitfield::{Bitfield, Shared as BitfieldContext, LiveBitfield}; +use crate::bitfield::{Shared as BitfieldContext, Bitfield}; -use super::{Equivocation, Prevote, Precommit, Chain, BlockNumberOps, threshold}; +use super::{Equivocation, Prevote, Precommit, Chain, BlockNumberOps, VoterSet}; #[derive(Hash, Eq, PartialEq)] struct Address; -#[derive(Debug, Clone)] -struct VoteWeight { +#[derive(Debug, PartialEq, Eq)] +struct TotalWeight { prevote: u64, precommit: u64, - bitfield: Bitfield, } -impl Default for VoteWeight { +#[derive(Debug, Clone)] +struct VoteWeight { + bitfield: Bitfield, +} + +impl Default for VoteWeight { fn default() -> Self { VoteWeight { - prevote: 0, - precommit: 0, bitfield: Bitfield::Blank, } } } -impl AddAssign for VoteWeight { - fn add_assign(&mut self, rhs: VoteWeight) { - self.prevote += rhs.prevote; - self.precommit += rhs.precommit; - - // if any votes are counted in both weights, undo the double-counting. - let (o_v, o_c) = self.bitfield.overlap(&rhs.bitfield) - .expect("vote-weights with different contexts are never compared in this module; qed"); - - self.prevote -= o_v; - self.precommit -= o_c; +impl VoteWeight { + // compute the total weight of all votes on this node. + // equivocators are counted as voting for everything, and must be provided. + fn total_weight( + &self, + equivocators: &Bitfield, + voter_set: &VoterSet, + ) -> TotalWeight { + let with_equivocators = self.bitfield.merge(equivocators) + .expect("this function is never invoked with \ + equivocators of different canonicality; qed"); + + // the unwrap-or is defensive only: there should be registered weights for + // all known indices. + let (prevote, precommit) = with_equivocators + .total_weight(|idx| voter_set.weight_by_index(idx).unwrap_or(0)); + + TotalWeight { prevote, precommit } + } +} +impl AddAssign for VoteWeight { + fn add_assign(&mut self, rhs: VoteWeight) { self.bitfield = self.bitfield.merge(&rhs.bitfield) - .expect("vote-weights with different contexts are never compared in this module; qed"); + .expect("both bitfields set to same length; qed"); } } @@ -67,61 +80,11 @@ impl AddAssign for VoteWeight { enum VoteMultiplicity { // validator voted once. Single(Vote, Signature), - // validator equivocated once. + // validator equivocated at least once. Equivocated((Vote, Signature), (Vote, Signature)), - // validator equivocated many times. - EquivocatedMany(Vec<(Vote, Signature)>), } impl VoteMultiplicity { - fn update_graph( - &self, - weight: u64, - mut import: F, - make_bitfield: G, - ) -> Result<(), crate::Error> where - F: FnMut(&Vote, u64, Bitfield) -> Result<(), crate::Error>, - G: Fn() -> Bitfield, - { - match *self { - VoteMultiplicity::Single(ref v, _) => { - import(v, weight, Bitfield::Blank) - } - VoteMultiplicity::Equivocated((ref v_a, _), (ref v_b, _)) => { - let bitfield = make_bitfield(); - - // import the second vote. some of the weight may have been double-counted. - import(v_b, weight, bitfield.clone())?; - - // re-import the first vote (but with zero weight) to undo double-counting - // and initialize bitfields on non-overlapping sections of the path. - import(v_a, 0, bitfield) - .expect("all vote-nodes present in graph already; no chain lookup necessary; qed"); - - Ok(()) - } - VoteMultiplicity::EquivocatedMany(ref votes) => { - let v = votes.last().expect("many equivocations means there is always a last vote; qed"); - import(&v.0, weight, make_bitfield()) - } - } - } - - fn equivocation(&self) -> Option<(&(Vote, Signature), &(Vote, Signature))> { - match *self { - VoteMultiplicity::Single(_, _) => None, - VoteMultiplicity::Equivocated(ref a, ref b) => Some((a, b)), - VoteMultiplicity::EquivocatedMany(ref v) => { - assert!(v.len() >= 2, "Multi-equivocations variant always has at least two distinct members; qed"); - - match (v.first(), v.last()) { - (Some(ref a), Some(ref b)) => Some((a, b)), - _ => panic!("length checked above; qed"), - } - } - } - } - fn contains(&self, vote: &Vote, signature: &Signature) -> bool { match self { VoteMultiplicity::Single(v, s) => @@ -130,9 +93,6 @@ impl VoteMultiplicity { v1 == vote && s1 == signature || v2 == vote && s2 == signature }, - VoteMultiplicity::EquivocatedMany(v) => { - v.iter().any(|(v, s)| v == vote && s == signature) - }, } } } @@ -151,9 +111,12 @@ impl VoteTracker } // track a vote, returning a value containing the multiplicity of all votes from this ID. - // if the vote is an equivocation, returns a value indicating + // if the vote is the first equivocation, returns a value indicating // it as such (the new vote is always the last in the multiplicity). // + // if the vote is a further equivocation, it is ignored and there is nothing + // to do. + // // since this struct doesn't track the round-number of votes, that must be set // by the caller. fn add_vote(&mut self, id: Id, vote: Vote, signature: Signature, weight: u64) @@ -169,15 +132,11 @@ impl VoteTracker return None; } + // import, but ignore further equivocations. let new_val = match *occupied.get_mut() { VoteMultiplicity::Single(ref v, ref s) => Some(VoteMultiplicity::Equivocated((v.clone(), s.clone()), (vote, signature))), - VoteMultiplicity::Equivocated(ref a, ref b) => - Some(VoteMultiplicity::EquivocatedMany(vec![a.clone(), b.clone(), (vote, signature)])), - VoteMultiplicity::EquivocatedMany(ref mut v) => { - v.push((vote, signature)); - None - } + VoteMultiplicity::Equivocated(_, _) => return None, }; if let Some(new_val) = new_val { @@ -202,10 +161,6 @@ impl VoteTracker votes.push((id.clone(), v1.clone(), s1.clone())); votes.push((id.clone(), v2.clone(), s2.clone())); }, - VoteMultiplicity::EquivocatedMany(vs) => { - votes.extend( - vs.iter().map(|(v, s)| (id.clone(), v.clone(), s.clone()))); - }, } } @@ -244,21 +199,20 @@ pub struct RoundParams { /// The round number for votes. pub round_number: u64, /// Actors and weights in the round. - pub voters: HashMap, + pub voters: VoterSet, /// The base block to build on. pub base: (H, N), } /// Stores data for a round. pub struct Round { - graph: VoteGraph>, // DAG of blocks which have been voted on. + graph: VoteGraph, // DAG of blocks which have been voted on. prevote: VoteTracker, Signature>, // tracks prevotes that have been counted precommit: VoteTracker, Signature>, // tracks precommits round_number: u64, - voters: HashMap, - threshold_weight: u64, + voters: VoterSet, total_weight: u64, - bitfield_context: BitfieldContext, + bitfield_context: BitfieldContext, prevote_ghost: Option<(H, N)>, // current memoized prevote-GHOST block finalized: Option<(H, N)>, // best finalized block in this round. estimate: Option<(H, N)>, // current memoized round-estimate @@ -275,14 +229,12 @@ impl Round where /// Not guaranteed to work correctly unless total_weight more than 3x larger than faulty_weight pub fn new(round_params: RoundParams) -> Self { let (base_hash, base_number) = round_params.base; - let total_weight: u64 = round_params.voters.values().cloned().sum(); - let threshold_weight = threshold(total_weight); + let total_weight = round_params.voters.total_weight(); let n_validators = round_params.voters.len(); Round { round_number: round_params.round_number, - threshold_weight, - total_weight: total_weight, + total_weight, voters: round_params.voters, graph: VoteGraph::new(base_hash, base_number), prevote: VoteTracker::new(), @@ -310,56 +262,59 @@ impl Round where signer: Id, signature: Signature, ) -> Result, Signature>>, crate::Error> { - let weight = match self.voters.get(&signer) { - Some(weight) => *weight, + let info = match self.voters.info(&signer) { + Some(info) => info, None => return Ok(None), }; + let weight = info.weight(); let equivocation = { - let graph = &mut self.graph; - let bitfield_context = &self.bitfield_context; let multiplicity = match self.prevote.add_vote(signer.clone(), vote, signature, weight) { Some(m) => m, _ => return Ok(None), }; let round_number = self.round_number; - multiplicity.update_graph( - weight, - move |vote, weight, bitfield| { + match multiplicity { + VoteMultiplicity::Single(ref vote, _) => { let vote_weight = VoteWeight { - prevote: weight, - precommit: 0, - bitfield, + bitfield: self.bitfield_context.prevote_bitfield(info) + .expect("info is instantiated from same voter set as context; qed"), }; - graph.insert( + self.graph.insert( vote.target_hash.clone(), vote.target_number, vote_weight, - chain - ) - }, - || { - let mut live = LiveBitfield::new(bitfield_context.clone()); - live.equivocated_prevote(signer.clone(), weight) - .expect("no unrecognized voters will be added as equivocators; qed"); - Bitfield::Live(live) + chain, + )?; + + None } - )?; - - multiplicity.equivocation().map(|(first, second)| Equivocation { - round_number, - identity: signer, - first: first.clone(), - second: second.clone(), - }) + VoteMultiplicity::Equivocated(ref first, ref second) => { + // mark the equivocator as such. no need to "undo" the first vote. + self.bitfield_context.equivocated_prevote(info) + .expect("info is instantiated from same voter set as bitfield; qed"); + + Some(Equivocation { + round_number, + identity: signer, + first: first.clone(), + second: second.clone(), + }) + } + } }; // update prevote-GHOST let threshold = self.threshold(); if self.prevote.current_weight >= threshold { - self.prevote_ghost = self.graph.find_ghost(self.prevote_ghost.take(), |v| v.prevote >= threshold); + let equivocators = self.bitfield_context.equivocators().read(); + + self.prevote_ghost = self.graph.find_ghost( + self.prevote_ghost.take(), + |v| v.total_weight(&equivocators, &self.voters).prevote >= threshold, + ); } self.update(); @@ -377,50 +332,48 @@ impl Round where signer: Id, signature: Signature, ) -> Result, Signature>>, crate::Error> { - let weight = match self.voters.get(&signer) { - Some(weight) => *weight, + let info = match self.voters.info(&signer) { + Some(info) => info, None => return Ok(None), }; + let weight = info.weight(); let equivocation = { - let graph = &mut self.graph; - let bitfield_context = &self.bitfield_context; let multiplicity = match self.precommit.add_vote(signer.clone(), vote, signature, weight) { Some(m) => m, _ => return Ok(None), }; let round_number = self.round_number; - multiplicity.update_graph( - weight, - move |vote, weight, bitfield| { + match multiplicity { + VoteMultiplicity::Single(ref vote, _) => { let vote_weight = VoteWeight { - prevote: 0, - precommit: weight, - bitfield, + bitfield: self.bitfield_context.precommit_bitfield(info) + .expect("info is instantiated from same voter set as context; qed"), }; - graph.insert( + self.graph.insert( vote.target_hash.clone(), vote.target_number, vote_weight, - chain - ) - }, - || { - let mut live = LiveBitfield::new(bitfield_context.clone()); - live.equivocated_precommit(signer.clone(), weight) - .expect("no unrecognized voters will be added as equivocators; qed"); - Bitfield::Live(live) + chain, + )?; + + None } - )?; - - multiplicity.equivocation().map(|(first, second)| Equivocation { - round_number, - identity: signer, - first: first.clone(), - second: second.clone(), - }) + VoteMultiplicity::Equivocated(ref first, ref second) => { + // mark the equivocator as such. no need to "undo" the first vote. + self.bitfield_context.equivocated_precommit(info) + .expect("info is instantiated from same voter set as bitfield; qed"); + + Some(Equivocation { + round_number, + identity: signer, + first: first.clone(), + second: second.clone(), + }) + } + } }; self.update(); @@ -443,6 +396,12 @@ impl Round where if self.prevote.current_weight < threshold { return } let remaining_commit_votes = self.total_weight - self.precommit.current_weight; + let equivocators = self.bitfield_context.equivocators().read(); + let equivocators = &*equivocators; + + let voters = &self.voters; + + let (g_hash, g_num) = match self.prevote_ghost.clone() { None => return, Some(x) => x, @@ -451,11 +410,12 @@ impl Round where // anything new finalized? finalized blocks are those which have both // 2/3+ prevote and precommit weight. let threshold = self.threshold(); - if self.precommit.current_weight >= threshold { + let current_precommits = self.precommit.current_weight; + if current_precommits >= threshold { self.finalized = self.graph.find_ancestor( g_hash.clone(), g_num, - |v| v.precommit >= threshold + |v| v.total_weight(&equivocators, voters).precommit >= threshold, ); }; @@ -463,22 +423,41 @@ impl Round where // not straightforward because we have to account for all possible future // equivocations and thus cannot discount weight from validators who // have already voted. - let tolerated_equivocations = self.total_weight - threshold; - let possible_to_precommit = |weight: &VoteWeight<_>| { - // find how many more equivocations we could still get on this - // block. - let (_, precommit_equivocations) = weight.bitfield.total_weight(); - let additional_equivocation_weight = tolerated_equivocations - .saturating_sub(precommit_equivocations); - - // all the votes already applied on this block, - // and assuming all remaining actors commit to this block, - // and assuming all possible equivocations end up on this block. - let full_possible_weight = weight.precommit - .saturating_add(remaining_commit_votes) - .saturating_add(additional_equivocation_weight); - - full_possible_weight >= threshold + let possible_to_precommit = { + let tolerated_equivocations = self.total_weight - threshold; + + // find how many more equivocations we could still get. + // + // it is only important to consider the voters whose votes + // we have already seen, because we are assuming any votes we + // haven't seen will target this block. + let current_equivocations = equivocators + .total_weight(|idx| self.voters.weight_by_index(idx).unwrap_or(0)) + .1; + + let additional_equiv = tolerated_equivocations.saturating_sub(current_equivocations); + + move |weight: &VoteWeight| { + // total precommits for this block, including equivocations. + let precommitted_for = weight.total_weight(&equivocators, voters) + .precommit; + + // equivocations we could still get are out of those who + // have already voted, but not on this block. + let possible_equivocations = std::cmp::min( + current_precommits.saturating_sub(precommitted_for), + additional_equiv, + ); + + // all the votes already applied on this block, + // assuming all remaining actors commit to this block, + // and that we get further equivocations + let full_possible_weight = precommitted_for + .saturating_add(remaining_commit_votes) + .saturating_add(possible_equivocations); + + full_possible_weight >= threshold + } }; // until we have threshold precommits, any new block could get supermajority @@ -538,7 +517,7 @@ impl Round where // Threshold number of weight for supermajority. pub fn threshold(&self) -> u64 { - self.threshold_weight + self.voters.threshold() } /// Return the round base. @@ -547,7 +526,7 @@ impl Round where } /// Return the round voters and weights. - pub fn voters(&self) -> &HashMap { + pub fn voters(&self) -> &VoterSet { &self.voters } @@ -562,7 +541,7 @@ mod tests { use super::*; use crate::testing::{GENESIS_HASH, DummyChain}; - fn voters() -> HashMap<&'static str, u64> { + fn voters() -> VoterSet<&'static str> { [ ("Alice", 4), ("Bob", 7), @@ -573,20 +552,6 @@ mod tests { #[derive(PartialEq, Eq, Hash, Clone, Debug)] struct Signature(&'static str); - #[test] - fn threshold_is_right() { - assert_eq!(threshold(3), 3); - assert_eq!(threshold(4), 3); - assert_eq!(threshold(5), 4); - assert_eq!(threshold(6), 5); - assert_eq!(threshold(7), 5); - assert_eq!(threshold(10), 7); - assert_eq!(threshold(100), 67); - assert_eq!(threshold(101), 68); - assert_eq!(threshold(102), 69); - assert_eq!(threshold(103), 69); - } - #[test] fn estimate_is_valid() { let mut chain = DummyChain::new(); @@ -707,6 +672,7 @@ mod tests { base: ("C", 4), }); + // first prevote by eve assert!(round.import_prevote( &chain, Prevote::new("FC", 10), @@ -717,6 +683,7 @@ mod tests { assert!(round.prevote_ghost.is_none()); + // second prevote by eve: comes with equivocation proof assert!(round.import_prevote( &chain, Prevote::new("ED", 10), @@ -724,12 +691,13 @@ mod tests { Signature("Eve-2"), ).unwrap().is_some()); + // third prevote: returns nothing. assert!(round.import_prevote( &chain, Prevote::new("F", 7), "Eve", // still 3 on F and E Signature("Eve-2"), - ).unwrap().is_some()); + ).unwrap().is_none()); // three eves together would be enough. @@ -744,4 +712,48 @@ mod tests { assert_eq!(round.prevote_ghost, Some(("FA", 8))); } + + #[test] + fn vote_weight_discounts_equivocators() { + let v: VoterSet<_> = [ + (1, 1), + (2, 2), + (3, 3), + (4, 4), + (5, 5), + ].iter().cloned().collect(); + + let ctx = BitfieldContext::new(5); + + let equivocators = { + let equiv_a = ctx.prevote_bitfield(v.info(&1).unwrap()).unwrap(); + let equiv_b = ctx.prevote_bitfield(v.info(&5).unwrap()).unwrap(); + + equiv_a.merge(&equiv_b).unwrap() + }; + + let votes = { + let vote_a = ctx.prevote_bitfield(v.info(&1).unwrap()).unwrap(); + let vote_b = ctx.prevote_bitfield(v.info(&2).unwrap()).unwrap(); + let vote_c = ctx.prevote_bitfield(v.info(&3).unwrap()).unwrap(); + + vote_a.merge(&vote_b).unwrap().merge(&vote_c).unwrap() + }; + + let weight = VoteWeight { bitfield: votes }; + let vote_weight = weight.total_weight(&equivocators, &v); + + // counts the prevotes from 2, 3, and the equivocations from 1, 5 without + // double-counting 1 + assert_eq!(vote_weight, TotalWeight { prevote: 1 + 5 + 2 + 3, precommit: 0 }); + + let votes = weight.bitfield.merge(&ctx.prevote_bitfield(v.info(&5).unwrap()).unwrap()).unwrap(); + + let weight = VoteWeight { bitfield: votes }; + let vote_weight = weight.total_weight(&equivocators, &v); + + + // adding an extra vote by 5 doesn't increase the count. + assert_eq!(vote_weight, TotalWeight { prevote: 1 + 5 + 2 + 3, precommit: 0 }); + } } diff --git a/src/voter.rs b/src/voter.rs index b6a671b2..1828184c 100644 --- a/src/voter.rs +++ b/src/voter.rs @@ -31,7 +31,10 @@ use std::sync::Arc; use parking_lot::Mutex; use crate::round::{Round, State as RoundState}; -use crate::{Chain, Commit, CompactCommit, Equivocation, Message, Prevote, Precommit, SignedMessage, SignedPrecommit, BlockNumberOps, threshold, validate_commit}; +use crate::{ + Chain, Commit, CompactCommit, Equivocation, Message, Prevote, Precommit, SignedMessage, + SignedPrecommit, BlockNumberOps, VoterSet, validate_commit +}; /// Necessary environment for a voter. /// @@ -190,7 +193,7 @@ impl> VotingRound where { fn new( round_number: u64, - voters: HashMap, + voters: VoterSet, base: (H, N), last_round_state: Option>, finalized_sender: UnboundedSender<(H, N, u64, Commit)>, @@ -546,7 +549,6 @@ impl> RoundCommitter where if validate_commit( &commit, voting_round.votes.voters(), - Some(voting_round.votes.threshold()), env, )?.is_none() { return Ok(false); @@ -605,8 +607,7 @@ struct Committer, In, Out> where Out: Sink), SinkError=E::Error>, { env: Arc, - voters: HashMap, - threshold: u64, + voters: VoterSet, rounds: HashMap>, incoming: In, outgoing: Buffered, @@ -621,16 +622,14 @@ impl, In, Out> Committer where { fn new( env: Arc, - voters: HashMap, + voters: VoterSet, incoming: In, outgoing: Out, last_finalized_number: Arc>, ) -> Committer { - let threshold = threshold(voters.values().sum()); Committer { env, voters, - threshold, rounds: HashMap::new(), outgoing: Buffered::new(outgoing), incoming, @@ -662,7 +661,6 @@ impl, In, Out> Committer where if let Some((finalized_hash, finalized_number)) = validate_commit( &commit.clone(), &self.voters, - Some(self.threshold), &*self.env, )? { match highest_incoming_foreign_commit { @@ -741,7 +739,7 @@ pub struct Voter, CommitIn, CommitOut> where CommitOut: Sink), SinkError=E::Error>, { env: Arc, - voters: HashMap, + voters: VoterSet, best_round: VotingRound, past_rounds: FuturesUnordered>, committer: Committer, @@ -772,7 +770,7 @@ impl, CommitIn, CommitOut> Voter, - voters: HashMap, + voters: VoterSet, committer_data: (CommitIn, CommitOut), last_round_number: u64, last_round_state: RoundState, @@ -1116,17 +1114,12 @@ mod tests { use tokio::prelude::FutureExt; use tokio::runtime::current_thread; use crate::testing::{self, GENESIS_HASH, Environment, Id}; - use std::collections::HashMap; use std::time::Duration; #[test] fn talking_to_myself() { let local_id = Id(5); - let voters = { - let mut map = HashMap::new(); - map.insert(local_id, 100); - map - }; + let voters = std::iter::once((local_id, 100)).collect(); let (network, routing_task) = testing::make_network(); let (signal, exit) = ::exit_future::signal(); @@ -1168,13 +1161,7 @@ mod tests { #[test] fn finalizing_at_fault_threshold() { // 10 voters - let voters = { - let mut map = HashMap::new(); - for i in 0..10 { - map.insert(Id(i), 1); - } - map - }; + let voters: VoterSet<_> = (0..10).map(|i| (Id(i), 1)).collect(); let (network, routing_task) = testing::make_network(); let (signal, exit) = ::exit_future::signal(); @@ -1220,11 +1207,7 @@ mod tests { #[test] fn broadcast_commit() { let local_id = Id(5); - let voters = { - let mut map = HashMap::new(); - map.insert(local_id, 100); - map - }; + let voters: VoterSet<_> = std::iter::once((local_id, 100)).collect(); let (network, routing_task) = testing::make_network(); let (commits, _) = network.make_commits_comms(); @@ -1265,12 +1248,10 @@ mod tests { fn broadcast_commit_only_if_newer() { let local_id = Id(5); let test_id = Id(42); - let voters = { - let mut map = HashMap::new(); - map.insert(local_id, 100); - map.insert(test_id, 201); - map - }; + let voters: VoterSet<_> = [ + (local_id, 100), + (test_id, 201), + ].iter().cloned().collect(); let (network, routing_task) = testing::make_network(); let (commits_stream, commits_sink) = network.make_commits_comms(); @@ -1363,12 +1344,10 @@ mod tests { fn import_commit_for_any_round() { let local_id = Id(5); let test_id = Id(42); - let voters = { - let mut map = HashMap::new(); - map.insert(local_id, 100); - map.insert(test_id, 201); - map - }; + let voters: VoterSet<_> = [ + (local_id, 100), + (test_id, 201), + ].iter().cloned().collect(); let (network, routing_task) = testing::make_network(); let (_, commits_sink) = network.make_commits_comms(); @@ -1424,13 +1403,7 @@ mod tests { #[test] fn skips_to_latest_round() { // 3 voters - let voters = { - let mut map = HashMap::new(); - for i in 0..3 { - map.insert(Id(i), 1); - } - map - }; + let voters: VoterSet<_> = (0..3).map(|i| (Id(i), 1)).collect(); let (network, routing_task) = testing::make_network(); let (signal, exit) = ::exit_future::signal();