finality_grandpa/
lib.rs

1// Copyright 2018-2019 Parity Technologies (UK) Ltd
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Finality gadget for blockchains.
16//!
17//! <https://github.com/w3f/consensus/blob/master/pdf/grandpa.pdf>
18//!
19//! Consensus proceeds in rounds. Each round, voters will cast a prevote
20//! and precommit message.
21//!
22//! Votes on blocks are then applied to the blockchain, being recursively applied to
23//! blocks before them. A DAG is superimposed over the blockchain with the `vote_graph` logic.
24//!
25//! Equivocation detection and vote-set management is done in the `round` module.
26//! The work for actually casting votes is done in the `voter` module.
27
28#![warn(missing_docs)]
29#![cfg_attr(not(feature = "std"), no_std)]
30
31#[cfg(not(feature = "std"))]
32#[macro_use]
33extern crate alloc;
34#[cfg(feature = "std")]
35extern crate std;
36
37pub mod round;
38pub mod vote_graph;
39#[cfg(feature = "std")]
40pub mod voter;
41pub mod voter_set;
42
43mod bitfield;
44#[cfg(feature = "std")]
45mod bridge_state;
46#[cfg(any(test, feature = "fuzz-helpers"))]
47pub mod fuzz_helpers;
48#[cfg(test)]
49mod testing;
50mod weights;
51#[cfg(not(feature = "std"))]
52mod std {
53	pub use core::{cmp, hash, iter, mem, num, ops};
54
55	pub mod vec {
56		pub use alloc::vec::Vec;
57	}
58
59	pub mod collections {
60		pub use alloc::collections::{
61			btree_map::{self, BTreeMap},
62			btree_set::{self, BTreeSet},
63		};
64	}
65
66	pub mod fmt {
67		pub use core::fmt::{Display, Formatter, Result};
68
69		pub trait Debug {}
70		impl<T> Debug for T {}
71	}
72}
73
74use crate::{std::vec::Vec, voter_set::VoterSet};
75#[cfg(feature = "derive-codec")]
76use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode};
77use round::ImportResult;
78#[cfg(feature = "derive-codec")]
79use scale_info::TypeInfo;
80
81// Overarching log target
82const LOG_TARGET: &str = "grandpa";
83
84/// A prevote for a block and its ancestors.
85#[derive(Clone, Debug, PartialEq, Eq)]
86#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, DecodeWithMemTracking, TypeInfo))]
87pub struct Prevote<H, N> {
88	/// The target block's hash.
89	pub target_hash: H,
90	/// The target block's number.
91	pub target_number: N,
92}
93
94impl<H, N> Prevote<H, N> {
95	/// Create a new prevote for the given block (hash and number).
96	pub fn new(target_hash: H, target_number: N) -> Self {
97		Prevote { target_hash, target_number }
98	}
99}
100
101/// A precommit for a block and its ancestors.
102#[derive(Clone, Debug, PartialEq, Eq)]
103#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, DecodeWithMemTracking, TypeInfo))]
104pub struct Precommit<H, N> {
105	/// The target block's hash.
106	pub target_hash: H,
107	/// The target block's number
108	pub target_number: N,
109}
110
111impl<H, N> Precommit<H, N> {
112	/// Create a new precommit for the given block (hash and number).
113	pub fn new(target_hash: H, target_number: N) -> Self {
114		Precommit { target_hash, target_number }
115	}
116}
117
118/// A primary proposed block, this is a broadcast of the last round's estimate.
119#[derive(Clone, PartialEq, Eq)]
120#[cfg_attr(any(feature = "std", test), derive(Debug))]
121#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
122pub struct PrimaryPropose<H, N> {
123	/// The target block's hash.
124	pub target_hash: H,
125	/// The target block's number
126	pub target_number: N,
127}
128
129impl<H, N> PrimaryPropose<H, N> {
130	/// Create a new primary proposal for the given block (hash and number).
131	pub fn new(target_hash: H, target_number: N) -> Self {
132		PrimaryPropose { target_hash, target_number }
133	}
134}
135
136/// Top-level error type used by this crate.
137#[derive(Clone, PartialEq)]
138#[cfg_attr(any(feature = "std", test), derive(Debug))]
139pub enum Error {
140	/// The block is not a descendent of the given base block.
141	NotDescendent,
142}
143
144#[cfg(feature = "std")]
145impl std::fmt::Display for Error {
146	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
147		match *self {
148			Error::NotDescendent => write!(f, "Block not descendent of base"),
149		}
150	}
151}
152
153#[cfg(feature = "std")]
154impl std::error::Error for Error {
155	fn description(&self) -> &str {
156		match *self {
157			Error::NotDescendent => "Block not descendent of base",
158		}
159	}
160}
161
162/// Arithmetic necessary for a block number.
163pub trait BlockNumberOps:
164	std::fmt::Debug
165	+ std::cmp::Ord
166	+ std::ops::Add<Output = Self>
167	+ std::ops::Sub<Output = Self>
168	+ num::One
169	+ num::Zero
170	+ num::AsPrimitive<usize>
171{
172}
173
174impl<T> BlockNumberOps for T
175where
176	T: std::fmt::Debug,
177	T: std::cmp::Ord,
178	T: std::ops::Add<Output = Self>,
179	T: std::ops::Sub<Output = Self>,
180	T: num::One,
181	T: num::Zero,
182	T: num::AsPrimitive<usize>,
183{
184}
185
186/// Chain context necessary for implementation of the finality gadget.
187pub trait Chain<H: Eq, N: Copy + BlockNumberOps> {
188	/// Get the ancestry of a block up to but not including the base hash.
189	/// Should be in reverse order from `block`'s parent.
190	///
191	/// If the block is not a descendent of `base`, returns an error.
192	fn ancestry(&self, base: H, block: H) -> Result<Vec<H>, Error>;
193
194	/// Returns true if `block` is a descendent of or equal to the given `base`.
195	fn is_equal_or_descendent_of(&self, base: H, block: H) -> bool {
196		if base == block {
197			return true
198		}
199
200		// TODO: currently this function always succeeds since the only error
201		// variant is `Error::NotDescendent`, this may change in the future as
202		// other errors (e.g. IO) are not being exposed.
203		match self.ancestry(base, block) {
204			Ok(_) => true,
205			Err(Error::NotDescendent) => false,
206		}
207	}
208}
209
210/// An equivocation (double-vote) in a given round.
211#[derive(Clone, Debug, PartialEq, Eq)]
212#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, DecodeWithMemTracking, TypeInfo))]
213pub struct Equivocation<Id, V, S> {
214	/// The round number equivocated in.
215	pub round_number: u64,
216	/// The identity of the equivocator.
217	pub identity: Id,
218	/// The first vote in the equivocation.
219	pub first: (V, S),
220	/// The second vote in the equivocation.
221	pub second: (V, S),
222}
223
224/// A protocol message or vote.
225#[derive(Clone, PartialEq, Eq)]
226#[cfg_attr(any(feature = "std", test), derive(Debug))]
227#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
228pub enum Message<H, N> {
229	/// A prevote message.
230	#[cfg_attr(feature = "derive-codec", codec(index = 0))]
231	Prevote(Prevote<H, N>),
232	/// A precommit message.
233	#[cfg_attr(feature = "derive-codec", codec(index = 1))]
234	Precommit(Precommit<H, N>),
235	/// A primary proposal message.
236	#[cfg_attr(feature = "derive-codec", codec(index = 2))]
237	PrimaryPropose(PrimaryPropose<H, N>),
238}
239
240impl<H, N: Copy> Message<H, N> {
241	/// Get the target block of the vote.
242	pub fn target(&self) -> (&H, N) {
243		match *self {
244			Message::Prevote(ref v) => (&v.target_hash, v.target_number),
245			Message::Precommit(ref v) => (&v.target_hash, v.target_number),
246			Message::PrimaryPropose(ref v) => (&v.target_hash, v.target_number),
247		}
248	}
249}
250
251/// A signed message.
252#[derive(Clone, PartialEq, Eq)]
253#[cfg_attr(any(feature = "std", test), derive(Debug))]
254#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
255pub struct SignedMessage<H, N, S, Id> {
256	/// The internal message which has been signed.
257	pub message: Message<H, N>,
258	/// The signature on the message.
259	pub signature: S,
260	/// The Id of the signer
261	pub id: Id,
262}
263
264impl<H, N, S, Id> Unpin for SignedMessage<H, N, S, Id> {}
265
266impl<H, N: Copy, S, Id> SignedMessage<H, N, S, Id> {
267	/// Get the target block of the vote.
268	pub fn target(&self) -> (&H, N) {
269		self.message.target()
270	}
271}
272
273/// A commit message which is an aggregate of precommits.
274#[derive(Clone, PartialEq, Eq)]
275#[cfg_attr(any(feature = "std", test), derive(Debug))]
276#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, DecodeWithMemTracking, TypeInfo))]
277pub struct Commit<H, N, S, Id> {
278	/// The target block's hash.
279	pub target_hash: H,
280	/// The target block's number.
281	pub target_number: N,
282	/// Precommits for target block or any block after it that justify this commit.
283	pub precommits: Vec<SignedPrecommit<H, N, S, Id>>,
284}
285
286/// A signed prevote message.
287#[derive(Clone, PartialEq, Eq)]
288#[cfg_attr(any(feature = "std", test), derive(Debug))]
289#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
290pub struct SignedPrevote<H, N, S, Id> {
291	/// The prevote message which has been signed.
292	pub prevote: Prevote<H, N>,
293	/// The signature on the message.
294	pub signature: S,
295	/// The Id of the signer.
296	pub id: Id,
297}
298
299/// A signed precommit message.
300#[derive(Clone, PartialEq, Eq)]
301#[cfg_attr(any(feature = "std", test), derive(Debug))]
302#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, DecodeWithMemTracking, TypeInfo))]
303pub struct SignedPrecommit<H, N, S, Id> {
304	/// The precommit message which has been signed.
305	pub precommit: Precommit<H, N>,
306	/// The signature on the message.
307	pub signature: S,
308	/// The Id of the signer.
309	pub id: Id,
310}
311
312/// A commit message with compact representation of authentication data.
313#[derive(Clone, PartialEq, Eq)]
314#[cfg_attr(any(feature = "std", test), derive(Debug))]
315#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
316pub struct CompactCommit<H, N, S, Id> {
317	/// The target block's hash.
318	pub target_hash: H,
319	/// The target block's number.
320	pub target_number: N,
321	/// Precommits for target block or any block after it that justify this commit.
322	pub precommits: Vec<Precommit<H, N>>,
323	/// Authentication data for the commit.
324	pub auth_data: MultiAuthData<S, Id>,
325}
326
327/// A catch-up message, which is an aggregate of prevotes and precommits necessary
328/// to complete a round.
329///
330/// This message contains a "base", which is a block all of the vote-targets are
331/// a descendent of.
332#[derive(Clone, PartialEq, Eq)]
333#[cfg_attr(any(feature = "std", test), derive(Debug))]
334#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
335pub struct CatchUp<H, N, S, Id> {
336	/// Round number.
337	pub round_number: u64,
338	/// Prevotes for target block or any block after it that justify this catch-up.
339	pub prevotes: Vec<SignedPrevote<H, N, S, Id>>,
340	/// Precommits for target block or any block after it that justify this catch-up.
341	pub precommits: Vec<SignedPrecommit<H, N, S, Id>>,
342	/// The base hash. See struct docs.
343	pub base_hash: H,
344	/// The base number. See struct docs.
345	pub base_number: N,
346}
347
348/// Authentication data for a set of many messages, currently a set of precommit signatures but
349/// in the future could be optimized with BLS signature aggregation.
350pub type MultiAuthData<S, Id> = Vec<(S, Id)>;
351
352impl<H, N, S, Id> From<CompactCommit<H, N, S, Id>> for Commit<H, N, S, Id> {
353	fn from(commit: CompactCommit<H, N, S, Id>) -> Commit<H, N, S, Id> {
354		Commit {
355			target_hash: commit.target_hash,
356			target_number: commit.target_number,
357			precommits: commit
358				.precommits
359				.into_iter()
360				.zip(commit.auth_data)
361				.map(|(precommit, (signature, id))| SignedPrecommit { precommit, signature, id })
362				.collect(),
363		}
364	}
365}
366
367impl<H: Clone, N: Clone, S, Id> From<Commit<H, N, S, Id>> for CompactCommit<H, N, S, Id> {
368	fn from(commit: Commit<H, N, S, Id>) -> CompactCommit<H, N, S, Id> {
369		CompactCommit {
370			target_hash: commit.target_hash,
371			target_number: commit.target_number,
372			precommits: commit.precommits.iter().map(|signed| signed.precommit.clone()).collect(),
373			auth_data: commit
374				.precommits
375				.into_iter()
376				.map(|signed| (signed.signature, signed.id))
377				.collect(),
378		}
379	}
380}
381
382/// Struct returned from `validate_commit` function with information
383/// about the validation result.
384#[derive(Debug, Default)]
385pub struct CommitValidationResult {
386	valid: bool,
387	num_precommits: usize,
388	num_duplicated_precommits: usize,
389	num_equivocations: usize,
390	num_invalid_voters: usize,
391}
392
393impl CommitValidationResult {
394	/// Returns `true` if the commit is valid, which implies that the target
395	/// block in the commit is finalized.
396	pub fn is_valid(&self) -> bool {
397		self.valid
398	}
399
400	/// Returns the number of precommits in the commit.
401	pub fn num_precommits(&self) -> usize {
402		self.num_precommits
403	}
404
405	/// Returns the number of duplicate precommits in the commit.
406	pub fn num_duplicated_precommits(&self) -> usize {
407		self.num_duplicated_precommits
408	}
409
410	/// Returns the number of equivocated precommits in the commit.
411	pub fn num_equivocations(&self) -> usize {
412		self.num_equivocations
413	}
414
415	/// Returns the number of invalid voters in the commit, i.e. votes from
416	/// identities that are not part of the voter set.
417	pub fn num_invalid_voters(&self) -> usize {
418		self.num_invalid_voters
419	}
420}
421
422/// Validates a GRANDPA commit message.
423///
424/// For a commit to be valid the round ghost is calculated using the precommits
425/// in the commit message, making sure that it exists and that it is the same
426/// as the commit target. The precommit with the lowest block number is used as
427/// the round base.
428///
429/// Signatures on precommits are assumed to have been checked.
430///
431/// Duplicate votes or votes from voters not in the voter-set will be ignored,
432/// but it is recommended for the caller of this function to remove those at
433/// signature-verification time.
434pub fn validate_commit<H, N, S, I, C: Chain<H, N>>(
435	commit: &Commit<H, N, S, I>,
436	voters: &VoterSet<I>,
437	chain: &C,
438) -> Result<CommitValidationResult, crate::Error>
439where
440	H: Clone + Eq + Ord + std::fmt::Debug,
441	N: Copy + BlockNumberOps + std::fmt::Debug,
442	I: Clone + Ord + Eq + std::fmt::Debug,
443	S: Clone + Eq,
444{
445	let mut validation_result =
446		CommitValidationResult { num_precommits: commit.precommits.len(), ..Default::default() };
447
448	// filter any precommits by voters that are not part of the set
449	let valid_precommits = commit
450		.precommits
451		.iter()
452		.filter(|signed| {
453			if !voters.contains(&signed.id) {
454				validation_result.num_invalid_voters += 1;
455				return false
456			}
457
458			true
459		})
460		.collect::<Vec<_>>();
461
462	// the base of the round should be the lowest block for which we can find a
463	// precommit (any vote would only have been accepted if it was targetting a
464	// block higher or equal to the round base)
465	let base = match valid_precommits
466		.iter()
467		.map(|signed| &signed.precommit)
468		.min_by_key(|precommit| precommit.target_number)
469		.map(|precommit| (precommit.target_hash.clone(), precommit.target_number))
470	{
471		None => return Ok(validation_result),
472		Some(base) => base,
473	};
474
475	// check that all precommits are for blocks that are equal to or descendants
476	// of the round base
477	let all_precommits_higher_than_base = valid_precommits.iter().all(|signed| {
478		chain.is_equal_or_descendent_of(base.0.clone(), signed.precommit.target_hash.clone())
479	});
480
481	if !all_precommits_higher_than_base {
482		return Ok(validation_result)
483	}
484
485	let mut equivocated = std::collections::BTreeSet::new();
486
487	// add all precommits to the round with correct counting logic
488	let mut round = round::Round::new(round::RoundParams {
489		round_number: 0, // doesn't matter here.
490		voters: voters.clone(),
491		base,
492	});
493
494	for SignedPrecommit { precommit, id, signature } in &valid_precommits {
495		match round.import_precommit(chain, precommit.clone(), id.clone(), signature.clone())? {
496			ImportResult { equivocation: Some(_), .. } => {
497				validation_result.num_equivocations += 1;
498				// allow only one equivocation per voter, as extras are redundant.
499				if !equivocated.insert(id) {
500					return Ok(validation_result)
501				}
502			},
503			ImportResult { duplicated, .. } =>
504				if duplicated {
505					validation_result.num_duplicated_precommits += 1;
506				},
507		}
508	}
509
510	// for the commit to be valid, then a precommit ghost must be found for the
511	// round and it must be equal to the commit target
512	match round.precommit_ghost() {
513		Some((precommit_ghost_hash, precommit_ghost_number))
514			if precommit_ghost_hash == commit.target_hash &&
515				precommit_ghost_number == commit.target_number =>
516		{
517			validation_result.valid = true;
518		},
519		_ => {},
520	}
521
522	Ok(validation_result)
523}
524
525/// Runs the callback with the appropriate `CommitProcessingOutcome` based on
526/// the given `CommitValidationResult`. Outcome is bad if ghost is undefined,
527/// good otherwise.
528#[cfg(feature = "std")]
529pub fn process_commit_validation_result(
530	validation_result: CommitValidationResult,
531	mut callback: voter::Callback<voter::CommitProcessingOutcome>,
532) {
533	if validation_result.is_valid() {
534		callback.run(voter::CommitProcessingOutcome::Good(voter::GoodCommit::new()))
535	} else {
536		callback.run(voter::CommitProcessingOutcome::Bad(voter::BadCommit::from(validation_result)))
537	}
538}
539
540/// Historical votes seen in a round.
541#[derive(Default, Clone, PartialEq, Eq)]
542#[cfg_attr(any(feature = "std", test), derive(Debug))]
543#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
544pub struct HistoricalVotes<H, N, S, Id> {
545	seen: Vec<SignedMessage<H, N, S, Id>>,
546	prevote_idx: Option<u64>,
547	precommit_idx: Option<u64>,
548}
549
550impl<H, N, S, Id> HistoricalVotes<H, N, S, Id> {
551	/// Create a new HistoricalVotes.
552	pub fn new() -> Self {
553		HistoricalVotes { seen: Vec::new(), prevote_idx: None, precommit_idx: None }
554	}
555
556	/// Create a new HistoricalVotes initialized from the parameters.
557	pub fn new_with(
558		seen: Vec<SignedMessage<H, N, S, Id>>,
559		prevote_idx: Option<u64>,
560		precommit_idx: Option<u64>,
561	) -> Self {
562		HistoricalVotes { seen, prevote_idx, precommit_idx }
563	}
564
565	/// Push a vote into the list. The value of `self` before this call
566	/// is considered to be a prefix of the value post-call.
567	pub fn push_vote(&mut self, msg: SignedMessage<H, N, S, Id>) {
568		self.seen.push(msg)
569	}
570
571	/// Return the messages seen so far.
572	pub fn seen(&self) -> &[SignedMessage<H, N, S, Id>] {
573		&self.seen
574	}
575
576	/// Return the number of messages seen before prevoting.
577	/// None in case we didn't prevote yet.
578	pub fn prevote_idx(&self) -> Option<u64> {
579		self.prevote_idx
580	}
581
582	/// Return the number of messages seen before precommiting.
583	/// None in case we didn't precommit yet.
584	pub fn precommit_idx(&self) -> Option<u64> {
585		self.precommit_idx
586	}
587
588	/// Set the number of messages seen before prevoting.
589	pub fn set_prevoted_idx(&mut self) {
590		self.prevote_idx = Some(self.seen.len() as u64)
591	}
592
593	/// Set the number of messages seen before precommiting.
594	pub fn set_precommitted_idx(&mut self) {
595		self.precommit_idx = Some(self.seen.len() as u64)
596	}
597}
598
599#[cfg(test)]
600mod tests {
601	use super::*;
602	use crate::testing::chain::{DummyChain, GENESIS_HASH};
603
604	#[cfg(feature = "derive-codec")]
605	#[test]
606	fn codec_was_derived() {
607		use parity_scale_codec::{Decode, Encode};
608
609		let signed = crate::SignedMessage {
610			message: crate::Message::Prevote(crate::Prevote {
611				target_hash: b"Hello".to_vec(),
612				target_number: 5,
613			}),
614			signature: b"Signature".to_vec(),
615			id: 5000,
616		};
617
618		let encoded = signed.encode();
619		let signed2 = crate::SignedMessage::decode(&mut &encoded[..]).unwrap();
620		assert_eq!(signed, signed2);
621	}
622
623	#[test]
624	fn commit_validation() {
625		let mut chain = DummyChain::new();
626		chain.push_blocks(GENESIS_HASH, &["A"]);
627
628		let voters = VoterSet::new((1..=100).map(|id| (id, 1))).unwrap();
629
630		let make_precommit = |target_hash, target_number, id| SignedPrecommit {
631			precommit: Precommit { target_hash, target_number },
632			id,
633			signature: (),
634		};
635
636		let mut precommits = Vec::new();
637		for id in 1..67 {
638			let precommit = make_precommit("C", 3, id);
639			precommits.push(precommit);
640		}
641
642		// we have still not reached threshold with 66/100 votes, so the commit
643		// is not valid.
644		let result = validate_commit(
645			&Commit { target_hash: "C", target_number: 3, precommits: precommits.clone() },
646			&voters,
647			&chain,
648		)
649		.unwrap();
650
651		assert!(!result.is_valid());
652
653		// after adding one more commit targetting the same block we are over
654		// the finalization threshold and the commit should be valid
655		precommits.push(make_precommit("C", 3, 67));
656
657		let result = validate_commit(
658			&Commit { target_hash: "C", target_number: 3, precommits: precommits.clone() },
659			&voters,
660			&chain,
661		)
662		.unwrap();
663
664		assert!(result.is_valid());
665
666		// the commit target must be the exact same as the round precommit ghost
667		// that is calculated with the given precommits for the commit to be valid
668		let result = validate_commit(
669			&Commit { target_hash: "B", target_number: 2, precommits: precommits.clone() },
670			&voters,
671			&chain,
672		)
673		.unwrap();
674
675		assert!(!result.is_valid());
676	}
677
678	#[test]
679	fn commit_validation_with_equivocation() {
680		let mut chain = DummyChain::new();
681		chain.push_blocks(GENESIS_HASH, &["A", "B", "C"]);
682
683		let voters = VoterSet::new((1..=100).map(|id| (id, 1))).unwrap();
684
685		let make_precommit = |target_hash, target_number, id| SignedPrecommit {
686			precommit: Precommit { target_hash, target_number },
687			id,
688			signature: (),
689		};
690
691		// we add 66/100 precommits targeting block C
692		let mut precommits = Vec::new();
693		for id in 1..67 {
694			let precommit = make_precommit("C", 3, id);
695			precommits.push(precommit);
696		}
697
698		// we then add two equivocated votes targeting A and B
699		// from the 67th validator
700		precommits.push(make_precommit("A", 1, 67));
701		precommits.push(make_precommit("B", 2, 67));
702
703		// this equivocation is treated as "voting for all blocks", which means
704		// that block C will now have 67/100 votes and therefore it can be
705		// finalized.
706		let result = validate_commit(
707			&Commit { target_hash: "C", target_number: 3, precommits: precommits.clone() },
708			&voters,
709			&chain,
710		)
711		.unwrap();
712
713		assert!(result.is_valid());
714		assert_eq!(result.num_equivocations(), 1);
715	}
716
717	#[test]
718	fn commit_validation_precommit_from_unknown_voter_is_ignored() {
719		let mut chain = DummyChain::new();
720		chain.push_blocks(GENESIS_HASH, &["A", "B", "C"]);
721
722		let voters = VoterSet::new((1..=100).map(|id| (id, 1))).unwrap();
723
724		let make_precommit = |target_hash, target_number, id| SignedPrecommit {
725			precommit: Precommit { target_hash, target_number },
726			id,
727			signature: (),
728		};
729
730		let mut precommits = Vec::new();
731
732		// invalid vote from unknown voter should not influence the base
733		precommits.push(make_precommit("Z", 1, 1000));
734
735		for id in 1..=67 {
736			let precommit = make_precommit("C", 3, id);
737			precommits.push(precommit);
738		}
739
740		let result = validate_commit(
741			&Commit { target_hash: "C", target_number: 3, precommits: precommits.clone() },
742			&voters,
743			&chain,
744		)
745		.unwrap();
746
747		// we have threshold votes for block "C" so it should be valid
748		assert!(result.is_valid());
749
750		// there is one invalid voter in the commit
751		assert_eq!(result.num_invalid_voters(), 1);
752	}
753}