#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
#[macro_use]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
pub mod round;
pub mod vote_graph;
#[cfg(feature = "std")]
pub mod voter;
pub mod voter_set;
mod bitfield;
#[cfg(feature = "std")]
mod bridge_state;
#[cfg(any(test, feature = "fuzz-helpers"))]
pub mod fuzz_helpers;
#[cfg(any(test))]
mod testing;
mod weights;
#[cfg(not(feature = "std"))]
mod std {
pub use core::{cmp, hash, iter, mem, num, ops};
pub mod vec {
pub use alloc::vec::Vec;
}
pub mod collections {
pub use alloc::collections::{
btree_map::{self, BTreeMap},
btree_set::{self, BTreeSet},
};
}
pub mod fmt {
pub use core::fmt::{Display, Formatter, Result};
pub trait Debug {}
impl<T> Debug for T {}
}
}
use crate::{std::vec::Vec, voter_set::VoterSet};
#[cfg(feature = "derive-codec")]
use parity_scale_codec::{Decode, Encode};
use round::ImportResult;
#[cfg(feature = "derive-codec")]
use scale_info::TypeInfo;
const LOG_TARGET: &str = "grandpa";
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
pub struct Prevote<H, N> {
pub target_hash: H,
pub target_number: N,
}
impl<H, N> Prevote<H, N> {
pub fn new(target_hash: H, target_number: N) -> Self {
Prevote { target_hash, target_number }
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
pub struct Precommit<H, N> {
pub target_hash: H,
pub target_number: N,
}
impl<H, N> Precommit<H, N> {
pub fn new(target_hash: H, target_number: N) -> Self {
Precommit { target_hash, target_number }
}
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(any(feature = "std", test), derive(Debug))]
#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
pub struct PrimaryPropose<H, N> {
pub target_hash: H,
pub target_number: N,
}
impl<H, N> PrimaryPropose<H, N> {
pub fn new(target_hash: H, target_number: N) -> Self {
PrimaryPropose { target_hash, target_number }
}
}
#[derive(Clone, PartialEq)]
#[cfg_attr(any(feature = "std", test), derive(Debug))]
pub enum Error {
NotDescendent,
}
#[cfg(feature = "std")]
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
Error::NotDescendent => write!(f, "Block not descendent of base"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::NotDescendent => "Block not descendent of base",
}
}
}
pub trait BlockNumberOps:
std::fmt::Debug
+ std::cmp::Ord
+ std::ops::Add<Output = Self>
+ std::ops::Sub<Output = Self>
+ num::One
+ num::Zero
+ num::AsPrimitive<usize>
{
}
impl<T> BlockNumberOps for T
where
T: std::fmt::Debug,
T: std::cmp::Ord,
T: std::ops::Add<Output = Self>,
T: std::ops::Sub<Output = Self>,
T: num::One,
T: num::Zero,
T: num::AsPrimitive<usize>,
{
}
pub trait Chain<H: Eq, N: Copy + BlockNumberOps> {
fn ancestry(&self, base: H, block: H) -> Result<Vec<H>, Error>;
fn is_equal_or_descendent_of(&self, base: H, block: H) -> bool {
if base == block {
return true
}
match self.ancestry(base, block) {
Ok(_) => true,
Err(Error::NotDescendent) => false,
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
pub struct Equivocation<Id, V, S> {
pub round_number: u64,
pub identity: Id,
pub first: (V, S),
pub second: (V, S),
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(any(feature = "std", test), derive(Debug))]
#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
pub enum Message<H, N> {
#[cfg_attr(feature = "derive-codec", codec(index = 0))]
Prevote(Prevote<H, N>),
#[cfg_attr(feature = "derive-codec", codec(index = 1))]
Precommit(Precommit<H, N>),
#[cfg_attr(feature = "derive-codec", codec(index = 2))]
PrimaryPropose(PrimaryPropose<H, N>),
}
impl<H, N: Copy> Message<H, N> {
pub fn target(&self) -> (&H, N) {
match *self {
Message::Prevote(ref v) => (&v.target_hash, v.target_number),
Message::Precommit(ref v) => (&v.target_hash, v.target_number),
Message::PrimaryPropose(ref v) => (&v.target_hash, v.target_number),
}
}
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(any(feature = "std", test), derive(Debug))]
#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
pub struct SignedMessage<H, N, S, Id> {
pub message: Message<H, N>,
pub signature: S,
pub id: Id,
}
impl<H, N, S, Id> Unpin for SignedMessage<H, N, S, Id> {}
impl<H, N: Copy, S, Id> SignedMessage<H, N, S, Id> {
pub fn target(&self) -> (&H, N) {
self.message.target()
}
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(any(feature = "std", test), derive(Debug))]
#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
pub struct Commit<H, N, S, Id> {
pub target_hash: H,
pub target_number: N,
pub precommits: Vec<SignedPrecommit<H, N, S, Id>>,
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(any(feature = "std", test), derive(Debug))]
#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
pub struct SignedPrevote<H, N, S, Id> {
pub prevote: Prevote<H, N>,
pub signature: S,
pub id: Id,
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(any(feature = "std", test), derive(Debug))]
#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
pub struct SignedPrecommit<H, N, S, Id> {
pub precommit: Precommit<H, N>,
pub signature: S,
pub id: Id,
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(any(feature = "std", test), derive(Debug))]
#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
pub struct CompactCommit<H, N, S, Id> {
pub target_hash: H,
pub target_number: N,
pub precommits: Vec<Precommit<H, N>>,
pub auth_data: MultiAuthData<S, Id>,
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(any(feature = "std", test), derive(Debug))]
#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
pub struct CatchUp<H, N, S, Id> {
pub round_number: u64,
pub prevotes: Vec<SignedPrevote<H, N, S, Id>>,
pub precommits: Vec<SignedPrecommit<H, N, S, Id>>,
pub base_hash: H,
pub base_number: N,
}
pub type MultiAuthData<S, Id> = Vec<(S, Id)>;
impl<H, N, S, Id> From<CompactCommit<H, N, S, Id>> for Commit<H, N, S, Id> {
fn from(commit: CompactCommit<H, N, S, Id>) -> Commit<H, N, S, Id> {
Commit {
target_hash: commit.target_hash,
target_number: commit.target_number,
precommits: commit
.precommits
.into_iter()
.zip(commit.auth_data.into_iter())
.map(|(precommit, (signature, id))| SignedPrecommit { precommit, signature, id })
.collect(),
}
}
}
impl<H: Clone, N: Clone, S, Id> From<Commit<H, N, S, Id>> for CompactCommit<H, N, S, Id> {
fn from(commit: Commit<H, N, S, Id>) -> CompactCommit<H, N, S, Id> {
CompactCommit {
target_hash: commit.target_hash,
target_number: commit.target_number,
precommits: commit.precommits.iter().map(|signed| signed.precommit.clone()).collect(),
auth_data: commit
.precommits
.into_iter()
.map(|signed| (signed.signature, signed.id))
.collect(),
}
}
}
#[derive(Debug, Default)]
pub struct CommitValidationResult {
valid: bool,
num_precommits: usize,
num_duplicated_precommits: usize,
num_equivocations: usize,
num_invalid_voters: usize,
}
impl CommitValidationResult {
pub fn is_valid(&self) -> bool {
self.valid
}
pub fn num_precommits(&self) -> usize {
self.num_precommits
}
pub fn num_duplicated_precommits(&self) -> usize {
self.num_duplicated_precommits
}
pub fn num_equivocations(&self) -> usize {
self.num_equivocations
}
pub fn num_invalid_voters(&self) -> usize {
self.num_invalid_voters
}
}
pub fn validate_commit<H, N, S, I, C: Chain<H, N>>(
commit: &Commit<H, N, S, I>,
voters: &VoterSet<I>,
chain: &C,
) -> Result<CommitValidationResult, crate::Error>
where
H: Clone + Eq + Ord + std::fmt::Debug,
N: Copy + BlockNumberOps + std::fmt::Debug,
I: Clone + Ord + Eq + std::fmt::Debug,
S: Clone + Eq,
{
let mut validation_result =
CommitValidationResult { num_precommits: commit.precommits.len(), ..Default::default() };
let valid_precommits = commit
.precommits
.iter()
.filter(|signed| {
if !voters.contains(&signed.id) {
validation_result.num_invalid_voters += 1;
return false
}
true
})
.collect::<Vec<_>>();
let base = match valid_precommits
.iter()
.map(|signed| &signed.precommit)
.min_by_key(|precommit| precommit.target_number)
.map(|precommit| (precommit.target_hash.clone(), precommit.target_number))
{
None => return Ok(validation_result),
Some(base) => base,
};
let all_precommits_higher_than_base = valid_precommits.iter().all(|signed| {
chain.is_equal_or_descendent_of(base.0.clone(), signed.precommit.target_hash.clone())
});
if !all_precommits_higher_than_base {
return Ok(validation_result)
}
let mut equivocated = std::collections::BTreeSet::new();
let mut round = round::Round::new(round::RoundParams {
round_number: 0, voters: voters.clone(),
base,
});
for SignedPrecommit { precommit, id, signature } in &valid_precommits {
match round.import_precommit(chain, precommit.clone(), id.clone(), signature.clone())? {
ImportResult { equivocation: Some(_), .. } => {
validation_result.num_equivocations += 1;
if !equivocated.insert(id) {
return Ok(validation_result)
}
},
ImportResult { duplicated, .. } =>
if duplicated {
validation_result.num_duplicated_precommits += 1;
},
}
}
match round.precommit_ghost() {
Some((precommit_ghost_hash, precommit_ghost_number))
if precommit_ghost_hash == commit.target_hash &&
precommit_ghost_number == commit.target_number =>
{
validation_result.valid = true;
},
_ => {},
}
Ok(validation_result)
}
#[cfg(feature = "std")]
pub fn process_commit_validation_result(
validation_result: CommitValidationResult,
mut callback: voter::Callback<voter::CommitProcessingOutcome>,
) {
if validation_result.is_valid() {
callback.run(voter::CommitProcessingOutcome::Good(voter::GoodCommit::new()))
} else {
callback.run(voter::CommitProcessingOutcome::Bad(voter::BadCommit::from(validation_result)))
}
}
#[derive(Default, Clone, PartialEq, Eq)]
#[cfg_attr(any(feature = "std", test), derive(Debug))]
#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, TypeInfo))]
pub struct HistoricalVotes<H, N, S, Id> {
seen: Vec<SignedMessage<H, N, S, Id>>,
prevote_idx: Option<u64>,
precommit_idx: Option<u64>,
}
impl<H, N, S, Id> HistoricalVotes<H, N, S, Id> {
pub fn new() -> Self {
HistoricalVotes { seen: Vec::new(), prevote_idx: None, precommit_idx: None }
}
pub fn new_with(
seen: Vec<SignedMessage<H, N, S, Id>>,
prevote_idx: Option<u64>,
precommit_idx: Option<u64>,
) -> Self {
HistoricalVotes { seen, prevote_idx, precommit_idx }
}
pub fn push_vote(&mut self, msg: SignedMessage<H, N, S, Id>) {
self.seen.push(msg)
}
pub fn seen(&self) -> &[SignedMessage<H, N, S, Id>] {
&self.seen
}
pub fn prevote_idx(&self) -> Option<u64> {
self.prevote_idx
}
pub fn precommit_idx(&self) -> Option<u64> {
self.precommit_idx
}
pub fn set_prevoted_idx(&mut self) {
self.prevote_idx = Some(self.seen.len() as u64)
}
pub fn set_precommitted_idx(&mut self) {
self.precommit_idx = Some(self.seen.len() as u64)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::chain::{DummyChain, GENESIS_HASH};
#[cfg(feature = "derive-codec")]
#[test]
fn codec_was_derived() {
use parity_scale_codec::{Decode, Encode};
let signed = crate::SignedMessage {
message: crate::Message::Prevote(crate::Prevote {
target_hash: b"Hello".to_vec(),
target_number: 5,
}),
signature: b"Signature".to_vec(),
id: 5000,
};
let encoded = signed.encode();
let signed2 = crate::SignedMessage::decode(&mut &encoded[..]).unwrap();
assert_eq!(signed, signed2);
}
#[test]
fn commit_validation() {
let mut chain = DummyChain::new();
chain.push_blocks(GENESIS_HASH, &["A"]);
let voters = VoterSet::new((1..=100).map(|id| (id, 1))).unwrap();
let make_precommit = |target_hash, target_number, id| SignedPrecommit {
precommit: Precommit { target_hash, target_number },
id,
signature: (),
};
let mut precommits = Vec::new();
for id in 1..67 {
let precommit = make_precommit("C", 3, id);
precommits.push(precommit);
}
let result = validate_commit(
&Commit { target_hash: "C", target_number: 3, precommits: precommits.clone() },
&voters,
&chain,
)
.unwrap();
assert!(!result.is_valid());
precommits.push(make_precommit("C", 3, 67));
let result = validate_commit(
&Commit { target_hash: "C", target_number: 3, precommits: precommits.clone() },
&voters,
&chain,
)
.unwrap();
assert!(result.is_valid());
let result = validate_commit(
&Commit { target_hash: "B", target_number: 2, precommits: precommits.clone() },
&voters,
&chain,
)
.unwrap();
assert!(!result.is_valid());
}
#[test]
fn commit_validation_with_equivocation() {
let mut chain = DummyChain::new();
chain.push_blocks(GENESIS_HASH, &["A", "B", "C"]);
let voters = VoterSet::new((1..=100).map(|id| (id, 1))).unwrap();
let make_precommit = |target_hash, target_number, id| SignedPrecommit {
precommit: Precommit { target_hash, target_number },
id,
signature: (),
};
let mut precommits = Vec::new();
for id in 1..67 {
let precommit = make_precommit("C", 3, id);
precommits.push(precommit);
}
precommits.push(make_precommit("A", 1, 67));
precommits.push(make_precommit("B", 2, 67));
let result = validate_commit(
&Commit { target_hash: "C", target_number: 3, precommits: precommits.clone() },
&voters,
&chain,
)
.unwrap();
assert!(result.is_valid());
assert_eq!(result.num_equivocations(), 1);
}
#[test]
fn commit_validation_precommit_from_unknown_voter_is_ignored() {
let mut chain = DummyChain::new();
chain.push_blocks(GENESIS_HASH, &["A", "B", "C"]);
let voters = VoterSet::new((1..=100).map(|id| (id, 1))).unwrap();
let make_precommit = |target_hash, target_number, id| SignedPrecommit {
precommit: Precommit { target_hash, target_number },
id,
signature: (),
};
let mut precommits = Vec::new();
precommits.push(make_precommit("Z", 1, 1000));
for id in 1..=67 {
let precommit = make_precommit("C", 3, id);
precommits.push(precommit);
}
let result = validate_commit(
&Commit { target_hash: "C", target_number: 3, precommits: precommits.clone() },
&voters,
&chain,
)
.unwrap();
assert!(result.is_valid());
assert_eq!(result.num_invalid_voters(), 1);
}
}