#![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;
pub mod voter_set;
#[cfg(feature = "std")]
pub mod voter;
mod bitfield;
mod weights;
#[cfg(feature = "std")]
mod bridge_state;
#[cfg(any(test))]
mod testing;
#[cfg(any(test, feature = "fuzz-helpers"))]
pub mod fuzz_helpers;
#[cfg(not(feature = "std"))]
mod std {
pub use core::cmp;
pub use core::hash;
pub use core::iter;
pub use core::mem;
pub use core::num;
pub use core::ops;
pub mod vec {
pub use alloc::vec::Vec;
}
pub mod collections {
pub use alloc::collections::btree_map::{self, BTreeMap};
pub use alloc::collections::btree_set::{self, BTreeSet};
}
pub mod fmt {
pub use core::fmt::{Display, Result, Formatter};
pub trait Debug {}
impl<T> Debug for T {}
}
}
use crate::std::vec::Vec;
use crate::voter_set::VoterSet;
#[cfg(feature = "derive-codec")]
use parity_scale_codec::{Encode, Decode};
use round::ImportResult;
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "derive-codec", derive(Encode, Decode))]
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))]
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))]
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 best_chain_containing(&self, base: H) -> Option<(H, N)>;
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))]
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))]
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))]
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))]
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))]
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))]
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))]
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))]
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(),
}
}
}
pub struct CommitValidationResult<H, N> {
ghost: Option<(H, N)>,
num_precommits: usize,
num_duplicated_precommits: usize,
num_equivocations: usize,
num_invalid_voters: usize,
}
impl<H, N> CommitValidationResult<H, N> {
pub fn ghost(&self) -> Option<&(H, N)> {
self.ghost.as_ref()
}
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
}
}
impl<H, N> Default for CommitValidationResult<H, N> {
fn default() -> Self {
CommitValidationResult {
ghost: None,
num_precommits: 0,
num_duplicated_precommits: 0,
num_equivocations: 0,
num_invalid_voters: 0,
}
}
}
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<H, N>, 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::default();
validation_result.num_precommits = commit.precommits.len();
let all_precommits_higher_than_target = commit.precommits.iter().all(|signed| {
signed.precommit.target_number >= commit.target_number &&
chain.is_equal_or_descendent_of(
commit.target_hash.clone(),
signed.precommit.target_hash.clone(),
)
});
if !all_precommits_higher_than_target {
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: (commit.target_hash.clone(), commit.target_number),
});
for SignedPrecommit { precommit, id, signature } in &commit.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, valid_voter, .. } => {
if duplicated {
validation_result.num_duplicated_precommits += 1;
}
if !valid_voter {
validation_result.num_invalid_voters += 1;
}
}
}
}
validation_result.ghost = round.precommit_ghost();
Ok(validation_result)
}
#[cfg(feature = "std")]
pub fn process_commit_validation_result<H, N>(
validation_result: CommitValidationResult<H, N>,
mut callback: voter::Callback<voter::CommitProcessingOutcome>,
) {
if validation_result.ghost.is_some() {
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))]
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 {
#[cfg(feature = "derive-codec")]
#[test]
fn codec_was_derived() {
use parity_scale_codec::{Encode, Decode};
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);
}
}