From 6fbe366f23f20f869f3ef1bb53c37d8951012843 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 15 Feb 2018 14:20:18 +0100 Subject: [PATCH] Split BFT into substrate-bft and runtime-specific proposer logic (#72) * reshuffle consensus libraries * polkadot-useful type definitions for statement table * begin BftService * primary selection logic * bft service implementation without I/O * extract out `BlockImport` trait * allow bft primitives to compile on wasm * take polkadot-consensus down to the core. * test for preemption * fix test build --- polkadot/candidate-agreement/Cargo.toml | 9 - .../src/bft/accumulator.rs | 602 --------------- polkadot/candidate-agreement/src/bft/mod.rs | 721 ------------------ polkadot/candidate-agreement/src/bft/tests.rs | 350 --------- .../src/handle_incoming.rs | 214 ------ polkadot/candidate-agreement/src/lib.rs | 625 --------------- .../candidate-agreement/src/round_robin.rs | 164 ---- polkadot/candidate-agreement/src/tests/mod.rs | 385 ---------- polkadot/consensus/Cargo.toml | 15 + polkadot/consensus/src/lib.rs | 243 ++++++ polkadot/primitives/src/parachain.rs | 121 ++- polkadot/primitives/src/transaction.rs | 3 +- .../release/polkadot_runtime.compact.wasm | Bin 75751 -> 80217 bytes .../release/polkadot_runtime.wasm | Bin 75800 -> 80296 bytes polkadot/statement-table/Cargo.toml | 8 + .../src/generic.rs} | 41 +- polkadot/statement-table/src/lib.rs | 108 +++ 17 files changed, 532 insertions(+), 3077 deletions(-) delete mode 100644 polkadot/candidate-agreement/Cargo.toml delete mode 100644 polkadot/candidate-agreement/src/bft/accumulator.rs delete mode 100644 polkadot/candidate-agreement/src/bft/mod.rs delete mode 100644 polkadot/candidate-agreement/src/bft/tests.rs delete mode 100644 polkadot/candidate-agreement/src/handle_incoming.rs delete mode 100644 polkadot/candidate-agreement/src/lib.rs delete mode 100644 polkadot/candidate-agreement/src/round_robin.rs delete mode 100644 polkadot/candidate-agreement/src/tests/mod.rs create mode 100644 polkadot/consensus/Cargo.toml create mode 100644 polkadot/consensus/src/lib.rs create mode 100644 polkadot/statement-table/Cargo.toml rename polkadot/{candidate-agreement/src/table.rs => statement-table/src/generic.rs} (97%) create mode 100644 polkadot/statement-table/src/lib.rs diff --git a/polkadot/candidate-agreement/Cargo.toml b/polkadot/candidate-agreement/Cargo.toml deleted file mode 100644 index 8aa2d0001b..0000000000 --- a/polkadot/candidate-agreement/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "polkadot-candidate-agreement" -version = "0.1.0" -authors = ["Parity Technologies "] - -[dependencies] -futures = "0.1.17" -parking_lot = "0.4" -tokio-timer = "0.1.2" diff --git a/polkadot/candidate-agreement/src/bft/accumulator.rs b/polkadot/candidate-agreement/src/bft/accumulator.rs deleted file mode 100644 index ab035737fb..0000000000 --- a/polkadot/candidate-agreement/src/bft/accumulator.rs +++ /dev/null @@ -1,602 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - -//! Message accumulator for each round of BFT consensus. - -use std::collections::{HashMap, HashSet}; -use std::collections::hash_map::Entry; -use std::hash::Hash; - -use super::{Message, LocalizedMessage}; - -/// Justification for some state at a given round. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct UncheckedJustification { - /// The round. - pub round_number: usize, - /// The digest prepared for. - pub digest: D, - /// Signatures for the prepare messages. - pub signatures: Vec, -} - -impl UncheckedJustification { - /// Fails if there are duplicate signatures or invalid. - /// - /// Provide a closure for checking whether the signature is valid on a - /// digest. - /// - /// The closure should returns a checked justification iff the round number, digest, and signature - /// represent a valid message and the signer was authorized to issue - /// it. - /// - /// The `check_message` closure may vary based on context. - pub fn check(self, threshold: usize, mut check_message: F) - -> Result, Self> - where - F: FnMut(usize, &D, &S) -> Option, - V: Hash + Eq, - { - let checks_out = { - let mut checks_out = || { - let mut voted = HashSet::new(); - - for signature in &self.signatures { - match check_message(self.round_number, &self.digest, signature) { - None => return false, - Some(v) => { - if !voted.insert(v) { - return false; - } - } - } - } - - voted.len() >= threshold - }; - - checks_out() - }; - - if checks_out { - Ok(Justification(self)) - } else { - Err(self) - } - } -} - -/// A checked justification. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Justification(UncheckedJustification); - -impl ::std::ops::Deref for Justification { - type Target = UncheckedJustification; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// Type alias to represent a justification specifically for a prepare. -pub type PrepareJustification = Justification; - -/// The round's state, based on imported messages. -#[derive(PartialEq, Eq, Debug)] -pub enum State { - /// No proposal yet. - Begin, - /// Proposal received. - Proposed(Candidate), - /// Seen n - f prepares for this digest. - Prepared(PrepareJustification), - /// Seen n - f commits for a digest. - Committed(Justification), - /// Seen n - f round-advancement messages. - Advanced(Option>), -} - -#[derive(Debug, Default)] -struct VoteCounts { - prepared: usize, - committed: usize, -} - -/// Accumulates messages for a given round of BFT consensus. -/// -/// This isn't tied to the "view" of a single authority. It -/// keeps accurate track of the state of the BFT consensus based -/// on all messages imported. -#[derive(Debug)] -pub struct Accumulator - where - Candidate: Eq + Clone, - Digest: Hash + Eq + Clone, - AuthorityId: Hash + Eq, - Signature: Eq + Clone, -{ - round_number: usize, - threshold: usize, - round_proposer: AuthorityId, - proposal: Option, - prepares: HashMap, - commits: HashMap, - vote_counts: HashMap, - advance_round: HashSet, - state: State, -} - -impl Accumulator - where - Candidate: Eq + Clone, - Digest: Hash + Eq + Clone, - AuthorityId: Hash + Eq, - Signature: Eq + Clone, -{ - /// Create a new state accumulator. - pub fn new(round_number: usize, threshold: usize, round_proposer: AuthorityId) -> Self { - Accumulator { - round_number, - threshold, - round_proposer, - proposal: None, - prepares: HashMap::new(), - commits: HashMap::new(), - vote_counts: HashMap::new(), - advance_round: HashSet::new(), - state: State::Begin, - } - } - - /// How advance votes we have seen. - pub fn advance_votes(&self) -> usize { - self.advance_round.len() - } - - /// Get the round number. - pub fn round_number(&self) -> usize { - self.round_number.clone() - } - - pub fn proposal(&self) -> Option<&Candidate> { - self.proposal.as_ref() - } - - /// Inspect the current consensus state. - pub fn state(&self) -> &State { - &self.state - } - - /// Import a message. Importing duplicates is fine, but the signature - /// and authorization should have already been checked. - pub fn import_message( - &mut self, - message: LocalizedMessage, - ) - { - // message from different round. - if message.message.round_number() != self.round_number { - return; - } - - let (sender, signature) = (message.sender, message.signature); - - match message.message { - Message::Propose(_, p) => self.import_proposal(p, sender), - Message::Prepare(_, d) => self.import_prepare(d, sender, signature), - Message::Commit(_, d) => self.import_commit(d, sender, signature), - Message::AdvanceRound(_) => self.import_advance_round(sender), - } - } - - fn import_proposal( - &mut self, - proposal: Candidate, - sender: AuthorityId, - ) { - if sender != self.round_proposer || self.proposal.is_some() { return } - - self.proposal = Some(proposal.clone()); - self.state = State::Proposed(proposal); - } - - fn import_prepare( - &mut self, - digest: Digest, - sender: AuthorityId, - signature: Signature, - ) { - // ignore any subsequent prepares by the same sender. - // TODO: if digest is different, that's misbehavior. - let threshold_prepared = if let Entry::Vacant(vacant) = self.prepares.entry(sender) { - vacant.insert((digest.clone(), signature)); - let count = self.vote_counts.entry(digest.clone()).or_insert_with(Default::default); - count.prepared += 1; - - if count.prepared >= self.threshold { - Some(digest) - } else { - None - } - } else { - None - }; - - // only allow transition to prepare from begin or proposed state. - let valid_transition = match self.state { - State::Begin | State::Proposed(_) => true, - _ => false, - }; - - if let (true, Some(threshold_prepared)) = (valid_transition, threshold_prepared) { - let signatures = self.prepares - .values() - .filter(|&&(ref d, _)| d == &threshold_prepared) - .map(|&(_, ref s)| s.clone()) - .collect(); - - self.state = State::Prepared(Justification(UncheckedJustification { - round_number: self.round_number, - digest: threshold_prepared, - signatures: signatures, - })); - } - } - - fn import_commit( - &mut self, - digest: Digest, - sender: AuthorityId, - signature: Signature, - ) { - // ignore any subsequent commits by the same sender. - // TODO: if digest is different, that's misbehavior. - let threshold_committed = if let Entry::Vacant(vacant) = self.commits.entry(sender) { - vacant.insert((digest.clone(), signature)); - let count = self.vote_counts.entry(digest.clone()).or_insert_with(Default::default); - count.committed += 1; - - if count.committed >= self.threshold { - Some(digest) - } else { - None - } - } else { - None - }; - - // transition to concluded state always valid. - // only weird case is if the prior state was "advanced", - // but technically it's the same behavior as if the order of receiving - // the last "advance round" and "commit" messages were reversed. - if let Some(threshold_committed) = threshold_committed { - let signatures = self.commits - .values() - .filter(|&&(ref d, _)| d == &threshold_committed) - .map(|&(_, ref s)| s.clone()) - .collect(); - - self.state = State::Committed(Justification(UncheckedJustification { - round_number: self.round_number, - digest: threshold_committed, - signatures: signatures, - })); - } - } - - fn import_advance_round( - &mut self, - sender: AuthorityId, - ) { - self.advance_round.insert(sender); - - if self.advance_round.len() < self.threshold { return } - - // allow transition to new round only if we haven't produced a justification - // yet. - self.state = match ::std::mem::replace(&mut self.state, State::Begin) { - State::Committed(j) => State::Committed(j), - State::Prepared(j) => State::Advanced(Some(j)), - State::Advanced(j) => State::Advanced(j), - State::Begin | State::Proposed(_) => State::Advanced(None), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Clone, PartialEq, Eq, Debug)] - pub struct Candidate(usize); - - #[derive(Hash, PartialEq, Eq, Clone, Debug)] - pub struct Digest(usize); - - #[derive(Hash, PartialEq, Eq, Debug)] - pub struct AuthorityId(usize); - - #[derive(PartialEq, Eq, Clone, Debug)] - pub struct Signature(usize, usize); - - #[test] - fn justification_checks_out() { - let mut justification = UncheckedJustification { - round_number: 2, - digest: Digest(600), - signatures: (0..10).map(|i| Signature(600, i)).collect(), - }; - - let check_message = |r, d: &Digest, s: &Signature| { - if r == 2 && d.0 == 600 && s.0 == 600 { - Some(AuthorityId(s.1)) - } else { - None - } - }; - - assert!(justification.clone().check(7, &check_message).is_ok()); - assert!(justification.clone().check(11, &check_message).is_err()); - - { - // one bad signature is enough to spoil it. - justification.signatures.push(Signature(1001, 255)); - assert!(justification.clone().check(7, &check_message).is_err()); - - justification.signatures.pop(); - } - // duplicates not allowed. - justification.signatures.extend((0..10).map(|i| Signature(600, i))); - assert!(justification.clone().check(11, &check_message).is_err()); - } - - #[test] - fn accepts_proposal_from_proposer_only() { - let mut accumulator = Accumulator::<_, Digest, _, _>::new(1, 7, AuthorityId(8)); - assert_eq!(accumulator.state(), &State::Begin); - - accumulator.import_message(LocalizedMessage { - sender: AuthorityId(5), - signature: Signature(999, 5), - message: Message::Propose(1, Candidate(999)), - }); - - assert_eq!(accumulator.state(), &State::Begin); - - accumulator.import_message(LocalizedMessage { - sender: AuthorityId(8), - signature: Signature(999, 8), - message: Message::Propose(1, Candidate(999)), - }); - - assert_eq!(accumulator.state(), &State::Proposed(Candidate(999))); - } - - #[test] - fn reaches_prepare_phase() { - let mut accumulator = Accumulator::new(1, 7, AuthorityId(8)); - assert_eq!(accumulator.state(), &State::Begin); - - accumulator.import_message(LocalizedMessage { - sender: AuthorityId(8), - signature: Signature(999, 8), - message: Message::Propose(1, Candidate(999)), - }); - - assert_eq!(accumulator.state(), &State::Proposed(Candidate(999))); - - for i in 0..6 { - accumulator.import_message(LocalizedMessage { - sender: AuthorityId(i), - signature: Signature(999, i), - message: Message::Prepare(1, Digest(999)), - }); - - assert_eq!(accumulator.state(), &State::Proposed(Candidate(999))); - } - - accumulator.import_message(LocalizedMessage { - sender: AuthorityId(7), - signature: Signature(999, 7), - message: Message::Prepare(1, Digest(999)), - }); - - match accumulator.state() { - &State::Prepared(ref j) => assert_eq!(j.digest, Digest(999)), - s => panic!("wrong state: {:?}", s), - } - } - - #[test] - fn prepare_to_commit() { - let mut accumulator = Accumulator::new(1, 7, AuthorityId(8)); - assert_eq!(accumulator.state(), &State::Begin); - - accumulator.import_message(LocalizedMessage { - sender: AuthorityId(8), - signature: Signature(999, 8), - message: Message::Propose(1, Candidate(999)), - }); - - assert_eq!(accumulator.state(), &State::Proposed(Candidate(999))); - - for i in 0..6 { - accumulator.import_message(LocalizedMessage { - sender: AuthorityId(i), - signature: Signature(999, i), - message: Message::Prepare(1, Digest(999)), - }); - - assert_eq!(accumulator.state(), &State::Proposed(Candidate(999))); - } - - accumulator.import_message(LocalizedMessage { - sender: AuthorityId(7), - signature: Signature(999, 7), - message: Message::Prepare(1, Digest(999)), - }); - - match accumulator.state() { - &State::Prepared(ref j) => assert_eq!(j.digest, Digest(999)), - s => panic!("wrong state: {:?}", s), - } - - for i in 0..6 { - accumulator.import_message(LocalizedMessage { - sender: AuthorityId(i), - signature: Signature(999, i), - message: Message::Commit(1, Digest(999)), - }); - - match accumulator.state() { - &State::Prepared(_) => {}, - s => panic!("wrong state: {:?}", s), - } - } - - accumulator.import_message(LocalizedMessage { - sender: AuthorityId(7), - signature: Signature(999, 7), - message: Message::Commit(1, Digest(999)), - }); - - match accumulator.state() { - &State::Committed(ref j) => assert_eq!(j.digest, Digest(999)), - s => panic!("wrong state: {:?}", s), - } - } - - #[test] - fn prepare_to_advance() { - let mut accumulator = Accumulator::new(1, 7, AuthorityId(8)); - assert_eq!(accumulator.state(), &State::Begin); - - accumulator.import_message(LocalizedMessage { - sender: AuthorityId(8), - signature: Signature(999, 8), - message: Message::Propose(1, Candidate(999)), - }); - - assert_eq!(accumulator.state(), &State::Proposed(Candidate(999))); - - for i in 0..7 { - accumulator.import_message(LocalizedMessage { - sender: AuthorityId(i), - signature: Signature(999, i), - message: Message::Prepare(1, Digest(999)), - }); - } - - match accumulator.state() { - &State::Prepared(ref j) => assert_eq!(j.digest, Digest(999)), - s => panic!("wrong state: {:?}", s), - } - - for i in 0..6 { - accumulator.import_message(LocalizedMessage { - sender: AuthorityId(i), - signature: Signature(999, i), - message: Message::AdvanceRound(1), - }); - - match accumulator.state() { - &State::Prepared(_) => {}, - s => panic!("wrong state: {:?}", s), - } - } - - accumulator.import_message(LocalizedMessage { - sender: AuthorityId(7), - signature: Signature(999, 7), - message: Message::AdvanceRound(1), - }); - - match accumulator.state() { - &State::Advanced(Some(_)) => {}, - s => panic!("wrong state: {:?}", s), - } - } - - #[test] - fn conclude_different_than_proposed() { - let mut accumulator = Accumulator::::new(1, 7, AuthorityId(8)); - assert_eq!(accumulator.state(), &State::Begin); - - for i in 0..7 { - accumulator.import_message(LocalizedMessage { - sender: AuthorityId(i), - signature: Signature(999, i), - message: Message::Prepare(1, Digest(999)), - }); - } - - match accumulator.state() { - &State::Prepared(ref j) => assert_eq!(j.digest, Digest(999)), - s => panic!("wrong state: {:?}", s), - } - - for i in 0..7 { - accumulator.import_message(LocalizedMessage { - sender: AuthorityId(i), - signature: Signature(999, i), - message: Message::Commit(1, Digest(999)), - }); - } - - match accumulator.state() { - &State::Committed(ref j) => assert_eq!(j.digest, Digest(999)), - s => panic!("wrong state: {:?}", s), - } - } - - #[test] - fn begin_to_advance() { - let mut accumulator = Accumulator::::new(1, 7, AuthorityId(8)); - assert_eq!(accumulator.state(), &State::Begin); - - for i in 0..7 { - accumulator.import_message(LocalizedMessage { - sender: AuthorityId(i), - signature: Signature(1, i), - message: Message::AdvanceRound(1), - }); - } - - match accumulator.state() { - &State::Advanced(ref j) => assert!(j.is_none()), - s => panic!("wrong state: {:?}", s), - } - } - - #[test] - fn conclude_without_prepare() { - let mut accumulator = Accumulator::::new(1, 7, AuthorityId(8)); - assert_eq!(accumulator.state(), &State::Begin); - - for i in 0..7 { - accumulator.import_message(LocalizedMessage { - sender: AuthorityId(i), - signature: Signature(999, i), - message: Message::Commit(1, Digest(999)), - }); - } - - match accumulator.state() { - &State::Committed(ref j) => assert_eq!(j.digest, Digest(999)), - s => panic!("wrong state: {:?}", s), - } - } -} diff --git a/polkadot/candidate-agreement/src/bft/mod.rs b/polkadot/candidate-agreement/src/bft/mod.rs deleted file mode 100644 index f131e44e1f..0000000000 --- a/polkadot/candidate-agreement/src/bft/mod.rs +++ /dev/null @@ -1,721 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - -//! BFT Agreement based on a rotating proposer in different rounds. - -mod accumulator; - -#[cfg(test)] -mod tests; - -use std::collections::{HashMap, VecDeque}; -use std::fmt::Debug; -use std::hash::Hash; - -use futures::{future, Future, Stream, Sink, Poll, Async, AsyncSink}; - -use self::accumulator::State; - -pub use self::accumulator::{Accumulator, Justification, PrepareJustification, UncheckedJustification}; - -/// Messages over the proposal. -/// Each message carries an associated round number. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Message { - /// Send a full proposal. - Propose(usize, C), - /// Prepare to vote for proposal with digest D. - Prepare(usize, D), - /// Commit to proposal with digest D.. - Commit(usize, D), - /// Propose advancement to a new round. - AdvanceRound(usize), -} - -impl Message { - fn round_number(&self) -> usize { - match *self { - Message::Propose(round, _) => round, - Message::Prepare(round, _) => round, - Message::Commit(round, _) => round, - Message::AdvanceRound(round) => round, - } - } -} - -/// A localized message, including the sender. -#[derive(Debug, Clone)] -pub struct LocalizedMessage { - /// The message received. - pub message: Message, - /// The sender of the message - pub sender: V, - /// The signature of the message. - pub signature: S, -} - -/// Context necessary for agreement. -/// -/// Provides necessary types for protocol messages, and functions necessary for a -/// participant to evaluate and create those messages. -pub trait Context { - /// Candidate proposed. - type Candidate: Debug + Eq + Clone; - /// Candidate digest. - type Digest: Debug + Hash + Eq + Clone; - /// Authority ID. - type AuthorityId: Debug + Hash + Eq + Clone; - /// Signature. - type Signature: Debug + Eq + Clone; - /// A future that resolves when a round timeout is concluded. - type RoundTimeout: Future; - /// A future that resolves when a proposal is ready. - type CreateProposal: Future; - - /// Get the local authority ID. - fn local_id(&self) -> Self::AuthorityId; - - /// Get the best proposal. - fn proposal(&self) -> Self::CreateProposal; - - /// Get the digest of a candidate. - fn candidate_digest(&self, candidate: &Self::Candidate) -> Self::Digest; - - /// Sign a message using the local authority ID. - fn sign_local(&self, message: Message) - -> LocalizedMessage; - - /// Get the proposer for a given round of consensus. - fn round_proposer(&self, round: usize) -> Self::AuthorityId; - - /// Whether the candidate is valid. - fn candidate_valid(&self, candidate: &Self::Candidate) -> bool; - - /// Create a round timeout. The context will determine the correct timeout - /// length, and create a future that will resolve when the timeout is - /// concluded. - fn begin_round_timeout(&self, round: usize) -> Self::RoundTimeout; -} - -/// Communication that can occur between participants in consensus. -#[derive(Debug, Clone)] -pub enum Communication { - /// A consensus message (proposal or vote) - Consensus(LocalizedMessage), - /// Auxiliary communication (just proof-of-lock for now). - Auxiliary(PrepareJustification), -} - -/// Type alias for a localized message using only type parameters from `Context`. -// TODO: actual type alias when it's no longer a warning. -pub struct ContextCommunication(pub Communication); - -impl Clone for ContextCommunication - where - LocalizedMessage: Clone, - PrepareJustification: Clone, -{ - fn clone(&self) -> Self { - ContextCommunication(self.0.clone()) - } -} - -#[derive(Debug)] -struct Sending { - items: VecDeque, - flushing: bool, -} - -impl Sending { - fn with_capacity(n: usize) -> Self { - Sending { - items: VecDeque::with_capacity(n), - flushing: false, - } - } - - fn push(&mut self, item: T) { - self.items.push_back(item); - self.flushing = false; - } - - // process all the sends into the sink. - fn process_all>(&mut self, sink: &mut S) -> Poll<(), S::SinkError> { - while let Some(item) = self.items.pop_front() { - match sink.start_send(item) { - Err(e) => return Err(e), - Ok(AsyncSink::NotReady(item)) => { - self.items.push_front(item); - return Ok(Async::NotReady); - } - Ok(AsyncSink::Ready) => { self.flushing = true; } - } - } - - if self.flushing { - match sink.poll_complete() { - Err(e) => return Err(e), - Ok(Async::NotReady) => return Ok(Async::NotReady), - Ok(Async::Ready(())) => { self.flushing = false; } - } - } - - Ok(Async::Ready(())) - } -} - -/// Error returned when the input stream concludes. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct InputStreamConcluded; - -impl ::std::fmt::Display for InputStreamConcluded { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "{}", ::std::error::Error::description(self)) - } -} - -impl ::std::error::Error for InputStreamConcluded { - fn description(&self) -> &str { - "input stream of messages concluded prematurely" - } -} - -// get the "full BFT" threshold based on an amount of nodes and -// a maximum faulty. if nodes == 3f + 1, then threshold == 2f + 1. -fn bft_threshold(nodes: usize, max_faulty: usize) -> usize { - nodes - max_faulty -} - -/// Committed successfully. -#[derive(Debug, Clone)] -pub struct Committed { - /// The candidate committed for. This will be unknown if - /// we never witnessed the proposal of the last round. - pub candidate: Option, - /// A justification for the candidate. - pub justification: Justification, -} - -struct Locked { - justification: PrepareJustification, -} - -impl Locked { - fn digest(&self) -> &D { - &self.justification.digest - } -} - -// the state of the local node during the current state of consensus. -// -// behavior is different when locked on a proposal. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum LocalState { - Start, - Proposed, - Prepared, - Committed, - VoteAdvance, -} - -// This structure manages a single "view" of consensus. -// -// We maintain two message accumulators: one for the round we are currently in, -// and one for a future round. -// -// We advance the round accumulators when one of two conditions is met: -// - we witness consensus of advancement in the current round. in this case we -// advance by one. -// - a higher threshold-prepare is broadcast to us. in this case we can -// advance to the round of the threshold-prepare. this is an indication -// that we have experienced severe asynchrony/clock drift with the remainder -// of the other authorities, and it is unlikely that we can assist in -// consensus meaningfully. nevertheless we make an attempt. -struct Strategy { - nodes: usize, - max_faulty: usize, - fetching_proposal: Option, - round_timeout: future::Fuse, - local_state: LocalState, - locked: Option>, - notable_candidates: HashMap, - current_accumulator: Accumulator, - future_accumulator: Accumulator, - local_id: C::AuthorityId, -} - -impl Strategy { - fn create(context: &C, nodes: usize, max_faulty: usize) -> Self { - let timeout = context.begin_round_timeout(0); - let threshold = bft_threshold(nodes, max_faulty); - - let current_accumulator = Accumulator::new( - 0, - threshold, - context.round_proposer(0), - ); - - let future_accumulator = Accumulator::new( - 1, - threshold, - context.round_proposer(1), - ); - - Strategy { - nodes, - max_faulty, - current_accumulator, - future_accumulator, - fetching_proposal: None, - local_state: LocalState::Start, - locked: None, - notable_candidates: HashMap::new(), - round_timeout: timeout.fuse(), - local_id: context.local_id(), - } - } - - fn import_message( - &mut self, - msg: LocalizedMessage - ) { - let round_number = msg.message.round_number(); - - if round_number == self.current_accumulator.round_number() { - self.current_accumulator.import_message(msg); - } else if round_number == self.future_accumulator.round_number() { - self.future_accumulator.import_message(msg); - } - } - - fn import_lock_proof( - &mut self, - context: &C, - justification: PrepareJustification, - ) { - // TODO: find a way to avoid processing of the signatures if the sender is - // not the primary or the round number is low. - if justification.round_number > self.current_accumulator.round_number() { - // jump ahead to the prior round as this is an indication of a supermajority - // good nodes being at least on that round. - self.advance_to_round(context, justification.round_number); - } - - let lock_to_new = self.locked.as_ref() - .map_or(true, |l| l.justification.round_number < justification.round_number); - - if lock_to_new { - self.locked = Some(Locked { justification }) - } - } - - // poll the strategy: this will queue messages to be sent and advance - // rounds if necessary. - // - // only call within the context of a `Task`. - fn poll(&mut self, context: &C, sending: &mut Sending>) - -> Poll, E> - where - C::RoundTimeout: Future, - C::CreateProposal: Future, - { - let mut last_watermark = ( - self.current_accumulator.round_number(), - self.local_state - ); - - // poll until either completion or state doesn't change. - loop { - match self.poll_once(context, sending)? { - Async::Ready(x) => return Ok(Async::Ready(x)), - Async::NotReady => { - let new_watermark = ( - self.current_accumulator.round_number(), - self.local_state - ); - - if new_watermark == last_watermark { - return Ok(Async::NotReady) - } else { - last_watermark = new_watermark; - } - } - } - } - } - - // perform one round of polling: attempt to broadcast messages and change the state. - // if the round or internal round-state changes, this should be called again. - fn poll_once(&mut self, context: &C, sending: &mut Sending>) - -> Poll, E> - where - C::RoundTimeout: Future, - C::CreateProposal: Future, - { - self.propose(context, sending)?; - self.prepare(context, sending); - self.commit(context, sending); - self.vote_advance(context, sending)?; - - let advance = match self.current_accumulator.state() { - &State::Advanced(ref p_just) => { - // lock to any witnessed prepare justification. - if let Some(p_just) = p_just.as_ref() { - self.locked = Some(Locked { justification: p_just.clone() }); - } - - let round_number = self.current_accumulator.round_number(); - Some(round_number + 1) - } - &State::Committed(ref just) => { - // fetch the agreed-upon candidate: - // - we may not have received the proposal in the first place - // - there is no guarantee that the proposal we got was agreed upon - // (can happen if faulty primary) - // - look in the candidates of prior rounds just in case. - let candidate = self.current_accumulator - .proposal() - .and_then(|c| if context.candidate_digest(c) == just.digest { - Some(c.clone()) - } else { - None - }) - .or_else(|| self.notable_candidates.get(&just.digest).cloned()); - - let committed = Committed { - candidate, - justification: just.clone() - }; - - return Ok(Async::Ready(committed)) - } - _ => None, - }; - - if let Some(new_round) = advance { - self.advance_to_round(context, new_round); - } - - Ok(Async::NotReady) - } - - fn propose(&mut self, context: &C, sending: &mut Sending>) - -> Result<(), ::Error> - { - if let LocalState::Start = self.local_state { - let mut propose = false; - if let &State::Begin = self.current_accumulator.state() { - let round_number = self.current_accumulator.round_number(); - let primary = context.round_proposer(round_number); - propose = self.local_id == primary; - }; - - if !propose { return Ok(()) } - - // obtain the proposal to broadcast. - let proposal = match self.locked { - Some(ref locked) => { - // TODO: it's possible but very unlikely that we don't have the - // corresponding proposal for what we are locked to. - // - // since this is an edge case on an edge case, it is fine - // to eat the round timeout for now, but it can be optimized by - // broadcasting an advance vote. - self.notable_candidates.get(locked.digest()).cloned() - } - None => { - let res = self.fetching_proposal - .get_or_insert_with(|| context.proposal()) - .poll()?; - - match res { - Async::Ready(p) => Some(p), - Async::NotReady => None, - } - } - }; - - if let Some(proposal) = proposal { - self.fetching_proposal = None; - - let message = Message::Propose( - self.current_accumulator.round_number(), - proposal - ); - - self.import_and_send_message(message, context, sending); - - // broadcast the justification along with the proposal if we are locked. - if let Some(ref locked) = self.locked { - sending.push( - ContextCommunication(Communication::Auxiliary(locked.justification.clone())) - ); - } - - self.local_state = LocalState::Proposed; - } - } - - Ok(()) - } - - fn prepare(&mut self, context: &C, sending: &mut Sending>) { - // prepare only upon start or having proposed. - match self.local_state { - LocalState::Start | LocalState::Proposed => {}, - _ => return - }; - - let mut prepare_for = None; - - // we can't prepare until something was proposed. - if let &State::Proposed(ref candidate) = self.current_accumulator.state() { - let digest = context.candidate_digest(candidate); - - // vote to prepare only if we believe the candidate to be valid and - // we are not locked on some other candidate. - match self.locked { - Some(ref locked) if locked.digest() != &digest => {} - Some(_) => { - // don't check validity if we are locked. - // this is necessary to preserve the liveness property. - prepare_for = Some(digest); - } - None => if context.candidate_valid(candidate) { - prepare_for = Some(digest); - } - } - } - - if let Some(digest) = prepare_for { - let message = Message::Prepare( - self.current_accumulator.round_number(), - digest - ); - - self.import_and_send_message(message, context, sending); - self.local_state = LocalState::Prepared; - } - } - - fn commit(&mut self, context: &C, sending: &mut Sending>) { - // commit only if we haven't voted to advance or committed already - match self.local_state { - LocalState::Committed | LocalState::VoteAdvance => return, - _ => {} - } - - let mut commit_for = None; - - if let &State::Prepared(ref p_just) = self.current_accumulator.state() { - // we are now locked to this prepare justification. - let digest = p_just.digest.clone(); - self.locked = Some(Locked { justification: p_just.clone() }); - commit_for = Some(digest); - } - - if let Some(digest) = commit_for { - let message = Message::Commit( - self.current_accumulator.round_number(), - digest - ); - - self.import_and_send_message(message, context, sending); - self.local_state = LocalState::Committed; - } - } - - fn vote_advance(&mut self, context: &C, sending: &mut Sending>) - -> Result<(), ::Error> - { - // we can vote for advancement under all circumstances unless we have already. - if let LocalState::VoteAdvance = self.local_state { return Ok(()) } - - // if we got f + 1 advance votes, or the timeout has fired, and we haven't - // sent an AdvanceRound message yet, do so. - let mut attempt_advance = self.current_accumulator.advance_votes() > self.max_faulty; - - if let Async::Ready(_) = self.round_timeout.poll()? { - attempt_advance = true; - } - - if attempt_advance { - let message = Message::AdvanceRound( - self.current_accumulator.round_number(), - ); - - self.import_and_send_message(message, context, sending); - self.local_state = LocalState::VoteAdvance; - } - - Ok(()) - } - - fn advance_to_round(&mut self, context: &C, round: usize) { - assert!(round > self.current_accumulator.round_number()); - - let threshold = self.nodes - self.max_faulty; - - self.fetching_proposal = None; - self.round_timeout = context.begin_round_timeout(round).fuse(); - self.local_state = LocalState::Start; - - let new_future = Accumulator::new( - round + 1, - threshold, - context.round_proposer(round + 1), - ); - - // when advancing from a round, store away the witnessed proposal. - // - // if we or other participants end up locked on that candidate, - // we will have it. - if let Some(proposal) = self.current_accumulator.proposal() { - let digest = context.candidate_digest(proposal); - self.notable_candidates.entry(digest).or_insert_with(|| proposal.clone()); - } - - // special case when advancing by a single round. - if self.future_accumulator.round_number() == round { - self.current_accumulator - = ::std::mem::replace(&mut self.future_accumulator, new_future); - } else { - self.future_accumulator = new_future; - self.current_accumulator = Accumulator::new( - round, - threshold, - context.round_proposer(round), - ); - } - } - - fn import_and_send_message( - &mut self, - message: Message, - context: &C, - sending: &mut Sending> - ) { - let signed_message = context.sign_local(message); - self.import_message(signed_message.clone()); - sending.push(ContextCommunication(Communication::Consensus(signed_message))); - } -} - -/// Future that resolves upon BFT agreement for a candidate. -#[must_use = "futures do nothing unless polled"] -pub struct Agreement { - context: C, - input: I, - output: O, - concluded: Option>, - sending: Sending>, - strategy: Strategy, -} - -impl Future for Agreement - where - C: Context, - C::RoundTimeout: Future, - C::CreateProposal: Future, - I: Stream,Error=E>, - O: Sink,SinkError=E>, - E: From, -{ - type Item = Committed; - type Error = E; - - fn poll(&mut self) -> Poll { - // even if we've observed the conclusion, wait until all - // pending outgoing messages are flushed. - if let Some(just) = self.concluded.take() { - return Ok(match self.sending.process_all(&mut self.output)? { - Async::Ready(()) => Async::Ready(just), - Async::NotReady => { - self.concluded = Some(just); - Async::NotReady - } - }) - } - - loop { - let message = match self.input.poll()? { - Async::Ready(msg) => msg.ok_or(InputStreamConcluded)?, - Async::NotReady => break, - }; - - match message.0 { - Communication::Consensus(message) => self.strategy.import_message(message), - Communication::Auxiliary(lock_proof) - => self.strategy.import_lock_proof(&self.context, lock_proof), - } - } - - // try to process timeouts. - let state_machine_res = self.strategy.poll(&self.context, &mut self.sending)?; - - // make progress on flushing all pending messages. - let _ = self.sending.process_all(&mut self.output)?; - - match state_machine_res { - Async::Ready(just) => { - self.concluded = Some(just); - self.poll() - } - Async::NotReady => { - - Ok(Async::NotReady) - } - } - } -} - -/// Attempt to reach BFT agreement on a candidate. -/// -/// `nodes` is the number of nodes in the system. -/// `max_faulty` is the maximum number of faulty nodes. Should be less than -/// 1/3 of `nodes`, otherwise agreement may never be reached. -/// -/// The input stream should never logically conclude. The logic here assumes -/// that messages flushed to the output stream will eventually reach other nodes. -/// -/// Note that it is possible to witness agreement being reached without ever -/// seeing the candidate. Any candidates seen will be checked for validity. -/// -/// Although technically the agreement will always complete (given the eventual -/// delivery of messages), in practice it is possible for this future to -/// conclude without having witnessed the conclusion. -/// In general, this future should be pre-empted by the import of a justification -/// set for this block height. -pub fn agree(context: C, nodes: usize, max_faulty: usize, input: I, output: O) - -> Agreement - where - C: Context, - C::RoundTimeout: Future, - C::CreateProposal: Future, - I: Stream,Error=E>, - O: Sink,SinkError=E>, - E: From, -{ - let strategy = Strategy::create(&context, nodes, max_faulty); - Agreement { - context, - input, - output, - concluded: None, - sending: Sending::with_capacity(4), - strategy: strategy, - } -} diff --git a/polkadot/candidate-agreement/src/bft/tests.rs b/polkadot/candidate-agreement/src/bft/tests.rs deleted file mode 100644 index 10ef932124..0000000000 --- a/polkadot/candidate-agreement/src/bft/tests.rs +++ /dev/null @@ -1,350 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - -//! Tests for the candidate agreement strategy. - -use super::*; - -use tests::Network; - -use std::sync::{Arc, Mutex}; -use std::time::Duration; - -use futures::prelude::*; -use futures::sync::oneshot; -use futures::future::FutureResult; - -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -struct Candidate(usize); - -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -struct Digest(usize); - -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -struct AuthorityId(usize); - -#[derive(Debug, PartialEq, Eq, Clone)] -struct Signature(Message, AuthorityId); - -struct SharedContext { - node_count: usize, - current_round: usize, - awaiting_round_timeouts: HashMap>>, -} - -#[derive(Debug)] -struct Error; - -impl From for Error { - fn from(_: InputStreamConcluded) -> Error { - Error - } -} - -impl SharedContext { - fn new(node_count: usize) -> Self { - SharedContext { - node_count, - current_round: 0, - awaiting_round_timeouts: HashMap::new() - } - } - - fn round_timeout(&mut self, round: usize) -> Box> { - let (tx, rx) = oneshot::channel(); - if round < self.current_round { - tx.send(()).unwrap(); - } else { - self.awaiting_round_timeouts - .entry(round) - .or_insert_with(Vec::new) - .push(tx); - } - - Box::new(rx.map_err(|_| Error)) - } - - fn bump_round(&mut self) { - let awaiting_timeout = self.awaiting_round_timeouts - .remove(&self.current_round) - .unwrap_or_else(Vec::new); - - for tx in awaiting_timeout { - let _ = tx.send(()); - } - - self.current_round += 1; - } - - fn round_proposer(&self, round: usize) -> AuthorityId { - AuthorityId(round % self.node_count) - } -} - -struct TestContext { - local_id: AuthorityId, - proposal: Mutex, - shared: Arc>, -} - -impl Context for TestContext { - type Candidate = Candidate; - type Digest = Digest; - type AuthorityId = AuthorityId; - type Signature = Signature; - type RoundTimeout = Box>; - type CreateProposal = FutureResult; - - fn local_id(&self) -> AuthorityId { - self.local_id.clone() - } - - fn proposal(&self) -> Self::CreateProposal { - let proposal = { - let mut p = self.proposal.lock().unwrap(); - let x = *p; - *p = (*p * 2) + 1; - x - }; - - Ok(Candidate(proposal)).into_future() - } - - fn candidate_digest(&self, candidate: &Candidate) -> Digest { - Digest(candidate.0) - } - - fn sign_local(&self, message: Message) - -> LocalizedMessage - { - let signature = Signature(message.clone(), self.local_id.clone()); - LocalizedMessage { - message, - signature, - sender: self.local_id.clone() - } - } - - fn round_proposer(&self, round: usize) -> AuthorityId { - self.shared.lock().unwrap().round_proposer(round) - } - - fn candidate_valid(&self, candidate: &Candidate) -> bool { - candidate.0 % 3 != 0 - } - - fn begin_round_timeout(&self, round: usize) -> Self::RoundTimeout { - self.shared.lock().unwrap().round_timeout(round) - } -} - -fn timeout_in(t: Duration) -> oneshot::Receiver<()> { - let (tx, rx) = oneshot::channel(); - ::std::thread::spawn(move || { - ::std::thread::sleep(t); - let _ = tx.send(()); - }); - - rx -} - -#[test] -fn consensus_completes_with_minimum_good() { - let node_count = 10; - let max_faulty = 3; - - let shared_context = Arc::new(Mutex::new(SharedContext::new(node_count))); - - let (network, net_send, net_recv) = Network::new(node_count); - network.route_on_thread(); - - let nodes = net_send - .into_iter() - .zip(net_recv) - .take(node_count - max_faulty) - .enumerate() - .map(|(i, (tx, rx))| { - let ctx = TestContext { - local_id: AuthorityId(i), - proposal: Mutex::new(i), - shared: shared_context.clone(), - }; - - agree( - ctx, - node_count, - max_faulty, - rx.map_err(|_| Error), - tx.sink_map_err(|_| Error).with(move |t| Ok((i, t))), - ) - }) - .collect::>(); - - ::std::thread::spawn(move || { - let mut timeout = ::std::time::Duration::from_millis(50); - loop { - ::std::thread::sleep(timeout.clone()); - shared_context.lock().unwrap().bump_round(); - timeout *= 2; - } - }); - - let timeout = timeout_in(Duration::from_millis(500)).map_err(|_| Error); - let results = ::futures::future::join_all(nodes) - .map(Some) - .select(timeout.map(|_| None)) - .wait() - .map(|(i, _)| i) - .map_err(|(e, _)| e) - .expect("to complete") - .expect("to not time out"); - - for result in &results { - assert_eq!(&result.justification.digest, &results[0].justification.digest); - } -} - -#[test] -fn consensus_does_not_complete_without_enough_nodes() { - let node_count = 10; - let max_faulty = 3; - - let shared_context = Arc::new(Mutex::new(SharedContext::new(node_count))); - - let (network, net_send, net_recv) = Network::new(node_count); - network.route_on_thread(); - - let nodes = net_send - .into_iter() - .zip(net_recv) - .take(node_count - max_faulty - 1) - .enumerate() - .map(|(i, (tx, rx))| { - let ctx = TestContext { - local_id: AuthorityId(i), - proposal: Mutex::new(i), - shared: shared_context.clone(), - }; - - agree( - ctx, - node_count, - max_faulty, - rx.map_err(|_| Error), - tx.sink_map_err(|_| Error).with(move |t| Ok((i, t))), - ) - }) - .collect::>(); - - let timeout = timeout_in(Duration::from_millis(500)).map_err(|_| Error); - let result = ::futures::future::join_all(nodes) - .map(Some) - .select(timeout.map(|_| None)) - .wait() - .map(|(i, _)| i) - .map_err(|(e, _)| e) - .expect("to complete"); - - assert!(result.is_none(), "not enough online nodes"); -} - -#[test] -fn threshold_plus_one_locked_on_proposal_only_one_with_candidate() { - let node_count = 10; - let max_faulty = 3; - - let locked_proposal = Candidate(999_999_999); - let locked_digest = Digest(999_999_999); - let locked_round = 1; - let justification = UncheckedJustification { - round_number: locked_round, - digest: locked_digest.clone(), - signatures: (0..7) - .map(|i| Signature(Message::Prepare(locked_round, locked_digest.clone()), AuthorityId(i))) - .collect() - }.check(7, |_, _, s| Some(s.1.clone())).unwrap(); - - let mut shared_context = SharedContext::new(node_count); - shared_context.current_round = locked_round + 1; - let shared_context = Arc::new(Mutex::new(shared_context)); - - let (network, net_send, net_recv) = Network::new(node_count); - network.route_on_thread(); - - let nodes = net_send - .into_iter() - .zip(net_recv) - .enumerate() - .map(|(i, (tx, rx))| { - let ctx = TestContext { - local_id: AuthorityId(i), - proposal: Mutex::new(i), - shared: shared_context.clone(), - }; - - let mut agreement = agree( - ctx, - node_count, - max_faulty, - rx.map_err(|_| Error), - tx.sink_map_err(|_| Error).with(move |t| Ok((i, t))), - ); - - agreement.strategy.advance_to_round( - &agreement.context, - locked_round + 1 - ); - - if i <= max_faulty { - agreement.strategy.locked = Some(Locked { - justification: justification.clone(), - }) - } - - if i == max_faulty { - agreement.strategy.notable_candidates.insert( - locked_digest.clone(), - locked_proposal.clone(), - ); - } - - agreement - }) - .collect::>(); - - ::std::thread::spawn(move || { - let mut timeout = ::std::time::Duration::from_millis(50); - loop { - ::std::thread::sleep(timeout.clone()); - shared_context.lock().unwrap().bump_round(); - timeout *= 2; - } - }); - - let timeout = timeout_in(Duration::from_millis(500)).map_err(|_| Error); - let results = ::futures::future::join_all(nodes) - .map(Some) - .select(timeout.map(|_| None)) - .wait() - .map(|(i, _)| i) - .map_err(|(e, _)| e) - .expect("to complete") - .expect("to not time out"); - - for result in &results { - assert_eq!(&result.justification.digest, &locked_digest); - } -} diff --git a/polkadot/candidate-agreement/src/handle_incoming.rs b/polkadot/candidate-agreement/src/handle_incoming.rs deleted file mode 100644 index 625c950784..0000000000 --- a/polkadot/candidate-agreement/src/handle_incoming.rs +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - -//! A stream that handles incoming messages to the BFT agreement module and statement -//! table. It forwards as necessary, and dispatches requests for determining availability -//! and validity of candidates as necessary. - -use std::collections::HashSet; - -use futures::prelude::*; -use futures::stream::{Fuse, FuturesUnordered}; -use futures::sync::mpsc; - -use table::{self, Statement, Context as TableContext}; - -use super::{Context, CheckedMessage, SharedTable, TypeResolve}; - -enum CheckResult { - Available, - Unavailable, - Valid, - Invalid, -} - -enum Checking { - Availability(D, A), - Validity(D, V), -} - -impl Future for Checking - where - D: Clone, - A: Future, - V: Future, -{ - type Item = (D, CheckResult); - type Error = E; - - fn poll(&mut self) -> Poll { - Ok(Async::Ready(match *self { - Checking::Availability(ref digest, ref mut f) => { - match try_ready!(f.poll()) { - true => (digest.clone(), CheckResult::Available), - false => (digest.clone(), CheckResult::Unavailable), - } - } - Checking::Validity(ref digest, ref mut f) => { - match try_ready!(f.poll()) { - true => (digest.clone(), CheckResult::Valid), - false => (digest.clone(), CheckResult::Invalid), - } - } - })) - } -} - -/// Handles incoming messages to the BFT service and statement table. -/// -/// Also triggers requests for determining validity and availability of other -/// parachain candidates. -pub struct HandleIncoming { - table: SharedTable, - messages_in: Fuse, - bft_out: mpsc::UnboundedSender<::BftCommunication>, - local_id: C::AuthorityId, - requesting_about: FuturesUnordered::Future, - ::Future, - >>, - checked_validity: HashSet, - checked_availability: HashSet, -} - -impl HandleIncoming { - fn sign_and_import_statement(&self, digest: C::Digest, result: CheckResult) { - let statement = match result { - CheckResult::Valid => Statement::Valid(digest), - CheckResult::Invalid => Statement::Invalid(digest), - CheckResult::Available => Statement::Available(digest), - CheckResult::Unavailable => return, // no such statement and not provable. - }; - - // TODO: trigger broadcast to peers immediately? - self.table.sign_and_import(statement); - } - - fn import_message(&mut self, origin: C::AuthorityId, message: CheckedMessage) { - match message { - CheckedMessage::Bft(msg) => { let _ = self.bft_out.unbounded_send(msg); } - CheckedMessage::Table(table_messages) => { - // import all table messages and check for any that we - // need to produce statements for. - let msg_iter = table_messages - .into_iter() - .map(|m| (m, Some(origin.clone()))); - let summaries: Vec<_> = self.table.import_statements(msg_iter); - - for summary in summaries { - self.dispatch_on_summary(summary) - } - } - } - } - - // on new candidates in our group, begin checking validity. - // on new candidates in our availability sphere, begin checking availability. - fn dispatch_on_summary(&mut self, summary: table::Summary) { - let is_validity_member = - self.table.context().is_member_of(&self.local_id, &summary.group_id); - - let is_availability_member = - self.table.context().is_availability_guarantor_of(&self.local_id, &summary.group_id); - - let digest = &summary.candidate; - - // TODO: consider a strategy based on the number of candidate votes as well. - let checking_validity = - is_validity_member && - self.checked_validity.insert(digest.clone()) && - self.table.proposed_digest() != Some(digest.clone()); - - let checking_availability = is_availability_member && self.checked_availability.insert(digest.clone()); - - if checking_validity || checking_availability { - let context = &*self.table.context(); - let requesting_about = &mut self.requesting_about; - self.table.with_candidate(digest, |c| match c { - None => {} // TODO: handle table inconsistency somehow? - Some(candidate) => { - if checking_validity { - let future = context.check_validity(candidate).into_future(); - let checking = Checking::Validity(digest.clone(), future); - requesting_about.push(checking); - } - - if checking_availability { - let future = context.check_availability(candidate).into_future(); - let checking = Checking::Availability(digest.clone(), future); - requesting_about.push(checking); - } - } - }) - } - } -} - -impl HandleIncoming - where - C: Context, - I: Stream),Error=E>, - C::CheckAvailability: IntoFuture, - C::CheckCandidate: IntoFuture, -{ - pub fn new( - table: SharedTable, - messages_in: I, - bft_out: mpsc::UnboundedSender<::BftCommunication>, - ) -> Self { - let local_id = table.context().local_id(); - - HandleIncoming { - table, - bft_out, - local_id, - messages_in: messages_in.fuse(), - requesting_about: FuturesUnordered::new(), - checked_validity: HashSet::new(), - checked_availability: HashSet::new(), - } - } -} - -impl Future for HandleIncoming - where - C: Context, - I: Stream),Error=E>, - C::CheckAvailability: IntoFuture, - C::CheckCandidate: IntoFuture, -{ - type Item = (); - type Error = E; - - fn poll(&mut self) -> Poll<(), E> { - loop { - // FuturesUnordered is safe to poll after it has completed. - while let Async::Ready(Some((d, r))) = self.requesting_about.poll()? { - self.sign_and_import_statement(d, r); - } - - match try_ready!(self.messages_in.poll()) { - None => if self.requesting_about.is_empty() { - return Ok(Async::Ready(())) - } else { - return Ok(Async::NotReady) - }, - Some((origin, msg)) => self.import_message(origin, msg), - } - } - } -} diff --git a/polkadot/candidate-agreement/src/lib.rs b/polkadot/candidate-agreement/src/lib.rs deleted file mode 100644 index 2cf4be5c54..0000000000 --- a/polkadot/candidate-agreement/src/lib.rs +++ /dev/null @@ -1,625 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - -//! Propagation and agreement of candidates. -//! -//! Authorities are split into groups by parachain, and each authority might come -//! up its own candidate for their parachain. Within groups, authorities pass around -//! their candidates and produce statements of validity. -//! -//! Any candidate that receives majority approval by the authorities in a group -//! may be subject to inclusion, unless any authorities flag that candidate as invalid. -//! -//! Wrongly flagging as invalid should be strongly disincentivized, so that in the -//! equilibrium state it is not expected to happen. Likewise with the submission -//! of invalid blocks. -//! -//! Groups themselves may be compromised by malicious authorities. - -#[macro_use] -extern crate futures; -extern crate parking_lot; -extern crate tokio_timer; - -use std::collections::{HashMap, HashSet}; -use std::fmt::Debug; -use std::hash::Hash; -use std::sync::Arc; -use std::time::Duration; - -use futures::prelude::*; -use futures::sync::{mpsc, oneshot}; -use parking_lot::Mutex; -use tokio_timer::Timer; - -use table::Table; - -mod bft; -mod handle_incoming; -mod round_robin; -mod table; - -#[cfg(test)] -pub mod tests; - -/// Context necessary for agreement. -pub trait Context: Send + Clone { - /// A authority ID - type AuthorityId: Debug + Hash + Eq + Clone + Ord; - /// The digest (hash or other unique attribute) of a candidate. - type Digest: Debug + Hash + Eq + Clone; - /// The group ID type - type GroupId: Debug + Hash + Ord + Eq + Clone; - /// A signature type. - type Signature: Debug + Eq + Clone; - /// Candidate type. In practice this will be a candidate receipt. - type ParachainCandidate: Debug + Ord + Eq + Clone; - /// The actual block proposal type. This is what is agreed upon, and - /// is composed of multiple candidates. - type Proposal: Debug + Eq + Clone; - - /// A future that resolves when a candidate is checked for validity. - /// - /// In Polkadot, this will involve fetching the corresponding block data, - /// producing the necessary ingress, and running the parachain validity function. - type CheckCandidate: IntoFuture; - - /// A future that resolves when availability of a candidate's external - /// data is checked. - type CheckAvailability: IntoFuture; - - /// The statement batch type. - type StatementBatch: StatementBatch< - Self::AuthorityId, - table::SignedStatement, - >; - - /// Get the digest of a candidate. - fn candidate_digest(candidate: &Self::ParachainCandidate) -> Self::Digest; - - /// Get the digest of a proposal. - fn proposal_digest(proposal: &Self::Proposal) -> Self::Digest; - - /// Get the group of a candidate. - fn candidate_group(candidate: &Self::ParachainCandidate) -> Self::GroupId; - - /// Get the primary for a given round. - fn round_proposer(&self, round: usize) -> Self::AuthorityId; - - /// Check a candidate for validity. - fn check_validity(&self, candidate: &Self::ParachainCandidate) -> Self::CheckCandidate; - - /// Check availability of candidate data. - fn check_availability(&self, candidate: &Self::ParachainCandidate) -> Self::CheckAvailability; - - /// Attempt to combine a set of parachain candidates into a proposal. - /// - /// This may arbitrarily return `None`, but the intent is for `Some` - /// to only be returned when candidates from enough groups are known. - /// - /// "enough" may be subjective as well. - fn create_proposal(&self, candidates: Vec<&Self::ParachainCandidate>) - -> Option; - - /// Check validity of a proposal. This should call out to the `check_candidate` - /// function for all parachain candidates contained within it, as well as - /// checking other validity constraints of the proposal. - fn proposal_valid(&self, proposal: &Self::Proposal, check_candidate: F) -> bool - where F: FnMut(&Self::ParachainCandidate) -> bool; - - /// Get the local authority ID. - fn local_id(&self) -> Self::AuthorityId; - - /// Sign a table validity statement with the local key. - fn sign_table_statement( - &self, - statement: &table::Statement - ) -> Self::Signature; - - /// Sign a BFT agreement message. - fn sign_bft_message(&self, &bft::Message) -> Self::Signature; -} - -/// Helper for type resolution for contexts until type aliases apply bounds. -pub trait TypeResolve { - type SignedTableStatement; - type BftCommunication; - type BftCommitted; - type Misbehavior; -} - -impl TypeResolve for C { - type SignedTableStatement = table::SignedStatement; - type BftCommunication = bft::Communication; - type BftCommitted = bft::Committed; - type Misbehavior = table::Misbehavior; -} - -/// Information about a specific group. -#[derive(Debug, Clone)] -pub struct GroupInfo { - /// Authorities meant to check validity of candidates. - pub validity_guarantors: HashSet, - /// Authorities meant to check availability of candidate data. - pub availability_guarantors: HashSet, - /// Number of votes needed for validity. - pub needed_validity: usize, - /// Number of votes needed for availability. - pub needed_availability: usize, -} - -struct TableContext { - context: C, - groups: HashMap>, -} - -impl ::std::ops::Deref for TableContext { - type Target = C; - - fn deref(&self) -> &C { - &self.context - } -} - -impl table::Context for TableContext { - type AuthorityId = C::AuthorityId; - type Digest = C::Digest; - type GroupId = C::GroupId; - type Signature = C::Signature; - type Candidate = C::ParachainCandidate; - - fn candidate_digest(candidate: &Self::Candidate) -> Self::Digest { - C::candidate_digest(candidate) - } - - fn candidate_group(candidate: &Self::Candidate) -> Self::GroupId { - C::candidate_group(candidate) - } - - fn is_member_of(&self, authority: &Self::AuthorityId, group: &Self::GroupId) -> bool { - self.groups.get(group).map_or(false, |g| g.validity_guarantors.contains(authority)) - } - - fn is_availability_guarantor_of(&self, authority: &Self::AuthorityId, group: &Self::GroupId) -> bool { - self.groups.get(group).map_or(false, |g| g.availability_guarantors.contains(authority)) - } - - fn requisite_votes(&self, group: &Self::GroupId) -> (usize, usize) { - self.groups.get(group).map_or( - (usize::max_value(), usize::max_value()), - |g| (g.needed_validity, g.needed_availability), - ) - } -} - -// A shared table object. -struct SharedTableInner { - table: Table>, - proposed_digest: Option, - awaiting_proposal: Vec>, -} - -impl SharedTableInner { - fn import_statement( - &mut self, - context: &TableContext, - statement: ::SignedTableStatement, - received_from: Option - ) -> Option> { - self.table.import_statement(context, statement, received_from) - } - - fn update_proposal(&mut self, context: &TableContext) { - if self.awaiting_proposal.is_empty() { return } - let proposal_candidates = self.table.proposed_candidates(context); - if let Some(proposal) = context.context.create_proposal(proposal_candidates) { - for sender in self.awaiting_proposal.drain(..) { - let _ = sender.send(proposal.clone()); - } - } - } - - fn get_proposal(&mut self, context: &TableContext) -> oneshot::Receiver { - let (tx, rx) = oneshot::channel(); - self.awaiting_proposal.push(tx); - self.update_proposal(context); - rx - } - - fn proposal_valid(&mut self, context: &TableContext, proposal: &C::Proposal) -> bool { - context.context.proposal_valid(proposal, |contained_candidate| { - // check that the candidate is valid (has enough votes) - let digest = C::candidate_digest(contained_candidate); - self.table.candidate_includable(&digest, context) - }) - } -} - -/// A shared table object. -pub struct SharedTable { - context: Arc>, - inner: Arc>>, -} - -impl Clone for SharedTable { - fn clone(&self) -> Self { - SharedTable { - context: self.context.clone(), - inner: self.inner.clone() - } - } -} - -impl SharedTable { - /// Create a new shared table. - pub fn new(context: C, groups: HashMap>) -> Self { - SharedTable { - context: Arc::new(TableContext { context, groups }), - inner: Arc::new(Mutex::new(SharedTableInner { - table: Table::default(), - awaiting_proposal: Vec::new(), - proposed_digest: None, - })) - } - } - - /// Import a single statement. - pub fn import_statement( - &self, - statement: ::SignedTableStatement, - received_from: Option, - ) -> Option> { - self.inner.lock().import_statement(&*self.context, statement, received_from) - } - - /// Sign and import a local statement. - pub fn sign_and_import( - &self, - statement: table::Statement, - ) -> Option> { - let proposed_digest = match statement { - table::Statement::Candidate(ref c) => Some(C::candidate_digest(c)), - _ => None, - }; - - let signed_statement = table::SignedStatement { - signature: self.context.sign_table_statement(&statement), - sender: self.context.local_id(), - statement, - }; - - let mut inner = self.inner.lock(); - if proposed_digest.is_some() { - inner.proposed_digest = proposed_digest; - } - - inner.import_statement(&*self.context, signed_statement, None) - } - - /// Import many statements at once. - /// - /// Provide an iterator yielding pairs of (statement, received_from). - pub fn import_statements(&self, iterable: I) -> U - where - I: IntoIterator::SignedTableStatement, Option)>, - U: ::std::iter::FromIterator>, - { - let mut inner = self.inner.lock(); - - iterable.into_iter().filter_map(move |(statement, received_from)| { - inner.import_statement(&*self.context, statement, received_from) - }).collect() - } - - /// Update the proposal sealing. - pub fn update_proposal(&self) { - self.inner.lock().update_proposal(&*self.context) - } - - /// Register interest in receiving a proposal when ready. - /// If one is ready immediately, it will be provided. - pub fn get_proposal(&self) -> oneshot::Receiver { - self.inner.lock().get_proposal(&*self.context) - } - - /// Check if a proposal is valid. - pub fn proposal_valid(&self, proposal: &C::Proposal) -> bool { - self.inner.lock().proposal_valid(&*self.context, proposal) - } - - /// Execute a closure using a specific candidate. - /// - /// Deadlocks if called recursively. - pub fn with_candidate(&self, digest: &C::Digest, f: F) -> U - where F: FnOnce(Option<&C::ParachainCandidate>) -> U - { - let inner = self.inner.lock(); - f(inner.table.get_candidate(digest)) - } - - /// Get all witnessed misbehavior. - pub fn get_misbehavior(&self) -> HashMap::Misbehavior> { - self.inner.lock().table.get_misbehavior().clone() - } - - /// Fill a statement batch. - pub fn fill_batch(&self, batch: &mut C::StatementBatch) { - self.inner.lock().table.fill_batch(batch); - } - - /// Get the local proposed candidate digest. - pub fn proposed_digest(&self) -> Option { - self.inner.lock().proposed_digest.clone() - } - - // Get a handle to the table context. - fn context(&self) -> &TableContext { - &*self.context - } -} - -/// Errors that can occur during agreement. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Error { - IoTerminated, - FaultyTimer, - CannotPropose, -} - -impl From for Error { - fn from(_: bft::InputStreamConcluded) -> Error { - Error::IoTerminated - } -} - -/// Context owned by the BFT future necessary to execute the logic. -pub struct BftContext { - context: C, - table: SharedTable, - timer: Timer, - round_timeout_multiplier: u64, -} - -impl bft::Context for BftContext - where C::Proposal: 'static, -{ - type AuthorityId = C::AuthorityId; - type Digest = C::Digest; - type Signature = C::Signature; - type Candidate = C::Proposal; - type RoundTimeout = Box>; - type CreateProposal = Box>; - - fn local_id(&self) -> Self::AuthorityId { - self.context.local_id() - } - - fn proposal(&self) -> Self::CreateProposal { - Box::new(self.table.get_proposal().map_err(|_| Error::CannotPropose)) - } - - fn candidate_digest(&self, candidate: &Self::Candidate) -> Self::Digest { - C::proposal_digest(candidate) - } - - fn sign_local(&self, message: bft::Message) - -> bft::LocalizedMessage - { - let sender = self.local_id(); - let signature = self.context.sign_bft_message(&message); - bft::LocalizedMessage { - message, - sender, - signature, - } - } - - fn round_proposer(&self, round: usize) -> Self::AuthorityId { - self.context.round_proposer(round) - } - - fn candidate_valid(&self, proposal: &Self::Candidate) -> bool { - self.table.proposal_valid(proposal) - } - - fn begin_round_timeout(&self, round: usize) -> Self::RoundTimeout { - let round = ::std::cmp::min(63, round) as u32; - let timeout = 1u64.checked_shl(round) - .unwrap_or_else(u64::max_value) - .saturating_mul(self.round_timeout_multiplier); - - Box::new(self.timer.sleep(Duration::from_secs(timeout)) - .map_err(|_| Error::FaultyTimer)) - } -} - - -/// Parameters necessary for agreement. -pub struct AgreementParams { - /// The context itself. - pub context: C, - /// For scheduling timeouts. - pub timer: Timer, - /// The statement table. - pub table: SharedTable, - /// The number of nodes. - pub nodes: usize, - /// The maximum number of faulty nodes. - pub max_faulty: usize, - /// The round timeout multiplier: 2^round_number is multiplied by this. - pub round_timeout_multiplier: u64, - /// The maximum amount of messages to queue. - pub message_buffer_size: usize, - /// Interval to attempt forming proposals over. - pub form_proposal_interval: Duration, -} - -/// Recovery for messages -pub trait MessageRecovery { - /// The unchecked message type. This implies that work hasn't been done - /// to decode the payload and check and authenticate a signature. - type UncheckedMessage; - - /// Attempt to transform a checked message into an unchecked. - fn check_message(&self, Self::UncheckedMessage) -> Option>; -} - -/// A batch of statements to send out. -pub trait StatementBatch { - /// Get the target authorities of these statements. - fn targets(&self) -> &[V]; - - /// If the batch is empty. - fn is_empty(&self) -> bool; - - /// Push a statement onto the batch. Returns false when the batch is full. - /// - /// This is meant to do work like incrementally serializing the statements - /// into a vector of bytes while making sure the length is below a certain - /// amount. - fn push(&mut self, statement: T) -> bool; -} - -/// Recovered and fully checked messages. -pub enum CheckedMessage { - /// Messages meant for the BFT agreement logic. - Bft(::BftCommunication), - /// Statements circulating about the table. - Table(Vec<::SignedTableStatement>), -} - -/// Outgoing messages to the network. -#[derive(Debug, Clone)] -pub enum OutgoingMessage { - /// Messages meant for BFT agreement peers. - Bft(::BftCommunication), - /// Batches of table statements. - Table(C::StatementBatch), -} - -/// Create an agreement future, and I/O streams. -// TODO: kill 'static bounds and use impl Future. -pub fn agree< - Context, - NetIn, - NetOut, - Recovery, - PropagateStatements, - LocalCandidate, - Err, ->( - params: AgreementParams, - net_in: NetIn, - net_out: NetOut, - recovery: Recovery, - propagate_statements: PropagateStatements, - local_candidate: LocalCandidate, -) - -> Box::BftCommitted,Error=Error>> - where - Context: ::Context + 'static, - Context::CheckCandidate: IntoFuture, - Context::CheckAvailability: IntoFuture, - NetIn: Stream),Error=Err> + 'static, - NetOut: Sink> + 'static, - Recovery: MessageRecovery + 'static, - PropagateStatements: Stream + 'static, - LocalCandidate: IntoFuture + 'static -{ - let (bft_in_in, bft_in_out) = mpsc::unbounded(); - let (bft_out_in, bft_out_out) = mpsc::unbounded(); - - let agreement = { - let bft_context = BftContext { - context: params.context, - table: params.table.clone(), - timer: params.timer.clone(), - round_timeout_multiplier: params.round_timeout_multiplier, - }; - - bft::agree( - bft_context, - params.nodes, - params.max_faulty, - bft_in_out.map(bft::ContextCommunication).map_err(|_| Error::IoTerminated), - bft_out_in.sink_map_err(|_| Error::IoTerminated), - ) - }; - - let route_messages_in = { - let round_robin = round_robin::RoundRobinBuffer::new(net_in, params.message_buffer_size); - - let round_robin_recovered = round_robin - .filter_map(move |(sender, msg)| recovery.check_message(msg).map(move |x| (sender, x))); - - handle_incoming::HandleIncoming::new( - params.table.clone(), - round_robin_recovered, - bft_in_in, - ).map_err(|_| Error::IoTerminated) - }; - - let route_messages_out = { - let table = params.table.clone(); - let periodic_table_statements = propagate_statements - .or_else(|_| ::futures::future::empty()) // halt the stream instead of error. - .map(move |mut batch| { table.fill_batch(&mut batch); batch }) - .filter(|b| !b.is_empty()) - .map(OutgoingMessage::Table); - - let complete_out_stream = bft_out_out - .map_err(|_| Error::IoTerminated) - .map(|bft::ContextCommunication(x)| x) - .map(OutgoingMessage::Bft) - .select(periodic_table_statements); - - net_out.sink_map_err(|_| Error::IoTerminated).send_all(complete_out_stream) - }; - - let import_local_candidate = { - let table = params.table.clone(); - local_candidate - .into_future() - .map(table::Statement::Candidate) - .map(Some) - .or_else(|_| Ok(None)) - .map(move |s| if let Some(s) = s { - table.sign_and_import(s); - }) - }; - - let create_proposal_on_interval = { - let table = params.table; - params.timer.interval(params.form_proposal_interval) - .map_err(|_| Error::FaultyTimer) - .for_each(move |_| { table.update_proposal(); Ok(()) }) - }; - - // if these auxiliary futures terminate before the agreement, then - // that is an error. - let auxiliary_futures = route_messages_in.join4( - create_proposal_on_interval, - route_messages_out, - import_local_candidate, - ).and_then(|_| Err(Error::IoTerminated)); - - let future = agreement - .select(auxiliary_futures) - .map(|(committed, _)| committed) - .map_err(|(e, _)| e); - - Box::new(future) -} diff --git a/polkadot/candidate-agreement/src/round_robin.rs b/polkadot/candidate-agreement/src/round_robin.rs deleted file mode 100644 index 3f98507cab..0000000000 --- a/polkadot/candidate-agreement/src/round_robin.rs +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - -//! Round-robin buffer for incoming messages. -//! -//! This takes batches of messages associated with a sender as input, -//! and yields messages in a fair order by sender. - -use std::collections::{Bound, BTreeMap, VecDeque}; - -use futures::prelude::*; -use futures::stream::Fuse; - -/// Implementation of the round-robin buffer for incoming messages. -#[derive(Debug)] -pub struct RoundRobinBuffer { - buffer: BTreeMap>, - last_processed_from: Option, - stored_messages: usize, - max_messages: usize, - inner: Fuse, -} - -impl RoundRobinBuffer { - /// Create a new round-robin buffer which holds up to a maximum - /// amount of messages. - pub fn new(stream: S, buffer_size: usize) -> Self { - RoundRobinBuffer { - buffer: BTreeMap::new(), - last_processed_from: None, - stored_messages: 0, - max_messages: buffer_size, - inner: stream.fuse(), - } - } -} - -impl RoundRobinBuffer { - fn next_message(&mut self) -> Option<(V, M)> { - if self.stored_messages == 0 { - return None - } - - // first pick up from the last authority we processed a message from - let mut next = { - let lower_bound = match self.last_processed_from { - None => Bound::Unbounded, - Some(ref x) => Bound::Excluded(x.clone()), - }; - - self.buffer.range_mut((lower_bound, Bound::Unbounded)) - .filter_map(|(k, v)| v.pop_front().map(|v| (k.clone(), v))) - .next() - }; - - // but wrap around to the beginning again if we got nothing. - if next.is_none() { - next = self.buffer.iter_mut() - .filter_map(|(k, v)| v.pop_front().map(|v| (k.clone(), v))) - .next(); - } - - if let Some((ref authority, _)) = next { - self.stored_messages -= 1; - self.last_processed_from = Some(authority.clone()); - } - - next - } - - // import messages, discarding when the buffer is full. - fn import_messages(&mut self, sender: V, messages: Vec) { - let space_remaining = self.max_messages - self.stored_messages; - self.stored_messages += ::std::cmp::min(space_remaining, messages.len()); - - let v = self.buffer.entry(sender).or_insert_with(VecDeque::new); - v.extend(messages.into_iter().take(space_remaining)); - } -} - -impl Stream for RoundRobinBuffer - where S: Stream)> -{ - type Item = (V, M); - type Error = S::Error; - - fn poll(&mut self) -> Poll, S::Error> { - loop { - match self.inner.poll()? { - Async::NotReady | Async::Ready(None) => break, - Async::Ready(Some((sender, msgs))) => self.import_messages(sender, msgs), - } - } - - let done = self.inner.is_done(); - Ok(match self.next_message() { - Some(msg) => Async::Ready(Some(msg)), - None => if done { Async::Ready(None) } else { Async::NotReady }, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use futures::stream::{self, Stream}; - - #[derive(Debug, PartialEq, Eq)] - struct UncheckedMessage { data: Vec } - - #[test] - fn is_fair_and_wraps_around() { - let stream = stream::iter_ok(vec![ - (1, vec![ - UncheckedMessage { data: vec![1, 3, 5] }, - UncheckedMessage { data: vec![3, 5, 7] }, - UncheckedMessage { data: vec![5, 7, 9] }, - ]), - (2, vec![ - UncheckedMessage { data: vec![2, 4, 6] }, - UncheckedMessage { data: vec![4, 6, 8] }, - UncheckedMessage { data: vec![6, 8, 10] }, - ]), - ]); - - let round_robin = RoundRobinBuffer::new(stream, 100); - let output = round_robin.wait().collect::, ()>>().unwrap(); - - assert_eq!(output, vec![ - (1, UncheckedMessage { data: vec![1, 3, 5] }), - (2, UncheckedMessage { data: vec![2, 4, 6] }), - (1, UncheckedMessage { data: vec![3, 5, 7] }), - - (2, UncheckedMessage { data: vec![4, 6, 8] }), - (1, UncheckedMessage { data: vec![5, 7, 9] }), - (2, UncheckedMessage { data: vec![6, 8, 10] }), - ]); - } - - #[test] - fn discards_when_full() { - let stream = stream::iter_ok(vec![ - (1, (0..200).map(|i| UncheckedMessage { data: vec![i] }).collect()) - ]); - - let round_robin = RoundRobinBuffer::new(stream, 100); - let output = round_robin.wait().collect::, ()>>().unwrap(); - - assert_eq!(output.len(), 100); - } -} diff --git a/polkadot/candidate-agreement/src/tests/mod.rs b/polkadot/candidate-agreement/src/tests/mod.rs deleted file mode 100644 index 1599a94aa6..0000000000 --- a/polkadot/candidate-agreement/src/tests/mod.rs +++ /dev/null @@ -1,385 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - -//! Tests and test helpers for the candidate agreement. - -const VALIDITY_CHECK_DELAY_MS: u64 = 100; -const AVAILABILITY_CHECK_DELAY_MS: u64 = 100; -const PROPOSAL_FORMATION_TICK_MS: u64 = 50; -const PROPAGATE_STATEMENTS_TICK_MS: u64 = 200; -const TIMER_TICK_DURATION_MS: u64 = 10; - -use std::collections::HashMap; - -use futures::prelude::*; -use futures::sync::mpsc; -use tokio_timer::Timer; - -use super::*; - -#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Clone, Copy)] -struct AuthorityId(usize); - -#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Clone)] -struct Digest(Vec); - -#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Clone)] -struct GroupId(usize); - -#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Clone)] -struct ParachainCandidate { - group: GroupId, - data: usize, -} - -#[derive(PartialEq, Eq, Debug, Clone)] -struct Proposal { - candidates: Vec, -} - -#[derive(PartialEq, Eq, Debug, Clone)] -enum Signature { - Table(AuthorityId, table::Statement), - Bft(AuthorityId, bft::Message), -} - -enum Error { - Timer(tokio_timer::TimerError), - NetOut, - NetIn, -} - -#[derive(Debug, Clone)] -struct SharedTestContext { - n_authorities: usize, - n_groups: usize, - timer: Timer, -} - -#[derive(Debug, Clone)] -struct TestContext { - shared: Arc, - local_id: AuthorityId, -} - -impl Context for TestContext { - type AuthorityId = AuthorityId; - type Digest = Digest; - type GroupId = GroupId; - type Signature = Signature; - type Proposal = Proposal; - type ParachainCandidate = ParachainCandidate; - - type CheckCandidate = Box>; - type CheckAvailability = Box>; - - type StatementBatch = VecBatch< - AuthorityId, - table::SignedStatement - >; - - fn candidate_digest(candidate: &ParachainCandidate) -> Digest { - Digest(vec![candidate.group.0, candidate.data]) - } - - fn proposal_digest(candidate: &Proposal) -> Digest { - Digest(candidate.candidates.iter().fold(Vec::new(), |mut a, c| { - a.extend(Self::candidate_digest(c).0); - a - })) - } - - fn candidate_group(candidate: &ParachainCandidate) -> GroupId { - candidate.group.clone() - } - - fn round_proposer(&self, round: usize) -> AuthorityId { - AuthorityId(round % self.shared.n_authorities) - } - - fn check_validity(&self, _candidate: &ParachainCandidate) -> Self::CheckCandidate { - let future = self.shared.timer - .sleep(::std::time::Duration::from_millis(VALIDITY_CHECK_DELAY_MS)) - .map_err(Error::Timer) - .map(|_| true); - - Box::new(future) - } - - fn check_availability(&self, _candidate: &ParachainCandidate) -> Self::CheckAvailability { - let future = self.shared.timer - .sleep(::std::time::Duration::from_millis(AVAILABILITY_CHECK_DELAY_MS)) - .map_err(Error::Timer) - .map(|_| true); - - Box::new(future) - } - - fn create_proposal(&self, candidates: Vec<&ParachainCandidate>) - -> Option - { - let t = self.shared.n_groups * 2 / 3; - if candidates.len() >= t { - Some(Proposal { - candidates: candidates.iter().map(|x| (&**x).clone()).collect() - }) - } else { - None - } - } - - fn proposal_valid(&self, proposal: &Proposal, check_candidate: F) -> bool - where F: FnMut(&ParachainCandidate) -> bool - { - if proposal.candidates.len() >= self.shared.n_groups * 2 / 3 { - proposal.candidates.iter().all(check_candidate) - } else { - false - } - } - - fn local_id(&self) -> AuthorityId { - self.local_id.clone() - } - - fn sign_table_statement( - &self, - statement: &table::Statement - ) -> Signature { - Signature::Table(self.local_id(), statement.clone()) - } - - fn sign_bft_message(&self, message: &bft::Message) -> Signature { - Signature::Bft(self.local_id(), message.clone()) - } -} - -struct TestRecovery; - -impl MessageRecovery for TestRecovery { - type UncheckedMessage = OutgoingMessage; - - fn check_message(&self, msg: Self::UncheckedMessage) -> Option> { - Some(match msg { - OutgoingMessage::Bft(c) => CheckedMessage::Bft(c), - OutgoingMessage::Table(batch) => CheckedMessage::Table(batch.items), - }) - } -} - -pub struct Network { - endpoints: Vec>, - input: mpsc::UnboundedReceiver<(usize, T)>, -} - -impl Network { - pub fn new(nodes: usize) - -> (Self, Vec>, Vec>) - { - let mut inputs = Vec::with_capacity(nodes); - let mut outputs = Vec::with_capacity(nodes); - let mut endpoints = Vec::with_capacity(nodes); - - let (in_tx, in_rx) = mpsc::unbounded(); - for _ in 0..nodes { - let (out_tx, out_rx) = mpsc::unbounded(); - inputs.push(in_tx.clone()); - outputs.push(out_rx); - endpoints.push(out_tx); - } - - let network = Network { - endpoints, - input: in_rx, - }; - - (network, inputs, outputs) - } - - pub fn route_on_thread(self) { - ::std::thread::spawn(move || { let _ = self.wait(); }); - } -} - -impl Future for Network { - type Item = (); - type Error = (); - - fn poll(&mut self) -> Poll<(), Self::Error> { - match try_ready!(self.input.poll()) { - None => Ok(Async::Ready(())), - Some((sender, item)) => { - { - let receiving_endpoints = self.endpoints - .iter() - .enumerate() - .filter(|&(i, _)| i != sender) - .map(|(_, x)| x); - - for endpoint in receiving_endpoints { - let _ = endpoint.unbounded_send(item.clone()); - } - } - - self.poll() - } - } - } -} - -#[derive(Debug, Clone)] -pub struct VecBatch { - pub max_len: usize, - pub targets: Vec, - pub items: Vec, -} - -impl ::StatementBatch for VecBatch { - fn targets(&self) -> &[V] { &self.targets } - fn is_empty(&self) -> bool { self.items.is_empty() } - fn push(&mut self, item: T) -> bool { - if self.items.len() == self.max_len { - false - } else { - self.items.push(item); - true - } - } -} - -fn make_group_assignments(n_authorities: usize, n_groups: usize) - -> HashMap> -{ - let mut map = HashMap::new(); - let threshold = (n_authorities / n_groups) / 2; - let make_blank_group = || { - GroupInfo { - validity_guarantors: HashSet::new(), - availability_guarantors: HashSet::new(), - needed_validity: threshold, - needed_availability: threshold, - } - }; - - // every authority checks validity of his ID modulo n_groups and - // guarantees availability for the group above that. - for a_id in 0..n_authorities { - let primary_group = a_id % n_groups; - let availability_groups = [ - (a_id + 1) % n_groups, - a_id.wrapping_sub(1) % n_groups, - ]; - - map.entry(GroupId(primary_group)) - .or_insert_with(&make_blank_group) - .validity_guarantors - .insert(AuthorityId(a_id)); - - for &availability_group in &availability_groups { - map.entry(GroupId(availability_group)) - .or_insert_with(&make_blank_group) - .availability_guarantors - .insert(AuthorityId(a_id)); - } - } - - map -} - -fn make_blank_batch(n_authorities: usize) -> VecBatch { - VecBatch { - max_len: 20, - targets: (0..n_authorities).map(AuthorityId).collect(), - items: Vec::new(), - } -} - -#[test] -fn consensus_completes_with_minimum_good() { - let n = 50; - let f = 16; - let n_groups = 10; - - let timer = ::tokio_timer::wheel() - .tick_duration(Duration::from_millis(TIMER_TICK_DURATION_MS)) - .num_slots(1 << 16) - .build(); - - let (network, inputs, outputs) = Network::<(AuthorityId, OutgoingMessage)>::new(n - f); - network.route_on_thread(); - - let shared_test_context = Arc::new(SharedTestContext { - n_authorities: n, - n_groups: n_groups, - timer: timer.clone(), - }); - - let groups = make_group_assignments(n, n_groups); - - let authorities = inputs.into_iter().zip(outputs).enumerate().map(|(raw_id, (input, output))| { - let id = AuthorityId(raw_id); - let context = TestContext { - shared: shared_test_context.clone(), - local_id: id, - }; - - let shared_table = SharedTable::new(context.clone(), groups.clone()); - let params = AgreementParams { - context, - timer: timer.clone(), - table: shared_table, - nodes: n, - max_faulty: f, - round_timeout_multiplier: 4, - message_buffer_size: 100, - form_proposal_interval: Duration::from_millis(PROPOSAL_FORMATION_TICK_MS), - }; - - let net_out = input - .sink_map_err(|_| Error::NetOut) - .with(move |x| Ok::<_, Error>((id.0, (id, x))) ); - - let net_in = output - .map_err(|_| Error::NetIn) - .map(move |(v, msg)| (v, vec![msg])); - - let propagate_statements = timer - .interval(Duration::from_millis(PROPAGATE_STATEMENTS_TICK_MS)) - .map(move |()| make_blank_batch(n)) - .map_err(Error::Timer); - - let local_candidate = if raw_id < n_groups { - let candidate = ParachainCandidate { - group: GroupId(raw_id), - data: raw_id, - }; - ::futures::future::Either::A(Ok::<_, Error>(candidate).into_future()) - } else { - ::futures::future::Either::B(::futures::future::empty()) - }; - - agree::<_, _, _, _, _, _, Error>( - params, - net_in, - net_out, - TestRecovery, - propagate_statements, - local_candidate - ) - }).collect::>(); - - futures::future::join_all(authorities).wait().unwrap(); -} diff --git a/polkadot/consensus/Cargo.toml b/polkadot/consensus/Cargo.toml new file mode 100644 index 0000000000..aeda287f80 --- /dev/null +++ b/polkadot/consensus/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "polkadot-consensus" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +futures = "0.1.17" +parking_lot = "0.4" +tokio-timer = "0.1.2" +ed25519 = { path = "../../substrate/ed25519" } +polkadot-primitives = { path = "../primitives" } +polkadot-statement-table = { path = "../statement-table" } +substrate-bft = { path = "../../substrate/bft" } +substrate-codec = { path = "../../substrate/codec" } +substrate-primitives = { path = "../../substrate/primitives" } diff --git a/polkadot/consensus/src/lib.rs b/polkadot/consensus/src/lib.rs new file mode 100644 index 0000000000..f3e62ba1d5 --- /dev/null +++ b/polkadot/consensus/src/lib.rs @@ -0,0 +1,243 @@ +// Copyright 2017 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Propagation and agreement of candidates. +//! +//! Authorities are split into groups by parachain, and each authority might come +//! up its own candidate for their parachain. Within groups, authorities pass around +//! their candidates and produce statements of validity. +//! +//! Any candidate that receives majority approval by the authorities in a group +//! may be subject to inclusion, unless any authorities flag that candidate as invalid. +//! +//! Wrongly flagging as invalid should be strongly disincentivized, so that in the +//! equilibrium state it is not expected to happen. Likewise with the submission +//! of invalid blocks. +//! +//! Groups themselves may be compromised by malicious authorities. + +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; + +use codec::Slicable; +use table::Table; +use table::generic::Statement as GenericStatement; +use polkadot_primitives::Hash; +use polkadot_primitives::parachain::{Id as ParaId, CandidateReceipt}; +use primitives::block::Block as SubstrateBlock; +use primitives::AuthorityId; + +use parking_lot::Mutex; + +extern crate futures; +extern crate ed25519; +extern crate parking_lot; +extern crate tokio_timer; +extern crate polkadot_statement_table as table; +extern crate polkadot_primitives; +extern crate substrate_bft as bft; +extern crate substrate_codec as codec; +extern crate substrate_primitives as primitives; + +/// Information about a specific group. +#[derive(Debug, Clone)] +pub struct GroupInfo { + /// Authorities meant to check validity of candidates. + pub validity_guarantors: HashSet, + /// Authorities meant to check availability of candidate data. + pub availability_guarantors: HashSet, + /// Number of votes needed for validity. + pub needed_validity: usize, + /// Number of votes needed for availability. + pub needed_availability: usize, +} + +struct TableContext { + parent_hash: Hash, + key: Arc, + groups: HashMap, +} + +impl table::Context for TableContext { + fn is_member_of(&self, authority: &AuthorityId, group: &ParaId) -> bool { + self.groups.get(group).map_or(false, |g| g.validity_guarantors.contains(authority)) + } + + fn is_availability_guarantor_of(&self, authority: &AuthorityId, group: &ParaId) -> bool { + self.groups.get(group).map_or(false, |g| g.availability_guarantors.contains(authority)) + } + + fn requisite_votes(&self, group: &ParaId) -> (usize, usize) { + self.groups.get(group).map_or( + (usize::max_value(), usize::max_value()), + |g| (g.needed_validity, g.needed_availability), + ) + } +} + +impl TableContext { + fn sign_statement(&self, statement: table::Statement) -> table::SignedStatement { + let signature = sign_table_statement(&statement, &self.key, &self.parent_hash); + let local_id = self.key.public().0; + + table::SignedStatement { + statement, + signature, + sender: local_id, + } + } +} + +/// Sign a table statement against a parent hash. +/// The actual message signed is the encoded statement concatenated with the +/// parent hash. +pub fn sign_table_statement(statement: &table::Statement, key: &ed25519::Pair, parent_hash: &Hash) -> ed25519::Signature { + use polkadot_primitives::parachain::Statement as RawStatement; + + let raw = match *statement { + GenericStatement::Candidate(ref c) => RawStatement::Candidate(c.clone()), + GenericStatement::Valid(h) => RawStatement::Valid(h), + GenericStatement::Invalid(h) => RawStatement::Invalid(h), + GenericStatement::Available(h) => RawStatement::Available(h), + }; + + let mut encoded = raw.encode(); + encoded.extend(&parent_hash.0); + + key.sign(&encoded) +} + +// A shared table object. +struct SharedTableInner { + table: Table, + proposed_digest: Option, +} + +impl SharedTableInner { + fn import_statement( + &mut self, + context: &TableContext, + statement: ::table::SignedStatement, + received_from: Option, + ) -> Option { + self.table.import_statement(context, statement, received_from) + } +} + +/// A shared table object. +pub struct SharedTable { + context: Arc, + inner: Arc>, +} + +impl Clone for SharedTable { + fn clone(&self) -> Self { + SharedTable { + context: self.context.clone(), + inner: self.inner.clone() + } + } +} + +impl SharedTable { + /// Create a new shared table. + /// + /// Provide the key to sign with, and the parent hash of the relay chain + /// block being built. + pub fn new(groups: HashMap, key: Arc, parent_hash: Hash) -> Self { + SharedTable { + context: Arc::new(TableContext { groups, key, parent_hash }), + inner: Arc::new(Mutex::new(SharedTableInner { + table: Table::default(), + proposed_digest: None, + })) + } + } + + /// Import a single statement. + pub fn import_statement( + &self, + statement: table::SignedStatement, + received_from: Option, + ) -> Option { + self.inner.lock().import_statement(&*self.context, statement, received_from) + } + + /// Sign and import a local statement. + pub fn sign_and_import( + &self, + statement: table::Statement, + ) -> Option { + let proposed_digest = match statement { + GenericStatement::Candidate(ref c) => Some(c.hash()), + _ => None, + }; + + let signed_statement = self.context.sign_statement(statement); + + let mut inner = self.inner.lock(); + if proposed_digest.is_some() { + inner.proposed_digest = proposed_digest; + } + + inner.import_statement(&*self.context, signed_statement, None) + } + + /// Import many statements at once. + /// + /// Provide an iterator yielding pairs of (statement, received_from). + pub fn import_statements(&self, iterable: I) -> U + where + I: IntoIterator)>, + U: ::std::iter::FromIterator, + { + let mut inner = self.inner.lock(); + + iterable.into_iter().filter_map(move |(statement, received_from)| { + inner.import_statement(&*self.context, statement, received_from) + }).collect() + } + + /// Check if a proposal is valid. + pub fn proposal_valid(&self, _proposal: &SubstrateBlock) -> bool { + false // TODO + } + + /// Execute a closure using a specific candidate. + /// + /// Deadlocks if called recursively. + pub fn with_candidate(&self, digest: &Hash, f: F) -> U + where F: FnOnce(Option<&CandidateReceipt>) -> U + { + let inner = self.inner.lock(); + f(inner.table.get_candidate(digest)) + } + + /// Get all witnessed misbehavior. + pub fn get_misbehavior(&self) -> HashMap { + self.inner.lock().table.get_misbehavior().clone() + } + + /// Fill a statement batch. + pub fn fill_batch(&self, batch: &mut B) { + self.inner.lock().table.fill_batch(batch); + } + + /// Get the local proposed block's hash. + pub fn proposed_hash(&self) -> Option { + self.inner.lock().proposed_digest.clone() + } +} diff --git a/polkadot/primitives/src/parachain.rs b/polkadot/primitives/src/parachain.rs index 0f4c4adefc..a39904f44e 100644 --- a/polkadot/primitives/src/parachain.rs +++ b/polkadot/primitives/src/parachain.rs @@ -20,7 +20,9 @@ use primitives::bytes; use primitives; use codec::{Input, Slicable, NonTrivialSlicable}; +use rstd::cmp::{PartialOrd, Ord, Ordering}; use rstd::vec::Vec; +use ::Hash; /// Unique identifier of a parachain. #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] @@ -159,6 +161,55 @@ pub struct CandidateReceipt { pub fees: u64, } +impl Slicable for CandidateReceipt { + fn encode(&self) -> Vec { + let mut v = Vec::new(); + + self.parachain_index.using_encoded(|s| v.extend(s)); + self.collator.using_encoded(|s| v.extend(s)); + self.head_data.0.using_encoded(|s| v.extend(s)); + self.balance_uploads.using_encoded(|s| v.extend(s)); + self.egress_queue_roots.using_encoded(|s| v.extend(s)); + self.fees.using_encoded(|s| v.extend(s)); + + v + } + + fn decode(input: &mut I) -> Option { + Some(CandidateReceipt { + parachain_index: try_opt!(Slicable::decode(input)), + collator: try_opt!(Slicable::decode(input)), + head_data: try_opt!(Slicable::decode(input).map(HeadData)), + balance_uploads: try_opt!(Slicable::decode(input)), + egress_queue_roots: try_opt!(Slicable::decode(input)), + fees: try_opt!(Slicable::decode(input)), + }) + } +} + +impl CandidateReceipt { + /// Get the blake2_256 hash + #[cfg(feature = "std")] + pub fn hash(&self) -> Hash { + let encoded = self.encode(); + primitives::hashing::blake2_256(&encoded).into() + } +} + +impl PartialOrd for CandidateReceipt { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for CandidateReceipt { + fn cmp(&self, other: &Self) -> Ordering { + // TODO: compare signatures or something more sane + self.parachain_index.cmp(&other.parachain_index) + .then_with(|| self.head_data.cmp(&other.head_data)) + } +} + /// Parachain ingress queue message. #[derive(PartialEq, Eq, Clone)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] @@ -185,7 +236,7 @@ pub struct BlockData(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); /// Parachain head data included in the chain. -#[derive(PartialEq, Eq, Clone)] +#[derive(PartialEq, Eq, Clone, PartialOrd, Ord)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] pub struct HeadData(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); @@ -209,6 +260,74 @@ impl Slicable for Activity { } } +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +#[repr(u8)] +enum StatementKind { + Candidate = 1, + Valid = 2, + Invalid = 3, + Available = 4, +} + +/// Statements which can be made about parachain candidates. +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub enum Statement { + /// Proposal of a parachain candidate. + Candidate(CandidateReceipt), + /// State that a parachain candidate is valid. + Valid(Hash), + /// Vote to commit to a candidate. + Invalid(Hash), + /// Vote to advance round after inactive primary. + Available(Hash), +} + +impl Slicable for Statement { + fn encode(&self) -> Vec { + let mut v = Vec::new(); + match *self { + Statement::Candidate(ref candidate) => { + v.push(StatementKind::Candidate as u8); + candidate.using_encoded(|s| v.extend(s)); + } + Statement::Valid(ref hash) => { + v.push(StatementKind::Valid as u8); + hash.using_encoded(|s| v.extend(s)); + } + Statement::Invalid(ref hash) => { + v.push(StatementKind::Invalid as u8); + hash.using_encoded(|s| v.extend(s)); + } + Statement::Available(ref hash) => { + v.push(StatementKind::Available as u8); + hash.using_encoded(|s| v.extend(s)); + } + } + + v + } + + fn decode(value: &mut I) -> Option { + match u8::decode(value) { + Some(x) if x == StatementKind::Candidate as u8 => { + Slicable::decode(value).map(Statement::Candidate) + } + Some(x) if x == StatementKind::Valid as u8 => { + Slicable::decode(value).map(Statement::Valid) + } + Some(x) if x == StatementKind::Invalid as u8 => { + Slicable::decode(value).map(Statement::Invalid) + } + Some(x) if x == StatementKind::Available as u8 => { + Slicable::decode(value).map(Statement::Available) + } + _ => None, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/polkadot/primitives/src/transaction.rs b/polkadot/primitives/src/transaction.rs index 24a3ae4ee3..6279a2d0aa 100644 --- a/polkadot/primitives/src/transaction.rs +++ b/polkadot/primitives/src/transaction.rs @@ -152,7 +152,6 @@ impl Slicable for Proposal { } } - /// Public functions that can be dispatched to. #[derive(Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] @@ -314,7 +313,7 @@ pub struct UncheckedTransaction { impl Slicable for UncheckedTransaction { fn decode(input: &mut I) -> Option { - // This is a little more complicated than usua since the binary format must be compatible + // This is a little more complicated than usual since the binary format must be compatible // with substrate's generic `Vec` type. Basically this just means accepting that there // will be a prefix of u32, which has the total number of bytes following (we don't need // to use this). diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm index b4b77e5ec9e4c2a74a57d38ded342bd8de036607..c6fc9b035c57be18a2c7c8e8686935e994fc47dc 100644 GIT binary patch literal 80217 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

1pYlHczCYNfqIV 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

Pp* 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 Ol^6!hl&e2}h4eqwXm@)6 literal 75751 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 diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm index 2b5124161a1f76f50f483c1c8bf74aa9942dd88a..9f4e4cc836056cee61e8be0cab4c4a64044b6789 100644 GIT binary patch literal 80296 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 PT!CT0Ou70KmrMTx)ZdN4 literal 75800 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 diff --git a/polkadot/statement-table/Cargo.toml b/polkadot/statement-table/Cargo.toml new file mode 100644 index 0000000000..5c8a61e81d --- /dev/null +++ b/polkadot/statement-table/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "polkadot-statement-table" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +substrate-primitives = { path = "../../substrate/primitives" } +polkadot-primitives = { path = "../primitives" } diff --git a/polkadot/candidate-agreement/src/table.rs b/polkadot/statement-table/src/generic.rs similarity index 97% rename from polkadot/candidate-agreement/src/table.rs rename to polkadot/statement-table/src/generic.rs index 2909d219c6..11665fe114 100644 --- a/polkadot/candidate-agreement/src/table.rs +++ b/polkadot/statement-table/src/generic.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! The statement table. +//! The statement table: generic implementation. //! //! This stores messages other authorities issue about candidates. //! @@ -32,7 +32,21 @@ use std::collections::hash_map::{HashMap, Entry}; use std::hash::Hash; use std::fmt::Debug; -use super::StatementBatch; +/// A batch of statements to send out. +pub trait StatementBatch { + /// Get the target authorities of these statements. + fn targets(&self) -> &[V]; + + /// If the batch is empty. + fn is_empty(&self) -> bool; + + /// Push a statement onto the batch. Returns false when the batch is full. + /// + /// This is meant to do work like incrementally serializing the statements + /// into a vector of bytes while making sure the length is below a certain + /// amount. + fn push(&mut self, statement: T) -> bool; +} /// Context for the statement table. pub trait Context { @@ -380,7 +394,7 @@ impl Table { &self.detected_misbehavior } - /// Fill a statement batch and note messages seen by the targets. + /// Fill a statement batch and note messages as seen by the targets. pub fn fill_batch(&mut self, batch: &mut B) where B: StatementBatch< C::AuthorityId, @@ -709,9 +723,28 @@ impl Table { #[cfg(test)] mod tests { use super::*; - use ::tests::VecBatch; use std::collections::HashMap; + #[derive(Debug, Clone)] + struct VecBatch { + pub max_len: usize, + pub targets: Vec, + pub items: Vec, + } + + impl ::generic::StatementBatch for VecBatch { + fn targets(&self) -> &[V] { &self.targets } + fn is_empty(&self) -> bool { self.items.is_empty() } + fn push(&mut self, item: T) -> bool { + if self.items.len() == self.max_len { + false + } else { + self.items.push(item); + true + } + } + } + fn create() -> Table { Table { authority_data: HashMap::default(), diff --git a/polkadot/statement-table/src/lib.rs b/polkadot/statement-table/src/lib.rs new file mode 100644 index 0000000000..e3abf95686 --- /dev/null +++ b/polkadot/statement-table/src/lib.rs @@ -0,0 +1,108 @@ +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! The statement table. +//! +//! This stores messages other authorities issue about candidates. +//! +//! These messages are used to create a proposal submitted to a BFT consensus process. +//! +//! Proposals are formed of sets of candidates which have the requisite number of +//! validity and availability votes. +//! +//! Each parachain is associated with two sets of authorities: those which can +//! propose and attest to validity of candidates, and those who can only attest +//! to availability. + +extern crate substrate_primitives; +extern crate polkadot_primitives as primitives; + +pub mod generic; + +pub use generic::Table; + +use primitives::parachain::{Id, CandidateReceipt}; +use primitives::{SessionKey, Hash, Signature}; + +/// Statements about candidates on the network. +pub type Statement = generic::Statement; + +/// Signed statements about candidates. +pub type SignedStatement = generic::SignedStatement; + +/// Kinds of misbehavior, along with proof. +pub type Misbehavior = generic::Misbehavior; + +/// A summary of import of a statement. +pub type Summary = generic::Summary; + +/// Context necessary to construct a table. +pub trait Context { + /// Whether a authority is a member of a group. + /// Members are meant to submit candidates and vote on validity. + fn is_member_of(&self, authority: &SessionKey, group: &Id) -> bool; + + /// Whether a authority is an availability guarantor of a group. + /// Guarantors are meant to vote on availability for candidates submitted + /// in a group. + fn is_availability_guarantor_of( + &self, + authority: &SessionKey, + group: &Id, + ) -> bool; + + // requisite number of votes for validity and availability respectively from a group. + fn requisite_votes(&self, group: &Id) -> (usize, usize); +} + +impl generic::Context for C { + type AuthorityId = SessionKey; + type Digest = Hash; + type GroupId = Id; + type Signature = Signature; + type Candidate = CandidateReceipt; + + fn candidate_digest(candidate: &CandidateReceipt) -> Hash { + candidate.hash() + } + + fn candidate_group(candidate: &CandidateReceipt) -> Id { + candidate.parachain_index.clone() + } + + fn is_member_of(&self, authority: &SessionKey, group: &Id) -> bool { + Context::is_member_of(self, authority, group) + } + + fn is_availability_guarantor_of(&self, authority: &SessionKey, group: &Id) -> bool { + Context::is_availability_guarantor_of(self, authority, group) + } + + fn requisite_votes(&self, group: &Id) -> (usize, usize) { + Context::requisite_votes(self, group) + } +} + +/// A batch of statements to send out. +pub trait StatementBatch { + /// Get the target authorities of these statements. + fn targets(&self) -> &[SessionKey]; + + /// If the batch is empty. + fn is_empty(&self) -> bool; + + /// Push a statement onto the batch. Returns false when the batch is full. + /// + /// This is meant to do work like incrementally serializing the statements + /// into a vector of bytes while making sure the length is below a certain + /// amount. + fn push(&mut self, statement: SignedStatement) -> bool; +} + +impl generic::StatementBatch for T { + fn targets(&self) -> &[SessionKey] { StatementBatch::targets(self ) } + fn is_empty(&self) -> bool { StatementBatch::is_empty(self) } + fn push(&mut self, statement: SignedStatement) -> bool { + StatementBatch::push(self, statement) + } +}