1#![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
81const LOG_TARGET: &str = "grandpa";
83
84#[derive(Clone, Debug, PartialEq, Eq)]
86#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, DecodeWithMemTracking, TypeInfo))]
87pub struct Prevote<H, N> {
88 pub target_hash: H,
90 pub target_number: N,
92}
93
94impl<H, N> Prevote<H, N> {
95 pub fn new(target_hash: H, target_number: N) -> Self {
97 Prevote { target_hash, target_number }
98 }
99}
100
101#[derive(Clone, Debug, PartialEq, Eq)]
103#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, DecodeWithMemTracking, TypeInfo))]
104pub struct Precommit<H, N> {
105 pub target_hash: H,
107 pub target_number: N,
109}
110
111impl<H, N> Precommit<H, N> {
112 pub fn new(target_hash: H, target_number: N) -> Self {
114 Precommit { target_hash, target_number }
115 }
116}
117
118#[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 pub target_hash: H,
125 pub target_number: N,
127}
128
129impl<H, N> PrimaryPropose<H, N> {
130 pub fn new(target_hash: H, target_number: N) -> Self {
132 PrimaryPropose { target_hash, target_number }
133 }
134}
135
136#[derive(Clone, PartialEq)]
138#[cfg_attr(any(feature = "std", test), derive(Debug))]
139pub enum Error {
140 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
162pub 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
186pub trait Chain<H: Eq, N: Copy + BlockNumberOps> {
188 fn ancestry(&self, base: H, block: H) -> Result<Vec<H>, Error>;
193
194 fn is_equal_or_descendent_of(&self, base: H, block: H) -> bool {
196 if base == block {
197 return true
198 }
199
200 match self.ancestry(base, block) {
204 Ok(_) => true,
205 Err(Error::NotDescendent) => false,
206 }
207 }
208}
209
210#[derive(Clone, Debug, PartialEq, Eq)]
212#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, DecodeWithMemTracking, TypeInfo))]
213pub struct Equivocation<Id, V, S> {
214 pub round_number: u64,
216 pub identity: Id,
218 pub first: (V, S),
220 pub second: (V, S),
222}
223
224#[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 #[cfg_attr(feature = "derive-codec", codec(index = 0))]
231 Prevote(Prevote<H, N>),
232 #[cfg_attr(feature = "derive-codec", codec(index = 1))]
234 Precommit(Precommit<H, N>),
235 #[cfg_attr(feature = "derive-codec", codec(index = 2))]
237 PrimaryPropose(PrimaryPropose<H, N>),
238}
239
240impl<H, N: Copy> Message<H, N> {
241 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#[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 pub message: Message<H, N>,
258 pub signature: S,
260 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 pub fn target(&self) -> (&H, N) {
269 self.message.target()
270 }
271}
272
273#[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 pub target_hash: H,
280 pub target_number: N,
282 pub precommits: Vec<SignedPrecommit<H, N, S, Id>>,
284}
285
286#[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 pub prevote: Prevote<H, N>,
293 pub signature: S,
295 pub id: Id,
297}
298
299#[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 pub precommit: Precommit<H, N>,
306 pub signature: S,
308 pub id: Id,
310}
311
312#[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 pub target_hash: H,
319 pub target_number: N,
321 pub precommits: Vec<Precommit<H, N>>,
323 pub auth_data: MultiAuthData<S, Id>,
325}
326
327#[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 pub round_number: u64,
338 pub prevotes: Vec<SignedPrevote<H, N, S, Id>>,
340 pub precommits: Vec<SignedPrecommit<H, N, S, Id>>,
342 pub base_hash: H,
344 pub base_number: N,
346}
347
348pub 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#[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 pub fn is_valid(&self) -> bool {
397 self.valid
398 }
399
400 pub fn num_precommits(&self) -> usize {
402 self.num_precommits
403 }
404
405 pub fn num_duplicated_precommits(&self) -> usize {
407 self.num_duplicated_precommits
408 }
409
410 pub fn num_equivocations(&self) -> usize {
412 self.num_equivocations
413 }
414
415 pub fn num_invalid_voters(&self) -> usize {
418 self.num_invalid_voters
419 }
420}
421
422pub 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 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 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 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 let mut round = round::Round::new(round::RoundParams {
489 round_number: 0, 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 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 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#[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#[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 pub fn new() -> Self {
553 HistoricalVotes { seen: Vec::new(), prevote_idx: None, precommit_idx: None }
554 }
555
556 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 pub fn push_vote(&mut self, msg: SignedMessage<H, N, S, Id>) {
568 self.seen.push(msg)
569 }
570
571 pub fn seen(&self) -> &[SignedMessage<H, N, S, Id>] {
573 &self.seen
574 }
575
576 pub fn prevote_idx(&self) -> Option<u64> {
579 self.prevote_idx
580 }
581
582 pub fn precommit_idx(&self) -> Option<u64> {
585 self.precommit_idx
586 }
587
588 pub fn set_prevoted_idx(&mut self) {
590 self.prevote_idx = Some(self.seen.len() as u64)
591 }
592
593 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 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 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 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 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 precommits.push(make_precommit("A", 1, 67));
701 precommits.push(make_precommit("B", 2, 67));
702
703 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 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 assert!(result.is_valid());
749
750 assert_eq!(result.num_invalid_voters(), 1);
752 }
753}