// Copyright 2017-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see .
//! BFT Agreement based on a rotating proposer in different rounds.
//!
//! Where this crate refers to input stream, should never logically conclude.
//! The logic in this crate assumes that messages flushed to the output stream
//! will eventually reach other nodes and that our own messages are not included
//! in the input stream.
//!
//! 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.
#![cfg(feature="rhd")]
// FIXME #1020 doesn't compile
// NOTE: this is the legacy constant used for transaction size. No longer used except
// for the rhd code which is not updated. Placed here for compatibility.
const MAX_TRANSACTIONS_SIZE: u32 = 4 * 1024 * 1024;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::{self, Instant, Duration};
use codec::{Decode, Encode};
use consensus::offline_tracker::OfflineTracker;
use consensus::error::{ErrorKind as CommonErrorKind};
use consensus::{Authorities, BlockImport, Environment, Proposer as BaseProposer};
use client::{Client as SubstrateClient, CallExecutor};
use client::runtime_api::{Core, BlockBuilder as BlockBuilderAPI, OldTxQueue, BlockBuilderError};
use sr_primitives::generic::{BlockId, Era, ImportResult, BlockImportParams, BlockOrigin};
use sr_primitives::traits::{Block, Header};
use sr_primitives::traits::{
Block as BlockT, Hash as HashT, Header as HeaderT,
BlockNumberToHash, SaturatedConversion
};
use sr_primitives::Justification;
use primitives::{AuthorityId, ed25519, Blake2Hasher, ed25519::LocalizedSignature};
use srml_system::Trait as SystemT;
use node_runtime::Runtime;
use transaction_pool::txpool::{self, Pool as TransactionPool};
use futures::prelude::*;
use futures::future;
use futures::sync::oneshot;
use tokio::runtime::TaskExecutor;
use tokio::timer::Delay;
use parking_lot::{RwLock, Mutex};
pub use rhododendron::{
self, InputStreamConcluded, AdvanceRoundReason, Message as RhdMessage,
Vote as RhdMessageVote, Communication as RhdCommunication,
};
pub use self::error::{Error, ErrorKind};
// pub mod misbehavior_check;
mod error;
mod service;
// statuses for an agreement
mod status {
pub const LIVE: usize = 0;
pub const BAD: usize = 1;
pub const GOOD: usize = 2;
}
pub type Timestamp = u64;
pub type AccountId = ::primitives::H256;
/// Localized message type.
pub type LocalizedMessage = rhododendron::LocalizedMessage<
B,
::Hash,
AuthorityId,
LocalizedSignature
>;
/// Justification of some hash.
pub struct RhdJustification(rhododendron::Justification);
/// Justification of a prepare message.
pub struct PrepareJustification(rhododendron::PrepareJustification);
/// Unchecked justification.
#[derive(Encode, Decode)]
pub struct UncheckedJustification(rhododendron::UncheckedJustification);
impl UncheckedJustification {
/// Create a new, unchecked justification.
pub fn new(digest: H, signatures: Vec, round_number: u32) -> Self {
UncheckedJustification(rhododendron::UncheckedJustification {
digest,
signatures,
round_number,
})
}
}
impl UncheckedJustification {
/// Decode a justification.
pub fn decode_justification(justification: Justification) -> Option {
let inner: rhododendron::UncheckedJustification<_, _> = Decode::decode(&mut &justification[..])?;
Some(UncheckedJustification(inner))
}
}
impl Into for UncheckedJustification {
fn into(self) -> Justification {
self.0.encode()
}
}
impl From> for UncheckedJustification {
fn from(inner: rhododendron::UncheckedJustification) -> Self {
UncheckedJustification(inner)
}
}
/// Result of a committed round of BFT
pub type Committed = rhododendron::Committed::Hash, LocalizedSignature>;
/// Communication between BFT participants.
pub type Communication = rhododendron::Communication::Hash, AuthorityId, LocalizedSignature>;
/// Misbehavior observed from BFT participants.
pub type Misbehavior = rhododendron::Misbehavior;
/// Shared offline validator tracker.
pub type SharedOfflineTracker = Arc>;
/// A proposer for a rhododendron instance. This must implement the base proposer logic.
pub trait LocalProposer: BaseProposer {
/// Import witnessed rhododendron misbehavior.
fn import_misbehavior(&self, misbehavior: Vec<(AuthorityId, Misbehavior)>);
/// Determine the proposer for a given round. This should be a deterministic function
/// with consistent results across all authorities.
fn round_proposer(&self, round_number: u32, authorities: &[AuthorityId]) -> AuthorityId;
/// Hook called when a BFT round advances without a proposal.
fn on_round_end(&self, _round_number: u32, _proposed: bool) { }
}
/// Build new blocks.
pub trait BlockBuilder {
/// Push an extrinsic onto the block. Fails if the extrinsic is invalid.
fn push_extrinsic(&mut self, extrinsic: ::Extrinsic) -> Result<(), Error>;
}
/// Local client abstraction for the consensus.
pub trait AuthoringApi:
Send
+ Sync
+ BlockBuilderAPI<::Block, InherentData, Error=::Error>
+ Core<::Block, AuthorityId, Error=::Error>
+ OldTxQueue<::Block, Error=::Error>
{
/// The block used for this API type.
type Block: BlockT;
/// The error used by this API type.
type Error: std::error::Error;
/// Build a block on top of the given, with inherent extrinsics pre-pushed.
fn build_block) -> ()>(
&self,
at: &BlockId,
inherent_data: InherentData,
build_ctx: F,
) -> Result;
}
/// A long-lived network which can create BFT message routing processes on demand.
pub trait Network {
/// The block used for this API type.
type Block: BlockT;
/// The input stream of BFT messages. Should never logically conclude.
type Input: Stream,Error=Error>;
/// The output sink of BFT messages. Messages sent here should eventually pass to all
/// current authorities.
type Output: Sink,SinkError=Error>;
/// Instantiate input and output streams.
fn communication_for(
&self,
validators: &[AuthorityId],
local_id: AuthorityId,
parent_hash: ::Hash,
task_executor: TaskExecutor
) -> (Self::Input, Self::Output);
}
// caches the round number to start at if we end up with BFT consensus on the same
// parent hash more than once (happens if block is bad).
//
// this will force a committed but locally-bad block to be considered analogous to
// a round advancement vote.
#[derive(Debug)]
struct RoundCache {
hash: Option,
start_round: u32,
}
/// Instance of BFT agreement.
struct BftInstance {
key: Arc,
authorities: Vec,
parent_hash: B::Hash,
round_timeout_multiplier: u64,
cache: Arc>>,
proposer: P,
}
impl> BftInstance
where
B: Clone + Eq,
B::Hash: ::std::hash::Hash
{
fn round_timeout_duration(&self, round: u32) -> Duration {
// 2^(min(6, x/8)) * 10
// Grows exponentially starting from 10 seconds, capped at 640 seconds.
const ROUND_INCREMENT_STEP: u32 = 8;
let round = round / ROUND_INCREMENT_STEP;
let round = ::std::cmp::min(6, round);
let timeout = 1u64.checked_shl(round)
.unwrap_or_else(u64::max_value)
.saturating_mul(self.round_timeout_multiplier);
Duration::from_secs(timeout)
}
fn update_round_cache(&self, current_round: u32) {
let mut cache = self.cache.lock();
if cache.hash.as_ref() == Some(&self.parent_hash) {
cache.start_round = current_round + 1;
}
}
}
impl> rhododendron::Context for BftInstance
where
B: Clone + Eq,
B::Hash: ::std::hash::Hash,
{
type Error = P::Error;
type AuthorityId = AuthorityId;
type Digest = B::Hash;
type Signature = LocalizedSignature;
type Candidate = B;
type RoundTimeout = Box>;
type CreateProposal = ::Future;
type EvaluateProposal = ::Future;
fn local_id(&self) -> AuthorityId {
self.key.public().into()
}
fn proposal(&self) -> Self::CreateProposal {
self.proposer.propose().into_future()
}
fn candidate_digest(&self, proposal: &B) -> B::Hash {
proposal.hash()
}
fn sign_local(&self, message: RhdMessage) -> LocalizedMessage {
sign_message(message, &*self.key, self.parent_hash.clone())
}
fn round_proposer(&self, round: u32) -> AuthorityId {
self.proposer.round_proposer(round, &self.authorities[..])
}
fn proposal_valid(&self, proposal: &B) -> Self::EvaluateProposal {
self.proposer.evaluate(proposal).into_future()
}
fn begin_round_timeout(&self, round: u32) -> Self::RoundTimeout {
let timeout = self.round_timeout_duration(round);
let fut = Delay::new(Instant::now() + timeout)
.map_err(|e| Error::from(CommonErrorKind::FaultyTimer(e)))
.map_err(Into::into);
Box::new(fut)
}
fn on_advance_round(
&self,
accumulator: &rhododendron::Accumulator,
round: u32,
next_round: u32,
reason: AdvanceRoundReason,
) {
use std::collections::HashSet;
let collect_pubkeys = |participants: HashSet<&Self::AuthorityId>| participants.into_iter()
.map(|p| ::ed25519::Public::from_raw(p.0))
.collect::>();
let round_timeout = self.round_timeout_duration(next_round);
debug!(target: "rhd", "Advancing to round {} from {}", next_round, round);
debug!(target: "rhd", "Participating authorities: {:?}",
collect_pubkeys(accumulator.participants()));
debug!(target: "rhd", "Voting authorities: {:?}",
collect_pubkeys(accumulator.voters()));
debug!(target: "rhd", "Round {} should end in at most {} seconds from now", next_round, round_timeout.as_secs());
self.update_round_cache(next_round);
if let AdvanceRoundReason::Timeout = reason {
self.proposer.on_round_end(round, accumulator.proposal().is_some());
}
}
}
/// A future that resolves either when canceled (witnessing a block from the network at same height)
/// or when agreement completes.
pub struct BftFuture where
B: Block + Clone + Eq,
B::Hash: ::std::hash::Hash,
P: LocalProposer,
P: BaseProposer,
InStream: Stream, Error=Error>,
OutSink: Sink, SinkError=Error>,
{
inner: rhododendron::Agreement, InStream, OutSink>,
status: Arc,
cancel: oneshot::Receiver<()>,
import: Arc,
}
impl Future for BftFuture where
B: Block + Clone + Eq,
B::Hash: ::std::hash::Hash,
P: LocalProposer,
P: BaseProposer,
I: BlockImport,
InStream: Stream, Error=Error>,
OutSink: Sink, SinkError=Error>,
{
type Item = ();
type Error = ();
fn poll(&mut self) -> ::futures::Poll<(), ()> {
// service has canceled the future. bail
let cancel = match self.cancel.poll() {
Ok(Async::Ready(())) | Err(_) => true,
Ok(Async::NotReady) => false,
};
let committed = match self.inner.poll().map_err(|_| ()) {
Ok(Async::Ready(x)) => x,
Ok(Async::NotReady) =>
return Ok(if cancel { Async::Ready(()) } else { Async::NotReady }),
Err(()) => return Err(()),
};
// if something was committed, the round leader must have proposed.
self.inner.context().proposer.on_round_end(committed.round_number, true);
// If we didn't see the proposal (very unlikely),
// we will get the block from the network later.
if let Some(justified_block) = committed.candidate {
let hash = justified_block.hash();
info!(target: "rhd", "Importing block #{} ({}) directly from BFT consensus",
justified_block.header().number(), hash);
let just: Justification = UncheckedJustification(committed.justification.uncheck()).into();
let (header, body) = justified_block.deconstruct();
let import_block = BlockImportParams {
origin: BlockOrigin::ConsensusBroadcast,
header: header,
justification: Some(just),
body: Some(body),
finalized: true,
post_digests: Default::default(),
auxiliary: Default::default()
};
let new_status = match self.import.import_block(import_block, None) {
Err(e) => {
warn!(target: "rhd", "Error importing block {:?} in round #{}: {:?}",
hash, committed.round_number, e);
status::BAD
}
Ok(ImportResult::KnownBad) => {
warn!(target: "rhd", "{:?} was bad block agreed on in round #{}",
hash, committed.round_number);
status::BAD
}
_ => status::GOOD
};
self.status.store(new_status, Ordering::Release);
} else {
// assume good unless we received the proposal.
self.status.store(status::GOOD, Ordering::Release);
}
self.inner.context().update_round_cache(committed.round_number);
Ok(Async::Ready(()))
}
}
impl Drop for BftFuture where
B: Block + Clone + Eq,
B::Hash: ::std::hash::Hash,
P: LocalProposer,
P: BaseProposer,
InStream: Stream, Error=Error>,
OutSink: Sink, SinkError=Error>,
{
fn drop(&mut self) {
let misbehavior = self.inner.drain_misbehavior().collect::>();
self.inner.context().proposer.import_misbehavior(misbehavior);
}
}
struct AgreementHandle {
status: Arc,
send_cancel: Option>,
}
impl AgreementHandle {
fn status(&self) -> usize {
self.status.load(Ordering::Acquire)
}
}
impl Drop for AgreementHandle {
fn drop(&mut self) {
if let Some(sender) = self.send_cancel.take() {
let _ = sender.send(());
}
}
}
/// The BftService kicks off the agreement process on top of any blocks it
/// is notified of.
///
/// This assumes that it is being run in the context of a tokio runtime.
pub struct BftService {
client: Arc,
live_agreement: Mutex