Phase 1 of repo reorg (#719)

* Remove unneeded script

* Rename Substrate Demo -> Substrate

* Rename demo -> node

* Build wasm from last rename.

* Merge ed25519 into substrate-primitives

* Minor tweak

* Rename substrate -> core

* Move substrate-runtime-support to core/runtime/support

* Rename/move substrate-runtime-version

* Move codec up a level

* Rename substrate-codec -> parity-codec

* Move environmental up a level

* Move pwasm-* up to top, ready for removal

* Remove requirement of s-r-support from s-r-primitives

* Move core/runtime/primitives into core/runtime-primitives

* Remove s-r-support dep from s-r-version

* Remove dep of s-r-support from bft

* Remove dep of s-r-support from node/consensus

* Sever all other core deps from s-r-support

* Forgot the no_std directive

* Rename non-SRML modules to sr-* to avoid match clashes

* Move runtime/* to srml/*

* Rename substrate-runtime-* -> srml-*

* Move srml to top-level
This commit is contained in:
Gav Wood
2018-09-12 11:13:31 +02:00
committed by Arkadiy Paronyan
parent 8fe5aa4c81
commit 1e01162505
374 changed files with 2845 additions and 2902 deletions
+51
View File
@@ -0,0 +1,51 @@
// Copyright 2018 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 <http://www.gnu.org/licenses/>.
//! Errors that can occur during the consensus process.
use primitives::AuthorityId;
error_chain! {
links {
Api(::node_api::Error, ::node_api::ErrorKind);
Bft(::bft::Error, ::bft::ErrorKind);
}
errors {
NotValidator(id: AuthorityId) {
description("Local account ID not a validator at this block."),
display("Local account ID ({:?}) not a validator at this block.", id),
}
PrematureDestruction {
description("Proposer destroyed before finishing proposing or evaluating"),
display("Proposer destroyed before finishing proposing or evaluating"),
}
Timer(e: ::tokio::timer::Error) {
description("Failed to register or resolve async timer."),
display("Timer failed: {}", e),
}
Executor(e: ::futures::future::ExecuteErrorKind) {
description("Unable to dispatch agreement future"),
display("Unable to dispatch agreement future: {:?}", e),
}
}
}
impl From<::bft::InputStreamConcluded> for Error {
fn from(err: ::bft::InputStreamConcluded) -> Self {
::bft::Error::from(err).into()
}
}
@@ -0,0 +1,96 @@
// Copyright 2018 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 <http://www.gnu.org/licenses/>.
//! Block evaluation and evaluation errors.
use super::MAX_TRANSACTIONS_SIZE;
use codec::{Decode, Encode};
use node_runtime::{Block as GenericBlock, CheckedBlock};
use node_primitives::{Block, Hash, BlockNumber, Timestamp};
error_chain! {
links {
Api(::node_api::Error, ::node_api::ErrorKind);
}
errors {
BadProposalFormat {
description("Proposal provided not a block."),
display("Proposal provided not a block."),
}
TimestampInFuture {
description("Proposal had timestamp too far in the future."),
display("Proposal had timestamp too far in the future."),
}
WrongParentHash(expected: Hash, got: Hash) {
description("Proposal had wrong parent hash."),
display("Proposal had wrong parent hash. Expected {:?}, got {:?}", expected, got),
}
WrongNumber(expected: BlockNumber, got: BlockNumber) {
description("Proposal had wrong number."),
display("Proposal had wrong number. Expected {:?}, got {:?}", expected, got),
}
ProposalTooLarge(size: usize) {
description("Proposal exceeded the maximum size."),
display(
"Proposal exceeded the maximum size of {} by {} bytes.",
MAX_TRANSACTIONS_SIZE, size.saturating_sub(MAX_TRANSACTIONS_SIZE)
),
}
}
}
/// Attempt to evaluate a substrate block as a node block, returning error
/// upon any initial validity checks failing.
pub fn evaluate_initial(
proposal: &Block,
now: Timestamp,
parent_hash: &Hash,
parent_number: BlockNumber,
) -> Result<CheckedBlock> {
const MAX_TIMESTAMP_DRIFT: Timestamp = 60;
let encoded = Encode::encode(proposal);
let proposal = GenericBlock::decode(&mut &encoded[..])
.and_then(|b| CheckedBlock::new(b).ok())
.ok_or_else(|| ErrorKind::BadProposalFormat)?;
let transactions_size = proposal.extrinsics.iter().fold(0, |a, tx| {
a + Encode::encode(tx).len()
});
if transactions_size > MAX_TRANSACTIONS_SIZE {
bail!(ErrorKind::ProposalTooLarge(transactions_size))
}
if proposal.header.parent_hash != *parent_hash {
bail!(ErrorKind::WrongParentHash(*parent_hash, proposal.header.parent_hash));
}
if proposal.header.number != parent_number + 1 {
bail!(ErrorKind::WrongNumber(parent_number + 1, proposal.header.number));
}
let block_timestamp = proposal.timestamp();
// lenient maximum -- small drifts will just be delayed using a timer.
if block_timestamp > now + MAX_TIMESTAMP_DRIFT {
bail!(ErrorKind::TimestampInFuture)
}
Ok(proposal)
}
+444
View File
@@ -0,0 +1,444 @@
// Copyright 2018 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 <http://www.gnu.org/licenses/>.
//! This service uses BFT consensus provided by the substrate.
extern crate parking_lot;
extern crate node_api;
extern crate node_transaction_pool as transaction_pool;
extern crate node_runtime;
extern crate node_primitives;
extern crate substrate_bft as bft;
extern crate parity_codec as codec;
extern crate substrate_primitives as primitives;
extern crate sr_primitives as runtime_primitives;
extern crate substrate_client as client;
extern crate exit_future;
extern crate tokio;
extern crate rhododendron;
#[macro_use]
extern crate error_chain;
extern crate futures;
#[macro_use]
extern crate log;
#[cfg(test)]
extern crate substrate_keyring;
use std::sync::Arc;
use std::time::{self, Duration, Instant};
use codec::{Decode, Encode};
use node_api::Api;
use node_primitives::{AccountId, Hash, Block, BlockId, BlockNumber, Header, Timestamp, SessionKey};
use primitives::{AuthorityId, ed25519};
use transaction_pool::TransactionPool;
use tokio::runtime::TaskExecutor;
use tokio::timer::Delay;
use futures::prelude::*;
use futures::future;
use parking_lot::RwLock;
pub use self::error::{ErrorKind, Error};
pub use self::offline_tracker::OfflineTracker;
pub use service::Service;
mod evaluation;
mod error;
mod offline_tracker;
mod service;
/// Shared offline validator tracker.
pub type SharedOfflineTracker = Arc<RwLock<OfflineTracker>>;
// block size limit.
const MAX_TRANSACTIONS_SIZE: usize = 4 * 1024 * 1024;
/// A long-lived network which can create BFT message routing processes on demand.
pub trait Network {
/// The input stream of BFT messages. Should never logically conclude.
type Input: Stream<Item=bft::Communication<Block>,Error=Error>;
/// The output sink of BFT messages. Messages sent here should eventually pass to all
/// current authorities.
type Output: Sink<SinkItem=bft::Communication<Block>,SinkError=Error>;
/// Instantiate input and output streams.
fn communication_for(
&self,
validators: &[SessionKey],
local_id: SessionKey,
parent_hash: Hash,
task_executor: TaskExecutor
) -> (Self::Input, Self::Output);
}
/// Proposer factory.
pub struct ProposerFactory<N, P>
where
P: Api + Send + Sync + 'static
{
/// The client instance.
pub client: Arc<P>,
/// The transaction pool.
pub transaction_pool: Arc<TransactionPool<P>>,
/// The backing network handle.
pub network: N,
/// handle to remote task executor
pub handle: TaskExecutor,
/// Offline-tracker.
pub offline: SharedOfflineTracker,
}
impl<N, P> bft::Environment<Block> for ProposerFactory<N, P>
where
N: Network,
P: Api + Send + Sync + 'static,
{
type Proposer = Proposer<P>;
type Input = N::Input;
type Output = N::Output;
type Error = Error;
fn init(
&self,
parent_header: &Header,
authorities: &[AuthorityId],
sign_with: Arc<ed25519::Pair>,
) -> Result<(Self::Proposer, Self::Input, Self::Output), Error> {
use runtime_primitives::traits::{Hash as HashT, BlakeTwo256};
// force delay in evaluation this long.
const FORCE_DELAY: Timestamp = 5;
let parent_hash = parent_header.hash().into();
let id = BlockId::hash(parent_hash);
let random_seed = self.client.random_seed(&id)?;
let random_seed = BlakeTwo256::hash(&*random_seed);
let validators = self.client.validators(&id)?;
self.offline.write().note_new_block(&validators[..]);
info!("Starting consensus session on top of parent {:?}", parent_hash);
let local_id = sign_with.public().0.into();
let (input, output) = self.network.communication_for(
authorities,
local_id,
parent_hash.clone(),
self.handle.clone(),
);
let now = Instant::now();
let proposer = Proposer {
client: self.client.clone(),
start: now,
local_key: sign_with,
parent_hash,
parent_id: id,
parent_number: parent_header.number,
random_seed,
transaction_pool: self.transaction_pool.clone(),
offline: self.offline.clone(),
validators,
minimum_timestamp: current_timestamp() + FORCE_DELAY,
};
Ok((proposer, input, output))
}
}
/// The proposer logic.
pub struct Proposer<C: Api + Send + Sync> {
client: Arc<C>,
start: Instant,
local_key: Arc<ed25519::Pair>,
parent_hash: Hash,
parent_id: BlockId,
parent_number: BlockNumber,
random_seed: Hash,
transaction_pool: Arc<TransactionPool<C>>,
offline: SharedOfflineTracker,
validators: Vec<AccountId>,
minimum_timestamp: u64,
}
impl<C: Api + Send + Sync> Proposer<C> {
fn primary_index(&self, round_number: usize, len: usize) -> usize {
use primitives::uint::U256;
let big_len = U256::from(len);
let offset = U256::from_big_endian(&self.random_seed.0) % big_len;
let offset = offset.low_u64() as usize + round_number;
offset % len
}
}
impl<C> bft::Proposer<Block> for Proposer<C>
where
C: Api + Send + Sync,
{
type Create = Result<Block, Error>;
type Error = Error;
type Evaluate = Box<Future<Item=bool, Error=Error>>;
fn propose(&self) -> Result<Block, Error> {
use node_api::BlockBuilder;
use runtime_primitives::traits::{Hash as HashT, BlakeTwo256};
use node_primitives::InherentData;
const MAX_VOTE_OFFLINE_SECONDS: Duration = Duration::from_secs(60);
// TODO: handle case when current timestamp behind that in state.
let timestamp = ::std::cmp::max(self.minimum_timestamp, current_timestamp());
let elapsed_since_start = self.start.elapsed();
let offline_indices = if elapsed_since_start > MAX_VOTE_OFFLINE_SECONDS {
Vec::new()
} else {
self.offline.read().reports(&self.validators[..])
};
if !offline_indices.is_empty() {
info!(
"Submitting offline validators {:?} for slash-vote",
offline_indices.iter().map(|&i| self.validators[i as usize]).collect::<Vec<_>>(),
)
}
let inherent_data = InherentData {
timestamp,
offline_indices,
};
let mut block_builder = self.client.build_block(&self.parent_id, inherent_data)?;
{
let mut unqueue_invalid = Vec::new();
let result = self.transaction_pool.cull_and_get_pending(&BlockId::hash(self.parent_hash), |pending_iterator| {
let mut pending_size = 0;
for pending in pending_iterator {
if pending_size + pending.verified.encoded_size() >= MAX_TRANSACTIONS_SIZE { break }
match block_builder.push_extrinsic(pending.original.clone()) {
Ok(()) => {
pending_size += pending.verified.encoded_size();
}
Err(e) => {
trace!(target: "transaction-pool", "Invalid transaction: {}", e);
unqueue_invalid.push(pending.verified.hash().clone());
}
}
}
});
if let Err(e) = result {
warn!("Unable to get the pending set: {:?}", e);
}
self.transaction_pool.remove(&unqueue_invalid, false);
}
let block = block_builder.bake()?;
info!("Proposing block [number: {}; hash: {}; parent_hash: {}; extrinsics: [{}]]",
block.header.number,
Hash::from(block.header.hash()),
block.header.parent_hash,
block.extrinsics.iter()
.map(|xt| format!("{}", BlakeTwo256::hash_of(xt)))
.collect::<Vec<_>>()
.join(", ")
);
let substrate_block = Decode::decode(&mut block.encode().as_slice())
.expect("blocks are defined to serialize to substrate blocks correctly; qed");
assert!(evaluation::evaluate_initial(
&substrate_block,
timestamp,
&self.parent_hash,
self.parent_number,
).is_ok());
Ok(substrate_block)
}
fn evaluate(&self, unchecked_proposal: &Block) -> Self::Evaluate {
debug!(target: "bft", "evaluating block on top of parent ({}, {:?})", self.parent_number, self.parent_hash);
let current_timestamp = current_timestamp();
// do initial serialization and structural integrity checks.
let maybe_proposal = evaluation::evaluate_initial(
unchecked_proposal,
current_timestamp,
&self.parent_hash,
self.parent_number,
);
let proposal = match maybe_proposal {
Ok(p) => p,
Err(e) => {
// TODO: these errors are easily re-checked in runtime.
debug!(target: "bft", "Invalid proposal: {:?}", e);
return Box::new(future::ok(false));
}
};
let vote_delays = {
let now = Instant::now();
// the duration until the given timestamp is current
let proposed_timestamp = ::std::cmp::max(self.minimum_timestamp, proposal.timestamp());
let timestamp_delay = if proposed_timestamp > current_timestamp {
let delay_s = proposed_timestamp - current_timestamp;
debug!(target: "bft", "Delaying evaluation of proposal for {} seconds", delay_s);
Some(now + Duration::from_secs(delay_s))
} else {
None
};
match timestamp_delay {
Some(duration) => future::Either::A(
Delay::new(duration).map_err(|e| Error::from(ErrorKind::Timer(e)))
),
None => future::Either::B(future::ok(())),
}
};
// refuse to vote if this block says a validator is offline that we
// think isn't.
let offline = proposal.noted_offline();
if !self.offline.read().check_consistency(&self.validators[..], offline) {
return Box::new(futures::empty());
}
// evaluate whether the block is actually valid.
// TODO: is it better to delay this until the delays are finished?
let evaluated = self.client
.evaluate_block(&self.parent_id, unchecked_proposal.clone())
.map_err(Into::into);
let future = future::result(evaluated).and_then(move |good| {
let end_result = future::ok(good);
if good {
// delay a "good" vote.
future::Either::A(vote_delays.and_then(|_| end_result))
} else {
// don't delay a "bad" evaluation.
future::Either::B(end_result)
}
});
Box::new(future) as Box<_>
}
fn round_proposer(&self, round_number: usize, authorities: &[AuthorityId]) -> AuthorityId {
let offset = self.primary_index(round_number, authorities.len());
let proposer = authorities[offset].clone();
trace!(target: "bft", "proposer for round {} is {}", round_number, proposer);
proposer
}
fn import_misbehavior(&self, misbehavior: Vec<(AuthorityId, bft::Misbehavior<Hash>)>) {
use rhododendron::Misbehavior as GenericMisbehavior;
use runtime_primitives::bft::{MisbehaviorKind, MisbehaviorReport};
use node_primitives::UncheckedExtrinsic as GenericExtrinsic;
use node_runtime::{Call, UncheckedExtrinsic, ConsensusCall};
let local_id = self.local_key.public().0.into();
let mut next_index = {
let cur_index = self.transaction_pool.cull_and_get_pending(&BlockId::hash(self.parent_hash), |pending| pending
.filter(|tx| tx.verified.sender == local_id)
.last()
.map(|tx| Ok(tx.verified.index()))
.unwrap_or_else(|| self.client.index(&self.parent_id, local_id))
);
match cur_index {
Ok(Ok(cur_index)) => cur_index + 1,
Ok(Err(e)) => {
warn!(target: "consensus", "Error computing next transaction index: {}", e);
return;
}
Err(e) => {
warn!(target: "consensus", "Error computing next transaction index: {}", e);
return;
}
}
};
for (target, misbehavior) in misbehavior {
let report = MisbehaviorReport {
parent_hash: self.parent_hash,
parent_number: self.parent_number,
target,
misbehavior: match misbehavior {
GenericMisbehavior::ProposeOutOfTurn(_, _, _) => continue,
GenericMisbehavior::DoublePropose(_, _, _) => continue,
GenericMisbehavior::DoublePrepare(round, (h1, s1), (h2, s2))
=> MisbehaviorKind::BftDoublePrepare(round as u32, (h1, s1.signature), (h2, s2.signature)),
GenericMisbehavior::DoubleCommit(round, (h1, s1), (h2, s2))
=> MisbehaviorKind::BftDoubleCommit(round as u32, (h1, s1.signature), (h2, s2.signature)),
}
};
let payload = (next_index, Call::Consensus(ConsensusCall::report_misbehavior(report)));
let signature = self.local_key.sign(&payload.encode()).into();
next_index += 1;
let local_id = self.local_key.public().0.into();
let extrinsic = UncheckedExtrinsic {
signature: Some((node_runtime::RawAddress::Id(local_id), signature)),
index: payload.0,
function: payload.1,
};
let uxt: GenericExtrinsic = Decode::decode(&mut extrinsic.encode().as_slice()).expect("Encoded extrinsic is valid");
self.transaction_pool.submit_one(&BlockId::hash(self.parent_hash), uxt)
.expect("locally signed extrinsic is valid; qed");
}
}
fn on_round_end(&self, round_number: usize, was_proposed: bool) {
let primary_validator = self.validators[
self.primary_index(round_number, self.validators.len())
];
// alter the message based on whether we think the empty proposer was forced to skip the round.
// this is determined by checking if our local validator would have been forced to skip the round.
if !was_proposed {
let public = ed25519::Public::from_raw(primary_validator.0);
info!(
"Potential Offline Validator: {} failed to propose during assigned slot: {}",
public,
round_number,
);
}
self.offline.write().note_round_end(primary_validator, was_proposed);
}
}
fn current_timestamp() -> Timestamp {
time::SystemTime::now().duration_since(time::UNIX_EPOCH)
.expect("now always later than unix epoch; qed")
.as_secs()
}
@@ -0,0 +1,137 @@
// Copyright 2018 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 <http://www.gnu.org/licenses/>.
//! Tracks offline validators.
use node_primitives::AccountId;
use std::collections::HashMap;
use std::time::{Instant, Duration};
// time before we report a validator.
const REPORT_TIME: Duration = Duration::from_secs(60 * 5);
struct Observed {
last_round_end: Instant,
offline_since: Instant,
}
impl Observed {
fn new() -> Observed {
let now = Instant::now();
Observed {
last_round_end: now,
offline_since: now,
}
}
fn note_round_end(&mut self, was_online: bool) {
let now = Instant::now();
self.last_round_end = now;
if was_online {
self.offline_since = now;
}
}
fn is_active(&self) -> bool {
// can happen if clocks are not monotonic
if self.offline_since > self.last_round_end { return true }
self.last_round_end.duration_since(self.offline_since) < REPORT_TIME
}
}
/// Tracks offline validators and can issue a report for those offline.
pub struct OfflineTracker {
observed: HashMap<AccountId, Observed>,
}
impl OfflineTracker {
/// Create a new tracker.
pub fn new() -> Self {
OfflineTracker { observed: HashMap::new() }
}
/// Note new consensus is starting with the given set of validators.
pub fn note_new_block(&mut self, validators: &[AccountId]) {
use std::collections::HashSet;
let set: HashSet<_> = validators.iter().cloned().collect();
self.observed.retain(|k, _| set.contains(k));
}
/// Note that a round has ended.
pub fn note_round_end(&mut self, validator: AccountId, was_online: bool) {
self.observed.entry(validator)
.or_insert_with(Observed::new)
.note_round_end(was_online);
}
/// Generate a vector of indices for offline account IDs.
pub fn reports(&self, validators: &[AccountId]) -> Vec<u32> {
validators.iter()
.enumerate()
.filter_map(|(i, v)| if self.is_online(v) {
None
} else {
Some(i as u32)
})
.collect()
}
/// Whether reports on a validator set are consistent with our view of things.
pub fn check_consistency(&self, validators: &[AccountId], reports: &[u32]) -> bool {
reports.iter().cloned().all(|r| {
let v = match validators.get(r as usize) {
Some(v) => v,
None => return false,
};
// we must think all validators reported externally are offline.
let thinks_online = self.is_online(v);
!thinks_online
})
}
fn is_online(&self, v: &AccountId) -> bool {
self.observed.get(v).map(Observed::is_active).unwrap_or(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validator_offline() {
let mut tracker = OfflineTracker::new();
let v = [0; 32].into();
let v2 = [1; 32].into();
let v3 = [2; 32].into();
tracker.note_round_end(v, true);
tracker.note_round_end(v2, true);
tracker.note_round_end(v3, true);
let slash_time = REPORT_TIME + Duration::from_secs(5);
tracker.observed.get_mut(&v).unwrap().offline_since -= slash_time;
tracker.observed.get_mut(&v2).unwrap().offline_since -= slash_time;
assert_eq!(tracker.reports(&[v, v2, v3]), vec![0, 1]);
tracker.note_new_block(&[v, v3]);
assert_eq!(tracker.reports(&[v, v2, v3]), vec![0]);
}
}
+172
View File
@@ -0,0 +1,172 @@
// Copyright 2018 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 <http://www.gnu.org/licenses/>.
//! Consensus service.
/// Consensus service. A long running service that manages BFT agreement
/// the network.
use std::thread;
use std::time::{Duration, Instant};
use std::sync::Arc;
use bft::{self, BftService};
use client::{BlockchainEvents, ChainHead, BlockBody};
use ed25519;
use futures::prelude::*;
use node_api::Api;
use node_primitives::{Block, Header};
use transaction_pool::TransactionPool;
use tokio::executor::current_thread::TaskExecutor as LocalThreadHandle;
use tokio::runtime::TaskExecutor as ThreadPoolHandle;
use tokio::runtime::current_thread::Runtime as LocalRuntime;
use tokio::timer::Interval;
use super::{Network, ProposerFactory};
use error;
const TIMER_DELAY_MS: u64 = 5000;
const TIMER_INTERVAL_MS: u64 = 500;
// spin up an instance of BFT agreement on the current thread's executor.
// panics if there is no current thread executor.
fn start_bft<F, C>(
header: Header,
bft_service: Arc<BftService<Block, F, C>>,
) where
F: bft::Environment<Block> + 'static,
C: bft::BlockImport<Block> + bft::Authorities<Block> + 'static,
F::Error: ::std::fmt::Debug,
<F::Proposer as bft::Proposer<Block>>::Error: ::std::fmt::Display + Into<error::Error>,
<F as bft::Environment<Block>>::Error: ::std::fmt::Display
{
let mut handle = LocalThreadHandle::current();
match bft_service.build_upon(&header) {
Ok(Some(bft_work)) => if let Err(e) = handle.spawn_local(Box::new(bft_work)) {
warn!(target: "bft", "Couldn't initialize BFT agreement: {:?}", e);
}
Ok(None) => trace!(target: "bft", "Could not start agreement on top of {}", header.hash()),
Err(e) => warn!(target: "bft", "BFT agreement error: {}", e),
}
}
/// Consensus service. Starts working when created.
pub struct Service {
thread: Option<thread::JoinHandle<()>>,
exit_signal: Option<::exit_future::Signal>,
}
impl Service {
/// Create and start a new instance.
pub fn new<A, C, N>(
client: Arc<C>,
api: Arc<A>,
network: N,
transaction_pool: Arc<TransactionPool<A>>,
thread_pool: ThreadPoolHandle,
key: ed25519::Pair,
) -> Service
where
A: Api + Send + Sync + 'static,
C: BlockchainEvents<Block> + ChainHead<Block> + BlockBody<Block>,
C: bft::BlockImport<Block> + bft::Authorities<Block> + Send + Sync + 'static,
N: Network + Send + 'static,
{
use parking_lot::RwLock;
use super::OfflineTracker;
let (signal, exit) = ::exit_future::signal();
let thread = thread::spawn(move || {
let mut runtime = LocalRuntime::new().expect("Could not create local runtime");
let key = Arc::new(key);
let factory = ProposerFactory {
client: api.clone(),
transaction_pool: transaction_pool.clone(),
network,
handle: thread_pool.clone(),
offline: Arc::new(RwLock::new(OfflineTracker::new())),
};
let bft_service = Arc::new(BftService::new(client.clone(), key, factory));
let notifications = {
let client = client.clone();
let bft_service = bft_service.clone();
client.import_notification_stream().for_each(move |notification| {
if notification.is_new_best {
start_bft(notification.header, bft_service.clone());
}
Ok(())
})
};
let interval = Interval::new(
Instant::now() + Duration::from_millis(TIMER_DELAY_MS),
Duration::from_millis(TIMER_INTERVAL_MS),
);
let mut prev_best = match client.best_block_header() {
Ok(header) => header.hash(),
Err(e) => {
warn!("Cant's start consensus service. Error reading best block header: {:?}", e);
return;
}
};
let timed = {
let c = client.clone();
let s = bft_service.clone();
interval.map_err(|e| debug!(target: "bft", "Timer error: {:?}", e)).for_each(move |_| {
if let Ok(best_block) = c.best_block_header() {
let hash = best_block.hash();
if hash == prev_best {
debug!(target: "bft", "Starting consensus round after a timeout");
start_bft(best_block, s.clone());
}
prev_best = hash;
}
Ok(())
})
};
runtime.spawn(notifications);
runtime.spawn(timed);
if let Err(e) = runtime.block_on(exit) {
debug!("BFT event loop error {:?}", e);
}
});
Service {
thread: Some(thread),
exit_signal: Some(signal),
}
}
}
impl Drop for Service {
fn drop(&mut self) {
if let Some(signal) = self.exit_signal.take() {
signal.fire();
}
if let Some(thread) = self.thread.take() {
thread.join().expect("The service thread has panicked");
}
}
}