Skip to content
Snippets Groups Projects
generic.rs 29.3 KiB
Newer Older
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.

// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.

//! The statement table: generic implementation.
//!
//! This stores messages other authorities issue about candidates.
//!
//! These messages are used to create a proposal submitted to a BFT consensus process.
//!
//! Each parachain is associated with a committee of authorities, who issue statements
//! indicating whether the candidate is valid or invalid. Once a threshold of the committee
//! has signed validity statements, the candidate may be marked includable.
use std::{
	collections::hash_map::{self, Entry, HashMap},
	fmt::Debug,
	hash::Hash,
};
use polkadot_primitives::{
	effective_minimum_backing_votes, ValidatorSignature,
	ValidityAttestation as PrimitiveValidityAttestation,
};
use codec::{Decode, Encode};
const LOG_TARGET: &str = "parachain::statement-table";
/// Context for the statement table.
pub trait Context {
	type AuthorityId: Debug + Hash + Eq + Clone;
	/// The digest (hash or other unique attribute) of a candidate.
	type Digest: Debug + Hash + Eq + Clone;
	/// The group ID type
	type GroupId: Debug + Hash + Ord + Eq + Clone;
	/// A signature type.
	type Signature: Debug + Eq + Clone;
	/// Candidate type. In practice this will be a candidate receipt.
	type Candidate: Debug + Ord + Eq + Clone;

	/// get the digest of a candidate.
	fn candidate_digest(candidate: &Self::Candidate) -> Self::Digest;

	/// Whether a authority is a member of a group.
	/// Members are meant to submit candidates and vote on validity.
	fn is_member_of(&self, authority: &Self::AuthorityId, group: &Self::GroupId) -> bool;

	/// Get a validator group size.
	fn get_group_size(&self, group: &Self::GroupId) -> Option<usize>;
/// Table configuration.
pub struct Config {
	/// When this is true, the table will allow multiple seconded candidates
	/// per authority. This flag means that higher-level code is responsible for
	/// bounding the number of candidates.
	pub allow_multiple_seconded: bool,
}

/// Statements circulated among peers.
#[derive(PartialEq, Eq, Debug, Clone, Encode, Decode)]
pub enum Statement<Candidate, Digest> {
	/// Broadcast by an authority to indicate that this is its candidate for inclusion.
	///
	/// Broadcasting two different candidate messages per round is not allowed.
	/// Broadcast by a authority to attest that the candidate with given digest is valid.
/// A signed statement.
#[derive(PartialEq, Eq, Debug, Clone, Encode, Decode)]
pub struct SignedStatement<Candidate, Digest, AuthorityId, Signature> {
	/// The statement.
	pub statement: Statement<Candidate, Digest>,
	/// The signature.
	/// The sender.
}

/// Misbehavior: voting more than one way on candidate validity.
///
/// Since there are three possible ways to vote, a double vote is possible in
/// three possible combinations (unordered)
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum ValidityDoubleVote<Candidate, Digest, Signature> {
Black3HDF's avatar
Black3HDF committed
	/// Implicit vote by issuing and explicitly voting validity.
	IssuedAndValidity((Candidate, Signature), (Digest, Signature)),
}

impl<Candidate, Digest, Signature> ValidityDoubleVote<Candidate, Digest, Signature> {
	/// Deconstruct this misbehavior into two `(Statement, Signature)` pairs, erasing the
	/// information about precisely what the problem was.
	pub fn deconstruct<Ctx>(
		self,
	) -> ((Statement<Candidate, Digest>, Signature), (Statement<Candidate, Digest>, Signature))
		Ctx: Context<Candidate = Candidate, Digest = Digest, Signature = Signature>,
		Candidate: Debug + Ord + Eq + Clone,
		Digest: Debug + Hash + Eq + Clone,
		Signature: Debug + Eq + Clone,
	{
		match self {
			Self::IssuedAndValidity((c, s1), (d, s2)) =>
				((Statement::Seconded(c), s1), (Statement::Valid(d), s2)),
/// Misbehavior: multiple signatures on same statement.
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum DoubleSign<Candidate, Digest, Signature> {
	Seconded(Candidate, Signature, Signature),
	Validity(Digest, Signature, Signature),
}

impl<Candidate, Digest, Signature> DoubleSign<Candidate, Digest, Signature> {
	/// Deconstruct this misbehavior into a statement with two signatures, erasing the information
	/// about precisely where in the process the issue was detected.
	pub fn deconstruct(self) -> (Statement<Candidate, Digest>, Signature, Signature) {
		match self {
			Self::Seconded(candidate, a, b) => (Statement::Seconded(candidate), a, b),
			Self::Validity(digest, a, b) => (Statement::Valid(digest), a, b),
		}
	}
/// Misbehavior: declaring multiple candidates.
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct MultipleCandidates<Candidate, Signature> {
	/// The first candidate seen.
	/// The second candidate seen.
}

/// Misbehavior: submitted statement for wrong group.
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct UnauthorizedStatement<Candidate, Digest, AuthorityId, Signature> {
	/// A signed statement which was submitted without proper authority.
	pub statement: SignedStatement<Candidate, Digest, AuthorityId, Signature>,
}

/// Different kinds of misbehavior. All of these kinds of malicious misbehavior
/// are easily provable and extremely disincentivized.
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Misbehavior<Candidate, Digest, AuthorityId, Signature> {
	/// Voted invalid and valid on validity.
	ValidityDoubleVote(ValidityDoubleVote<Candidate, Digest, Signature>),
	/// Submitted multiple candidates.
	MultipleCandidates(MultipleCandidates<Candidate, Signature>),
	/// Submitted a message that was unauthorized.
	UnauthorizedStatement(UnauthorizedStatement<Candidate, Digest, AuthorityId, Signature>),
	/// Submitted two valid signatures for the same message.
	DoubleSign(DoubleSign<Candidate, Digest, Signature>),
/// Type alias for misbehavior corresponding to context type.
pub type MisbehaviorFor<Ctx> = Misbehavior<
	<Ctx as Context>::Candidate,
	<Ctx as Context>::Digest,
	<Ctx as Context>::AuthorityId,
	<Ctx as Context>::Signature,
>;
// Kinds of votes for validity on a particular candidate.
#[derive(Clone, PartialEq, Eq)]
enum ValidityVote<Signature: Eq + Clone> {
	// Implicit validity vote.
	// Direct validity vote.
}

/// A summary of import of a statement.
#[derive(Clone, PartialEq, Eq, Debug)]
	/// The digest of the candidate referenced.
	/// The group that the candidate is in.
	/// How many validity votes are currently witnessed.
	pub validity_votes: usize,
}

/// A validity attestation.
#[derive(Clone, PartialEq, Decode, Encode)]
	/// implicit validity attestation by issuing.
	/// This corresponds to issuance of a `Candidate` statement.
	/// An explicit attestation. This corresponds to issuance of a
	/// `Valid` statement.
impl Into<PrimitiveValidityAttestation> for ValidityAttestation<ValidatorSignature> {
	fn into(self) -> PrimitiveValidityAttestation {
		match self {
			Self::Implicit(s) => PrimitiveValidityAttestation::Implicit(s),
			Self::Explicit(s) => PrimitiveValidityAttestation::Explicit(s),
		}
	}
}

/// An attested-to candidate.
#[derive(Clone, PartialEq, Decode, Encode)]
pub struct AttestedCandidate<Group, Candidate, AuthorityId, Signature> {
	/// The group ID that the candidate is in.
	pub group_id: Group,
	/// The candidate data.
	pub candidate: Candidate,
	/// Validity attestations.
	pub validity_votes: Vec<(AuthorityId, ValidityAttestation<Signature>)>,
}

/// Stores votes and data about a candidate.
pub struct CandidateData<Ctx: Context> {
	group_id: Ctx::GroupId,
	candidate: Ctx::Candidate,
	validity_votes: HashMap<Ctx::AuthorityId, ValidityVote<Ctx::Signature>>,
	/// Yield a full attestation for a candidate.
	/// If the candidate can be included, it will return `Some`.
	pub fn attested(
		&self,
		validity_threshold: usize,
	) -> Option<AttestedCandidate<Ctx::GroupId, Ctx::Candidate, Ctx::AuthorityId, Ctx::Signature>> {
		let valid_votes = self.validity_votes.len();
		if valid_votes < validity_threshold {
			return None
		let validity_votes = self
			.validity_votes
			.iter()
			.map(|(a, v)| match *v {
				ValidityVote::Valid(ref s) => (a.clone(), ValidityAttestation::Explicit(s.clone())),
				ValidityVote::Issued(ref s) =>
					(a.clone(), ValidityAttestation::Implicit(s.clone())),
			})
			.collect();

		Some(AttestedCandidate {
			group_id: self.group_id.clone(),
			candidate: self.candidate.clone(),
			validity_votes,
		})
	fn summary(&self, digest: Ctx::Digest) -> Summary<Ctx::Digest, Ctx::GroupId> {
		Summary {
			candidate: digest,
			group_id: self.group_id.clone(),
asynchronous rob's avatar
asynchronous rob committed
			validity_votes: self.validity_votes.len(),
		}
	}
}

// authority metadata
	proposals: Vec<(Ctx::Digest, Ctx::Signature)>,
impl<Ctx: Context> Default for AuthorityData<Ctx> {
	fn default() -> Self {
		AuthorityData { proposals: Vec::new() }
/// Type alias for the result of a statement import.
pub type ImportResult<Ctx> = Result<
	Option<Summary<<Ctx as Context>::Digest, <Ctx as Context>::GroupId>>,
	MisbehaviorFor<Ctx>,
/// Stores votes
pub struct Table<Ctx: Context> {
	authority_data: HashMap<Ctx::AuthorityId, AuthorityData<Ctx>>,
	detected_misbehavior: HashMap<Ctx::AuthorityId, Vec<MisbehaviorFor<Ctx>>>,
	candidate_votes: HashMap<Ctx::Digest, CandidateData<Ctx>>,
	config: Config,
impl<Ctx: Context> Table<Ctx> {
	/// Create a new `Table` from a `Config`.
	pub fn new(config: Config) -> Self {
		Table {
			authority_data: HashMap::default(),
			detected_misbehavior: HashMap::default(),
			candidate_votes: HashMap::default(),
			config,
asynchronous rob's avatar
asynchronous rob committed
	/// Get the attested candidate for `digest`.
	///
	/// Returns `Some(_)` if the candidate exists and is includable.
	pub fn attested_candidate(
		&self,
		digest: &Ctx::Digest,
		context: &Ctx,
		minimum_backing_votes: u32,
	) -> Option<AttestedCandidate<Ctx::GroupId, Ctx::Candidate, Ctx::AuthorityId, Ctx::Signature>> {
asynchronous rob's avatar
asynchronous rob committed
		self.candidate_votes.get(digest).and_then(|data| {
			let v_threshold = context.get_group_size(&data.group_id).map_or(usize::MAX, |len| {
				effective_minimum_backing_votes(len, minimum_backing_votes)
			});
asynchronous rob's avatar
asynchronous rob committed
			data.attested(v_threshold)
		})
	}

	/// Import a signed statement. Signatures should be checked for validity, and the
	/// sender should be checked to actually be an authority.
	/// Validity and invalidity statements are only valid if the corresponding
	/// candidate has already been imported.
	///
	/// If this returns `None`, the statement was either duplicate or invalid.
	pub fn import_statement(
		&mut self,
		statement: SignedStatement<Ctx::Candidate, Ctx::Digest, Ctx::AuthorityId, Ctx::Signature>,
	) -> Option<Summary<Ctx::Digest, Ctx::GroupId>> {
		let SignedStatement { statement, signature, sender: signer } = statement;
			Statement::Seconded(candidate) =>
				self.import_candidate(context, signer.clone(), candidate, signature, group_id),
			Statement::Valid(digest) =>
				self.validity_vote(context, signer.clone(), digest, ValidityVote::Valid(signature)),
		match res {
			Ok(maybe_summary) => maybe_summary,
			Err(misbehavior) => {
				// all misbehavior in agreement is provable and actively malicious.
				// punishments may be cumulative.
				self.detected_misbehavior.entry(signer).or_default().push(misbehavior);
		}
	}

	/// Get a candidate by digest.
	pub fn get_candidate(&self, digest: &Ctx::Digest) -> Option<&Ctx::Candidate> {
		self.candidate_votes.get(digest).map(|d| &d.candidate)
	}

	/// Access all witnessed misbehavior.
	pub fn get_misbehavior(&self) -> &HashMap<Ctx::AuthorityId, Vec<MisbehaviorFor<Ctx>>> {
		&self.detected_misbehavior
	}

	/// Create a draining iterator of misbehaviors.
	///
	/// This consumes all detected misbehaviors, even if the iterator is not completely consumed.
	pub fn drain_misbehaviors(&mut self) -> DrainMisbehaviors<'_, Ctx> {
		self.detected_misbehavior.drain().into()
	fn import_candidate(
		&mut self,
		context: &Ctx,
		authority: Ctx::AuthorityId,
		candidate: Ctx::Candidate,
		signature: Ctx::Signature,
	) -> ImportResult<Ctx> {
		if !context.is_member_of(&authority, &group) {
			gum::debug!(target: LOG_TARGET,  authority = ?authority, group = ?group, "New `Misbehavior::UnauthorizedStatement`, candidate backed by validator that doesn't belong to expected group" );
			return Err(Misbehavior::UnauthorizedStatement(UnauthorizedStatement {
				statement: SignedStatement {
					signature,
					statement: Statement::Seconded(candidate),
		}

		// check that authority hasn't already specified another candidate.
		let digest = Ctx::candidate_digest(&candidate);
		let new_proposal = match self.authority_data.entry(authority.clone()) {
			Entry::Occupied(mut occ) => {
				// if digest is different, fetch candidate and
				// note misbehavior.
				let existing = occ.get_mut();

				if !self.config.allow_multiple_seconded && existing.proposals.len() == 1 {
					let (old_digest, old_sig) = &existing.proposals[0];

					if old_digest != &digest {
						const EXISTENCE_PROOF: &str =
							"when proposal first received from authority, candidate \
							votes entry is created. proposal here is `Some`, therefore \
							candidate votes entry exists; qed";

						let old_candidate = self
							.candidate_votes
							.get(old_digest)
							.expect(EXISTENCE_PROOF)
							.candidate
							.clone();

						return Err(Misbehavior::MultipleCandidates(MultipleCandidates {
							first: (old_candidate, old_sig.clone()),
							second: (candidate, signature.clone()),
					false
				} else if self.config.allow_multiple_seconded &&
					existing.proposals.iter().any(|(ref od, _)| od == &digest)
				{
					false
				} else {
					existing.proposals.push((digest.clone(), signature.clone()));
			Entry::Vacant(vacant) => {
				vacant
					.insert(AuthorityData { proposals: vec![(digest.clone(), signature.clone())] });
		};

		// NOTE: altering this code may affect the existence proof above. ensure it remains
		// valid.
		if new_proposal {
			self.candidate_votes
				.entry(digest.clone())
				.or_insert_with(move || CandidateData {
					group_id: group,
					candidate,
					validity_votes: HashMap::new(),
				});
		self.validity_vote(context, authority, digest, ValidityVote::Issued(signature))
	}

	fn validity_vote(
		&mut self,
		context: &Ctx,
		from: Ctx::AuthorityId,
		digest: Ctx::Digest,
		vote: ValidityVote<Ctx::Signature>,
	) -> ImportResult<Ctx> {
		let votes = match self.candidate_votes.get_mut(&digest) {
			Some(votes) => votes,
		};

		// check that this authority actually can vote in this group.
		if !context.is_member_of(&from, &votes.group_id) {
			let sig = match vote {
				ValidityVote::Valid(s) => s,
				ValidityVote::Issued(s) => s,
			return Err(Misbehavior::UnauthorizedStatement(UnauthorizedStatement {
				statement: SignedStatement {
					signature: sig,
					sender: from,
					statement: Statement::Valid(digest),
		}

		// check for double votes.
		match votes.validity_votes.entry(from.clone()) {
			Entry::Occupied(occ) => {
				let make_vdv = |v| Misbehavior::ValidityDoubleVote(v);
				let make_ds = |ds| Misbehavior::DoubleSign(ds);
				return if occ.get() != &vote {
					Err(match (occ.get().clone(), vote) {
						// valid vote conflicting with candidate statement
						(ValidityVote::Issued(iss), ValidityVote::Valid(good)) |
						(ValidityVote::Valid(good), ValidityVote::Issued(iss)) =>
							make_vdv(ValidityDoubleVote::IssuedAndValidity(
								(votes.candidate.clone(), iss),
								(digest, good),
							)),

						// two signatures on same candidate
						(ValidityVote::Issued(a), ValidityVote::Issued(b)) =>
							make_ds(DoubleSign::Seconded(votes.candidate.clone(), a, b)),

						// two signatures on same validity vote
						(ValidityVote::Valid(a), ValidityVote::Valid(b)) =>
							make_ds(DoubleSign::Validity(digest, a, b)),
					})
				} else {
					Ok(None)
				}
			Entry::Vacant(vacant) => {
				vacant.insert(vote);
type Drain<'a, Ctx> = hash_map::Drain<'a, <Ctx as Context>::AuthorityId, Vec<MisbehaviorFor<Ctx>>>;

struct MisbehaviorForAuthority<Ctx: Context> {
	id: Ctx::AuthorityId,
	misbehaviors: Vec<MisbehaviorFor<Ctx>>,
}

impl<Ctx: Context> From<(Ctx::AuthorityId, Vec<MisbehaviorFor<Ctx>>)>
	for MisbehaviorForAuthority<Ctx>
{
	fn from((id, mut misbehaviors): (Ctx::AuthorityId, Vec<MisbehaviorFor<Ctx>>)) -> Self {
		// we're going to be popping items off this list in the iterator, so reverse it now to
		// preserve the original ordering.
		misbehaviors.reverse();
		Self { id, misbehaviors }
	}
}

impl<Ctx: Context> Iterator for MisbehaviorForAuthority<Ctx> {
	type Item = (Ctx::AuthorityId, MisbehaviorFor<Ctx>);

	fn next(&mut self) -> Option<Self::Item> {
		self.misbehaviors.pop().map(|misbehavior| (self.id.clone(), misbehavior))
	}
}

pub struct DrainMisbehaviors<'a, Ctx: Context> {
	drain: Drain<'a, Ctx>,
	in_progress: Option<MisbehaviorForAuthority<Ctx>>,
}

impl<'a, Ctx: Context> From<Drain<'a, Ctx>> for DrainMisbehaviors<'a, Ctx> {
	fn from(drain: Drain<'a, Ctx>) -> Self {
		Self { drain, in_progress: None }
	}
}

impl<'a, Ctx: Context> DrainMisbehaviors<'a, Ctx> {
	fn maybe_item(&mut self) -> Option<(Ctx::AuthorityId, MisbehaviorFor<Ctx>)> {
		self.in_progress.as_mut().and_then(Iterator::next)
	}
}

impl<'a, Ctx: Context> Iterator for DrainMisbehaviors<'a, Ctx> {
	type Item = (Ctx::AuthorityId, MisbehaviorFor<Ctx>);

	fn next(&mut self) -> Option<Self::Item> {
		// Note: this implementation will prematurely return `None` if `self.drain.next()` ever
		// returns a tuple whose vector is empty. That will never currently happen, as the only
		// modification to the backing map is currently via `drain` and
		// `entry(...).or_default().push(...)`. However, future code changes might change that
		// property.
		self.maybe_item().or_else(|| {
			self.in_progress = self.drain.next().map(Into::into);
			self.maybe_item()
		})
	}
}

#[cfg(test)]
mod tests {
	use super::*;
	use std::collections::HashMap;

	fn create_single_seconded<Candidate: Context>() -> Table<Candidate> {
		Table::new(Config { allow_multiple_seconded: false })
	}

	fn create_many_seconded<Candidate: Context>() -> Table<Candidate> {
		Table::new(Config { allow_multiple_seconded: true })
	}

	#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
	struct AuthorityId(usize);

	#[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
	struct GroupId(usize);

	// group, body
	#[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
	struct Candidate(usize, usize);

	#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
	struct Signature(usize);

	#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
	struct Digest(usize);

	#[derive(Debug, PartialEq, Eq)]
	struct TestContext {
		// v -> parachain group
		authorities: HashMap<AuthorityId, GroupId>,
	}

	impl Context for TestContext {
		type AuthorityId = AuthorityId;
		type Digest = Digest;
		type Candidate = Candidate;
		type GroupId = GroupId;
		type Signature = Signature;

		fn candidate_digest(candidate: &Candidate) -> Digest {
			Digest(candidate.1)
		}

		fn is_member_of(&self, authority: &AuthorityId, group: &GroupId) -> bool {
			self.authorities.get(authority).map(|v| v == group).unwrap_or(false)
		fn get_group_size(&self, group: &Self::GroupId) -> Option<usize> {
			let count = self.authorities.values().filter(|g| *g == group).count();
			if count == 0 {
				None
			} else {
				Some(count)
	fn submitting_two_candidates_can_be_misbehavior() {
		let context = TestContext {
			authorities: {
				let mut map = HashMap::new();
				map.insert(AuthorityId(1), GroupId(2));
		let mut table = create_single_seconded();
		let statement_a = SignedStatement {
			statement: Statement::Seconded(Candidate(2, 100)),
			signature: Signature(1),
			sender: AuthorityId(1),
		};

		let statement_b = SignedStatement {
			statement: Statement::Seconded(Candidate(2, 999)),
			signature: Signature(1),
			sender: AuthorityId(1),
		};

		table.import_statement(&context, GroupId(2), statement_a);
		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1)));

		table.import_statement(&context, GroupId(2), statement_b);
		assert_eq!(
			table.detected_misbehavior[&AuthorityId(1)][0],
			Misbehavior::MultipleCandidates(MultipleCandidates {
				first: (Candidate(2, 100), Signature(1)),
				second: (Candidate(2, 999), Signature(1)),
			})
		);
	}

	#[test]
	fn submitting_two_candidates_can_be_allowed() {
		let context = TestContext {
			authorities: {
				let mut map = HashMap::new();
				map.insert(AuthorityId(1), GroupId(2));
				map
			},
		};

		let mut table = create_many_seconded();
		let statement_a = SignedStatement {
			statement: Statement::Seconded(Candidate(2, 100)),
			signature: Signature(1),
			sender: AuthorityId(1),
		};

		let statement_b = SignedStatement {
			statement: Statement::Seconded(Candidate(2, 999)),
			signature: Signature(1),
			sender: AuthorityId(1),
		};

		table.import_statement(&context, GroupId(2), statement_a);
		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1)));

		table.import_statement(&context, GroupId(2), statement_b);
		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1)));
	}

	#[test]
	fn submitting_candidate_from_wrong_group_is_misbehavior() {
		let context = TestContext {
			authorities: {
				let mut map = HashMap::new();
				map.insert(AuthorityId(1), GroupId(3));
		let mut table = create_single_seconded();
		let statement = SignedStatement {
			statement: Statement::Seconded(Candidate(2, 100)),
			signature: Signature(1),
			sender: AuthorityId(1),
		};

		table.import_statement(&context, GroupId(2), statement);

		assert_eq!(
			table.detected_misbehavior[&AuthorityId(1)][0],
			Misbehavior::UnauthorizedStatement(UnauthorizedStatement {
				statement: SignedStatement {
					statement: Statement::Seconded(Candidate(2, 100)),
					signature: Signature(1),
					sender: AuthorityId(1),
				},
			})
		);
	}

	#[test]
	fn unauthorized_votes() {
		let context = TestContext {
			authorities: {
				let mut map = HashMap::new();
				map.insert(AuthorityId(1), GroupId(2));
				map.insert(AuthorityId(2), GroupId(3));
		let mut table = create_single_seconded();

		let candidate_a = SignedStatement {
			statement: Statement::Seconded(Candidate(2, 100)),
			signature: Signature(1),
			sender: AuthorityId(1),
		};
		let candidate_a_digest = Digest(100);

		table.import_statement(&context, GroupId(2), candidate_a);
		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1)));
		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2)));

		// authority 2 votes for validity on 1's candidate.
		let bad_validity_vote = SignedStatement {
			statement: Statement::Valid(candidate_a_digest),
			signature: Signature(2),
			sender: AuthorityId(2),
		};
		table.import_statement(&context, GroupId(3), bad_validity_vote);

		assert_eq!(
			table.detected_misbehavior[&AuthorityId(2)][0],
			Misbehavior::UnauthorizedStatement(UnauthorizedStatement {
				statement: SignedStatement {
					statement: Statement::Valid(candidate_a_digest),
					signature: Signature(2),
					sender: AuthorityId(2),
				},
			})
		);
	}

	#[test]
	fn candidate_double_signature_is_misbehavior() {
		let context = TestContext {
			authorities: {
				let mut map = HashMap::new();
				map.insert(AuthorityId(1), GroupId(2));
				map.insert(AuthorityId(2), GroupId(2));
		let mut table = create_single_seconded();
			statement: Statement::Seconded(Candidate(2, 100)),
			signature: Signature(1),
			sender: AuthorityId(1),
		};

		table.import_statement(&context, GroupId(2), statement);
		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1)));

		let invalid_statement = SignedStatement {
			statement: Statement::Seconded(Candidate(2, 100)),
			signature: Signature(999),
			sender: AuthorityId(1),
		};

		table.import_statement(&context, GroupId(2), invalid_statement);
		assert!(table.detected_misbehavior.contains_key(&AuthorityId(1)));
	}

	#[test]
	fn issue_and_vote_is_misbehavior() {
		let context = TestContext {
			authorities: {
				let mut map = HashMap::new();
				map.insert(AuthorityId(1), GroupId(2));
		let mut table = create_single_seconded();
		let statement = SignedStatement {
			statement: Statement::Seconded(Candidate(2, 100)),
			signature: Signature(1),
			sender: AuthorityId(1),
		};
		let candidate_digest = Digest(100);

		table.import_statement(&context, GroupId(2), statement);
		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1)));

		let extra_vote = SignedStatement {
			statement: Statement::Valid(candidate_digest),
			signature: Signature(1),
			sender: AuthorityId(1),
		};

		table.import_statement(&context, GroupId(2), extra_vote);
		assert_eq!(
			table.detected_misbehavior[&AuthorityId(1)][0],
			Misbehavior::ValidityDoubleVote(ValidityDoubleVote::IssuedAndValidity(
				(Candidate(2, 100), Signature(1)),
				(Digest(100), Signature(1)),
			))
		);
	}

	#[test]
	fn candidate_attested_works() {
		let validity_threshold = 6;

		let mut candidate = CandidateData::<TestContext> {
			group_id: GroupId(4),
			candidate: Candidate(4, 12345),
			validity_votes: HashMap::new(),
		};

		assert!(candidate.attested(validity_threshold).is_none());

		for i in 0..validity_threshold {
			candidate
				.validity_votes
				.insert(AuthorityId(i + 100), ValidityVote::Valid(Signature(i + 100)));
		assert!(candidate.attested(validity_threshold).is_some());
		candidate.validity_votes.insert(
			AuthorityId(validity_threshold + 100),
			ValidityVote::Valid(Signature(validity_threshold + 100)),
		);
		assert!(candidate.attested(validity_threshold).is_some());
	fn includability_works() {
		let context = TestContext {
			authorities: {
				let mut map = HashMap::new();
				map.insert(AuthorityId(1), GroupId(2));
				map.insert(AuthorityId(2), GroupId(2));
				map.insert(AuthorityId(3), GroupId(2));
		};

		// have 2/3 validity guarantors note validity.
		let mut table = create_single_seconded();
		let statement = SignedStatement {
			statement: Statement::Seconded(Candidate(2, 100)),
			signature: Signature(1),
			sender: AuthorityId(1),
		};
		let candidate_digest = Digest(100);

		table.import_statement(&context, GroupId(2), statement);
		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1)));
		assert!(table.attested_candidate(&candidate_digest, &context, 2).is_none());

		let vote = SignedStatement {
			statement: Statement::Valid(candidate_digest),
			signature: Signature(2),
			sender: AuthorityId(2),
		};

		table.import_statement(&context, GroupId(2), vote);
		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2)));
		assert!(table.attested_candidate(&candidate_digest, &context, 2).is_some());
	#[test]
	fn candidate_import_gives_summary() {
		let context = TestContext {
			authorities: {
				let mut map = HashMap::new();
				map.insert(AuthorityId(1), GroupId(2));
		let mut table = create_single_seconded();
		let statement = SignedStatement {
			statement: Statement::Seconded(Candidate(2, 100)),
			signature: Signature(1),
			sender: AuthorityId(1),
		};

		let summary = table
			.import_statement(&context, GroupId(2), statement)
			.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);
	}

	#[test]
	fn candidate_vote_gives_summary() {
		let context = TestContext {
			authorities: {
				let mut map = HashMap::new();
				map.insert(AuthorityId(1), GroupId(2));
				map.insert(AuthorityId(2), GroupId(2));
		let mut table = create_single_seconded();
		let statement = SignedStatement {
			statement: Statement::Seconded(Candidate(2, 100)),
			signature: Signature(1),
			sender: AuthorityId(1),
		};
		let candidate_digest = Digest(100);

		table.import_statement(&context, GroupId(2), statement);
		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1)));

		let vote = SignedStatement {
			statement: Statement::Valid(candidate_digest),
			signature: Signature(2),
			sender: AuthorityId(2),
		};

		let summary = table
			.import_statement(&context, GroupId(2), vote)
			.expect("candidate vote to give summary");

		assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2)));

		assert_eq!(summary.candidate, Digest(100));
		assert_eq!(summary.group_id, GroupId(2));
		assert_eq!(summary.validity_votes, 2);
	}
}