From dd7061e0d72f3d1a91ab09139d6c2e6862e64dd2 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 18 Dec 2017 14:17:22 +0100 Subject: [PATCH] return summary upon statement import --- substrate/candidate-agreement/src/table.rs | 225 +++++++++++++++++---- 1 file changed, 185 insertions(+), 40 deletions(-) diff --git a/substrate/candidate-agreement/src/table.rs b/substrate/candidate-agreement/src/table.rs index 209e43fd47..c072be3ee0 100644 --- a/substrate/candidate-agreement/src/table.rs +++ b/substrate/candidate-agreement/src/table.rs @@ -176,6 +176,21 @@ enum ValidityVote { Invalid(S), } +/// A summary of import of a statement. +#[derive(Clone, PartialEq, Eq)] +pub struct Summary { + /// The digest of the candidate referenced. + pub candidate: D, + /// The group that candidate is in. + pub group_id: G, + /// How many validity votes are currently witnessed. + pub validity_votes: usize, + /// How many availability votes are currently witnessed. + pub availability_votes: usize, + /// Whether this has been signalled bad by at least one participant. + pub signalled_bad: bool, +} + /// Stores votes and data about a candidate. pub struct CandidateData { group_id: C::GroupId, @@ -210,6 +225,16 @@ impl CandidateData { && self.validity_votes.len() >= validity_threshold && self.availability_votes.len() >= availability_threshold } + + fn summary(&self, digest: C::Digest) -> Summary { + Summary { + candidate: digest, + group_id: self.group_id.clone(), + validity_votes: self.validity_votes.len() - self.indicated_bad_by.len(), + availability_votes: self.availability_votes.len(), + signalled_bad: self.indicated_bad(), + } + } } // validator metadata @@ -282,9 +307,11 @@ impl Table { /// /// This can note the origin of the statement to indicate that he has /// seen it already. - pub fn import_statement(&mut self, context: &C, statement: SignedStatement, from: Option) { + pub fn import_statement(&mut self, context: &C, statement: SignedStatement, from: Option) + -> Option> + { let signer = match context.statement_signer(&statement) { - None => return, + None => return None, Some(signer) => signer, }; @@ -295,7 +322,7 @@ impl Table { Statement::Available(ref d) => StatementTrace::Available(signer.clone(), d.clone()), }; - let maybe_misbehavior = match statement.statement { + let (maybe_misbehavior, maybe_summary) = match statement.statement { Statement::Candidate(candidate) => self.import_candidate( context, signer.clone(), @@ -328,14 +355,16 @@ impl Table { self.detected_misbehavior.insert(signer, misbehavior); } else { if let Some(from) = from { - self.note_trace(trace.clone(), from); + self.note_trace_seen(trace.clone(), from); } - self.note_trace(trace, signer); + self.note_trace_seen(trace, signer); } + + maybe_summary } - fn note_trace(&mut self, trace: StatementTrace, known_by: C::ValidatorId) { + fn note_trace_seen(&mut self, trace: StatementTrace, known_by: C::ValidatorId) { self.validator_data.entry(known_by).or_insert_with(|| ValidatorData { proposal: None, known_statements: HashSet::default(), @@ -348,15 +377,18 @@ impl Table { from: C::ValidatorId, candidate: C::Candidate, signature: C::Signature, - ) -> Option> { + ) -> (Option>, Option>) { let group = context.candidate_group(&candidate); if !context.is_member_of(&from, &group) { - return Some(Misbehavior::UnauthorizedStatement(UnauthorizedStatement { - statement: SignedStatement { - signature, - statement: Statement::Candidate(candidate), - }, - })); + return ( + Some(Misbehavior::UnauthorizedStatement(UnauthorizedStatement { + statement: SignedStatement { + signature, + statement: Statement::Candidate(candidate), + }, + })), + None, + ); } // check that validator hasn't already specified another candidate. @@ -375,10 +407,13 @@ impl Table { .candidate .clone(); - return Some(Misbehavior::MultipleCandidates(MultipleCandidates { - first: (old_candidate, old_sig.clone()), - second: (candidate, signature.clone()), - })); + return ( + Some(Misbehavior::MultipleCandidates(MultipleCandidates { + first: (old_candidate, old_sig.clone()), + second: (candidate, signature.clone()), + })), + None, + ); } } else { existing.proposal = Some((digest.clone(), signature.clone())); @@ -415,9 +450,9 @@ impl Table { from: C::ValidatorId, digest: C::Digest, vote: ValidityVote, - ) -> Option> { + ) -> (Option>, Option>) { let votes = match self.candidate_votes.get_mut(&digest) { - None => return None, // TODO: queue up but don't get DoS'ed + None => return (None, None), // TODO: queue up but don't get DoS'ed Some(votes) => votes, }; @@ -430,16 +465,19 @@ impl Table { panic!("implicit issuance vote only cast if the candidate entry already created successfully; qed"), }; - return Some(Misbehavior::UnauthorizedStatement(UnauthorizedStatement { - statement: SignedStatement { - signature: sig, - statement: if valid { - Statement::Valid(digest) - } else { - Statement::Invalid(digest) + return ( + Some(Misbehavior::UnauthorizedStatement(UnauthorizedStatement { + statement: SignedStatement { + signature: sig, + statement: if valid { + Statement::Valid(digest) + } else { + Statement::Invalid(digest) + } } - } - })); + })), + None, + ); } // check for double votes. @@ -459,12 +497,17 @@ impl Table { _ => { // this would occur if two different but valid signatures // on the same kind of vote occurred. - return None; + return (None, None); } }; - return Some(Misbehavior::ValidityDoubleVote(double_vote_proof)); + return ( + Some(Misbehavior::ValidityDoubleVote(double_vote_proof)), + None, + ) } + + return (None, None); } Entry::Vacant(vacant) => { if let ValidityVote::Invalid(_) = vote { @@ -475,7 +518,7 @@ impl Table { } } - None + (None, Some(votes.summary(digest))) } fn availability_vote( @@ -484,24 +527,27 @@ impl Table { from: C::ValidatorId, digest: C::Digest, signature: C::Signature, - ) -> Option> { + ) -> (Option>, Option>) { let votes = match self.candidate_votes.get_mut(&digest) { - None => return None, // TODO: queue up but don't get DoS'ed + None => return (None, None), // TODO: queue up but don't get DoS'ed Some(votes) => votes, }; // check that this validator actually can vote in this group. if !context.is_availability_guarantor_of(&from, &votes.group_id) { - return Some(Misbehavior::UnauthorizedStatement(UnauthorizedStatement { - statement: SignedStatement { - signature: signature.clone(), - statement: Statement::Available(digest), - } - })); + return ( + Some(Misbehavior::UnauthorizedStatement(UnauthorizedStatement { + statement: SignedStatement { + signature: signature.clone(), + statement: Statement::Available(digest), + } + })), + None + ); } votes.availability_votes.insert(from, signature); - None + (None, Some(votes.summary(digest))) } } @@ -815,4 +861,103 @@ mod tests { assert!(!candidate.can_be_included(validity_threshold, availability_threshold)); } + + #[test] + fn candidate_import_gives_summary() { + let context = TestContext { + validators: { + let mut map = HashMap::new(); + map.insert(ValidatorId(1), (GroupId(2), GroupId(455))); + map + } + }; + + let mut table = create(); + let statement = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + }; + + let summary = table.import_statement(&context, statement, None) + .expect("candidate import to give summary"); + + assert_eq!(summary.candidate, Digest(100)); + assert_eq!(summary.group_id, GroupId(2)); + assert_eq!(summary.validity_votes, 1); + assert_eq!(summary.availability_votes, 0); + } + + #[test] + fn candidate_vote_gives_summary() { + let context = TestContext { + validators: { + let mut map = HashMap::new(); + map.insert(ValidatorId(1), (GroupId(2), GroupId(455))); + map.insert(ValidatorId(2), (GroupId(2), GroupId(455))); + map + } + }; + + let mut table = create(); + let statement = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + }; + let candidate_digest = Digest(100); + + table.import_statement(&context, statement, None); + assert!(!table.detected_misbehavior.contains_key(&ValidatorId(1))); + + let vote = SignedStatement { + statement: Statement::Valid(candidate_digest.clone()), + signature: Signature(2), + }; + + let summary = table.import_statement(&context, vote, None) + .expect("candidate vote to give summary"); + + assert!(!table.detected_misbehavior.contains_key(&ValidatorId(2))); + + assert_eq!(summary.candidate, Digest(100)); + assert_eq!(summary.group_id, GroupId(2)); + assert_eq!(summary.validity_votes, 2); + assert_eq!(summary.availability_votes, 0); + } + + #[test] + fn availability_vote_gives_summary() { + let context = TestContext { + validators: { + let mut map = HashMap::new(); + map.insert(ValidatorId(1), (GroupId(2), GroupId(455))); + map.insert(ValidatorId(2), (GroupId(5), GroupId(2))); + map + } + }; + + let mut table = create(); + let statement = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + }; + let candidate_digest = Digest(100); + + table.import_statement(&context, statement, None); + assert!(!table.detected_misbehavior.contains_key(&ValidatorId(1))); + + let vote = SignedStatement { + statement: Statement::Available(candidate_digest.clone()), + signature: Signature(2), + }; + + let summary = table.import_statement(&context, vote, None) + .expect("candidate vote to give summary"); + + assert!(!table.detected_misbehavior.contains_key(&ValidatorId(2))); + + assert_eq!(summary.candidate, Digest(100)); + assert_eq!(summary.group_id, GroupId(2)); + assert_eq!(summary.validity_votes, 1); + assert_eq!(summary.availability_votes, 1); + } }