1use 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#[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
64pub 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#[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 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 self.previous_votes.insert(vote_key, vote.clone());
179 }
180
181 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 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 assert!(rt.add_vote(bob_vote.clone()));
252 assert!(!rt.add_vote(bob_vote));
254
255 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 assert!(rt.add_vote(alice_vote));
264
265 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 assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok);
333
334 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 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 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 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 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 let session_start = 10u64.into();
383 let mut rounds = Rounds::<Block, ecdsa_crypto::AuthorityId>::new(session_start, validators);
384
385 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 assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Stale);
396 assert!(rounds.rounds.is_empty());
398
399 rounds.best_done = Some(11);
401 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 assert!(rounds.rounds.is_empty());
408
409 vote.commitment.block_number = 12;
411 assert_eq!(rounds.add_vote(vote), VoteImportResult::Ok);
412 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 assert_eq!(rounds.add_vote(alice_vote.clone()), VoteImportResult::Ok);
455 assert_eq!(rounds.add_vote(charlie_vote.clone()), VoteImportResult::Ok);
456 assert_eq!(1, rounds.rounds.len());
458
459 charlie_vote.commitment.block_number = 2;
461 assert_eq!(rounds.add_vote(charlie_vote.clone()), VoteImportResult::Ok);
462 assert_eq!(2, rounds.rounds.len());
464
465 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 assert_eq!(2, rounds.rounds.len());
480
481 rounds.conclude(2);
483 assert!(rounds.rounds.is_empty());
485
486 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 assert_eq!(rounds.add_vote(alice_vote1), VoteImportResult::Ok);
524
525 assert_eq!(rounds.add_vote(alice_vote2), expected_result);
527 }
528}