Skip to main content

polkadot_statement_table/
generic.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! The statement table: generic implementation.
18//!
19//! This stores messages other authorities issue about candidates.
20//!
21//! These messages are used to create a proposal submitted to a BFT consensus process.
22//!
23//! Each parachain is associated with a committee of authorities, who issue statements
24//! indicating whether the candidate is valid or invalid. Once a threshold of the committee
25//! has signed validity statements, the candidate may be marked includable.
26
27use std::{
28	collections::hash_map::{self, Entry, HashMap},
29	fmt::Debug,
30	hash::Hash,
31};
32
33use polkadot_primitives::{
34	effective_minimum_backing_votes, ValidatorSignature,
35	ValidityAttestation as PrimitiveValidityAttestation,
36};
37
38use codec::{Decode, Encode};
39const LOG_TARGET: &str = "parachain::statement-table";
40
41/// Context for the statement table.
42pub trait Context {
43	/// An authority ID
44	type AuthorityId: Debug + Hash + Eq + Clone;
45	/// The digest (hash or other unique attribute) of a candidate.
46	type Digest: Debug + Hash + Eq + Clone;
47	/// The group ID type
48	type GroupId: Debug + Hash + Ord + Eq + Clone;
49	/// A signature type.
50	type Signature: Debug + Eq + Clone;
51	/// Candidate type. In practice this will be a candidate receipt.
52	type Candidate: Debug + Ord + Eq + Clone;
53
54	/// get the digest of a candidate.
55	fn candidate_digest(candidate: &Self::Candidate) -> Self::Digest;
56
57	/// Whether a authority is a member of a group.
58	/// Members are meant to submit candidates and vote on validity.
59	fn is_member_of(&self, authority: &Self::AuthorityId, group: &Self::GroupId) -> bool;
60
61	/// Get a validator group size.
62	fn get_group_size(&self, group: &Self::GroupId) -> Option<usize>;
63}
64
65/// Statements circulated among peers.
66#[derive(PartialEq, Eq, Debug, Clone, Encode, Decode)]
67pub enum Statement<Candidate, Digest> {
68	/// Broadcast by an authority to indicate that this is its candidate for inclusion.
69	///
70	/// Broadcasting two different candidate messages per round is not allowed.
71	#[codec(index = 1)]
72	Seconded(Candidate),
73	/// Broadcast by a authority to attest that the candidate with given digest is valid.
74	#[codec(index = 2)]
75	Valid(Digest),
76}
77
78/// A signed statement.
79#[derive(PartialEq, Eq, Debug, Clone, Encode, Decode)]
80pub struct SignedStatement<Candidate, Digest, AuthorityId, Signature> {
81	/// The statement.
82	pub statement: Statement<Candidate, Digest>,
83	/// The signature.
84	pub signature: Signature,
85	/// The sender.
86	pub sender: AuthorityId,
87}
88
89/// Misbehavior: voting more than one way on candidate validity.
90///
91/// Since there are three possible ways to vote, a double vote is possible in
92/// three possible combinations (unordered)
93#[derive(PartialEq, Eq, Debug, Clone)]
94pub enum ValidityDoubleVote<Candidate, Digest, Signature> {
95	/// Implicit vote by issuing and explicitly voting validity.
96	IssuedAndValidity((Candidate, Signature), (Digest, Signature)),
97}
98
99impl<Candidate, Digest, Signature> ValidityDoubleVote<Candidate, Digest, Signature> {
100	/// Deconstruct this misbehavior into two `(Statement, Signature)` pairs, erasing the
101	/// information about precisely what the problem was.
102	pub fn deconstruct<Ctx>(
103		self,
104	) -> ((Statement<Candidate, Digest>, Signature), (Statement<Candidate, Digest>, Signature))
105	where
106		Ctx: Context<Candidate = Candidate, Digest = Digest, Signature = Signature>,
107		Candidate: Debug + Ord + Eq + Clone,
108		Digest: Debug + Hash + Eq + Clone,
109		Signature: Debug + Eq + Clone,
110	{
111		match self {
112			Self::IssuedAndValidity((c, s1), (d, s2)) => {
113				((Statement::Seconded(c), s1), (Statement::Valid(d), s2))
114			},
115		}
116	}
117}
118
119/// Misbehavior: multiple signatures on same statement.
120#[derive(PartialEq, Eq, Debug, Clone)]
121pub enum DoubleSign<Candidate, Digest, Signature> {
122	/// On candidate.
123	Seconded(Candidate, Signature, Signature),
124	/// On validity.
125	Validity(Digest, Signature, Signature),
126}
127
128impl<Candidate, Digest, Signature> DoubleSign<Candidate, Digest, Signature> {
129	/// Deconstruct this misbehavior into a statement with two signatures, erasing the information
130	/// about precisely where in the process the issue was detected.
131	pub fn deconstruct(self) -> (Statement<Candidate, Digest>, Signature, Signature) {
132		match self {
133			Self::Seconded(candidate, a, b) => (Statement::Seconded(candidate), a, b),
134			Self::Validity(digest, a, b) => (Statement::Valid(digest), a, b),
135		}
136	}
137}
138
139/// Misbehavior: submitted statement for wrong group.
140#[derive(PartialEq, Eq, Debug, Clone)]
141pub struct UnauthorizedStatement<Candidate, Digest, AuthorityId, Signature> {
142	/// A signed statement which was submitted without proper authority.
143	pub statement: SignedStatement<Candidate, Digest, AuthorityId, Signature>,
144}
145
146/// Different kinds of misbehavior. All of these kinds of malicious misbehavior
147/// are easily provable and extremely disincentivized.
148#[derive(PartialEq, Eq, Debug, Clone)]
149pub enum Misbehavior<Candidate, Digest, AuthorityId, Signature> {
150	/// Voted invalid and valid on validity.
151	ValidityDoubleVote(ValidityDoubleVote<Candidate, Digest, Signature>),
152	/// Submitted a message that was unauthorized.
153	UnauthorizedStatement(UnauthorizedStatement<Candidate, Digest, AuthorityId, Signature>),
154	/// Submitted two valid signatures for the same message.
155	DoubleSign(DoubleSign<Candidate, Digest, Signature>),
156}
157
158/// Type alias for misbehavior corresponding to context type.
159pub type MisbehaviorFor<Ctx> = Misbehavior<
160	<Ctx as Context>::Candidate,
161	<Ctx as Context>::Digest,
162	<Ctx as Context>::AuthorityId,
163	<Ctx as Context>::Signature,
164>;
165
166// Kinds of votes for validity on a particular candidate.
167#[derive(Clone, PartialEq, Eq)]
168enum ValidityVote<Signature: Eq + Clone> {
169	// Implicit validity vote.
170	Issued(Signature),
171	// Direct validity vote.
172	Valid(Signature),
173}
174
175/// A summary of import of a statement.
176#[derive(Clone, PartialEq, Eq, Debug)]
177pub struct Summary<Digest, Group> {
178	/// The digest of the candidate referenced.
179	pub candidate: Digest,
180	/// The group that the candidate is in.
181	pub group_id: Group,
182	/// How many validity votes are currently witnessed.
183	pub validity_votes: usize,
184}
185
186/// A validity attestation.
187#[derive(Clone, PartialEq, Decode, Encode)]
188pub enum ValidityAttestation<Signature> {
189	/// implicit validity attestation by issuing.
190	/// This corresponds to issuance of a `Candidate` statement.
191	Implicit(Signature),
192	/// An explicit attestation. This corresponds to issuance of a
193	/// `Valid` statement.
194	Explicit(Signature),
195}
196
197impl Into<PrimitiveValidityAttestation> for ValidityAttestation<ValidatorSignature> {
198	fn into(self) -> PrimitiveValidityAttestation {
199		match self {
200			Self::Implicit(s) => PrimitiveValidityAttestation::Implicit(s),
201			Self::Explicit(s) => PrimitiveValidityAttestation::Explicit(s),
202		}
203	}
204}
205
206/// An attested-to candidate.
207#[derive(Clone, PartialEq, Decode, Encode)]
208pub struct AttestedCandidate<Group, Candidate, AuthorityId, Signature> {
209	/// The group ID that the candidate is in.
210	pub group_id: Group,
211	/// The candidate data.
212	pub candidate: Candidate,
213	/// Validity attestations.
214	pub validity_votes: Vec<(AuthorityId, ValidityAttestation<Signature>)>,
215}
216
217/// Stores votes and data about a candidate.
218pub struct CandidateData<Ctx: Context> {
219	group_id: Ctx::GroupId,
220	candidate: Ctx::Candidate,
221	validity_votes: HashMap<Ctx::AuthorityId, ValidityVote<Ctx::Signature>>,
222}
223
224impl<Ctx: Context> CandidateData<Ctx> {
225	/// Yield a full attestation for a candidate.
226	/// If the candidate can be included, it will return `Some`.
227	pub fn attested(
228		&self,
229		validity_threshold: usize,
230	) -> Option<AttestedCandidate<Ctx::GroupId, Ctx::Candidate, Ctx::AuthorityId, Ctx::Signature>>
231	{
232		let valid_votes = self.validity_votes.len();
233		if valid_votes < validity_threshold {
234			return None;
235		}
236
237		let validity_votes = self
238			.validity_votes
239			.iter()
240			.map(|(a, v)| match *v {
241				ValidityVote::Valid(ref s) => (a.clone(), ValidityAttestation::Explicit(s.clone())),
242				ValidityVote::Issued(ref s) => {
243					(a.clone(), ValidityAttestation::Implicit(s.clone()))
244				},
245			})
246			.collect();
247
248		Some(AttestedCandidate {
249			group_id: self.group_id.clone(),
250			candidate: self.candidate.clone(),
251			validity_votes,
252		})
253	}
254
255	fn summary(&self, digest: Ctx::Digest) -> Summary<Ctx::Digest, Ctx::GroupId> {
256		Summary {
257			candidate: digest,
258			group_id: self.group_id.clone(),
259			validity_votes: self.validity_votes.len(),
260		}
261	}
262}
263
264// authority metadata
265struct AuthorityData<Ctx: Context> {
266	proposals: Vec<(Ctx::Digest, Ctx::Signature)>,
267}
268
269impl<Ctx: Context> Default for AuthorityData<Ctx> {
270	fn default() -> Self {
271		AuthorityData { proposals: Vec::new() }
272	}
273}
274
275/// Type alias for the result of a statement import.
276pub type ImportResult<Ctx> = Result<
277	Option<Summary<<Ctx as Context>::Digest, <Ctx as Context>::GroupId>>,
278	MisbehaviorFor<Ctx>,
279>;
280
281/// Stores votes
282pub struct Table<Ctx: Context> {
283	authority_data: HashMap<Ctx::AuthorityId, AuthorityData<Ctx>>,
284	detected_misbehavior: HashMap<Ctx::AuthorityId, Vec<MisbehaviorFor<Ctx>>>,
285	candidate_votes: HashMap<Ctx::Digest, CandidateData<Ctx>>,
286}
287
288impl<Ctx: Context> Table<Ctx> {
289	pub fn new() -> Self {
290		Table {
291			authority_data: HashMap::default(),
292			detected_misbehavior: HashMap::default(),
293			candidate_votes: HashMap::default(),
294		}
295	}
296
297	/// Get the attested candidate for `digest`.
298	///
299	/// Returns `Some(_)` if the candidate exists and is includable.
300	pub fn attested_candidate(
301		&self,
302		digest: &Ctx::Digest,
303		context: &Ctx,
304		minimum_backing_votes: u32,
305	) -> Option<AttestedCandidate<Ctx::GroupId, Ctx::Candidate, Ctx::AuthorityId, Ctx::Signature>>
306	{
307		self.candidate_votes.get(digest).and_then(|data| {
308			let v_threshold = context.get_group_size(&data.group_id).map_or(usize::MAX, |len| {
309				effective_minimum_backing_votes(len, minimum_backing_votes)
310			});
311			data.attested(v_threshold)
312		})
313	}
314
315	/// Import a signed statement. Signatures should be checked for validity, and the
316	/// sender should be checked to actually be an authority.
317	///
318	/// Validity and invalidity statements are only valid if the corresponding
319	/// candidate has already been imported.
320	///
321	/// If this returns `None`, the statement was either duplicate or invalid.
322	pub fn import_statement(
323		&mut self,
324		context: &Ctx,
325		group_id: Ctx::GroupId,
326		statement: SignedStatement<Ctx::Candidate, Ctx::Digest, Ctx::AuthorityId, Ctx::Signature>,
327	) -> Option<Summary<Ctx::Digest, Ctx::GroupId>> {
328		let SignedStatement { statement, signature, sender: signer } = statement;
329		let res = match statement {
330			Statement::Seconded(candidate) => {
331				self.import_candidate(context, signer.clone(), candidate, signature, group_id)
332			},
333			Statement::Valid(digest) => {
334				self.validity_vote(context, signer.clone(), digest, ValidityVote::Valid(signature))
335			},
336		};
337
338		match res {
339			Ok(maybe_summary) => maybe_summary,
340			Err(misbehavior) => {
341				// all misbehavior in agreement is provable and actively malicious.
342				// punishments may be cumulative.
343				self.detected_misbehavior.entry(signer).or_default().push(misbehavior);
344				None
345			},
346		}
347	}
348
349	/// Get a candidate by digest.
350	pub fn get_candidate(&self, digest: &Ctx::Digest) -> Option<&Ctx::Candidate> {
351		self.candidate_votes.get(digest).map(|d| &d.candidate)
352	}
353
354	/// Access all witnessed misbehavior.
355	pub fn get_misbehavior(&self) -> &HashMap<Ctx::AuthorityId, Vec<MisbehaviorFor<Ctx>>> {
356		&self.detected_misbehavior
357	}
358
359	/// Create a draining iterator of misbehaviors.
360	///
361	/// This consumes all detected misbehaviors, even if the iterator is not completely consumed.
362	pub fn drain_misbehaviors(&mut self) -> DrainMisbehaviors<'_, Ctx> {
363		self.detected_misbehavior.drain().into()
364	}
365
366	fn import_candidate(
367		&mut self,
368		context: &Ctx,
369		authority: Ctx::AuthorityId,
370		candidate: Ctx::Candidate,
371		signature: Ctx::Signature,
372		group: Ctx::GroupId,
373	) -> ImportResult<Ctx> {
374		if !context.is_member_of(&authority, &group) {
375			gum::debug!(target: LOG_TARGET,  authority = ?authority, group = ?group, "New `Misbehavior::UnauthorizedStatement`, candidate backed by validator that doesn't belong to expected group" );
376			return Err(Misbehavior::UnauthorizedStatement(UnauthorizedStatement {
377				statement: SignedStatement {
378					signature,
379					statement: Statement::Seconded(candidate),
380					sender: authority,
381				},
382			}));
383		}
384
385		// check that authority hasn't already specified another candidate.
386		let digest = Ctx::candidate_digest(&candidate);
387
388		let new_proposal = match self.authority_data.entry(authority.clone()) {
389			Entry::Occupied(mut occ) => {
390				// if digest is different, fetch candidate and
391				// note misbehavior.
392				let existing = occ.get_mut();
393				if existing.proposals.iter().any(|(ref od, _)| od == &digest) {
394					false
395				} else {
396					existing.proposals.push((digest.clone(), signature.clone()));
397					true
398				}
399			},
400			Entry::Vacant(vacant) => {
401				vacant
402					.insert(AuthorityData { proposals: vec![(digest.clone(), signature.clone())] });
403				true
404			},
405		};
406
407		// NOTE: altering this code may affect the existence proof above. ensure it remains
408		// valid.
409		if new_proposal {
410			self.candidate_votes
411				.entry(digest.clone())
412				.or_insert_with(move || CandidateData {
413					group_id: group,
414					candidate,
415					validity_votes: HashMap::new(),
416				});
417		}
418
419		self.validity_vote(context, authority, digest, ValidityVote::Issued(signature))
420	}
421
422	fn validity_vote(
423		&mut self,
424		context: &Ctx,
425		from: Ctx::AuthorityId,
426		digest: Ctx::Digest,
427		vote: ValidityVote<Ctx::Signature>,
428	) -> ImportResult<Ctx> {
429		let votes = match self.candidate_votes.get_mut(&digest) {
430			None => return Ok(None),
431			Some(votes) => votes,
432		};
433
434		// check that this authority actually can vote in this group.
435		if !context.is_member_of(&from, &votes.group_id) {
436			let sig = match vote {
437				ValidityVote::Valid(s) => s,
438				ValidityVote::Issued(s) => s,
439			};
440
441			return Err(Misbehavior::UnauthorizedStatement(UnauthorizedStatement {
442				statement: SignedStatement {
443					signature: sig,
444					sender: from,
445					statement: Statement::Valid(digest),
446				},
447			}));
448		}
449
450		// check for double votes.
451		match votes.validity_votes.entry(from.clone()) {
452			Entry::Occupied(occ) => {
453				let make_vdv = |v| Misbehavior::ValidityDoubleVote(v);
454				let make_ds = |ds| Misbehavior::DoubleSign(ds);
455				return if occ.get() != &vote {
456					Err(match (occ.get().clone(), vote) {
457						// valid vote conflicting with candidate statement
458						(ValidityVote::Issued(iss), ValidityVote::Valid(good)) |
459						(ValidityVote::Valid(good), ValidityVote::Issued(iss)) => {
460							make_vdv(ValidityDoubleVote::IssuedAndValidity(
461								(votes.candidate.clone(), iss),
462								(digest, good),
463							))
464						},
465
466						// two signatures on same candidate
467						(ValidityVote::Issued(a), ValidityVote::Issued(b)) => {
468							make_ds(DoubleSign::Seconded(votes.candidate.clone(), a, b))
469						},
470
471						// two signatures on same validity vote
472						(ValidityVote::Valid(a), ValidityVote::Valid(b)) => {
473							make_ds(DoubleSign::Validity(digest, a, b))
474						},
475					})
476				} else {
477					Ok(None)
478				};
479			},
480			Entry::Vacant(vacant) => {
481				vacant.insert(vote);
482			},
483		}
484
485		Ok(Some(votes.summary(digest)))
486	}
487}
488
489type Drain<'a, Ctx> = hash_map::Drain<'a, <Ctx as Context>::AuthorityId, Vec<MisbehaviorFor<Ctx>>>;
490
491struct MisbehaviorForAuthority<Ctx: Context> {
492	id: Ctx::AuthorityId,
493	misbehaviors: Vec<MisbehaviorFor<Ctx>>,
494}
495
496impl<Ctx: Context> From<(Ctx::AuthorityId, Vec<MisbehaviorFor<Ctx>>)>
497	for MisbehaviorForAuthority<Ctx>
498{
499	fn from((id, mut misbehaviors): (Ctx::AuthorityId, Vec<MisbehaviorFor<Ctx>>)) -> Self {
500		// we're going to be popping items off this list in the iterator, so reverse it now to
501		// preserve the original ordering.
502		misbehaviors.reverse();
503		Self { id, misbehaviors }
504	}
505}
506
507impl<Ctx: Context> Iterator for MisbehaviorForAuthority<Ctx> {
508	type Item = (Ctx::AuthorityId, MisbehaviorFor<Ctx>);
509
510	fn next(&mut self) -> Option<Self::Item> {
511		self.misbehaviors.pop().map(|misbehavior| (self.id.clone(), misbehavior))
512	}
513}
514
515pub struct DrainMisbehaviors<'a, Ctx: Context> {
516	drain: Drain<'a, Ctx>,
517	in_progress: Option<MisbehaviorForAuthority<Ctx>>,
518}
519
520impl<'a, Ctx: Context> From<Drain<'a, Ctx>> for DrainMisbehaviors<'a, Ctx> {
521	fn from(drain: Drain<'a, Ctx>) -> Self {
522		Self { drain, in_progress: None }
523	}
524}
525
526impl<'a, Ctx: Context> DrainMisbehaviors<'a, Ctx> {
527	fn maybe_item(&mut self) -> Option<(Ctx::AuthorityId, MisbehaviorFor<Ctx>)> {
528		self.in_progress.as_mut().and_then(Iterator::next)
529	}
530}
531
532impl<'a, Ctx: Context> Iterator for DrainMisbehaviors<'a, Ctx> {
533	type Item = (Ctx::AuthorityId, MisbehaviorFor<Ctx>);
534
535	fn next(&mut self) -> Option<Self::Item> {
536		// Note: this implementation will prematurely return `None` if `self.drain.next()` ever
537		// returns a tuple whose vector is empty. That will never currently happen, as the only
538		// modification to the backing map is currently via `drain` and
539		// `entry(...).or_default().push(...)`. However, future code changes might change that
540		// property.
541		self.maybe_item().or_else(|| {
542			self.in_progress = self.drain.next().map(Into::into);
543			self.maybe_item()
544		})
545	}
546}
547
548#[cfg(test)]
549mod tests {
550	use super::*;
551	use std::collections::HashMap;
552
553	#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
554	struct AuthorityId(usize);
555
556	#[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
557	struct GroupId(usize);
558
559	// group, body
560	#[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
561	struct Candidate(usize, usize);
562
563	#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
564	struct Signature(usize);
565
566	#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
567	struct Digest(usize);
568
569	#[derive(Debug, PartialEq, Eq)]
570	struct TestContext {
571		// v -> parachain group
572		authorities: HashMap<AuthorityId, GroupId>,
573	}
574
575	impl Context for TestContext {
576		type AuthorityId = AuthorityId;
577		type Digest = Digest;
578		type Candidate = Candidate;
579		type GroupId = GroupId;
580		type Signature = Signature;
581
582		fn candidate_digest(candidate: &Candidate) -> Digest {
583			Digest(candidate.1)
584		}
585
586		fn is_member_of(&self, authority: &AuthorityId, group: &GroupId) -> bool {
587			self.authorities.get(authority).map(|v| v == group).unwrap_or(false)
588		}
589
590		fn get_group_size(&self, group: &Self::GroupId) -> Option<usize> {
591			let count = self.authorities.values().filter(|g| *g == group).count();
592			if count == 0 {
593				None
594			} else {
595				Some(count)
596			}
597		}
598	}
599
600	#[test]
601	fn submitting_two_candidates_can_be_allowed() {
602		let context = TestContext {
603			authorities: {
604				let mut map = HashMap::new();
605				map.insert(AuthorityId(1), GroupId(2));
606				map
607			},
608		};
609
610		let mut table = Table::new();
611		let statement_a = SignedStatement {
612			statement: Statement::Seconded(Candidate(2, 100)),
613			signature: Signature(1),
614			sender: AuthorityId(1),
615		};
616
617		let statement_b = SignedStatement {
618			statement: Statement::Seconded(Candidate(2, 999)),
619			signature: Signature(1),
620			sender: AuthorityId(1),
621		};
622
623		table.import_statement(&context, GroupId(2), statement_a);
624		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1)));
625
626		table.import_statement(&context, GroupId(2), statement_b);
627		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1)));
628	}
629
630	#[test]
631	fn submitting_candidate_from_wrong_group_is_misbehavior() {
632		let context = TestContext {
633			authorities: {
634				let mut map = HashMap::new();
635				map.insert(AuthorityId(1), GroupId(3));
636				map
637			},
638		};
639
640		let mut table = Table::new();
641		let statement = SignedStatement {
642			statement: Statement::Seconded(Candidate(2, 100)),
643			signature: Signature(1),
644			sender: AuthorityId(1),
645		};
646
647		table.import_statement(&context, GroupId(2), statement);
648
649		assert_eq!(
650			table.detected_misbehavior[&AuthorityId(1)][0],
651			Misbehavior::UnauthorizedStatement(UnauthorizedStatement {
652				statement: SignedStatement {
653					statement: Statement::Seconded(Candidate(2, 100)),
654					signature: Signature(1),
655					sender: AuthorityId(1),
656				},
657			})
658		);
659	}
660
661	#[test]
662	fn unauthorized_votes() {
663		let context = TestContext {
664			authorities: {
665				let mut map = HashMap::new();
666				map.insert(AuthorityId(1), GroupId(2));
667				map.insert(AuthorityId(2), GroupId(3));
668				map
669			},
670		};
671
672		let mut table = Table::new();
673
674		let candidate_a = SignedStatement {
675			statement: Statement::Seconded(Candidate(2, 100)),
676			signature: Signature(1),
677			sender: AuthorityId(1),
678		};
679		let candidate_a_digest = Digest(100);
680
681		table.import_statement(&context, GroupId(2), candidate_a);
682		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1)));
683		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2)));
684
685		// authority 2 votes for validity on 1's candidate.
686		let bad_validity_vote = SignedStatement {
687			statement: Statement::Valid(candidate_a_digest),
688			signature: Signature(2),
689			sender: AuthorityId(2),
690		};
691		table.import_statement(&context, GroupId(3), bad_validity_vote);
692
693		assert_eq!(
694			table.detected_misbehavior[&AuthorityId(2)][0],
695			Misbehavior::UnauthorizedStatement(UnauthorizedStatement {
696				statement: SignedStatement {
697					statement: Statement::Valid(candidate_a_digest),
698					signature: Signature(2),
699					sender: AuthorityId(2),
700				},
701			})
702		);
703	}
704
705	#[test]
706	fn candidate_double_signature_is_misbehavior() {
707		let context = TestContext {
708			authorities: {
709				let mut map = HashMap::new();
710				map.insert(AuthorityId(1), GroupId(2));
711				map.insert(AuthorityId(2), GroupId(2));
712				map
713			},
714		};
715
716		let mut table = Table::new();
717		let statement = SignedStatement {
718			statement: Statement::Seconded(Candidate(2, 100)),
719			signature: Signature(1),
720			sender: AuthorityId(1),
721		};
722
723		table.import_statement(&context, GroupId(2), statement);
724		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1)));
725
726		let invalid_statement = SignedStatement {
727			statement: Statement::Seconded(Candidate(2, 100)),
728			signature: Signature(999),
729			sender: AuthorityId(1),
730		};
731
732		table.import_statement(&context, GroupId(2), invalid_statement);
733		assert!(table.detected_misbehavior.contains_key(&AuthorityId(1)));
734	}
735
736	#[test]
737	fn issue_and_vote_is_misbehavior() {
738		let context = TestContext {
739			authorities: {
740				let mut map = HashMap::new();
741				map.insert(AuthorityId(1), GroupId(2));
742				map
743			},
744		};
745
746		let mut table = Table::new();
747		let statement = SignedStatement {
748			statement: Statement::Seconded(Candidate(2, 100)),
749			signature: Signature(1),
750			sender: AuthorityId(1),
751		};
752		let candidate_digest = Digest(100);
753
754		table.import_statement(&context, GroupId(2), statement);
755		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1)));
756
757		let extra_vote = SignedStatement {
758			statement: Statement::Valid(candidate_digest),
759			signature: Signature(1),
760			sender: AuthorityId(1),
761		};
762
763		table.import_statement(&context, GroupId(2), extra_vote);
764		assert_eq!(
765			table.detected_misbehavior[&AuthorityId(1)][0],
766			Misbehavior::ValidityDoubleVote(ValidityDoubleVote::IssuedAndValidity(
767				(Candidate(2, 100), Signature(1)),
768				(Digest(100), Signature(1)),
769			))
770		);
771	}
772
773	#[test]
774	fn candidate_attested_works() {
775		let validity_threshold = 6;
776
777		let mut candidate = CandidateData::<TestContext> {
778			group_id: GroupId(4),
779			candidate: Candidate(4, 12345),
780			validity_votes: HashMap::new(),
781		};
782
783		assert!(candidate.attested(validity_threshold).is_none());
784
785		for i in 0..validity_threshold {
786			candidate
787				.validity_votes
788				.insert(AuthorityId(i + 100), ValidityVote::Valid(Signature(i + 100)));
789		}
790
791		assert!(candidate.attested(validity_threshold).is_some());
792
793		candidate.validity_votes.insert(
794			AuthorityId(validity_threshold + 100),
795			ValidityVote::Valid(Signature(validity_threshold + 100)),
796		);
797
798		assert!(candidate.attested(validity_threshold).is_some());
799	}
800
801	#[test]
802	fn includability_works() {
803		let context = TestContext {
804			authorities: {
805				let mut map = HashMap::new();
806				map.insert(AuthorityId(1), GroupId(2));
807				map.insert(AuthorityId(2), GroupId(2));
808				map.insert(AuthorityId(3), GroupId(2));
809				map
810			},
811		};
812
813		// have 2/3 validity guarantors note validity.
814		let mut table = Table::new();
815		let statement = SignedStatement {
816			statement: Statement::Seconded(Candidate(2, 100)),
817			signature: Signature(1),
818			sender: AuthorityId(1),
819		};
820		let candidate_digest = Digest(100);
821
822		table.import_statement(&context, GroupId(2), statement);
823
824		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1)));
825		assert!(table.attested_candidate(&candidate_digest, &context, 2).is_none());
826
827		let vote = SignedStatement {
828			statement: Statement::Valid(candidate_digest),
829			signature: Signature(2),
830			sender: AuthorityId(2),
831		};
832
833		table.import_statement(&context, GroupId(2), vote);
834		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2)));
835		assert!(table.attested_candidate(&candidate_digest, &context, 2).is_some());
836	}
837
838	#[test]
839	fn candidate_import_gives_summary() {
840		let context = TestContext {
841			authorities: {
842				let mut map = HashMap::new();
843				map.insert(AuthorityId(1), GroupId(2));
844				map
845			},
846		};
847
848		let mut table = Table::new();
849		let statement = SignedStatement {
850			statement: Statement::Seconded(Candidate(2, 100)),
851			signature: Signature(1),
852			sender: AuthorityId(1),
853		};
854
855		let summary = table
856			.import_statement(&context, GroupId(2), statement)
857			.expect("candidate import to give summary");
858
859		assert_eq!(summary.candidate, Digest(100));
860		assert_eq!(summary.group_id, GroupId(2));
861		assert_eq!(summary.validity_votes, 1);
862	}
863
864	#[test]
865	fn candidate_vote_gives_summary() {
866		let context = TestContext {
867			authorities: {
868				let mut map = HashMap::new();
869				map.insert(AuthorityId(1), GroupId(2));
870				map.insert(AuthorityId(2), GroupId(2));
871				map
872			},
873		};
874
875		let mut table = Table::new();
876		let statement = SignedStatement {
877			statement: Statement::Seconded(Candidate(2, 100)),
878			signature: Signature(1),
879			sender: AuthorityId(1),
880		};
881		let candidate_digest = Digest(100);
882
883		table.import_statement(&context, GroupId(2), statement);
884		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1)));
885
886		let vote = SignedStatement {
887			statement: Statement::Valid(candidate_digest),
888			signature: Signature(2),
889			sender: AuthorityId(2),
890		};
891
892		let summary = table
893			.import_statement(&context, GroupId(2), vote)
894			.expect("candidate vote to give summary");
895
896		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2)));
897
898		assert_eq!(summary.candidate, Digest(100));
899		assert_eq!(summary.group_id, GroupId(2));
900		assert_eq!(summary.validity_votes, 2);
901	}
902}