diff --git a/src/lib.rs b/src/lib.rs index 063299cb..84e89a2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -478,6 +478,71 @@ pub fn process_commit_validation_result( } } +/// Historical votes seen in a round. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "derive-codec", derive(Encode, Decode))] +pub struct HistoricalVotes { + seen: Vec>, + prevote_idx: Option, + precommit_idx: Option, +} + +impl HistoricalVotes { + /// Create a new HistoricalVotes. + pub fn new() -> Self { + HistoricalVotes { + seen: Vec::new(), + prevote_idx: None, + precommit_idx: None, + } + } + + /// Create a new HistoricalVotes initialized from the parameters. + pub fn new_with( + seen: Vec>, + prevote_idx: Option, + precommit_idx: Option + ) -> Self { + HistoricalVotes { + seen, + prevote_idx, + precommit_idx, + } + } + + /// Push a vote into the list. + pub fn push_vote(&mut self, msg: SignedMessage) { + self.seen.push(msg) + } + + /// Return the messages seen so far. + pub fn seen(&self) -> &Vec> { + &self.seen + } + + /// Return the number of messages seen before prevoting. + /// None in case we didn't prevote yet. + pub fn prevote_idx(&self) -> Option { + self.prevote_idx + } + + /// Return the number of messages seen before precommiting. + /// None in case we didn't precommit yet. + pub fn precommit_idx(&self) -> Option { + self.precommit_idx + } + + /// Set the number of messages seen before prevoting. + pub fn set_prevoted_idx(&mut self) { + self.prevote_idx = Some(self.seen.len()) + } + + /// Set the number of messages seen before precommiting. + pub fn set_precommited_idx(&mut self) { + self.precommit_idx = Some(self.seen.len()) + } +} + #[cfg(test)] mod tests { use super::threshold; diff --git a/src/round.rs b/src/round.rs index 16d2e1dd..e5b1bc29 100644 --- a/src/round.rs +++ b/src/round.rs @@ -22,7 +22,7 @@ use crate::bitfield::{Shared as BitfieldContext, Bitfield}; use crate::vote_graph::VoteGraph; use crate::voter_set::VoterSet; -use super::{Equivocation, Prevote, Precommit, Chain, BlockNumberOps}; +use super::{Equivocation, Prevote, Precommit, Chain, BlockNumberOps, HistoricalVotes, Message, SignedMessage}; #[derive(Hash, Eq, PartialEq)] struct Address; @@ -131,6 +131,7 @@ impl VoteTracker Entry::Vacant(vacant) => { self.current_weight += weight; let multiplicity = vacant.insert(VoteMultiplicity::Single(vote, signature)); + return AddVoteResult { multiplicity: Some(multiplicity), duplicated: false, @@ -223,6 +224,7 @@ pub struct Round { graph: VoteGraph, // DAG of blocks which have been voted on. prevote: VoteTracker, Signature>, // tracks prevotes that have been counted precommit: VoteTracker, Signature>, // tracks precommits + historical_votes: HistoricalVotes, round_number: u64, voters: VoterSet, total_weight: u64, @@ -274,6 +276,7 @@ impl Round where graph: VoteGraph::new(base_hash, base_number), prevote: VoteTracker::new(), precommit: VoteTracker::new(), + historical_votes: HistoricalVotes::new(), bitfield_context: BitfieldContext::new(n_validators), prevote_ghost: None, precommit_ghost: None, @@ -309,7 +312,7 @@ impl Round where let weight = info.weight(); let equivocation = { - let multiplicity = match self.prevote.add_vote(signer.clone(), vote, signature, weight) { + let multiplicity = match self.prevote.add_vote(signer.clone(), vote.clone(), signature.clone(), weight) { AddVoteResult { multiplicity: Some(m), .. } => m, AddVoteResult { duplicated, .. } => { import_result.duplicated = duplicated; @@ -319,19 +322,24 @@ impl Round where let round_number = self.round_number; match multiplicity { - VoteMultiplicity::Single(ref vote, _) => { + VoteMultiplicity::Single(single_vote, _) => { let vote_weight = VoteWeight { bitfield: self.bitfield_context.prevote_bitfield(info) .expect("info is instantiated from same voter set as context; qed"), }; self.graph.insert( - vote.target_hash.clone(), - vote.target_number, + single_vote.target_hash.clone(), + single_vote.target_number, vote_weight, chain, )?; + // Push the vote into HistoricalVotes. + let message = Message::Prevote(vote); + let signed_message = SignedMessage { id: signer, signature, message }; + self.historical_votes.push_vote(signed_message); + None } VoteMultiplicity::Equivocated(ref first, ref second) => { @@ -339,6 +347,11 @@ impl Round where self.bitfield_context.equivocated_prevote(info) .expect("info is instantiated from same voter set as bitfield; qed"); + // Push the vote into HistoricalVotes. + let message = Message::Prevote(vote); + let signed_message = SignedMessage { id: signer.clone(), signature, message }; + self.historical_votes.push_vote(signed_message); + Some(Equivocation { round_number, identity: signer, @@ -386,7 +399,7 @@ impl Round where let weight = info.weight(); let equivocation = { - let multiplicity = match self.precommit.add_vote(signer.clone(), vote, signature, weight) { + let multiplicity = match self.precommit.add_vote(signer.clone(), vote.clone(), signature.clone(), weight) { AddVoteResult { multiplicity: Some(m), .. } => m, AddVoteResult { duplicated, .. } => { import_result.duplicated = duplicated; @@ -396,33 +409,42 @@ impl Round where let round_number = self.round_number; match multiplicity { - VoteMultiplicity::Single(ref vote, _) => { + VoteMultiplicity::Single(single_vote, _) => { let vote_weight = VoteWeight { bitfield: self.bitfield_context.precommit_bitfield(info) .expect("info is instantiated from same voter set as context; qed"), }; self.graph.insert( - vote.target_hash.clone(), - vote.target_number, + single_vote.target_hash.clone(), + single_vote.target_number, vote_weight, chain, )?; + let message = Message::Precommit(vote); + let signed_message = SignedMessage { id: signer, signature, message }; + self.historical_votes.push_vote(signed_message); + None - } + }, 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"); + // Push the vote into HistoricalVotes. + let message = Message::Precommit(vote); + let signed_message = SignedMessage { id: signer.clone(), signature, message }; + self.historical_votes.push_vote(signed_message); + Some(Equivocation { round_number, identity: signer, first: first.clone(), second: second.clone(), }) - } + }, } }; @@ -472,7 +494,7 @@ impl Round where type Item = (V, S); fn next(&mut self) -> Option<(V, S)> { - match *self.multiplicity { + match self.multiplicity { VoteMultiplicity::Single(ref v, ref s) => { if self.yielded == 0 { self.yielded += 1; @@ -674,6 +696,35 @@ impl Round where pub fn precommits(&self) -> Vec<(Id, Precommit, Signature)> { self.precommit.votes() } + + /// Return all votes (prevotes and precommits) by importing order. + pub fn historical_votes(&self) -> &HistoricalVotes { + &self.historical_votes + } + + /// Set the number of prevotes and precommits received at the moment of prevoting. + /// It should be called inmediatly after prevoting. + pub fn set_prevoted_index(&mut self) { + self.historical_votes.set_prevoted_idx() + } + + /// Set the number of prevotes and precommits received at the moment of precommiting. + /// It should be called inmediatly after precommiting. + pub fn set_precommited_index(&mut self) { + self.historical_votes.set_precommited_idx() + } + + /// Get the number of prevotes and precommits received at the moment of prevoting. + /// Returns None if the prevote wasn't realized. + pub fn prevoted_index(&self) -> Option { + self.historical_votes.prevote_idx + } + + /// Get the number of prevotes and precommits received at the moment of precommiting. + /// Returns None if the precommit wasn't realized. + pub fn precommited_index(&self) -> Option { + self.historical_votes.precommit_idx + } } #[cfg(test)] @@ -896,4 +947,85 @@ mod tests { // adding an extra vote by 5 doesn't increase the count. assert_eq!(vote_weight, TotalWeight { prevote: 1 + 5 + 2 + 3, precommit: 0 }); } + + #[test] + fn historical_votes_works() { + let mut chain = DummyChain::new(); + chain.push_blocks(GENESIS_HASH, &["A", "B", "C", "D", "E", "F"]); + chain.push_blocks("E", &["EA", "EB", "EC", "ED"]); + chain.push_blocks("F", &["FA", "FB", "FC"]); + + let mut round = Round::new(RoundParams { + round_number: 1, + voters: voters(), + base: ("C", 4), + }); + + round.import_prevote( + &chain, + Prevote::new("FC", 10), + "Alice", + Signature("Alice"), + ).unwrap(); + + round.set_prevoted_index(); + + round.import_prevote( + &chain, + Prevote::new("EA", 7), + "Eve", + Signature("Eve"), + ).unwrap(); + + round.import_precommit( + &chain, + Precommit::new("EA", 7), + "Eve", + Signature("Eve"), + ).unwrap(); + + round.import_prevote( + &chain, + Prevote::new("EC", 10), + "Alice", + Signature("Alice"), + ).unwrap(); + + round.set_precommited_index(); + + assert_eq!(round.historical_votes(), &HistoricalVotes::new_with( + vec![ + SignedMessage { + message: Message::Prevote( + Prevote { target_hash: "FC", target_number: 10 } + ), + signature: Signature("Alice"), + id: "Alice" + }, + SignedMessage { + message: Message::Prevote( + Prevote { target_hash: "EA", target_number: 7 } + ), + signature: Signature("Eve"), + id: "Eve" + }, + SignedMessage { + message: Message::Precommit( + Precommit { target_hash: "EA", target_number: 7 } + ), + signature: Signature("Eve"), + id: "Eve" + }, + SignedMessage { + message: Message::Prevote( + Prevote { target_hash: "EC", target_number: 10 } + ), + signature: Signature("Alice"), + id: "Alice" + }, + ], + Some(1), + Some(4), + )); + } } diff --git a/src/testing.rs b/src/testing.rs index 4d987603..886c1883 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -24,7 +24,7 @@ use tokio::timer::Delay; use parking_lot::Mutex; use futures::prelude::*; use futures::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; -use super::{Chain, Commit, Error, Equivocation, Message, Prevote, Precommit, PrimaryPropose, SignedMessage}; +use super::{Chain, Commit, Error, Equivocation, Message, Prevote, Precommit, PrimaryPropose, SignedMessage, HistoricalVotes}; pub const GENESIS_HASH: &str = "genesis"; const NULL_HASH: &str = "NULL"; @@ -53,8 +53,6 @@ impl DummyChain { } pub fn push_blocks(&mut self, mut parent: &'static str, blocks: &[&'static str]) { - use std::cmp::Ord; - if blocks.is_empty() { return } let base_number = self.inner.get(parent).unwrap().number + 1; @@ -218,7 +216,7 @@ impl crate::voter::Environment<&'static str, u32> for Environment { _round: u64, _state: RoundState<&'static str, u32>, _base: (&'static str, u32), - _votes: Vec>, + _votes: &HistoricalVotes<&'static str, u32, Self::Signature, Self::Id>, ) -> Result<(), Error> { Ok(()) } diff --git a/src/voter/mod.rs b/src/voter/mod.rs index d30c3bb1..66da7523 100644 --- a/src/voter/mod.rs +++ b/src/voter/mod.rs @@ -36,7 +36,7 @@ use std::sync::Arc; use crate::round::State as RoundState; use crate::{ Chain, Commit, CompactCommit, Equivocation, Message, Prevote, Precommit, PrimaryPropose, - SignedMessage, BlockNumberOps, validate_commit, CommitValidationResult + SignedMessage, BlockNumberOps, validate_commit, CommitValidationResult, HistoricalVotes, }; use crate::voter_set::VoterSet; use past_rounds::PastRounds; @@ -101,7 +101,7 @@ pub trait Environment: Chain { round: u64, state: RoundState, base: (H, N), - votes: Vec>, + votes: &HistoricalVotes, ) -> Result<(), Self::Error>; /// Called when a block should be finalized. @@ -662,7 +662,7 @@ impl, GlobalIn, GlobalOut> Voter, GlobalIn, GlobalOut> Voter> VotingRound where self.best_finalized.as_ref() } - /// Return all imported votes for the round (prevotes and precommits). - pub(super) fn votes(&self) -> Vec> { - let prevotes = self.votes.prevotes().into_iter().map(|(id, prevote, signature)| { - SignedMessage { - id, - signature, - message: Message::Prevote(prevote), - } - }); - - let precommits = self.votes.precommits().into_iter().map(|(id, precommit, signature)| { - SignedMessage { - id, - signature, - message: Message::Precommit(precommit), - } - }); - - prevotes.chain(precommits).collect() + /// Return all votes for the round (prevotes and precommits), + /// sorted by imported order and indicating the indices where we voted. + pub(super) fn historical_votes(&self) -> &HistoricalVotes { + self.votes.historical_votes() } fn process_incoming(&mut self) -> Result<(), E::Error> { @@ -370,6 +356,7 @@ impl> VotingRound where if let Some(prevote) = self.construct_prevote(last_round_state)? { debug!(target: "afg", "Casting prevote for round {}", self.votes.number()); self.env.prevoted(self.round_number(), prevote.clone())?; + self.votes.set_prevoted_index(); self.outgoing.push(Message::Prevote(prevote)); } } @@ -422,6 +409,7 @@ impl> VotingRound where debug!(target: "afg", "Casting precommit for round {}", self.votes.number()); let precommit = self.construct_precommit(); self.env.precommitted(self.round_number(), precommit.clone())?; + self.votes.set_precommited_index(); self.outgoing.push(Message::Precommit(precommit)); } self.state = Some(State::Precommitted);