referrerpolicy=no-referrer-when-downgrade

sc_consensus_beefy/
round.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use crate::LOG_TARGET;
20
21use codec::{Decode, Encode};
22use log::{debug, info};
23use sp_application_crypto::RuntimeAppPublic;
24use sp_consensus_beefy::{
25	AuthorityIdBound, Commitment, DoubleVotingProof, SignedCommitment, ValidatorSet,
26	ValidatorSetId, VoteMessage,
27};
28use sp_runtime::traits::{Block, NumberFor};
29use std::collections::BTreeMap;
30
31/// Tracks for each round which validators have voted/signed and
32/// whether the local `self` validator has voted/signed.
33///
34/// Does not do any validation on votes or signatures, layers above need to handle that (gossip).
35#[derive(Debug, Decode, Encode, PartialEq)]
36pub(crate) struct RoundTracker<AuthorityId: AuthorityIdBound> {
37	votes: BTreeMap<AuthorityId, <AuthorityId as RuntimeAppPublic>::Signature>,
38}
39
40impl<AuthorityId: AuthorityIdBound> Default for RoundTracker<AuthorityId> {
41	fn default() -> Self {
42		Self { votes: Default::default() }
43	}
44}
45
46impl<AuthorityId: AuthorityIdBound> RoundTracker<AuthorityId> {
47	fn add_vote(
48		&mut self,
49		vote: (AuthorityId, <AuthorityId as RuntimeAppPublic>::Signature),
50	) -> bool {
51		if self.votes.contains_key(&vote.0) {
52			return false;
53		}
54
55		self.votes.insert(vote.0, vote.1);
56		true
57	}
58
59	fn is_done(&self, threshold: usize) -> bool {
60		self.votes.len() >= threshold
61	}
62}
63
64/// Minimum size of `authorities` subset that produced valid signatures for a block to finalize.
65pub fn threshold(authorities: usize) -> usize {
66	let faulty = authorities.saturating_sub(1) / 3;
67	authorities - faulty
68}
69
70#[derive(Debug, PartialEq)]
71pub enum VoteImportResult<B: Block, AuthorityId: AuthorityIdBound> {
72	Ok,
73	RoundConcluded(SignedCommitment<NumberFor<B>, <AuthorityId as RuntimeAppPublic>::Signature>),
74	DoubleVoting(
75		DoubleVotingProof<NumberFor<B>, AuthorityId, <AuthorityId as RuntimeAppPublic>::Signature>,
76	),
77	Invalid,
78	Stale,
79}
80
81/// Keeps track of all voting rounds (block numbers) within a session.
82/// Only round numbers > `best_done` are of interest, all others are considered stale.
83///
84/// Does not do any validation on votes or signatures, layers above need to handle that (gossip).
85#[derive(Debug, Decode, Encode, PartialEq)]
86pub(crate) struct Rounds<B: Block, AuthorityId: AuthorityIdBound> {
87	rounds: BTreeMap<Commitment<NumberFor<B>>, RoundTracker<AuthorityId>>,
88	previous_votes: BTreeMap<
89		(AuthorityId, NumberFor<B>),
90		VoteMessage<NumberFor<B>, AuthorityId, <AuthorityId as RuntimeAppPublic>::Signature>,
91	>,
92	session_start: NumberFor<B>,
93	validator_set: ValidatorSet<AuthorityId>,
94	mandatory_done: bool,
95	best_done: Option<NumberFor<B>>,
96}
97
98impl<B, AuthorityId> Rounds<B, AuthorityId>
99where
100	B: Block,
101	AuthorityId: AuthorityIdBound,
102{
103	pub(crate) fn new(
104		session_start: NumberFor<B>,
105		validator_set: ValidatorSet<AuthorityId>,
106	) -> Self {
107		Rounds {
108			rounds: BTreeMap::new(),
109			previous_votes: BTreeMap::new(),
110			session_start,
111			validator_set,
112			mandatory_done: false,
113			best_done: None,
114		}
115	}
116
117	pub(crate) fn validator_set(&self) -> &ValidatorSet<AuthorityId> {
118		&self.validator_set
119	}
120
121	pub(crate) fn validator_set_id(&self) -> ValidatorSetId {
122		self.validator_set.id()
123	}
124
125	pub(crate) fn validators(&self) -> &[AuthorityId] {
126		self.validator_set.validators()
127	}
128
129	pub(crate) fn session_start(&self) -> NumberFor<B> {
130		self.session_start
131	}
132
133	pub(crate) fn mandatory_done(&self) -> bool {
134		self.mandatory_done
135	}
136
137	pub(crate) fn add_vote(
138		&mut self,
139		vote: VoteMessage<NumberFor<B>, AuthorityId, <AuthorityId as RuntimeAppPublic>::Signature>,
140	) -> VoteImportResult<B, AuthorityId> {
141		let num = vote.commitment.block_number;
142		let vote_key = (vote.id.clone(), num);
143
144		if num < self.session_start || Some(num) <= self.best_done {
145			debug!(target: LOG_TARGET, "🥩 received vote for old stale round {:?}, ignoring", num);
146			return VoteImportResult::Stale;
147		} else if vote.commitment.validator_set_id != self.validator_set_id() {
148			debug!(
149				target: LOG_TARGET,
150				"🥩 expected set_id {:?}, ignoring vote {:?}.",
151				self.validator_set_id(),
152				vote,
153			);
154			return VoteImportResult::Invalid;
155		} else if !self.validators().iter().any(|id| &vote.id == id) {
156			debug!(
157				target: LOG_TARGET,
158				"🥩 received vote {:?} from validator that is not in the validator set, ignoring",
159				vote
160			);
161			return VoteImportResult::Invalid;
162		}
163
164		if let Some(previous_vote) = self.previous_votes.get(&vote_key) {
165			// is the same public key voting for a different payload?
166			if previous_vote.commitment.payload != vote.commitment.payload {
167				debug!(
168					target: LOG_TARGET,
169					"🥩 detected equivocated vote: 1st: {:?}, 2nd: {:?}", previous_vote, vote
170				);
171				return VoteImportResult::DoubleVoting(DoubleVotingProof {
172					first: previous_vote.clone(),
173					second: vote,
174				});
175			}
176		} else {
177			// this is the first vote sent by `id` for `num`, all good
178			self.previous_votes.insert(vote_key, vote.clone());
179		}
180
181		// add valid vote
182		let round = self.rounds.entry(vote.commitment.clone()).or_default();
183		if round.add_vote((vote.id, vote.signature)) &&
184			round.is_done(threshold(self.validator_set.len()))
185		{
186			if let Some(round) = self.rounds.remove_entry(&vote.commitment) {
187				return VoteImportResult::RoundConcluded(self.signed_commitment(round));
188			}
189		}
190		VoteImportResult::Ok
191	}
192
193	fn signed_commitment(
194		&mut self,
195		round: (Commitment<NumberFor<B>>, RoundTracker<AuthorityId>),
196	) -> SignedCommitment<NumberFor<B>, <AuthorityId as RuntimeAppPublic>::Signature> {
197		let votes = round.1.votes;
198		let signatures = self
199			.validators()
200			.iter()
201			.map(|authority_id| votes.get(authority_id).cloned())
202			.collect();
203		SignedCommitment { commitment: round.0, signatures }
204	}
205
206	pub(crate) fn conclude(&mut self, round_num: NumberFor<B>) {
207		// Remove this and older (now stale) rounds.
208		self.rounds.retain(|commitment, _| commitment.block_number > round_num);
209		self.previous_votes.retain(|&(_, number), _| number > round_num);
210		self.mandatory_done = self.mandatory_done || round_num == self.session_start;
211		self.best_done = self.best_done.max(Some(round_num));
212		if round_num == self.session_start {
213			info!(target: LOG_TARGET, "🥩 Concluded mandatory round #{}", round_num);
214		} else {
215			debug!(target: LOG_TARGET, "🥩 Concluded optional round #{}", round_num);
216		}
217	}
218}
219
220#[cfg(test)]
221mod tests {
222	use sc_network_test::Block;
223
224	use sp_consensus_beefy::{
225		ecdsa_crypto, known_payloads::MMR_ROOT_ID, test_utils::Keyring, Commitment,
226		DoubleVotingProof, Payload, SignedCommitment, ValidatorSet, VoteMessage,
227	};
228
229	use super::{threshold, Block as BlockT, RoundTracker, Rounds};
230	use crate::round::VoteImportResult;
231
232	impl<B> Rounds<B, ecdsa_crypto::AuthorityId>
233	where
234		B: BlockT,
235	{
236		pub(crate) fn test_set_mandatory_done(&mut self, done: bool) {
237			self.mandatory_done = done;
238		}
239	}
240
241	#[test]
242	fn round_tracker() {
243		let mut rt = RoundTracker::<ecdsa_crypto::AuthorityId>::default();
244		let bob_vote = (
245			Keyring::<ecdsa_crypto::AuthorityId>::Bob.public(),
246			Keyring::<ecdsa_crypto::AuthorityId>::Bob.sign(b"I am committed"),
247		);
248		let threshold = 2;
249
250		// adding new vote allowed
251		assert!(rt.add_vote(bob_vote.clone()));
252		// adding existing vote not allowed
253		assert!(!rt.add_vote(bob_vote));
254
255		// vote is not done
256		assert!(!rt.is_done(threshold));
257
258		let alice_vote = (
259			Keyring::<ecdsa_crypto::AuthorityId>::Alice.public(),
260			Keyring::<ecdsa_crypto::AuthorityId>::Alice.sign(b"I am committed"),
261		);
262		// adding new vote (self vote this time) allowed
263		assert!(rt.add_vote(alice_vote));
264
265		// vote is now done
266		assert!(rt.is_done(threshold));
267	}
268
269	#[test]
270	fn vote_threshold() {
271		assert_eq!(threshold(1), 1);
272		assert_eq!(threshold(2), 2);
273		assert_eq!(threshold(3), 3);
274		assert_eq!(threshold(4), 3);
275		assert_eq!(threshold(100), 67);
276		assert_eq!(threshold(300), 201);
277	}
278
279	#[test]
280	fn new_rounds() {
281		sp_tracing::try_init_simple();
282
283		let validators = ValidatorSet::<ecdsa_crypto::AuthorityId>::new(
284			vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()],
285			42,
286		)
287		.unwrap();
288
289		let session_start = 1u64.into();
290		let rounds = Rounds::<Block, ecdsa_crypto::AuthorityId>::new(session_start, validators);
291
292		assert_eq!(42, rounds.validator_set_id());
293		assert_eq!(1, rounds.session_start());
294		assert_eq!(
295			&vec![
296				Keyring::<ecdsa_crypto::AuthorityId>::Alice.public(),
297				Keyring::<ecdsa_crypto::AuthorityId>::Bob.public(),
298				Keyring::<ecdsa_crypto::AuthorityId>::Charlie.public()
299			],
300			rounds.validators()
301		);
302	}
303
304	#[test]
305	fn add_and_conclude_votes() {
306		sp_tracing::try_init_simple();
307
308		let validators = ValidatorSet::<ecdsa_crypto::AuthorityId>::new(
309			vec![
310				Keyring::Alice.public(),
311				Keyring::Bob.public(),
312				Keyring::Charlie.public(),
313				Keyring::Eve.public(),
314			],
315			Default::default(),
316		)
317		.unwrap();
318		let validator_set_id = validators.id();
319
320		let session_start = 1u64.into();
321		let mut rounds = Rounds::<Block, ecdsa_crypto::AuthorityId>::new(session_start, validators);
322
323		let payload = Payload::from_single_entry(MMR_ROOT_ID, vec![]);
324		let block_number = 1;
325		let commitment = Commitment { block_number, payload, validator_set_id };
326		let mut vote = VoteMessage {
327			id: Keyring::Alice.public(),
328			commitment: commitment.clone(),
329			signature: Keyring::<ecdsa_crypto::AuthorityId>::Alice.sign(b"I am committed"),
330		};
331		// add 1st good vote
332		assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok);
333
334		// double voting (same vote), ok, no effect
335		assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok);
336
337		vote.id = Keyring::Dave.public();
338		vote.signature = Keyring::<ecdsa_crypto::AuthorityId>::Dave.sign(b"I am committed");
339		// invalid vote (Dave is not a validator)
340		assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Invalid);
341
342		vote.id = Keyring::Bob.public();
343		vote.signature = Keyring::<ecdsa_crypto::AuthorityId>::Bob.sign(b"I am committed");
344		// add 2nd good vote
345		assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok);
346
347		vote.id = Keyring::Charlie.public();
348		vote.signature = Keyring::<ecdsa_crypto::AuthorityId>::Charlie.sign(b"I am committed");
349		// add 3rd good vote -> round concluded -> signatures present
350		assert_eq!(
351			rounds.add_vote(vote.clone()),
352			VoteImportResult::RoundConcluded(SignedCommitment {
353				commitment,
354				signatures: vec![
355					Some(Keyring::<ecdsa_crypto::AuthorityId>::Alice.sign(b"I am committed")),
356					Some(Keyring::<ecdsa_crypto::AuthorityId>::Bob.sign(b"I am committed")),
357					Some(Keyring::<ecdsa_crypto::AuthorityId>::Charlie.sign(b"I am committed")),
358					None,
359				]
360			})
361		);
362		rounds.conclude(block_number);
363
364		vote.id = Keyring::Eve.public();
365		vote.signature = Keyring::<ecdsa_crypto::AuthorityId>::Eve.sign(b"I am committed");
366		// Eve is a validator, but round was concluded, adding vote disallowed
367		assert_eq!(rounds.add_vote(vote), VoteImportResult::Stale);
368	}
369
370	#[test]
371	fn old_rounds_not_accepted() {
372		sp_tracing::try_init_simple();
373
374		let validators = ValidatorSet::<ecdsa_crypto::AuthorityId>::new(
375			vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()],
376			42,
377		)
378		.unwrap();
379		let validator_set_id = validators.id();
380
381		// active rounds starts at block 10
382		let session_start = 10u64.into();
383		let mut rounds = Rounds::<Block, ecdsa_crypto::AuthorityId>::new(session_start, validators);
384
385		// vote on round 9
386		let block_number = 9;
387		let payload = Payload::from_single_entry(MMR_ROOT_ID, vec![]);
388		let commitment = Commitment { block_number, payload, validator_set_id };
389		let mut vote = VoteMessage {
390			id: Keyring::Alice.public(),
391			commitment,
392			signature: Keyring::<ecdsa_crypto::AuthorityId>::Alice.sign(b"I am committed"),
393		};
394		// add vote for previous session, should fail
395		assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Stale);
396		// no votes present
397		assert!(rounds.rounds.is_empty());
398
399		// simulate 11 was concluded
400		rounds.best_done = Some(11);
401		// add votes for current session, but already concluded rounds, should fail
402		vote.commitment.block_number = 10;
403		assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Stale);
404		vote.commitment.block_number = 11;
405		assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Stale);
406		// no votes present
407		assert!(rounds.rounds.is_empty());
408
409		// add vote for active round 12
410		vote.commitment.block_number = 12;
411		assert_eq!(rounds.add_vote(vote), VoteImportResult::Ok);
412		// good vote present
413		assert_eq!(rounds.rounds.len(), 1);
414	}
415
416	#[test]
417	fn multiple_rounds() {
418		sp_tracing::try_init_simple();
419
420		let validators = ValidatorSet::<ecdsa_crypto::AuthorityId>::new(
421			vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()],
422			Default::default(),
423		)
424		.unwrap();
425		let validator_set_id = validators.id();
426
427		let session_start = 1u64.into();
428		let mut rounds = Rounds::<Block, ecdsa_crypto::AuthorityId>::new(session_start, validators);
429
430		let payload = Payload::from_single_entry(MMR_ROOT_ID, vec![]);
431		let commitment = Commitment { block_number: 1, payload, validator_set_id };
432		let mut alice_vote = VoteMessage {
433			id: Keyring::Alice.public(),
434			commitment: commitment.clone(),
435			signature: Keyring::<ecdsa_crypto::AuthorityId>::Alice.sign(b"I am committed"),
436		};
437		let mut bob_vote = VoteMessage {
438			id: Keyring::Bob.public(),
439			commitment: commitment.clone(),
440			signature: Keyring::<ecdsa_crypto::AuthorityId>::Bob.sign(b"I am committed"),
441		};
442		let mut charlie_vote = VoteMessage {
443			id: Keyring::Charlie.public(),
444			commitment,
445			signature: Keyring::<ecdsa_crypto::AuthorityId>::Charlie.sign(b"I am committed"),
446		};
447		let expected_signatures = vec![
448			Some(Keyring::<ecdsa_crypto::AuthorityId>::Alice.sign(b"I am committed")),
449			Some(Keyring::<ecdsa_crypto::AuthorityId>::Bob.sign(b"I am committed")),
450			Some(Keyring::<ecdsa_crypto::AuthorityId>::Charlie.sign(b"I am committed")),
451		];
452
453		// round 1 - only 2 out of 3 vote
454		assert_eq!(rounds.add_vote(alice_vote.clone()), VoteImportResult::Ok);
455		assert_eq!(rounds.add_vote(charlie_vote.clone()), VoteImportResult::Ok);
456		// should be 1 active round
457		assert_eq!(1, rounds.rounds.len());
458
459		// round 2 - only Charlie votes
460		charlie_vote.commitment.block_number = 2;
461		assert_eq!(rounds.add_vote(charlie_vote.clone()), VoteImportResult::Ok);
462		// should be 2 active rounds
463		assert_eq!(2, rounds.rounds.len());
464
465		// round 3 - all validators vote -> round is concluded
466		alice_vote.commitment.block_number = 3;
467		bob_vote.commitment.block_number = 3;
468		charlie_vote.commitment.block_number = 3;
469		assert_eq!(rounds.add_vote(alice_vote.clone()), VoteImportResult::Ok);
470		assert_eq!(rounds.add_vote(bob_vote.clone()), VoteImportResult::Ok);
471		assert_eq!(
472			rounds.add_vote(charlie_vote.clone()),
473			VoteImportResult::RoundConcluded(SignedCommitment {
474				commitment: charlie_vote.commitment,
475				signatures: expected_signatures
476			})
477		);
478		// should be only 2 active since this one auto-concluded
479		assert_eq!(2, rounds.rounds.len());
480
481		// conclude round 2
482		rounds.conclude(2);
483		// should be no more active rounds since 2 was officially concluded and round "1" is stale
484		assert!(rounds.rounds.is_empty());
485
486		// conclude round 3
487		rounds.conclude(3);
488		assert!(rounds.previous_votes.is_empty());
489	}
490
491	#[test]
492	fn should_provide_equivocation_proof() {
493		sp_tracing::try_init_simple();
494
495		let validators = ValidatorSet::<ecdsa_crypto::AuthorityId>::new(
496			vec![Keyring::Alice.public(), Keyring::Bob.public()],
497			Default::default(),
498		)
499		.unwrap();
500		let validator_set_id = validators.id();
501		let session_start = 1u64.into();
502		let mut rounds = Rounds::<Block, ecdsa_crypto::AuthorityId>::new(session_start, validators);
503
504		let payload1 = Payload::from_single_entry(MMR_ROOT_ID, vec![1, 1, 1, 1]);
505		let payload2 = Payload::from_single_entry(MMR_ROOT_ID, vec![2, 2, 2, 2]);
506		let commitment1 = Commitment { block_number: 1, payload: payload1, validator_set_id };
507		let commitment2 = Commitment { block_number: 1, payload: payload2, validator_set_id };
508
509		let alice_vote1 = VoteMessage {
510			id: Keyring::Alice.public(),
511			commitment: commitment1,
512			signature: Keyring::<ecdsa_crypto::AuthorityId>::Alice.sign(b"I am committed"),
513		};
514		let mut alice_vote2 = alice_vote1.clone();
515		alice_vote2.commitment = commitment2;
516
517		let expected_result = VoteImportResult::DoubleVoting(DoubleVotingProof {
518			first: alice_vote1.clone(),
519			second: alice_vote2.clone(),
520		});
521
522		// vote on one payload - ok
523		assert_eq!(rounds.add_vote(alice_vote1), VoteImportResult::Ok);
524
525		// vote on _another_ commitment/payload -> expected equivocation proof
526		assert_eq!(rounds.add_vote(alice_vote2), expected_result);
527	}
528}