Consensus Engines Implementation: Aura (#911)

* Generalize BlockImport

 - move ImportBlock, BlockOrigin, ImportResult into shared sr-primitives
 - let Consensus provide  and  traits again
 - update consensus traits to latest development
 - implement traits on client::Client, test_client::TestClient
 - update RHD to use the new import_block API

* Move ImportBlock into consensus-common
* Send import notification in aura tests
* Integrating aura into service
* Make Signatures more generic
* Aura Block Production with the given key
* run aura on the thread pool
* start at exact step start in aura
* Add needed wasm blob, in leiu of better solutions.
* Make API ids consistent with traits and bring upstream for sharing.
* Add decrease_free_balance to Balances module
* Encode `Metadata` once instead of two times
* Bitops include xor
* Upgrade key module.
* Default pages to somewhat bigger.
* Introduce upgrade key into node
* Add `Created` event
This commit is contained in:
Benjamin Kampmann
2018-10-27 15:59:18 +02:00
committed by GitHub
parent c0f7021427
commit 50adea6220
82 changed files with 3125 additions and 1902 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ hex-literal = "0.1"
futures = "0.1.17"
slog = "^2"
heapsize = "0.4"
substrate-consensus-rhd = { path = "../consensus/rhd" }
substrate-consensus-common = { path = "../consensus/common" }
parity-codec = "2.1"
substrate-executor = { path = "../executor" }
substrate-primitives = { path = "../primitives" }
+54 -41
View File
@@ -534,7 +534,9 @@ impl<Block: BlockT> Backend<Block> {
meta.finalized_hash, f_hash),
).into())
}
transaction.put(columns::META, meta_keys::FINALIZED_BLOCK, f_hash.as_ref());
let lookup_key = ::utils::number_to_lookup_key(f_num);
transaction.put(columns::META, meta_keys::FINALIZED_BLOCK, &lookup_key);
let commit = self.storage.state_db.canonicalize_block(&f_hash);
apply_state_commit(transaction, commit);
@@ -586,11 +588,20 @@ impl<Block> client::backend::Backend<Block, Blake2Hasher> for Backend<Block> whe
-> Result<(), client::error::Error>
{
let mut transaction = DBTransaction::new();
if let Some(pending_block) = operation.pending_block {
let hash = pending_block.header.hash();
let parent_hash = *pending_block.header.parent_hash();
let number = pending_block.header.number().clone();
// blocks in longest chain are keyed by number
let lookup_key = if pending_block.leaf_state.is_best() {
::utils::number_to_lookup_key(number).to_vec()
} else {
// other blocks are keyed by number + hash
::utils::number_and_hash_to_lookup_key(number, hash)
};
if pending_block.leaf_state.is_best() {
let meta = self.blockchain.meta.read();
@@ -678,17 +689,9 @@ impl<Block> client::backend::Backend<Block, Blake2Hasher> for Backend<Block> whe
}
}
transaction.put(columns::META, meta_keys::BEST_BLOCK, hash.as_ref());
transaction.put(columns::META, meta_keys::BEST_BLOCK, &lookup_key);
}
// blocks in longest chain are keyed by number
let lookup_key = if pending_block.leaf_state.is_best() {
::utils::number_to_lookup_key(number).to_vec()
} else {
// other blocks are keyed by number + hash
::utils::number_and_hash_to_lookup_key(number, hash)
};
transaction.put(columns::HEADER, &lookup_key, &pending_block.header.encode());
if let Some(body) = pending_block.body {
transaction.put(columns::BODY, &lookup_key, &body.encode());
@@ -700,7 +703,7 @@ impl<Block> client::backend::Backend<Block, Blake2Hasher> for Backend<Block> whe
transaction.put(columns::HASH_LOOKUP, hash.as_ref(), &lookup_key);
if number == Zero::zero() {
transaction.put(columns::META, meta_keys::FINALIZED_BLOCK, hash.as_ref());
transaction.put(columns::META, meta_keys::FINALIZED_BLOCK, &lookup_key);
transaction.put(columns::META, meta_keys::GENESIS_HASH, hash.as_ref());
}
@@ -797,7 +800,8 @@ impl<Block> client::backend::Backend<Block, Blake2Hasher> for Backend<Block> whe
|| client::error::ErrorKind::UnknownBlock(
format!("Error reverting to {}. Block header not found.", best)))?;
transaction.put(columns::META, meta_keys::BEST_BLOCK, header.hash().as_ref());
let lookup_key = ::utils::number_to_lookup_key(header.number().clone());
transaction.put(columns::META, meta_keys::BEST_BLOCK, &lookup_key);
transaction.delete(columns::HASH_LOOKUP, header.hash().as_ref());
self.storage.db.write(transaction).map_err(db_err)?;
self.blockchain.update_meta(header.hash().clone(), best.clone(), true, false);
@@ -927,40 +931,49 @@ mod tests {
#[test]
fn block_hash_inserted_correctly() {
let db = Backend::<Block>::new_test(1, 0);
for i in 0..10 {
assert!(db.blockchain().hash(i).unwrap().is_none());
let backing = {
let db = Backend::<Block>::new_test(1, 0);
for i in 0..10 {
assert!(db.blockchain().hash(i).unwrap().is_none());
{
let id = if i == 0 {
BlockId::Hash(Default::default())
} else {
BlockId::Number(i - 1)
};
let mut op = db.begin_operation(id).unwrap();
let header = Header {
number: i,
parent_hash: if i == 0 {
Default::default()
{
let id = if i == 0 {
BlockId::Hash(Default::default())
} else {
db.blockchain.hash(i - 1).unwrap().unwrap()
},
state_root: Default::default(),
digest: Default::default(),
extrinsics_root: Default::default(),
};
BlockId::Number(i - 1)
};
op.set_block_data(
header,
Some(vec![]),
None,
NewBlockState::Best,
).unwrap();
db.commit_operation(op).unwrap();
let mut op = db.begin_operation(id).unwrap();
let header = Header {
number: i,
parent_hash: if i == 0 {
Default::default()
} else {
db.blockchain.hash(i - 1).unwrap().unwrap()
},
state_root: Default::default(),
digest: Default::default(),
extrinsics_root: Default::default(),
};
op.set_block_data(
header,
Some(vec![]),
None,
NewBlockState::Best,
).unwrap();
db.commit_operation(op).unwrap();
}
assert!(db.blockchain().hash(i).unwrap().is_some())
}
db.storage.db.clone()
};
assert!(db.blockchain().hash(i).unwrap().is_some())
let backend = Backend::<Block>::from_kvdb(backing, PruningMode::keep_blocks(1), 0).unwrap();
assert_eq!(backend.blockchain().info().unwrap().best_number, 9);
for i in 0..10 {
assert!(backend.blockchain().hash(i).unwrap().is_some())
}
}
+11 -10
View File
@@ -196,7 +196,8 @@ impl<Block: BlockT> LightStorage<Block> {
).into())
}
transaction.put(columns::META, meta_keys::FINALIZED_BLOCK, hash.as_ref());
let lookup_key = ::utils::number_to_lookup_key(header.number().clone());
transaction.put(columns::META, meta_keys::FINALIZED_BLOCK, &lookup_key);
// build new CHT if required
if let Some(new_cht_number) = cht::is_build_required(cht::SIZE, *header.number()) {
@@ -244,6 +245,14 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
let number = *header.number();
let parent_hash = *header.parent_hash();
// blocks in longest chain are keyed by number
let lookup_key = if leaf_state.is_best() {
::utils::number_to_lookup_key(number).to_vec()
} else {
// other blocks are keyed by number + hash
::utils::number_and_hash_to_lookup_key(number, hash)
};
if leaf_state.is_best() {
// handle reorg.
{
@@ -298,17 +307,9 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
}
}
transaction.put(columns::META, meta_keys::BEST_BLOCK, hash.as_ref());
transaction.put(columns::META, meta_keys::BEST_BLOCK, &lookup_key);
}
// blocks in longest chain are keyed by number
let lookup_key = if leaf_state.is_best() {
::utils::number_to_lookup_key(number).to_vec()
} else {
// other blocks are keyed by number + hash
::utils::number_and_hash_to_lookup_key(number, hash)
};
transaction.put(columns::HEADER, &lookup_key, &header.encode());
transaction.put(columns::HASH_LOOKUP, hash.as_ref(), &lookup_key);
+1
View File
@@ -53,6 +53,7 @@ pub mod meta_keys {
}
/// Database metadata.
#[derive(Debug)]
pub struct Meta<N, H> {
/// Hash of the best known block.
pub best_hash: H,
+1 -1
View File
@@ -150,7 +150,7 @@ where
let heap_pages = state.storage(well_known_keys::HEAP_PAGES)
.map_err(|e| error::ErrorKind::Execution(Box::new(e)))?
.and_then(|v| u64::decode(&mut &v[..]))
.unwrap_or(8) as usize;
.unwrap_or(1024) as usize;
let mut ext = Ext::new(&mut overlay, &state, self.backend.changes_trie_storage());
self.executor.runtime_version(&mut ext, heap_pages, &code)
+125 -179
View File
@@ -26,6 +26,7 @@ use runtime_primitives::{
generic::{BlockId, SignedBlock, Block as RuntimeBlock},
transaction_validity::{TransactionValidity, TransactionTag},
};
use consensus::{ImportBlock, ImportResult, BlockOrigin};
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Zero, As, NumberFor, CurrentHeight, BlockNumberToHash};
use runtime_primitives::{ApplyResult, BuildStorage};
use runtime_api as api;
@@ -44,7 +45,7 @@ use blockchain::{self, Info as ChainInfo, Backend as ChainBackend, HeaderBackend
use call_executor::{CallExecutor, LocalCallExecutor};
use executor::{RuntimeVersion, RuntimeInfo};
use notifications::{StorageNotifications, StorageEventStream};
use {cht, error, in_mem, block_builder, genesis};
use {cht, error, in_mem, block_builder, genesis, consensus};
/// Type that implements `futures::Stream` of block import events.
pub type ImportNotifications<Block> = mpsc::UnboundedReceiver<BlockImportNotification<Block>>;
@@ -106,21 +107,6 @@ pub struct ClientInfo<Block: BlockT> {
pub best_queued_hash: Option<Block::Hash>,
}
/// Block import result.
#[derive(Debug)]
pub enum ImportResult {
/// Added to the import queue.
Queued,
/// Already in the import queue.
AlreadyQueued,
/// Already in the blockchain.
AlreadyInChain,
/// Block or parent is known to be bad.
KnownBad,
/// Block parent is not in the chain.
UnknownParent,
}
/// Block status.
#[derive(Debug, PartialEq, Eq)]
pub enum BlockStatus {
@@ -134,70 +120,6 @@ pub enum BlockStatus {
Unknown,
}
/// Block data origin.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum BlockOrigin {
/// Genesis block built into the client.
Genesis,
/// Block is part of the initial sync with the network.
NetworkInitialSync,
/// Block was broadcasted on the network.
NetworkBroadcast,
/// Block that was received from the network and validated in the consensus process.
ConsensusBroadcast,
/// Block that was collated by this node.
Own,
/// Block was imported from a file.
File,
}
/// Data required to import a Block
pub struct ImportBlock<Block: BlockT> {
/// Origin of the Block
pub origin: BlockOrigin,
/// Header
pub header: Block::Header,
/// Justification provided for this block from the outside
pub external_justification: Justification,
/// Internal Justification for the block
pub internal_justification: Vec<u8>, // Block::Digest::DigestItem?
/// Block's body
pub body: Option<Vec<Block::Extrinsic>>,
/// Is this block finalized already?
/// `true` implies instant finality.
pub finalized: bool,
/// Auxiliary consensus data produced by the block.
/// Contains a list of key-value pairs. If values are `None`, the keys
/// will be deleted.
pub auxiliary: Vec<(Vec<u8>, Option<Vec<u8>>)>,
}
impl<Block: BlockT> ImportBlock<Block> {
/// Deconstruct the justified header into parts.
pub fn into_inner(self)
-> (
BlockOrigin,
<Block as BlockT>::Header,
Justification,
Justification,
Option<Vec<<Block as BlockT>::Extrinsic>>,
bool,
Vec<(Vec<u8>, Option<Vec<u8>>)>,
) {
(
self.origin,
self.header,
self.external_justification,
self.internal_justification,
self.body,
self.finalized,
self.auxiliary,
)
}
}
/// Summary of an imported block
#[derive(Clone, Debug)]
pub struct BlockImportNotification<Block: BlockT> {
@@ -222,6 +144,41 @@ pub struct FinalityNotification<Block: BlockT> {
pub header: Block::Header,
}
// used in importing a block, where additional changes are made after the runtime
// executed.
enum PrePostHeader<H> {
// they are the same: no post-runtime digest items.
Same(H),
// different headers (pre, post).
Different(H, H),
}
impl<H> PrePostHeader<H> {
// get a reference to the "pre-header" -- the header as it should be just after the runtime.
fn pre(&self) -> &H {
match *self {
PrePostHeader::Same(ref h) => h,
PrePostHeader::Different(ref h, _) => h,
}
}
// get a reference to the "post-header" -- the header as it should be after all changes are applied.
fn post(&self) -> &H {
match *self {
PrePostHeader::Same(ref h) => h,
PrePostHeader::Different(_, ref h) => h,
}
}
// convert to the "post-header" -- the header as it should be after all changes are applied.
fn into_post(self) -> H {
match self {
PrePostHeader::Same(h) => h,
PrePostHeader::Different(_, h) => h,
}
}
}
/// Create an instance of in-memory client.
pub fn new_in_mem<E, Block, S>(
executor: E,
@@ -513,52 +470,6 @@ impl<B, E, Block> Client<B, E, Block> where
)
}
/// Import a checked and validated block
pub fn import_block(
&self,
import_block: ImportBlock<Block>,
new_authorities: Option<Vec<AuthorityId>>,
) -> error::Result<ImportResult> {
let (
origin,
header,
_,
justification,
body,
finalized,
_aux, // TODO: write this to DB also
) = import_block.into_inner();
let parent_hash = header.parent_hash().clone();
match self.backend.blockchain().status(BlockId::Hash(parent_hash))? {
blockchain::BlockStatus::InChain => {},
blockchain::BlockStatus::Unknown => return Ok(ImportResult::UnknownParent),
}
let hash = header.hash();
let _import_lock = self.import_lock.lock();
let height: u64 = header.number().as_();
*self.importing_block.write() = Some(hash);
let result = self.execute_and_import_block(
origin,
hash,
header,
justification,
body,
new_authorities,
finalized,
);
*self.importing_block.write() = None;
telemetry!("block.import";
"height" => height,
"best" => ?hash,
"origin" => ?origin
);
result
}
// TODO [ToDr] Optimize and re-use tags from the pool.
fn transaction_tags(&self, at: Block::Hash, body: &Option<Vec<Block::Extrinsic>>) -> error::Result<Vec<TransactionTag>> {
let id = BlockId::Hash(at);
@@ -587,13 +498,13 @@ impl<B, E, Block> Client<B, E, Block> where
&self,
origin: BlockOrigin,
hash: Block::Hash,
header: Block::Header,
import_headers: PrePostHeader<Block::Header>,
justification: Justification,
body: Option<Vec<Block::Extrinsic>>,
authorities: Option<Vec<AuthorityId>>,
finalized: bool,
) -> error::Result<ImportResult> {
let parent_hash = header.parent_hash().clone();
let parent_hash = import_headers.post().parent_hash().clone();
match self.backend.blockchain().status(BlockId::Hash(hash))? {
blockchain::BlockStatus::InChain => return Ok(ImportResult::AlreadyInChain),
blockchain::BlockStatus::Unknown => {},
@@ -627,12 +538,13 @@ impl<B, E, Block> Client<B, E, Block> where
transaction_state,
&mut overlay,
"execute_block",
&<Block as BlockT>::new(header.clone(), body.clone().unwrap_or_default()).encode(),
&<Block as BlockT>::new(import_headers.pre().clone(), body.clone().unwrap_or_default()).encode(),
match (origin, self.block_execution_strategy) {
(BlockOrigin::NetworkInitialSync, _) | (_, ExecutionStrategy::NativeWhenPossible) =>
ExecutionManager::NativeWhenPossible,
(_, ExecutionStrategy::AlwaysWasm) => ExecutionManager::AlwaysWasm,
_ => ExecutionManager::Both(|wasm_result, native_result| {
let header = import_headers.post();
warn!("Consensus error between wasm and native block execution at block {}", hash);
warn!(" Header {:?}", header);
warn!(" Native result {:?}", native_result);
@@ -654,7 +566,7 @@ impl<B, E, Block> Client<B, E, Block> where
};
// TODO: non longest-chain rule.
let is_new_best = finalized || header.number() > &last_best_number;
let is_new_best = finalized || import_headers.post().number() > &last_best_number;
let leaf_state = if finalized {
::backend::NewBlockState::Final
} else if is_new_best {
@@ -663,10 +575,10 @@ impl<B, E, Block> Client<B, E, Block> where
::backend::NewBlockState::Normal
};
trace!("Imported {}, (#{}), best={}, origin={:?}", hash, header.number(), is_new_best, origin);
trace!("Imported {}, (#{}), best={}, origin={:?}", hash, import_headers.post().number(), is_new_best, origin);
transaction.set_block_data(
header.clone(),
import_headers.post().clone(),
body,
Some(justification),
leaf_state,
@@ -693,7 +605,7 @@ impl<B, E, Block> Client<B, E, Block> where
if finalized {
let notification = FinalityNotification::<Block> {
hash,
header: header.clone(),
header: import_headers.post().clone(),
};
self.finality_notification_sinks.lock()
@@ -703,7 +615,7 @@ impl<B, E, Block> Client<B, E, Block> where
let notification = BlockImportNotification::<Block> {
hash,
origin,
header,
header: import_headers.into_post(),
is_new_best,
tags,
};
@@ -979,6 +891,84 @@ impl<B, E, Block> Client<B, E, Block> where
}
}
impl<B, E, Block> consensus::BlockImport<Block> for Client<B, E, Block> where
B: backend::Backend<Block, Blake2Hasher>,
E: CallExecutor<Block, Blake2Hasher> + Clone,
Block: BlockT,
{
type Error = Error;
/// Import a checked and validated block
fn import_block(
&self,
import_block: ImportBlock<Block>,
new_authorities: Option<Vec<AuthorityId>>,
) -> Result<ImportResult, Self::Error> {
use runtime_primitives::traits::Digest;
let ImportBlock {
origin,
header,
external_justification,
post_runtime_digests,
body,
finalized,
..
} = import_block;
let parent_hash = header.parent_hash().clone();
match self.backend.blockchain().status(BlockId::Hash(parent_hash))? {
blockchain::BlockStatus::InChain => {},
blockchain::BlockStatus::Unknown => return Ok(ImportResult::UnknownParent),
}
let import_headers = if post_runtime_digests.is_empty() {
PrePostHeader::Same(header)
} else {
let mut post_header = header.clone();
for item in post_runtime_digests {
post_header.digest_mut().push(item);
}
PrePostHeader::Different(header, post_header)
};
let hash = import_headers.post().hash();
let _import_lock = self.import_lock.lock();
let height: u64 = import_headers.post().number().as_();
*self.importing_block.write() = Some(hash);
let result = self.execute_and_import_block(
origin,
hash,
import_headers,
external_justification,
body,
new_authorities,
finalized,
);
*self.importing_block.write() = None;
telemetry!("block.import";
"height" => height,
"best" => ?hash,
"origin" => ?origin
);
result.map_err(|e| e.into())
}
}
impl<B, E, Block> consensus::Authorities<Block> for Client<B, E, Block> where
B: backend::Backend<Block, Blake2Hasher>,
E: CallExecutor<Block, Blake2Hasher> + Clone,
Block: BlockT,
{
type Error = Error;
fn authorities(&self, at: &BlockId<Block>) -> Result<Vec<AuthorityId>, Self::Error> {
self.authorities_at(at).map_err(|e| e.into())
}
}
impl<B, E, Block> CurrentHeight for Client<B, E, Block> where
B: backend::Backend<Block, Blake2Hasher>,
E: CallExecutor<Block, Blake2Hasher> + Clone,
@@ -1135,26 +1125,6 @@ impl<B, E, Block> api::BlockBuilder<Block> for Client<B, E, Block> where
}
}
impl<B, E, Block> api::OldTxQueue<Block> for Client<B, E, Block> where
B: backend::Backend<Block, Blake2Hasher>,
E: CallExecutor<Block, Blake2Hasher>,
Block: BlockT,
{
type Error = Error;
fn account_nonce<AccountId: Encode + Decode, Index: Encode + Decode>(
&self, at: &BlockId<Block>, account: &AccountId
) -> Result<Index, Self::Error> {
self.call_api_at(at, "account_nonce", &(account))
}
fn lookup_address<Address: Encode + Decode, AccountId: Encode + Decode>(
&self, at: &BlockId<Block>, address: &Address
) -> Result<Option<AccountId>, Self::Error> {
self.call_api_at(at, "lookup_address", &(address))
}
}
impl<B, E, Block> api::TaggedTransactionQueue<Block> for Client<B, E, Block> where
B: backend::Backend<Block, Blake2Hasher>,
E: CallExecutor<Block, Blake2Hasher>,
@@ -1169,30 +1139,6 @@ impl<B, E, Block> api::TaggedTransactionQueue<Block> for Client<B, E, Block> whe
}
}
impl<B, E, Block> api::Miscellaneous<Block> for Client<B, E, Block> where
B: backend::Backend<Block, Blake2Hasher>,
E: CallExecutor<Block, Blake2Hasher>,
Block: BlockT,
{
type Error = Error;
fn validator_count(&self, at: &BlockId<Block>) -> Result<u32, Self::Error> {
self.call_api_at(at, "validator_count", &())
}
fn validators<AccountId: Encode + Decode>(
&self, at: &BlockId<Block>
) -> Result<Vec<AccountId>, Self::Error> {
self.call_api_at(at, "validators", &())
}
fn timestamp<Moment: Encode + Decode>(
&self, at: &BlockId<Block>
) -> Result<Moment, Self::Error> {
self.call_api_at(at, "timestamp", &())
}
}
#[cfg(test)]
pub(crate) mod tests {
use std::collections::HashMap;
@@ -1202,7 +1148,7 @@ pub(crate) mod tests {
use runtime_primitives::traits::{Digest as DigestT, DigestItem as DigestItemT};
use runtime_primitives::generic::DigestItem;
use test_client::{self, TestClient};
use test_client::client::BlockOrigin;
use consensus::BlockOrigin;
use test_client::client::backend::Backend as TestBackend;
use test_client::BlockBuilderExt;
use test_client::runtime::{self, Block, Transfer};
+6
View File
@@ -16,11 +16,17 @@
//! Substrate client possible errors.
#![allow(missing_docs)]
use std;
use state_machine;
use runtime_primitives::ApplyError;
use consensus;
error_chain! {
links {
Consensus(consensus::Error, consensus::ErrorKind);
}
errors {
/// Backend error.
Backend(s: String) {
+3 -2
View File
@@ -26,6 +26,7 @@ extern crate parity_codec as codec;
extern crate substrate_primitives as primitives;
extern crate sr_primitives as runtime_primitives;
extern crate substrate_state_machine as state_machine;
extern crate substrate_consensus_common as consensus;
#[cfg(test)] extern crate substrate_keyring as keyring;
#[cfg(test)] extern crate substrate_test_client as test_client;
#[macro_use] extern crate substrate_telemetry;
@@ -63,8 +64,8 @@ pub use call_executor::{CallResult, CallExecutor, LocalCallExecutor};
pub use client::{
new_with_backend,
new_in_mem,
BlockBody, BlockStatus, BlockOrigin, ImportNotifications, FinalityNotifications, BlockchainEvents,
Client, ClientInfo, ChainHead, ImportResult, ImportBlock,
BlockBody, BlockStatus, ImportNotifications, FinalityNotifications, BlockchainEvents,
Client, ClientInfo, ChainHead,
};
pub use notifications::{StorageEventStream, StorageChangeSet};
pub use state_machine::ExecutionStrategy;
+2 -1
View File
@@ -270,7 +270,8 @@ pub mod tests {
use error::Error as ClientError;
use test_client::{self, TestClient};
use test_client::runtime::{self, Hash, Block, Header};
use test_client::client::BlockOrigin;
use consensus::BlockOrigin;
use in_mem::{Blockchain as InMemoryBlockchain};
use light::fetcher::{Fetcher, FetchChecker, LightDataChecker,
RemoteCallRequest, RemoteHeaderRequest};
+42
View File
@@ -0,0 +1,42 @@
[package]
name = "substrate-consensus-aura"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
description = "Rhododendron Round-Based consensus-algorithm for substrate"
[dependencies]
futures = "0.1.17"
parity-codec = { version = "2.1" }
substrate-consensus-common = { path = "../common" }
substrate-client = { path = "../../client" }
substrate-primitives = { path = "../../primitives" }
substrate-network = { path = "../../network" }
srml-support = { path = "../../../srml/support" }
sr-primitives = { path = "../../sr-primitives" }
sr-version = { path = "../../sr-version" }
sr-io = { path = "../../sr-io" }
srml-consensus = { path = "../../../srml/consensus" }
tokio = "0.1.7"
parking_lot = "0.4"
error-chain = "0.12"
log = "0.3"
[dev-dependencies]
substrate-keyring = { path = "../../keyring" }
substrate-executor = { path = "../../executor" }
substrate-service = { path = "../../service" }
substrate-test-client = { path = "../../test-client" }
env_logger = { version = "0.4" }
[target.'cfg(test)'.dependencies]
substrate-network = { path = "../../network", features = ["test-helpers"] }
[features]
default = ["std"]
std = [
"substrate-primitives/std",
"srml-support/std",
"sr-primitives/std",
"sr-version/std",
]
+560
View File
@@ -0,0 +1,560 @@
// 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/>.
//! Aura (Authority-round) consensus in substrate.
//!
//! Aura works by having a list of authorities A who are expected to roughly
//! agree on the current time. Time is divided up into discrete slots of t
//! seconds each. For each slot s, the author of that slot is A[s % |A|].
//!
//! The author is allowed to issue one block but not more during that slot,
//! and it will be built upon the longest valid chain that has been seen.
//!
//! Blocks from future steps will be either deferred or rejected depending on how
//! far in the future they are.
extern crate parity_codec as codec;
extern crate substrate_consensus_common as consensus_common;
extern crate substrate_client as client;
extern crate substrate_primitives as primitives;
extern crate substrate_network as network;
extern crate srml_support as runtime_support;
extern crate sr_primitives as runtime_primitives;
extern crate sr_version as runtime_version;
extern crate sr_io as runtime_io;
extern crate tokio;
#[cfg(test)]
extern crate substrate_keyring as keyring;
#[cfg(test)]
extern crate substrate_service as service;
#[cfg(test)]
extern crate substrate_test_client as test_client;
#[cfg(test)]
extern crate env_logger;
extern crate parking_lot;
#[macro_use]
extern crate log;
extern crate futures;
use std::sync::Arc;
use std::time::{Duration, Instant};
use codec::Encode;
use consensus_common::{Authorities, BlockImport, Environment, Proposer};
use client::ChainHead;
use consensus_common::{ImportBlock, BlockOrigin};
use runtime_primitives::{generic, generic::BlockId};
use runtime_primitives::traits::{Block, Header, Digest, DigestItemFor};
use network::import_queue::{Verifier, BasicQueue};
use primitives::{AuthorityId, ed25519};
use futures::{Stream, Future, IntoFuture, future::{self, Either}};
use tokio::timer::Interval;
pub use consensus_common::SyncOracle;
/// A handle to the network. This is generally implemented by providing some
/// handle to a gossip service or similar.
///
/// Intended to be a lightweight handle such as an `Arc`.
pub trait Network: Clone {
/// A stream of input messages for a topic.
type In: Stream<Item=Vec<u8>,Error=()>;
/// Send a message at a specific round out.
fn send_message(&self, slot: u64, message: Vec<u8>);
}
/// Configuration for Aura consensus.
#[derive(Clone)]
pub struct Config {
/// The local authority keypair. Can be none if this is just an observer.
pub local_key: Option<Arc<ed25519::Pair>>,
/// The slot duration in seconds.
pub slot_duration: u64
}
/// Get slot author for given block along with authorities.
fn slot_author(slot_num: u64, authorities: &[AuthorityId]) -> Option<AuthorityId> {
if authorities.is_empty() { return None }
let idx = slot_num % (authorities.len() as u64);
assert!(idx <= usize::max_value() as u64,
"It is impossible to have a vector with length beyond the address space; qed");
let current_author = *authorities.get(idx as usize)
.expect("authorities not empty; index constrained to list length;\
this is a valid index; qed");
Some(current_author)
}
fn duration_now() -> Option<Duration> {
use std::time::SystemTime;
let now = SystemTime::now();
now.duration_since(SystemTime::UNIX_EPOCH).map_err(|e| {
warn!("Current time {:?} is before unix epoch. Something is wrong: {:?}", now, e);
}).ok()
}
/// Get the slot for now.
fn slot_now(slot_duration: u64) -> Option<u64> {
duration_now().map(|s| s.as_secs() / slot_duration)
}
/// A digest item which is usable with aura consensus.
pub trait CompatibleDigestItem: Sized {
/// Construct a digest item which is a slot number and a signature on the
/// hash.
fn aura_seal(slot_number: u64, signature: ed25519::Signature) -> Self;
/// If this item is an Aura seal, return the slot number and signature.
fn as_aura_seal(&self) -> Option<(u64, &ed25519::Signature)>;
}
impl CompatibleDigestItem for generic::DigestItem<primitives::H256, u64> {
/// Construct a digest item which is a slot number and a signature on the
/// hash.
fn aura_seal(slot_number: u64, signature: ed25519::Signature) -> Self {
generic::DigestItem::Seal(slot_number, signature)
}
/// If this item is an Aura seal, return the slot number and signature.
fn as_aura_seal(&self) -> Option<(u64, &ed25519::Signature)> {
match self {
generic::DigestItem::Seal(slot, ref sign) => Some((*slot, sign)),
_ => None
}
}
}
impl CompatibleDigestItem for generic::DigestItem<primitives::H256, primitives::AuthorityId> {
/// Construct a digest item which is a slot number and a signature on the
/// hash.
fn aura_seal(slot_number: u64, signature: ed25519::Signature) -> Self {
generic::DigestItem::Seal(slot_number, signature)
}
/// If this item is an Aura seal, return the slot number and signature.
fn as_aura_seal(&self) -> Option<(u64, &ed25519::Signature)> {
match self {
generic::DigestItem::Seal(slot, ref sign) => Some((*slot, sign)),
_ => None
}
}
}
/// Start the aura worker. This should be run in a tokio runtime.
pub fn start_aura<B, C, E, SO, Error>(
config: Config,
client: Arc<C>,
env: Arc<E>,
sync_oracle: SO,
)
-> impl Future<Item=(),Error=()> where
B: Block,
C: Authorities<B, Error=Error> + BlockImport<B, Error=Error> + ChainHead<B>,
E: Environment<B, Error=Error>,
E::Proposer: Proposer<B, Error=Error>,
SO: SyncOracle + Send + Clone,
DigestItemFor<B>: CompatibleDigestItem,
Error: ::std::error::Error + Send + 'static + From<::consensus_common::Error>,
{
let make_authorship = move || {
let config = config.clone();
let client = client.clone();
let env = env.clone();
let sync_oracle = sync_oracle.clone();
let local_keys = config.local_key.map(|pair| (pair.public(), pair));
let slot_duration = config.slot_duration;
let mut last_authored_slot = 0;
let next_slot_start = duration_now().map(|now| {
let remaining_full_secs = slot_duration - (now.as_secs() % slot_duration) - 1;
let remaining_nanos = 1_000_000_000 - now.subsec_nanos();
Instant::now() + Duration::new(remaining_full_secs, remaining_nanos)
}).unwrap_or_else(|| Instant::now());
Interval::new(next_slot_start, Duration::from_secs(slot_duration))
.filter(move |_| !sync_oracle.is_major_syncing()) // only propose when we are not syncing.
.filter_map(move |_| local_keys.clone()) // skip if not authoring.
.map_err(|e| debug!(target: "aura", "Faulty timer: {:?}", e))
.for_each(move |(public_key, key)| {
use futures::future;
let slot_num = match slot_now(slot_duration) {
Some(n) => n,
None => return Either::B(future::err(())),
};
if last_authored_slot >= slot_num { return Either::B(future::ok(())) }
last_authored_slot = slot_num;
let chain_head = match client.best_block_header() {
Ok(x) => x,
Err(e) => {
warn!(target:"aura", "Unable to author block in slot {}. no best block header: {:?}", slot_num, e);
return Either::B(future::ok(()))
}
};
let authorities = match client.authorities(&BlockId::Hash(chain_head.hash())){
Ok(authorities) => authorities,
Err(e) => {
warn!("Unable to fetch authorities at block {:?}: {:?}", chain_head.hash(), e);
return Either::B(future::ok(()));
}
};
let proposal_work = match slot_author(slot_num, &authorities) {
None => return Either::B(future::ok(())),
Some(author) => if author.0 == public_key.0 {
// we are the slot author. make a block and sign it.
let proposer = match env.init(&chain_head, &authorities, key.clone()) {
Ok(p) => p,
Err(e) => {
warn!("Unable to author block in slot {:?}: {:?}", slot_num, e);
return Either::B(future::ok(()))
}
};
proposer.propose().into_future()
} else {
return Either::B(future::ok(()));
}
};
let block_import = client.clone();
Either::A(proposal_work
.map(move |b| {
let (header, body) = b.deconstruct();
let pre_hash = header.hash();
let parent_hash = header.parent_hash().clone();
// sign the pre-sealed hash of the block and then
// add it to a digest item.
let to_sign = (slot_num, pre_hash).encode();
let signature = key.sign(&to_sign[..]);
let item = <DigestItemFor<B> as CompatibleDigestItem>::aura_seal(slot_num, signature);
let import_block = ImportBlock {
origin: BlockOrigin::Own,
header,
external_justification: Vec::new(),
post_runtime_digests: vec![item],
body: Some(body),
finalized: false,
auxiliary: Vec::new(),
};
if let Err(e) = block_import.import_block(import_block, None) {
warn!(target: "aura", "Error with block built on {:?}: {:?}", parent_hash, e);
}
})
.map_err(|e| warn!("Failed to construct block: {:?}", e))
)
})
};
future::loop_fn((), move |()| {
let authorship_task = ::std::panic::AssertUnwindSafe(make_authorship());
authorship_task.catch_unwind().then(|res| {
match res {
Ok(Ok(())) => (),
Ok(Err(())) => warn!("Aura authorship task terminated unexpectedly. Restarting"),
Err(e) => {
if let Some(s) = e.downcast_ref::<&'static str>() {
warn!("Aura authorship task panicked at {:?}", s);
}
warn!("Restarting Aura authorship task");
}
}
Ok(future::Loop::Continue(()))
})
})
}
// a header which has been checked
enum CheckedHeader<H> {
// a header which has slot in the future. this is the full header (not stripped)
// and the slot in which it should be processed.
Deferred(H, u64),
// a header which is fully checked, including signature. This is the pre-header
// accompanied by the seal components.
Checked(H, u64, ed25519::Signature),
}
/// check a header has been signed by the right key. If the slot is too far in the future, an error will be returned.
/// if it's successful, returns the pre-header, the slot number, and the signat.
//
// FIXME: needs misbehavior types - https://github.com/paritytech/substrate/issues/1018
fn check_header<B: Block>(slot_now: u64, mut header: B::Header, hash: B::Hash, authorities: &[AuthorityId])
-> Result<CheckedHeader<B::Header>, String>
where DigestItemFor<B>: CompatibleDigestItem
{
let digest_item = match header.digest_mut().pop() {
Some(x) => x,
None => return Err(format!("Header {:?} is unsealed", hash)),
};
let (slot_num, &sig) = match digest_item.as_aura_seal() {
Some(x) => x,
None => return Err(format!("Header {:?} is unsealed", hash)),
};
if slot_num > slot_now {
header.digest_mut().push(digest_item);
Ok(CheckedHeader::Deferred(header, slot_num))
} else {
// check the signature is valid under the expected authority and
// chain state.
let expected_author = match slot_author(slot_num, &authorities) {
None => return Err("Slot Author not found".to_string()),
Some(author) => author
};
let pre_hash = header.hash();
let to_sign = (slot_num, pre_hash).encode();
let public = ed25519::Public(expected_author.0);
if ed25519::verify_strong(&sig, &to_sign[..], public) {
Ok(CheckedHeader::Checked(header, slot_num, sig))
} else {
Err(format!("Bad signature on {:?}", hash))
}
}
}
/// A verifier for Aura blocks.
pub struct AuraVerifier<C> {
config: Config,
client: Arc<C>,
}
impl<B: Block, C> Verifier<B> for AuraVerifier<C> where
C: Authorities<B> + BlockImport<B> + Send + Sync,
DigestItemFor<B>: CompatibleDigestItem,
{
fn verify(
&self,
origin: BlockOrigin,
header: B::Header,
_justification: Vec<u8>,
body: Option<Vec<B::Extrinsic>>
) -> Result<(ImportBlock<B>, Option<Vec<AuthorityId>>), String> {
let slot_now = slot_now(self.config.slot_duration)
.ok_or("System time is before UnixTime?".to_owned())?;
let hash = header.hash();
let parent_hash = *header.parent_hash();
let authorities = self.client.authorities(&BlockId::Hash(parent_hash))
.map_err(|e| format!("Could not fetch authorities at {:?}: {:?}", parent_hash, e))?;
// we add one to allow for some small drift.
// FIXME: in the future, alter this queue to allow deferring of headers
// https://github.com/paritytech/substrate/issues/1019
let checked_header = check_header::<B>(slot_now + 1, header, hash, &authorities[..])?;
match checked_header {
CheckedHeader::Checked(pre_header, slot_num, sig) => {
let item = <DigestItemFor<B>>::aura_seal(slot_num, sig);
debug!(target: "aura", "Checked {:?}; importing.", pre_header);
let import_block = ImportBlock {
origin,
header: pre_header,
external_justification: Vec::new(),
post_runtime_digests: vec![item],
body,
finalized: false,
auxiliary: Vec::new(),
};
// FIXME: extract authorities - https://github.com/paritytech/substrate/issues/1019
Ok((import_block, None))
}
CheckedHeader::Deferred(a, b) => {
debug!(target: "aura", "Checking {:?} failed; {:?}, {:?}.", hash, a, b);
Err(format!("Header {:?} rejected: too far in the future", hash))
}
}
}
}
/// The Aura import queue type.
pub type AuraImportQueue<B, C> = BasicQueue<B, AuraVerifier<C>>;
/// Start an import queue for the Aura consensus algorithm.
pub fn import_queue<B, C>(config: Config, client: Arc<C>) -> AuraImportQueue<B, C> where
B: Block,
C: Authorities<B> + BlockImport<B> + Send + Sync,
DigestItemFor<B>: CompatibleDigestItem,
{
let verifier = Arc::new(AuraVerifier { config, client });
BasicQueue::new(verifier)
}
#[cfg(test)]
mod tests {
use super::*;
use consensus_common::NoNetwork as DummyOracle;
use network::test::*;
use network::test::{Block as TestBlock, PeersClient};
use runtime_primitives::traits::Block as BlockT;
use network::ProtocolConfig;
use parking_lot::Mutex;
use tokio::runtime::current_thread;
use keyring::Keyring;
use client::BlockchainEvents;
use test_client;
type Error = client::error::Error;
type TestClient = client::Client<test_client::Backend, test_client::Executor, TestBlock>;
struct DummyFactory(Arc<TestClient>);
struct DummyProposer(u64, Arc<TestClient>);
impl Environment<TestBlock> for DummyFactory {
type Proposer = DummyProposer;
type Error = Error;
fn init(&self, parent_header: &<TestBlock as BlockT>::Header, _authorities: &[AuthorityId], _sign_with: Arc<ed25519::Pair>)
-> Result<DummyProposer, Error>
{
Ok(DummyProposer(parent_header.number + 1, self.0.clone()))
}
}
impl Proposer<TestBlock> for DummyProposer {
type Error = Error;
type Create = Result<TestBlock, Error>;
fn propose(&self) -> Result<TestBlock, Error> {
self.1.new_block().unwrap().bake().map_err(|e| e.into())
}
}
const SLOT_DURATION: u64 = 1;
const TEST_ROUTING_INTERVAL: Duration = Duration::from_millis(50);
pub struct AuraTestNet {
peers: Vec<Arc<Peer<AuraVerifier<PeersClient>>>>,
started: bool
}
impl TestNetFactory for AuraTestNet {
type Verifier = AuraVerifier<PeersClient>;
/// Create new test network with peers and given config.
fn from_config(_config: &ProtocolConfig) -> Self {
AuraTestNet {
peers: Vec::new(),
started: false
}
}
fn make_verifier(&self, client: Arc<PeersClient>, _cfg: &ProtocolConfig)
-> Arc<Self::Verifier>
{
let config = Config { local_key: None, slot_duration: SLOT_DURATION };
Arc::new(AuraVerifier { client, config })
}
fn peer(&self, i: usize) -> &Peer<Self::Verifier> {
&self.peers[i]
}
fn peers(&self) -> &Vec<Arc<Peer<Self::Verifier>>> {
&self.peers
}
fn mut_peers<F: Fn(&mut Vec<Arc<Peer<Self::Verifier>>>)>(&mut self, closure: F ) {
closure(&mut self.peers);
}
fn started(&self) -> bool {
self.started
}
fn set_started(&mut self, new: bool) {
self.started = new;
}
}
#[test]
fn authoring_blocks() {
::env_logger::init().ok();
let mut net = AuraTestNet::new(3);
net.start();
let peers = &[
(0, Keyring::Alice),
(1, Keyring::Bob),
(2, Keyring::Charlie),
];
let net = Arc::new(Mutex::new(net));
let mut import_notifications = Vec::new();
let mut runtime = current_thread::Runtime::new().unwrap();
for (peer_id, key) in peers {
let mut client = net.lock().peer(*peer_id).client().clone();
let environ = Arc::new(DummyFactory(client.clone()));
import_notifications.push(
client.import_notification_stream()
.take_while(|n| {
Ok(!(n.origin != BlockOrigin::Own && n.header.number() < &5))
})
.for_each(move |_| Ok(()))
);
let aura = start_aura(
Config {
local_key: Some(Arc::new(key.clone().into())),
slot_duration: SLOT_DURATION
},
client,
environ.clone(),
DummyOracle,
);
runtime.spawn(aura);
}
// wait for all finalized on each.
let wait_for = ::futures::future::join_all(import_notifications)
.map(|_| ())
.map_err(|_| ());
let drive_to_completion = ::tokio::timer::Interval::new_interval(TEST_ROUTING_INTERVAL)
.for_each(move |_| {
net.lock().send_import_notifications();
net.lock().sync();
Ok(())
})
.map(|_| ())
.map_err(|_| ());
runtime.block_on(wait_for.select(drive_to_completion).map_err(|_| ())).unwrap();
}
}
+9 -2
View File
@@ -4,5 +4,12 @@ version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
description = "Common utilities for substrate consensus"
[dev-dependencies]
substrate-primitives = { path= "../../primitives"}
[dependencies]
substrate-primitives = { path= "../../primitives" }
error-chain = "0.12"
futures = "0.1"
sr-version = { path = "../../sr-version" }
sr-primitives = { path = "../../sr-primitives" }
tokio = "0.1.7"
parity-codec = "2.1"
parity-codec-derive = "2.0"
@@ -0,0 +1,104 @@
use primitives::AuthorityId;
use runtime_primitives::traits::{Block as BlockT, DigestItemFor};
use runtime_primitives::Justification;
/// Block import result.
#[derive(Debug)]
pub enum ImportResult {
/// Added to the import queue.
Queued,
/// Already in the import queue.
AlreadyQueued,
/// Already in the blockchain.
AlreadyInChain,
/// Block or parent is known to be bad.
KnownBad,
/// Block parent is not in the chain.
UnknownParent,
}
/// Block data origin.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum BlockOrigin {
/// Genesis block built into the client.
Genesis,
/// Block is part of the initial sync with the network.
NetworkInitialSync,
/// Block was broadcasted on the network.
NetworkBroadcast,
/// Block that was received from the network and validated in the consensus process.
ConsensusBroadcast,
/// Block that was collated by this node.
Own,
/// Block was imported from a file.
File,
}
/// Data required to import a Block
pub struct ImportBlock<Block: BlockT> {
/// Origin of the Block
pub origin: BlockOrigin,
/// The header, without consensus post-digests applied. This should be in the same
/// state as it comes out of the runtime.
///
/// Consensus engines which alter the header (by adding post-runtime digests)
/// should strip those off in the initial verification process and pass them
/// via the `post_runtime_digests` field. During block authorship, they should
/// not be pushed to the header directly.
///
/// The reason for this distinction is so the header can be directly
/// re-executed in a runtime that checks digest equivalence -- the
/// post-runtime digests are pushed back on after.
pub header: Block::Header,
/// Justification provided for this block from the outside:.
pub external_justification: Justification,
/// Digest items that have been added after the runtime for external
/// work, like a consensus signature.
pub post_runtime_digests: Vec<DigestItemFor<Block>>,
/// Block's body
pub body: Option<Vec<Block::Extrinsic>>,
/// Is this block finalized already?
/// `true` implies instant finality.
pub finalized: bool,
/// Auxiliary consensus data produced by the block.
/// Contains a list of key-value pairs. If values are `None`, the keys
/// will be deleted.
pub auxiliary: Vec<(Vec<u8>, Option<Vec<u8>>)>,
}
impl<Block: BlockT> ImportBlock<Block> {
/// Deconstruct the justified header into parts.
pub fn into_inner(self)
-> (
BlockOrigin,
<Block as BlockT>::Header,
Justification,
Vec<DigestItemFor<Block>>,
Option<Vec<<Block as BlockT>::Extrinsic>>,
bool,
Vec<(Vec<u8>, Option<Vec<u8>>)>,
) {
(
self.origin,
self.header,
self.external_justification,
self.post_runtime_digests,
self.body,
self.finalized,
self.auxiliary,
)
}
}
/// Block import trait.
pub trait BlockImport<B: BlockT> {
type Error: ::std::error::Error + Send + 'static;
/// Import a Block alongside the new authorities valid form this block forward
fn import_block(&self,
block: ImportBlock<B>,
new_authorities: Option<Vec<AuthorityId>>
) -> Result<ImportResult, Self::Error>;
}
@@ -0,0 +1,88 @@
// Copyright 2017-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/>.
//! Error types in Consensus
use runtime_version::RuntimeVersion;
error_chain! {
errors {
/// Missing state at block with given descriptor.
StateUnavailable(b: String) {
description("State missing at given block."),
display("State unavailable at block {}", b),
}
/// I/O terminated unexpectedly
IoTerminated {
description("I/O terminated unexpectedly."),
display("I/O terminated unexpectedly."),
}
/// Unable to schedule wakeup.
FaultyTimer(e: ::tokio::timer::Error) {
description("Timer error"),
display("Timer error: {}", e),
}
/// Unable to propose a block.
CannotPropose {
description("Unable to create block proposal."),
display("Unable to create block proposal."),
}
/// Error checking signature
InvalidSignature(s: ::primitives::ed25519::Signature, a: ::primitives::AuthorityId) {
description("Message signature is invalid"),
display("Message signature {:?} by {:?} is invalid.", s, a),
}
/// Account is not an authority.
InvalidAuthority(a: ::primitives::AuthorityId) {
description("Message sender is not a valid authority"),
display("Message sender {:?} is not a valid authority.", a),
}
/// Authoring interface does not match the runtime.
IncompatibleAuthoringRuntime(native: RuntimeVersion, on_chain: RuntimeVersion) {
description("Authoring for current runtime is not supported"),
display("Authoring for current runtime is not supported. Native ({}) cannot author for on-chain ({}).", native, on_chain),
}
/// Authoring interface does not match the runtime.
RuntimeVersionMissing {
description("Current runtime has no version"),
display("Authoring for current runtime is not supported since it has no version."),
}
/// Authoring interface does not match the runtime.
NativeRuntimeMissing {
description("This build has no native runtime"),
display("Authoring in current build is not supported since it has no runtime."),
}
/// Justification requirements not met.
InvalidJustification {
description("Invalid justification"),
display("Invalid justification."),
}
/// Some other error.
Other(e: Box<::std::error::Error + Send>) {
description("Other error")
display("Other error: {}", e.description())
}
}
}
@@ -0,0 +1,82 @@
// 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::Encode;
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, As};
type BlockNumber = u64;
error_chain! {
errors {
BadProposalFormat {
description("Proposal provided not a block."),
display("Proposal provided not a block."),
}
WrongParentHash(expected: String, got: String) {
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<Block: BlockT>(
proposal: &Block,
parent_hash: &<Block as BlockT>::Hash,
parent_number: <<Block as BlockT>::Header as HeaderT>::Number,
) -> Result<()> {
let encoded = Encode::encode(proposal);
let proposal = Block::decode(&mut &encoded[..])
.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 *parent_hash != *proposal.header().parent_hash() {
bail!(ErrorKind::WrongParentHash(
format!("{:?}", *parent_hash),
format!("{:?}", proposal.header().parent_hash())
));
}
if parent_number.as_() + 1 != proposal.header().number().as_() {
bail!(ErrorKind::WrongNumber(parent_number.as_() + 1, proposal.header().number().as_()));
}
Ok(())
}
+105 -9
View File
@@ -15,22 +15,118 @@
// along with Substrate Consensus Common. If not, see <http://www.gnu.org/licenses/>.
// tag::description[]
//! Tracks offline validators.
//! Consensus basics and common features
// end::description[]
// This provides "unused" building blocks to other crates
#![allow(dead_code)]
#![cfg(feature="rhd")]
// our error-chain could potentially blow up otherwise
#![recursion_limit="128"]
extern crate substrate_primitives as primitives;
extern crate futures;
extern crate sr_version as runtime_version;
extern crate sr_primitives as runtime_primitives;
extern crate tokio;
use primitives::{generic::BlockId, Justification};
use primitives::traits::{Block, Header};
extern crate parity_codec as codec;
#[macro_use]
extern crate parity_codec_derive;
/// Block import trait.
pub trait BlockImport<B: Block> {
/// Import a block alongside its corresponding justification.
fn import_block(&self, block: B, justification: Justification, authorities: &[AuthorityId]) -> bool;
#[macro_use]
extern crate error_chain;
use std::sync::Arc;
use primitives::{ed25519, AuthorityId};
use runtime_primitives::generic::BlockId;
use runtime_primitives::traits::Block;
use futures::prelude::*;
pub mod offline_tracker;
pub mod error;
mod block_import;
pub mod evaluation;
// block size limit.
const MAX_TRANSACTIONS_SIZE: usize = 4 * 1024 * 1024;
pub use self::error::{Error, ErrorKind};
pub use block_import::{BlockImport, ImportBlock, BlockOrigin, ImportResult};
/// Trait for getting the authorities at a given block.
pub trait Authorities<B: Block> {
type Error: ::std::error::Error + Send + 'static; /// Get the authorities at the given block.
fn authorities(&self, at: &BlockId<B>) -> Result<Vec<AuthorityId>, Self::Error>;
}
pub mod offline_tracker;
/// Environment producer for a Consensus instance. Creates proposer instance and communication streams.
pub trait Environment<B: Block> {
/// The proposer type this creates.
type Proposer: Proposer<B>;
/// Error which can occur upon creation.
type Error: From<Error>;
/// Initialize the proposal logic on top of a specific header. Provide
/// the authorities at that header, and a local key to sign any additional
/// consensus messages with as well.
fn init(&self, parent_header: &B::Header, authorities: &[AuthorityId], sign_with: Arc<ed25519::Pair>)
-> Result<Self::Proposer, Self::Error>;
}
/// Logic for a proposer.
///
/// This will encapsulate creation and evaluation of proposals at a specific
/// block.
pub trait Proposer<B: Block> {
/// Error type which can occur when proposing or evaluating.
type Error: From<Error> + ::std::fmt::Debug + 'static;
/// Future that resolves to a committed proposal.
type Create: IntoFuture<Item=B,Error=Self::Error>;
/// Create a proposal.
fn propose(&self) -> Self::Create;
}
/// Inherent data to include in a block.
#[derive(Encode, Decode)]
pub struct InherentData {
/// Current timestamp.
pub timestamp: u64,
/// Indices of offline validators.
pub offline_indices: Vec<u32>,
}
impl InherentData {
/// Create a new `InherentData` instance.
pub fn new(timestamp: u64, offline_indices: Vec<u32>) -> Self {
Self {
timestamp,
offline_indices
}
}
}
/// An oracle for when major synchronization work is being undertaken.
///
/// Generally, consensus authoring work isn't undertaken while well behind
/// the head of the chain.
pub trait SyncOracle {
/// Whether the synchronization service is undergoing major sync.
/// Returns true if so.
fn is_major_syncing(&self) -> bool;
}
/// A synchronization oracle for when there is no network.
#[derive(Clone, Copy, Debug)]
pub struct NoNetwork;
impl SyncOracle for NoNetwork {
fn is_major_syncing(&self) -> bool { false }
}
impl<T: SyncOracle> SyncOracle for Arc<T> {
fn is_major_syncing(&self) -> bool {
T::is_major_syncing(&*self)
}
}
@@ -16,7 +16,7 @@
//! Tracks offline validators.
use node_primitives::AccountId;
use primitives::AuthorityId;
use std::collections::HashMap;
use std::time::{Instant, Duration};
@@ -56,7 +56,7 @@ impl Observed {
/// Tracks offline validators and can issue a report for those offline.
pub struct OfflineTracker {
observed: HashMap<AccountId, Observed>,
observed: HashMap<AuthorityId, Observed>,
}
impl OfflineTracker {
@@ -66,7 +66,7 @@ impl OfflineTracker {
}
/// Note new consensus is starting with the given set of validators.
pub fn note_new_block(&mut self, validators: &[AccountId]) {
pub fn note_new_block(&mut self, validators: &[AuthorityId]) {
use std::collections::HashSet;
let set: HashSet<_> = validators.iter().cloned().collect();
@@ -74,14 +74,14 @@ impl OfflineTracker {
}
/// Note that a round has ended.
pub fn note_round_end(&mut self, validator: AccountId, was_online: bool) {
pub fn note_round_end(&mut self, validator: AuthorityId, 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> {
pub fn reports(&self, validators: &[AuthorityId]) -> Vec<u32> {
validators.iter()
.enumerate()
.filter_map(|(i, v)| if self.is_online(v) {
@@ -93,7 +93,7 @@ impl OfflineTracker {
}
/// Whether reports on a validator set are consistent with our view of things.
pub fn check_consistency(&self, validators: &[AccountId], reports: &[u32]) -> bool {
pub fn check_consistency(&self, validators: &[AuthorityId], reports: &[u32]) -> bool {
reports.iter().cloned().all(|r| {
let v = match validators.get(r as usize) {
Some(v) => v,
@@ -106,7 +106,7 @@ impl OfflineTracker {
})
}
fn is_online(&self, v: &AccountId) -> bool {
fn is_online(&self, v: &AuthorityId) -> bool {
self.observed.get(v).map(Observed::is_active).unwrap_or(true)
}
}
+10 -7
View File
@@ -6,30 +6,33 @@ description = "Rhododendron Round-Based consensus-algorithm for substrate"
[dependencies]
futures = "0.1.17"
parity-codec = { version = "1.1" }
parity-codec = { version = "2.1" }
parity-codec-derive = { version = "2.0" }
substrate-primitives = { path = "../../primitives" }
substrate-consensus-common = { path = "../common" }
substrate-client = { path = "../../client" }
substrate-transaction-pool = { path = "../../transaction-pool" }
srml-support = { path = "../../../srml/support" }
srml-system = { path = "../../../srml/system" }
srml-consensus = { path = "../../../srml/consensus" }
sr-primitives = { path = "../../sr-primitives" }
sr-version = { path = "../../sr-version" }
sr-io = { path = "../../sr-io" }
srml-consensus = { path = "../../../srml/consensus" }
tokio = "0.1.7"
parking_lot = "0.4"
error-chain = "0.12"
log = "0.3"
rhododendron = { git = "https://github.com/paritytech/rhododendron.git", features = ["codec"] }
serde = { version = "1.0", features = ["derive"] }
log = "0.4"
rhododendron = { version = "0.4.0", features = ["codec"] }
exit-future = "0.1"
[dev-dependencies]
substrate-keyring = { path = "../../keyring" }
substrate-executor = { path = "../../executor" }
[features]
default = ["std"]
std = [
"serde/std",
"substrate-primitives/std",
"srml-support/std",
"sr-primitives/std",
+29 -66
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2018 Parity Technologies (UK) Ltd.
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
@@ -14,81 +14,44 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Error types in the BFT service.
use runtime_version::RuntimeVersion;
//! Error types in the rhododendron Consensus service.
use consensus::error::{Error as CommonError, ErrorKind as CommonErrorKind};
use primitives::AuthorityId;
use client;
error_chain! {
links {
Client(client::error::Error, client::error::ErrorKind);
Common(CommonError, CommonErrorKind);
}
errors {
/// Missing state at block with given descriptor.
StateUnavailable(b: String) {
description("State missing at given block."),
display("State unavailable at block {}", b),
NotValidator(id: AuthorityId) {
description("Local account ID not a validator at this block."),
display("Local account ID ({:?}) not a validator at this block.", id),
}
/// I/O terminated unexpectedly
IoTerminated {
description("I/O terminated unexpectedly."),
display("I/O terminated unexpectedly."),
PrematureDestruction {
description("Proposer destroyed before finishing proposing or evaluating"),
display("Proposer destroyed before finishing proposing or evaluating"),
}
/// Unable to schedule wakeup.
FaultyTimer(e: ::tokio::timer::Error) {
description("Timer error"),
display("Timer error: {}", e),
Timer(e: ::tokio::timer::Error) {
description("Failed to register or resolve async timer."),
display("Timer failed: {}", e),
}
/// Unable to propose a block.
CannotPropose {
description("Unable to create block proposal."),
display("Unable to create block proposal."),
}
/// Error checking signature
InvalidSignature(s: ::primitives::ed25519::Signature, a: ::primitives::AuthorityId) {
description("Message signature is invalid"),
display("Message signature {:?} by {:?} is invalid.", s, a),
}
/// Account is not an authority.
InvalidAuthority(a: ::primitives::AuthorityId) {
description("Message sender is not a valid authority"),
display("Message sender {:?} is not a valid authority.", a),
}
/// Authoring interface does not match the runtime.
IncompatibleAuthoringRuntime(native: RuntimeVersion, on_chain: RuntimeVersion) {
description("Authoring for current runtime is not supported"),
display("Authoring for current runtime is not supported. Native ({}) cannot author for on-chain ({}).", native, on_chain),
}
/// Authoring interface does not match the runtime.
RuntimeVersionMissing {
description("Current runtime has no version"),
display("Authoring for current runtime is not supported since it has no version."),
}
/// Authoring interface does not match the runtime.
NativeRuntimeMissing {
description("This build has no native runtime"),
display("Authoring in current build is not supported since it has no runtime."),
}
/// Justification requirements not met.
InvalidJustification {
description("Invalid justification"),
display("Invalid justification."),
}
/// Some other error.
Other(e: Box<::std::error::Error + Send>) {
description("Other error")
display("Other error: {}", e.description())
Executor(e: ::futures::future::ExecuteErrorKind) {
description("Unable to dispatch agreement future"),
display("Unable to dispatch agreement future: {:?}", e),
}
}
}
impl From<::rhododendron::InputStreamConcluded> for Error {
fn from(_: ::rhododendron::InputStreamConcluded) -> Error {
ErrorKind::IoTerminated.into()
fn from(_: ::rhododendron::InputStreamConcluded) -> Self {
CommonErrorKind::IoTerminated.into()
}
}
impl From<CommonErrorKind> for Error {
fn from(e: CommonErrorKind) -> Self {
CommonError::from(e).into()
}
}
File diff suppressed because it is too large Load Diff
+181
View File
@@ -0,0 +1,181 @@
// 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 client::{BlockchainEvents, ChainHead, BlockBody};
use futures::prelude::*;
use transaction_pool::txpool::{Pool as TransactionPool, ChainApi as PoolChainApi};
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, BlockNumberToHash};
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 parking_lot::RwLock;
use consensus::offline_tracker::OfflineTracker;
use super::{Network, ProposerFactory, AuthoringApi};
use {consensus, primitives, ed25519, error, BftService, LocalProposer};
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, Block>(
header: <Block as BlockT>::Header,
bft_service: Arc<BftService<Block, F, C>>,
) where
F: consensus::Environment<Block> + 'static,
C: consensus::BlockImport<Block> + consensus::Authorities<Block> + 'static,
F::Error: ::std::fmt::Debug,
<F::Proposer as consensus::Proposer<Block>>::Error: ::std::fmt::Display + Into<error::Error>,
<F as consensus::Environment<Block>>::Proposer : LocalProposer<Block>,
<F as consensus::Environment<Block>>::Error: ::std::fmt::Display,
Block: BlockT,
{
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, P, C, N>(
client: Arc<C>,
api: Arc<A>,
network: N,
transaction_pool: Arc<TransactionPool<P>>,
thread_pool: ThreadPoolHandle,
key: ed25519::Pair,
block_delay: u64,
) -> Service
where
error::Error: From<<A as AuthoringApi>::Error>,
A: AuthoringApi + BlockNumberToHash + 'static,
P: PoolChainApi<Block = <A as AuthoringApi>::Block> + 'static,
C: BlockchainEvents<<A as AuthoringApi>::Block>
+ ChainHead<<A as AuthoringApi>::Block>
+ BlockBody<<A as AuthoringApi>::Block>,
C: consensus::BlockImport<<A as AuthoringApi>::Block>
+ consensus::Authorities<<A as AuthoringApi>::Block> + Send + Sync + 'static,
primitives::H256: From<<<A as AuthoringApi>::Block as BlockT>::Hash>,
<<A as AuthoringApi>::Block as BlockT>::Hash: PartialEq<primitives::H256> + PartialEq,
N: Network<Block = <A as AuthoringApi>::Block> + Send + 'static,
{
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())),
force_delay: block_delay,
};
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");
}
}
}
+1
View File
@@ -17,6 +17,7 @@ linked-hash-map = "0.5"
rustc-hex = "1.0"
rand = "0.5"
substrate-primitives = { path = "../../core/primitives" }
substrate-consensus-common = { path = "../../core/consensus/common" }
substrate-client = { path = "../../core/client" }
sr-primitives = { path = "../../core/sr-primitives" }
parity-codec = "2.1"
+9 -4
View File
@@ -16,10 +16,12 @@
//! Blockchain access trait
use client::{self, Client as SubstrateClient, ImportBlock, ImportResult, ClientInfo, BlockStatus, CallExecutor};
use client::{self, Client as SubstrateClient, ClientInfo, BlockStatus, CallExecutor};
use client::error::Error;
use consensus::BlockImport;
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, NumberFor};
use runtime_primitives::generic::BlockId;
use runtime_primitives::generic::{BlockId};
use consensus::{ImportBlock, ImportResult};
use runtime_primitives::Justification;
use primitives::{Blake2Hasher, AuthorityId};
@@ -69,9 +71,12 @@ pub trait Client<Block: BlockT>: Send + Sync {
impl<B, E, Block> Client<Block> for SubstrateClient<B, E, Block> where
B: client::backend::Backend<Block, Blake2Hasher> + Send + Sync + 'static,
E: CallExecutor<Block, Blake2Hasher> + Send + Sync + 'static,
Block: BlockT,
Self: BlockImport<Block, Error=Error>,
Block: BlockT
{
fn import(&self, block: ImportBlock<Block>, new_authorities: Option<Vec<AuthorityId>>) -> Result<ImportResult, Error> {
fn import(&self, block: ImportBlock<Block>, new_authorities: Option<Vec<AuthorityId>>)
-> Result<ImportResult, Error>
{
(self as &SubstrateClient<B, E, Block>).import_block(block, new_authorities)
}
+6 -7
View File
@@ -28,8 +28,6 @@ use std::collections::{HashSet, VecDeque};
use std::sync::{Arc, Weak};
use std::sync::atomic::{AtomicBool, Ordering};
use parking_lot::{Condvar, Mutex, RwLock};
pub use client::{BlockOrigin, ImportBlock, ImportResult};
use network_libp2p::{NodeIndex, Severity};
use primitives::AuthorityId;
@@ -42,6 +40,9 @@ use protocol::Context;
use service::ExecuteInContext;
use sync::ChainSync;
pub use consensus::{ImportBlock, ImportResult, BlockOrigin};
#[cfg(any(test, feature = "test-helpers"))]
use std::cell::RefCell;
@@ -65,7 +66,6 @@ pub trait ImportQueue<B: BlockT>: Send + Sync {
///
/// This is called automatically by the network service when synchronization
/// begins.
fn start<E>(
&self,
_sync: Weak<RwLock<ChainSync<B>>>,
@@ -284,7 +284,7 @@ struct SyncLink<'a, B: 'a + BlockT, E: 'a + ExecuteInContext<B>> {
}
impl<'a, B: 'static + BlockT, E: 'a + ExecuteInContext<B>> SyncLink<'a, B, E> {
/// Execute closure with locked ChainSync.
/// Execute closure with locked ChainSync.
fn with_sync<F: Fn(&mut ChainSync<B>, &mut Context<B>)>(&mut self, closure: F) {
let service = self.context;
let sync = self.chain;
@@ -440,7 +440,6 @@ fn import_single_block<B: BlockT, V: Verifier<B>>(
trace!(target: "sync", "Verifying {}({}) failed: {}", number, hash, msg);
}
BlockImportError::VerificationFailed(peer, msg)
})?;
match chain.import(import_block, new_authorities) {
@@ -545,7 +544,7 @@ unsafe impl<B: BlockT> Sync for ImportCB<B> {}
#[cfg(any(test, feature = "test-helpers"))]
/// A Verifier that accepts all blocks and passes them on with the configured
/// finality to be imported.
/// finality to be imported.
pub struct PassThroughVerifier(pub bool);
#[cfg(any(test, feature = "test-helpers"))]
@@ -564,7 +563,7 @@ impl<B: BlockT> Verifier<B> for PassThroughVerifier {
body,
finalized: self.0,
external_justification: justification,
internal_justification: vec![],
post_runtime_digests: vec![],
auxiliary: Vec::new(),
}, None))
}
+1
View File
@@ -28,6 +28,7 @@ extern crate substrate_primitives as primitives;
extern crate substrate_client as client;
extern crate sr_primitives as runtime_primitives;
extern crate substrate_network_libp2p as network_libp2p;
extern crate substrate_consensus_common as consensus;
extern crate parity_codec as codec;
extern crate futures;
extern crate rustc_hex;
+16 -16
View File
@@ -776,41 +776,41 @@ macro_rules! construct_simple_protocol {
fn on_connect(
&mut self,
ctx: &mut $crate::Context<$block>,
who: $crate::NodeIndex,
status: $crate::StatusMessage<$block>
_ctx: &mut $crate::Context<$block>,
_who: $crate::NodeIndex,
_status: $crate::StatusMessage<$block>
) {
$( self.$sub_protocol_name.on_connect(ctx, who, status); )*
$( self.$sub_protocol_name.on_connect(_ctx, _who, _status); )*
}
fn on_disconnect(&mut self, ctx: &mut $crate::Context<$block>, who: $crate::NodeIndex) {
$( self.$sub_protocol_name.on_disconnect(ctx, who); )*
fn on_disconnect(&mut self, _ctx: &mut $crate::Context<$block>, _who: $crate::NodeIndex) {
$( self.$sub_protocol_name.on_disconnect(_ctx, _who); )*
}
fn on_message(
&mut self,
ctx: &mut $crate::Context<$block>,
who: $crate::NodeIndex,
message: &mut Option<$crate::message::Message<$block>>
_ctx: &mut $crate::Context<$block>,
_who: $crate::NodeIndex,
_message: &mut Option<$crate::message::Message<$block>>
) {
$( self.$sub_protocol_name.on_message(ctx, who, message); )*
$( self.$sub_protocol_name.on_message(_ctx, _who, _message); )*
}
fn on_abort(&mut self) {
$( self.$sub_protocol_name.on_abort(); )*
}
fn maintain_peers(&mut self, ctx: &mut $crate::Context<$block>) {
$( self.$sub_protocol_name.maintain_peers(ctx); )*
fn maintain_peers(&mut self, _ctx: &mut $crate::Context<$block>) {
$( self.$sub_protocol_name.maintain_peers(_ctx); )*
}
fn on_block_imported(
&mut self,
ctx: &mut $crate::Context<$block>,
hash: <$block as $crate::BlockT>::Hash,
header: &<$block as $crate::BlockT>::Header
_ctx: &mut $crate::Context<$block>,
_hash: <$block as $crate::BlockT>::Hash,
_header: &<$block as $crate::BlockT>::Header
) {
$( self.$sub_protocol_name.on_block_imported(ctx, hash, header); )*
$( self.$sub_protocol_name.on_block_imported(_ctx, _hash, _header); )*
}
}
}
+6
View File
@@ -178,6 +178,12 @@ impl<B: BlockT + 'static, S: Specialization<B>, H: ExHashT> Service<B, S, H> {
}
}
impl<B: BlockT + 'static, S: Specialization<B>, H: ExHashT> ::consensus::SyncOracle for Service<B, S, H> {
fn is_major_syncing(&self) -> bool {
self.handler.sync().read().status().is_major_syncing()
}
}
impl<B: BlockT + 'static, S: Specialization<B>, H:ExHashT> Drop for Service<B, S, H> {
fn drop(&mut self) {
self.handler.stop();
@@ -25,9 +25,6 @@ pub trait Specialization<B: BlockT>: Send + Sync + 'static {
/// Get the current specialization-status.
fn status(&self) -> Vec<u8>;
/// Called on start-up.
fn on_start(&mut self) { }
/// Called when a peer successfully handshakes.
fn on_connect(&mut self, ctx: &mut Context<B>, who: NodeIndex, status: ::message::Status<B>);
+13 -1
View File
@@ -18,7 +18,8 @@ use std::collections::HashMap;
use std::sync::Arc;
use protocol::Context;
use network_libp2p::{Severity, NodeIndex};
use client::{BlockStatus, BlockOrigin, ClientInfo};
use client::{BlockStatus, ClientInfo};
use consensus::BlockOrigin;
use client::error::Error as ClientError;
use blocks::{self, BlockCollection};
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, As, NumberFor};
@@ -77,6 +78,17 @@ pub struct Status<B: BlockT> {
pub best_seen_block: Option<NumberFor<B>>,
}
impl<B: BlockT> Status<B> {
/// Whether the synchronization status is doing major downloading work or
/// is near the head of the chain.
pub fn is_major_syncing(&self) -> bool {
match self.state {
SyncState::Idle => false,
SyncState::Downloading => true,
}
}
}
impl<B: BlockT> ChainSync<B> {
/// Create a new instance.
pub(crate) fn new(role: Roles, info: &ClientInfo<B>, import_queue: Arc<ImportQueue<B>>) -> Self {
+152 -75
View File
@@ -34,14 +34,16 @@ use service::TransactionPool;
use network_libp2p::{NodeIndex, PeerId, Severity};
use keyring::Keyring;
use codec::Encode;
use import_queue::{SyncImportQueue, PassThroughVerifier};
use test_client::{self, TestClient};
use import_queue::{SyncImportQueue, PassThroughVerifier, Verifier};
use consensus::BlockOrigin;
use specialization::Specialization;
use consensus_gossip::ConsensusGossip;
use import_queue::ImportQueue;
use service::ExecuteInContext;
use test_client;
pub use test_client::runtime::{Block, Hash, Transfer, Extrinsic};
pub use test_client::TestClient;
struct DummyContextExecutor(Arc<Protocol<Block, DummySpecialization, Hash>>, Arc<RwLock<VecDeque<TestPacket>>>);
unsafe impl Send for DummyContextExecutor {}
@@ -135,20 +137,22 @@ pub struct TestPacket {
recipient: NodeIndex,
}
pub struct Peer {
client: Arc<client::Client<test_client::Backend, test_client::Executor, Block>>,
pub type PeersClient = client::Client<test_client::Backend, test_client::Executor, Block>;
pub struct Peer<V: Verifier<Block>> {
client: Arc<PeersClient>,
pub sync: Arc<Protocol<Block, DummySpecialization, Hash>>,
pub queue: Arc<RwLock<VecDeque<TestPacket>>>,
import_queue: Arc<SyncImportQueue<Block, PassThroughVerifier>>,
import_queue: Arc<SyncImportQueue<Block, V>>,
executor: Arc<DummyContextExecutor>,
}
impl Peer {
impl<V: 'static + Verifier<Block>> Peer<V> {
fn new(
client: Arc<client::Client<test_client::Backend, test_client::Executor, Block>>,
client: Arc<PeersClient>,
sync: Arc<Protocol<Block, DummySpecialization, Hash>>,
queue: Arc<RwLock<VecDeque<TestPacket>>>,
import_queue: Arc<SyncImportQueue<Block, PassThroughVerifier>>,
import_queue: Arc<SyncImportQueue<Block, V>>,
) -> Self {
let executor = Arc::new(DummyContextExecutor(sync.clone(), queue.clone()));
Peer { client, sync, queue, import_queue, executor}
@@ -201,6 +205,13 @@ impl Peer {
self.sync.tick(&mut TestIo::new(&self.queue, None));
}
/// Send block import notifications.
fn send_import_notifications(&self) {
let info = self.client.info().expect("In-mem client does not fail");
let header = self.client.header(&BlockId::Hash(info.chain.best_hash)).unwrap().unwrap();
self.sync.on_block_imported(&mut TestIo::new(&self.queue, None), info.chain.best_hash, &header);
}
/// Restart sync for a peer.
fn restart_sync(&self) {
self.sync.abort();
@@ -218,15 +229,18 @@ impl Peer {
}
/// Add blocks to the peer -- edit the block before adding
pub fn generate_blocks<F>(&self, count: usize, mut edit_block: F)
pub fn generate_blocks<F>(&self, count: usize, origin: BlockOrigin, mut edit_block: F)
where F: FnMut(&mut BlockBuilder<test_client::Backend, test_client::Executor, Block, Blake2Hasher>)
{
for _ in 0 .. count {
let mut builder = self.client.new_block().unwrap();
edit_block(&mut builder);
let block = builder.bake().unwrap();
trace!("Generating {}, (#{}, parent={})", block.header.hash(), block.header.number, block.header.parent_hash);
self.client.justify_and_import(client::BlockOrigin::File, block).unwrap();
let hash = block.header.hash();
trace!("Generating {}, (#{}, parent={})", hash, block.header.number, block.header.parent_hash);
let header = block.header.clone();
self.client.justify_and_import(origin, block).unwrap();
self.sync.on_block_imported(&mut TestIo::new(&self.queue, None), hash, &header);
}
}
@@ -234,7 +248,7 @@ impl Peer {
pub fn push_blocks(&self, count: usize, with_tx: bool) {
let mut nonce = 0;
if with_tx {
self.generate_blocks(count, |builder| {
self.generate_blocks(count, BlockOrigin::File, |builder| {
let transfer = Transfer {
from: Keyring::Alice.to_raw_public().into(),
to: Keyring::Alice.to_raw_public().into(),
@@ -246,7 +260,7 @@ impl Peer {
nonce = nonce + 1;
});
} else {
self.generate_blocks(count, |_| ());
self.generate_blocks(count, BlockOrigin::File, |_| ());
}
}
@@ -258,7 +272,7 @@ impl Peer {
}
/// Get a reference to the client.
pub fn client(&self) -> &Arc<client::Client<test_client::Backend, test_client::Executor, Block>> {
pub fn client(&self) -> &Arc<PeersClient> {
&self.client
}
}
@@ -277,25 +291,30 @@ impl TransactionPool<Hash, Block> for EmptyTransactionPool {
fn on_broadcasted(&self, _: HashMap<Hash, Vec<String>>) {}
}
pub struct TestNet {
peers: Vec<Arc<Peer>>,
started: bool,
disconnect_events: Vec<(NodeIndex, NodeIndex)>, //disconnected (initiated by, to)
}
pub trait TestNetFactory: Sized {
type Verifier: 'static + Verifier<Block>;
impl TestNet {
/// Create new test network with this many peers.
pub fn new(n: usize) -> Self {
Self::new_with_config(n, ProtocolConfig::default())
/// These two need to be implemented!
fn from_config(config: &ProtocolConfig) -> Self;
fn make_verifier(&self, client: Arc<PeersClient>, config: &ProtocolConfig) -> Arc<Self::Verifier>;
/// Get reference to peer.
fn peer(&self, i: usize) -> &Peer<Self::Verifier>;
fn peers(&self) -> &Vec<Arc<Peer<Self::Verifier>>>;
fn mut_peers<F: Fn(&mut Vec<Arc<Peer<Self::Verifier>>>)>(&mut self, closure: F );
fn started(&self) -> bool;
fn set_started(&mut self, now: bool);
fn default_config() -> ProtocolConfig {
ProtocolConfig::default()
}
/// Create new test network with peers and given config.
pub fn new_with_config(n: usize, config: ProtocolConfig) -> Self {
let mut net = TestNet {
peers: Vec::new(),
started: false,
disconnect_events: Vec::new(),
};
/// Create new test network with this many peers.
fn new(n: usize) -> Self {
let config = Self::default_config();
let mut net = Self::from_config(&config);
for _ in 0..n {
net.add_peer(&config);
@@ -304,10 +323,11 @@ impl TestNet {
}
/// Add a peer.
pub fn add_peer(&mut self, config: &ProtocolConfig) {
fn add_peer(&mut self, config: &ProtocolConfig) {
let client = Arc::new(test_client::new());
let tx_pool = Arc::new(EmptyTransactionPool);
let import_queue = Arc::new(SyncImportQueue::new(Arc::new(PassThroughVerifier(false))));
let verifier = self.make_verifier(client.clone(), config);
let import_queue = Arc::new(SyncImportQueue::new(verifier));
let specialization = DummySpecialization {
gossip: ConsensusGossip::new(),
};
@@ -320,93 +340,107 @@ impl TestNet {
specialization
).unwrap();
self.peers.push(Arc::new(Peer::new(
let peer = Arc::new(Peer::new(
client,
Arc::new(sync),
Arc::new(RwLock::new(VecDeque::new())),
import_queue
)));
}
));
/// Get reference to peer.
pub fn peer(&self, i: usize) -> &Peer {
&self.peers[i]
self.mut_peers(|peers| {
peers.push(peer.clone())
});
}
/// Start network.
fn start(&mut self) {
if self.started {
if self.started() {
return;
}
for peer in 0..self.peers.len() {
self.peers[peer].start();
for client in 0..self.peers.len() {
if peer != client {
self.peers[peer].on_connect(client as NodeIndex);
self.mut_peers(|peers| {
for peer in 0..peers.len() {
peers[peer].start();
for client in 0..peers.len() {
if peer != client {
peers[peer].on_connect(client as NodeIndex);
}
}
}
}
self.started = true;
});
self.set_started(true);
}
/// Do one step of routing.
pub fn route(&mut self) {
for peer in 0..self.peers.len() {
let packet = self.peers[peer].pending_message();
if let Some(packet) = packet {
let disconnecting = {
let recipient = packet.recipient;
trace!("--- {} -> {} ---", peer, recipient);
let to_disconnect = self.peers[recipient].receive_message(peer as NodeIndex, packet);
for d in &to_disconnect {
// notify this that disconnecting peers are disconnecting
self.peers[recipient].on_disconnect(*d as NodeIndex);
self.disconnect_events.push((peer, *d));
fn route(&mut self) {
self.mut_peers(move |peers| {
for peer in 0..peers.len() {
let packet = peers[peer].pending_message();
if let Some(packet) = packet {
let disconnecting = {
let recipient = packet.recipient;
trace!(target: "sync", "--- {} -> {} ---", peer, recipient);
let to_disconnect = peers[recipient].receive_message(peer as NodeIndex, packet);
for d in &to_disconnect {
// notify this that disconnecting peers are disconnecting
peers[recipient].on_disconnect(*d as NodeIndex);
}
to_disconnect
};
for d in &disconnecting {
// notify other peers that this peer is disconnecting
peers[*d].on_disconnect(peer as NodeIndex);
}
to_disconnect
};
for d in &disconnecting {
// notify other peers that this peer is disconnecting
self.peers[*d].on_disconnect(peer as NodeIndex);
}
}
}
});
}
/// Route messages between peers until all queues are empty.
pub fn route_until_complete(&mut self) {
fn route_until_complete(&mut self) {
while !self.done() {
self.route()
}
}
/// Do a step of synchronization.
pub fn sync_step(&mut self) {
fn sync_step(&mut self) {
self.route();
for peer in &mut self.peers {
peer.sync_step();
}
self.mut_peers(|peers| {
for peer in peers {
peer.sync_step();
}
})
}
/// Send block import notifications for all peers.
fn send_import_notifications(&mut self) {
self.mut_peers(|peers| {
for peer in peers {
peer.send_import_notifications();
}
})
}
/// Restart sync for a peer.
pub fn restart_peer(&mut self, i: usize) {
self.peers[i].restart_sync();
fn restart_peer(&mut self, i: usize) {
self.peers()[i].restart_sync();
}
/// Perform synchronization until complete.
pub fn sync(&mut self) -> u32 {
fn sync(&mut self) -> u32 {
self.start();
let mut total_steps = 0;
while !self.done() {
self.sync_step();
total_steps += 1;
self.route();
}
total_steps
}
/// Do the given amount of sync steps.
pub fn sync_steps(&mut self, count: usize) {
fn sync_steps(&mut self, count: usize) {
self.start();
for _ in 0..count {
self.sync_step();
@@ -414,7 +448,50 @@ impl TestNet {
}
/// Whether all peers have synced.
pub fn done(&self) -> bool {
self.peers.iter().all(|p| p.is_done())
fn done(&self) -> bool {
self.peers().iter().all(|p| p.is_done())
}
}
pub struct TestNet {
peers: Vec<Arc<Peer<PassThroughVerifier>>>,
started: bool
}
impl TestNetFactory for TestNet {
type Verifier = PassThroughVerifier;
/// Create new test network with peers and given config.
fn from_config(_config: &ProtocolConfig) -> Self {
TestNet {
peers: Vec::new(),
started: false
}
}
fn make_verifier(&self, _client: Arc<PeersClient>, _config: &ProtocolConfig)
-> Arc<Self::Verifier>
{
Arc::new(PassThroughVerifier(false))
}
fn peer(&self, i: usize) -> &Peer<Self::Verifier> {
&self.peers[i]
}
fn peers(&self) -> &Vec<Arc<Peer<Self::Verifier>>> {
&self.peers
}
fn mut_peers<F: Fn(&mut Vec<Arc<Peer<Self::Verifier>>>)>(&mut self, closure: F ) {
closure(&mut self.peers);
}
fn started(&self) -> bool {
self.started
}
fn set_started(&mut self, new: bool) {
self.started = new;
}
}
+16
View File
@@ -16,6 +16,7 @@
use client::backend::Backend;
use client::blockchain::HeaderBackend as BlockchainHeaderBackend;
use consensus::BlockOrigin;
use sync::SyncState;
use Roles;
use super::*;
@@ -68,6 +69,7 @@ fn sync_no_common_longer_chain_fails() {
fn sync_after_fork_works() {
::env_logger::init().ok();
let mut net = TestNet::new(3);
net.sync_step();
net.peer(0).push_blocks(30, false);
net.peer(1).push_blocks(30, false);
net.peer(2).push_blocks(30, false);
@@ -87,6 +89,20 @@ fn sync_after_fork_works() {
assert!(net.peer(2).client.backend().blockchain().canon_equals_to(&peer1_chain));
}
#[test]
fn own_blocks_are_announced() {
::env_logger::init().ok();
let mut net = TestNet::new(3);
net.sync(); // connect'em
net.peer(0).generate_blocks(1, BlockOrigin::Own, |_| ());
net.sync();
assert_eq!(net.peer(0).client.backend().blockchain().info().unwrap().best_number, 1);
assert_eq!(net.peer(1).client.backend().blockchain().info().unwrap().best_number, 1);
let peer0_chain = net.peer(0).client.backend().blockchain().clone();
assert!(net.peer(1).client.backend().blockchain().canon_equals_to(&peer0_chain));
assert!(net.peer(2).client.backend().blockchain().canon_equals_to(&peer0_chain));
}
#[test]
fn blocks_are_not_announced_by_light_nodes() {
::env_logger::init().ok();
+4 -3
View File
@@ -31,7 +31,7 @@ pub type Signature = H512;
pub const PKCS_LEN: usize = 85;
/// A localized signature also contains sender information.
#[derive(PartialEq, Eq, Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug, Encode, Decode)]
pub struct LocalizedSignature {
/// The signer of the signature.
pub signer: Public,
@@ -40,6 +40,7 @@ pub struct LocalizedSignature {
}
/// Verify a message without type checking the parameters' types for the right size.
/// Returns true if the signature is good.
pub fn verify<P: AsRef<[u8]>>(sig: &[u8], message: &[u8], public: P) -> bool {
let public_key = untrusted::Input::from(public.as_ref());
let msg = untrusted::Input::from(message);
@@ -52,7 +53,7 @@ pub fn verify<P: AsRef<[u8]>>(sig: &[u8], message: &[u8], public: P) -> bool {
}
/// A public key.
#[derive(PartialEq, Eq, Clone)]
#[derive(PartialEq, Eq, Clone, Encode, Decode)]
pub struct Public(pub [u8; 32]);
/// A key pair.
@@ -246,7 +247,7 @@ impl Pair {
}
}
/// Verify a signature on a message.
/// Verify a signature on a message. Returns true if the signature is good.
pub fn verify_strong<P: AsRef<Public>>(sig: &Signature, message: &[u8], pubkey: P) -> bool {
let public_key = untrusted::Input::from(&pubkey.as_ref().0[..]);
let msg = untrusted::Input::from(message);
+1
View File
@@ -22,5 +22,6 @@ tokio = "0.1.7"
[dev-dependencies]
assert_matches = "1.1"
substrate-test-client = { path = "../test-client" }
substrate-consensus-common = { path = "../consensus/common" }
rustc-hex = "2.0"
hex-literal = "0.1"
+1 -1
View File
@@ -16,9 +16,9 @@
use super::*;
use jsonrpc_macros::pubsub;
use client::BlockOrigin;
use test_client::{self, TestClient};
use test_client::runtime::{Block, Header};
use consensus::BlockOrigin;
#[test]
fn should_return_header() {
+2
View File
@@ -47,6 +47,8 @@ extern crate hex_literal;
#[cfg(test)]
extern crate substrate_test_client as test_client;
#[cfg(test)]
extern crate substrate_consensus_common as consensus;
#[cfg(test)]
extern crate rustc_hex;
mod errors;
+1 -1
View File
@@ -17,7 +17,7 @@
use super::*;
use self::error::{Error, ErrorKind};
use client::BlockOrigin;
use consensus::BlockOrigin;
use jsonrpc_macros::pubsub;
use rustc_hex::FromHex;
use test_client::{self, runtime, keyring::Keyring, TestClient, BlockBuilderExt};
+1
View File
@@ -20,6 +20,7 @@ substrate-keystore = { path = "../../core/keystore" }
sr-io = { path = "../../core/sr-io" }
sr-primitives = { path = "../../core/sr-primitives" }
substrate-primitives = { path = "../../core/primitives" }
substrate-consensus-common = { path = "../../core/consensus/common" }
substrate-network = { path = "../../core/network" }
substrate-client = { path = "../../core/client" }
substrate-client-db = { path = "../../core/client/db" }
+2 -1
View File
@@ -20,11 +20,12 @@ use std::{self, io::{Read, Write}};
use futures::Future;
use serde_json;
use client::BlockOrigin;
use runtime_primitives::generic::{SignedBlock, BlockId};
use runtime_primitives::traits::{As, Block, Header};
use network::import_queue::{ImportQueue, BlockData};
use network::message;
use consensus_common::BlockOrigin;
use components::{self, Components, ServiceFactory, FactoryFullConfiguration, FactoryBlockNumber, RuntimeGenesis};
use new_client;
use codec::{Decode, Encode};
+16 -9
View File
@@ -25,7 +25,7 @@ use chain_spec::ChainSpec;
use client_db;
use client::{self, Client};
use {error, Service};
use network::{self, OnDemand};
use network::{self, OnDemand, import_queue::ImportQueue};
use substrate_executor::{NativeExecutor, NativeExecutionDispatch};
use transaction_pool::txpool::{self, Options as TransactionPoolOptions, Pool as TransactionPool};
use runtime_primitives::{traits::Block as BlockT, traits::Header as HeaderT, BuildStorage};
@@ -136,8 +136,10 @@ pub trait ServiceFactory: 'static + Sized {
type FullService: Deref<Target = Service<FullComponents<Self>>> + Send + Sync + 'static;
/// Extended light service type.
type LightService: Deref<Target = Service<LightComponents<Self>>> + Send + Sync + 'static;
/// ImportQueue
type ImportQueue: network::import_queue::ImportQueue<Self::Block> + 'static;
/// ImportQueue for full client
type FullImportQueue: network::import_queue::ImportQueue<Self::Block> + 'static;
/// ImportQueue for light clients
type LightImportQueue: network::import_queue::ImportQueue<Self::Block> + 'static;
//TODO: replace these with a constructor trait. that TransactionPool implements.
/// Extrinsic pool constructor for the full client.
@@ -162,7 +164,7 @@ pub trait ServiceFactory: 'static + Sized {
fn build_full_import_queue(
config: &FactoryFullConfiguration<Self>,
_client: Arc<FullClient<Self>>
) -> Result<Self::ImportQueue, error::Error> {
) -> Result<Self::FullImportQueue, error::Error> {
if let Some(name) = config.chain_spec.consensus_engine() {
match name {
_ => Err(format!("Chain Specification defines unknown consensus engine '{}'", name).into())
@@ -177,7 +179,7 @@ pub trait ServiceFactory: 'static + Sized {
fn build_light_import_queue(
config: &FactoryFullConfiguration<Self>,
_client: Arc<LightClient<Self>>
) -> Result<Self::ImportQueue, error::Error> {
) -> Result<Self::LightImportQueue, error::Error> {
if let Some(name) = config.chain_spec.consensus_engine() {
match name {
_ => Err(format!("Chain Specification defines unknown consensus engine '{}'", name).into())
@@ -196,13 +198,16 @@ pub trait Components: 'static {
/// Client backend.
type Backend: 'static + client::backend::Backend<FactoryBlock<Self::Factory>, Blake2Hasher>;
/// Client executor.
type Executor: 'static + client::CallExecutor<FactoryBlock<Self::Factory>, Blake2Hasher> + Send + Sync;
type Executor: 'static + client::CallExecutor<FactoryBlock<Self::Factory>, Blake2Hasher> + Send + Sync + Clone;
/// Extrinsic pool type.
type TransactionPoolApi: 'static + txpool::ChainApi<
Hash = <<Self::Factory as ServiceFactory>::Block as BlockT>::Hash,
Block = FactoryBlock<Self::Factory>
>;
/// Our Import Queue
type ImportQueue: ImportQueue<FactoryBlock<Self::Factory>> + 'static;
/// Create client.
fn build_client(
config: &FactoryFullConfiguration<Self::Factory>,
@@ -221,7 +226,7 @@ pub trait Components: 'static {
fn build_import_queue(
config: &FactoryFullConfiguration<Self::Factory>,
client: Arc<ComponentClient<Self>>
) -> Result<<Self::Factory as ServiceFactory>::ImportQueue, error::Error>;
) -> Result<Self::ImportQueue, error::Error>;
}
/// A struct that implement `Components` for the full client.
@@ -234,6 +239,7 @@ impl<Factory: ServiceFactory> Components for FullComponents<Factory> {
type Executor = FullExecutor<Factory>;
type Backend = FullBackend<Factory>;
type TransactionPoolApi = <Factory as ServiceFactory>::FullTransactionPoolApi;
type ImportQueue = Factory::FullImportQueue;
fn build_client(
config: &FactoryFullConfiguration<Factory>,
@@ -267,7 +273,7 @@ impl<Factory: ServiceFactory> Components for FullComponents<Factory> {
fn build_import_queue(
config: &FactoryFullConfiguration<Self::Factory>,
client: Arc<ComponentClient<Self>>
) -> Result<<Self::Factory as ServiceFactory>::ImportQueue, error::Error> {
) -> Result<Self::ImportQueue, error::Error> {
Factory::build_full_import_queue(config, client)
}
}
@@ -282,6 +288,7 @@ impl<Factory: ServiceFactory> Components for LightComponents<Factory> {
type Executor = LightExecutor<Factory>;
type Backend = LightBackend<Factory>;
type TransactionPoolApi = <Factory as ServiceFactory>::LightTransactionPoolApi;
type ImportQueue = <Factory as ServiceFactory>::LightImportQueue;
fn build_client(
config: &FactoryFullConfiguration<Factory>,
@@ -316,7 +323,7 @@ impl<Factory: ServiceFactory> Components for LightComponents<Factory> {
fn build_import_queue(
config: &FactoryFullConfiguration<Self::Factory>,
client: Arc<ComponentClient<Self>>
) -> Result<<Self::Factory as ServiceFactory>::ImportQueue, error::Error> {
) -> Result<Self::ImportQueue, error::Error> {
Factory::build_light_import_queue(config, client)
}
}
+263
View File
@@ -0,0 +1,263 @@
// 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/>.
//! provide consensus service to substrate.
// FIXME: move this into substrate-consensus-common - https://github.com/paritytech/substrate/issues/1021
use std::sync::Arc;
use std::time::{self, Duration, Instant};
use std;
use client::{self, error, Client as SubstrateClient, CallExecutor};
use client::runtime_api::{Core, BlockBuilder as BlockBuilderAPI, id::BLOCK_BUILDER};
use codec::{Decode, Encode};
use consensus_common::{self, InherentData, evaluation, offline_tracker::OfflineTracker};
use primitives::{AuthorityId, ed25519, Blake2Hasher};
use runtime_primitives::traits::{Block as BlockT, Hash as HashT, Header as HeaderT};
use runtime_primitives::generic::BlockId;
use transaction_pool::txpool::{self, Pool as TransactionPool};
use parking_lot::RwLock;
/// Shared offline validator tracker.
pub type SharedOfflineTracker = Arc<RwLock<OfflineTracker>>;
type Timestamp = u64;
// block size limit.
const MAX_TRANSACTIONS_SIZE: usize = 4 * 1024 * 1024;
/// Build new blocks.
pub trait BlockBuilder<Block: BlockT> {
/// Push an extrinsic onto the block. Fails if the extrinsic is invalid.
fn push_extrinsic(&mut self, extrinsic: <Block as BlockT>::Extrinsic) -> Result<(), error::Error>;
}
/// Local client abstraction for the consensus.
pub trait AuthoringApi:
Send
+ Sync
+ BlockBuilderAPI<<Self as AuthoringApi>::Block, Error=<Self as AuthoringApi>::Error>
+ Core<<Self as AuthoringApi>::Block, AuthorityId, Error=<Self as AuthoringApi>::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<F: FnMut(&mut BlockBuilder<Self::Block>) -> ()>(
&self,
at: &BlockId<Self::Block>,
inherent_data: InherentData,
build_ctx: F,
) -> Result<Self::Block, error::Error>;
}
impl<'a, B, E, Block> BlockBuilder<Block> for client::block_builder::BlockBuilder<'a, B, E, Block, Blake2Hasher> where
B: client::backend::Backend<Block, Blake2Hasher> + Send + Sync + 'static,
E: CallExecutor<Block, Blake2Hasher> + Send + Sync + Clone + 'static,
Block: BlockT
{
fn push_extrinsic(&mut self, extrinsic: <Block as BlockT>::Extrinsic) -> Result<(), error::Error> {
client::block_builder::BlockBuilder::push(self, extrinsic).map_err(Into::into)
}
}
impl<'a, B, E, Block> AuthoringApi for SubstrateClient<B, E, Block> where
B: client::backend::Backend<Block, Blake2Hasher> + Send + Sync + 'static,
E: CallExecutor<Block, Blake2Hasher> + Send + Sync + Clone + 'static,
Block: BlockT,
{
type Block = Block;
type Error = client::error::Error;
fn build_block<F: FnMut(&mut BlockBuilder<Self::Block>) -> ()>(
&self,
at: &BlockId<Self::Block>,
inherent_data: InherentData,
mut build_ctx: F,
) -> Result<Self::Block, error::Error> {
let runtime_version = self.runtime_version_at(at)?;
let mut block_builder = self.new_block_at(at)?;
if runtime_version.has_api(BLOCK_BUILDER, 1) {
self.inherent_extrinsics(at, &inherent_data)?
.into_iter().try_for_each(|i| block_builder.push(i))?;
}
build_ctx(&mut block_builder);
block_builder.bake().map_err(Into::into)
}
}
/// Proposer factory.
pub struct ProposerFactory<C, A> where
C: AuthoringApi,
A: txpool::ChainApi,
{
/// The client instance.
pub client: Arc<C>,
/// The transaction pool.
pub transaction_pool: Arc<TransactionPool<A>>,
/// Offline-tracker.
pub offline: SharedOfflineTracker,
/// Force delay in evaluation this long.
pub force_delay: Timestamp,
}
impl<C, A> consensus_common::Environment<<C as AuthoringApi>::Block> for ProposerFactory<C, A> where
C: AuthoringApi,
A: txpool::ChainApi<Block=<C as AuthoringApi>::Block>,
client::error::Error: From<<C as AuthoringApi>::Error>
{
type Proposer = Proposer<C, A>;
type Error = error::Error;
fn init(
&self,
parent_header: &<<C as AuthoringApi>::Block as BlockT>::Header,
_: &[AuthorityId],
_: Arc<ed25519::Pair>,
) -> Result<Self::Proposer, error::Error> {
let parent_hash = parent_header.hash();
let id = BlockId::hash(parent_hash);
let authorities: Vec<AuthorityId> = self.client.authorities(&id)?;
self.offline.write().note_new_block(&authorities[..]);
info!("Starting consensus session on top of parent {:?}", parent_hash);
let now = Instant::now();
let proposer = Proposer {
client: self.client.clone(),
start: now,
parent_hash,
parent_id: id,
parent_number: *parent_header.number(),
transaction_pool: self.transaction_pool.clone(),
offline: self.offline.clone(),
authorities,
minimum_timestamp: current_timestamp() + self.force_delay,
};
Ok(proposer)
}
}
/// The proposer logic.
pub struct Proposer<C: AuthoringApi, A: txpool::ChainApi> {
client: Arc<C>,
start: Instant,
parent_hash: <<C as AuthoringApi>::Block as BlockT>::Hash,
parent_id: BlockId<<C as AuthoringApi>::Block>,
parent_number: <<<C as AuthoringApi>::Block as BlockT>::Header as HeaderT>::Number,
transaction_pool: Arc<TransactionPool<A>>,
offline: SharedOfflineTracker,
authorities: Vec<AuthorityId>,
minimum_timestamp: u64,
}
impl<C, A> consensus_common::Proposer<<C as AuthoringApi>::Block> for Proposer<C, A> where
C: AuthoringApi,
A: txpool::ChainApi<Block=<C as AuthoringApi>::Block>,
client::error::Error: From<<C as AuthoringApi>::Error>
{
type Create = Result<<C as AuthoringApi>::Block, error::Error>;
type Error = error::Error;
fn propose(&self) -> Result<<C as AuthoringApi>::Block, error::Error> {
use runtime_primitives::traits::BlakeTwo256;
const MAX_VOTE_OFFLINE_SECONDS: Duration = Duration::from_secs(60);
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.authorities[..])
};
if !offline_indices.is_empty() {
info!("Submitting offline authorities {:?} for slash-vote",
offline_indices.iter().map(|&i| self.authorities[i as usize]).collect::<Vec<_>>(),
)
}
let inherent_data = InherentData {
timestamp,
offline_indices,
};
let block = self.client.build_block(
&self.parent_id,
inherent_data,
|block_builder| {
let mut unqueue_invalid = Vec::new();
let mut pending_size = 0;
let pending_iterator = self.transaction_pool.ready();
for pending in pending_iterator {
// TODO [ToDr] Probably get rid of it, and validate in runtime.
let encoded_size = pending.data.encode().len();
if pending_size + encoded_size >= MAX_TRANSACTIONS_SIZE { break }
match block_builder.push_extrinsic(pending.data.clone()) {
Ok(()) => {
pending_size += encoded_size;
}
Err(e) => {
trace!(target: "transaction-pool", "Invalid transaction: {}", e);
unqueue_invalid.push(pending.hash.clone());
}
}
}
self.transaction_pool.remove_invalid(&unqueue_invalid);
})?;
info!("Proposing block [number: {}; hash: {}; parent_hash: {}; extrinsics: [{}]]",
block.header().number(),
<<C as AuthoringApi>::Block as BlockT>::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,
&self.parent_hash,
self.parent_number,
).is_ok());
Ok(substrate_block)
}
}
fn current_timestamp() -> Timestamp {
time::SystemTime::now().duration_since(time::UNIX_EPOCH)
.expect("now always later than unix epoch; qed")
.as_secs()
}
+32 -8
View File
@@ -29,6 +29,7 @@ extern crate parking_lot;
extern crate substrate_keystore as keystore;
extern crate substrate_primitives as primitives;
extern crate sr_primitives as runtime_primitives;
extern crate substrate_consensus_common as consensus_common;
extern crate substrate_network as network;
extern crate substrate_executor;
extern crate substrate_client as client;
@@ -56,6 +57,7 @@ mod error;
mod chain_spec;
pub mod config;
pub mod chain_ops;
pub mod consensus;
use std::io;
use std::net::SocketAddr;
@@ -63,7 +65,7 @@ use std::collections::HashMap;
#[doc(hidden)]
pub use std::{ops::Deref, result::Result, sync::Arc};
use futures::prelude::*;
use parking_lot::Mutex;
use parking_lot::{Mutex, RwLock};
use keystore::Store as Keystore;
use client::BlockchainEvents;
use runtime_primitives::traits::{Header, As};
@@ -80,6 +82,8 @@ pub use chain_spec::ChainSpec;
pub use transaction_pool::txpool::{self, Pool as TransactionPool, Options as TransactionPoolOptions, ChainApi, IntoPoolError};
pub use client::ExecutionStrategy;
use consensus_common::offline_tracker::OfflineTracker;
pub use consensus::ProposerFactory;
pub use components::{ServiceFactory, FullBackend, FullExecutor, LightBackend,
LightExecutor, Components, PoolApi, ComponentClient,
ComponentBlock, FullClient, LightClient, FullComponents, LightComponents,
@@ -98,6 +102,7 @@ pub struct Service<Components: components::Components> {
keystore: Keystore,
exit: ::exit_future::Exit,
signal: Option<Signal>,
proposer: Arc<ProposerFactory<ComponentClient<Components>, Components::TransactionPoolApi>>,
_rpc_http: Option<rpc::HttpServer>,
_rpc_ws: Option<Mutex<rpc::WsServer>>, // WsServer is not `Sync`, but the service needs to be.
_telemetry: Option<tel::Telemetry>,
@@ -118,6 +123,7 @@ pub fn new_client<Factory: components::ServiceFactory>(config: &FactoryFullConfi
impl<Components> Service<Components>
where
Components: components::Components,
<Components as components::Components>::Executor: std::clone::Clone,
txpool::ExHash<Components::TransactionPoolApi>: serde::de::DeserializeOwned + serde::Serialize,
txpool::ExtrinsicFor<Components::TransactionPoolApi>: serde::de::DeserializeOwned + serde::Serialize,
{
@@ -254,6 +260,13 @@ impl<Components> Service<Components>
)
};
let proposer = Arc::new(ProposerFactory {
client: client.clone(),
transaction_pool: transaction_pool.clone(),
offline: Arc::new(RwLock::new(OfflineTracker::new())),
force_delay: 0 // FIXME: allow this to be configured
});
// Telemetry
let telemetry = match config.telemetry_url {
Some(url) => {
@@ -287,6 +300,7 @@ impl<Components> Service<Components>
transaction_pool: transaction_pool,
signal: Some(signal),
keystore: keystore,
proposer,
exit,
_rpc_http: rpc_http,
_rpc_ws: rpc_ws.map(Mutex::new),
@@ -303,6 +317,13 @@ impl<Components> Service<Components> where
self.client.clone()
}
/// Get shared proposer instance
pub fn proposer(&self)
-> Arc<ProposerFactory<ComponentClient<Components>, Components::TransactionPoolApi>>
{
self.proposer.clone()
}
/// Get shared network instance.
pub fn network(&self) -> Arc<components::NetworkService<Components::Factory>> {
self.network.as_ref().expect("self.network always Some").clone()
@@ -324,6 +345,7 @@ impl<Components> Service<Components> where
}
}
impl<Components> Drop for Service<Components> where Components: components::Components {
fn drop(&mut self) {
debug!(target: "service", "Substrate service shutdown");
@@ -450,7 +472,7 @@ macro_rules! construct_simple_service {
$name: ident
) => {
pub struct $name<C: $crate::Components> {
inner: $crate::Service<C>,
inner: $crate::Arc<$crate::Service<C>>,
}
impl<C: $crate::Components> $name<C> {
@@ -460,7 +482,7 @@ macro_rules! construct_simple_service {
) -> $crate::Result<Self, $crate::Error> {
Ok(
Self {
inner: $crate::Service::new(config, executor)?
inner: $crate::Arc::new($crate::Service::new(config, executor)?)
}
)
}
@@ -525,8 +547,9 @@ macro_rules! construct_service_factory {
Configuration = $config:ty,
FullService = $full_service:ty { $( $full_service_init:tt )* },
LightService = $light_service:ty { $( $light_service_init:tt )* },
ImportQueue = $import_queue:ty
{ $( $full_import_queue_init:tt )* }
FullImportQueue = $full_import_queue:ty
{ $( $full_import_queue_init:tt )* },
LightImportQueue = $light_import_queue:ty
{ $( $light_import_queue_init:tt )* },
}
) => {
@@ -544,7 +567,8 @@ macro_rules! construct_service_factory {
type Configuration = $config;
type FullService = $full_service;
type LightService = $light_service;
type ImportQueue = $import_queue;
type FullImportQueue = $full_import_queue;
type LightImportQueue = $light_import_queue;
fn build_full_transaction_pool(
config: $crate::TransactionPoolOptions,
@@ -571,14 +595,14 @@ macro_rules! construct_service_factory {
fn build_full_import_queue(
config: &$crate::FactoryFullConfiguration<Self>,
client: $crate::Arc<$crate::FullClient<Self>>,
) -> $crate::Result<Self::ImportQueue, $crate::Error> {
) -> $crate::Result<Self::FullImportQueue, $crate::Error> {
( $( $full_import_queue_init )* ) (config, client)
}
fn build_light_import_queue(
config: &FactoryFullConfiguration<Self>,
client: Arc<$crate::LightClient<Self>>,
) -> Result<Self::ImportQueue, $crate::Error> {
) -> Result<Self::LightImportQueue, $crate::Error> {
( $( $light_import_queue_init )* ) (config, client)
}
+1
View File
@@ -12,6 +12,7 @@ env_logger = "0.4"
fdlimit = "0.1"
substrate-service = { path = "../../../core/service" }
substrate-network = { path = "../../../core/network" }
substrate-consensus-common = { path = "../../../core/consensus/common" }
substrate-primitives = { path = "../../../core/primitives" }
substrate-client = { path = "../../../core/client" }
sr-primitives = { path = "../../../core/sr-primitives" }
+2 -1
View File
@@ -27,6 +27,7 @@ extern crate substrate_service as service;
extern crate substrate_network as network;
extern crate substrate_primitives as primitives;
extern crate substrate_client as client;
extern crate substrate_consensus_common as consensus;
extern crate sr_primitives;
use std::iter;
use std::sync::Arc;
@@ -47,9 +48,9 @@ use service::{
FactoryExtrinsic,
};
use network::{NetworkConfiguration, NonReservedPeerMode, Protocol, SyncProvider, ManageNetwork};
use client::ImportBlock;
use sr_primitives::traits::As;
use sr_primitives::generic::BlockId;
use consensus::{ImportBlock, BlockImport};
struct TestNet<F: ServiceFactory> {
runtime: Runtime,
+15 -8
View File
@@ -28,7 +28,7 @@ extern crate sr_version as runtime_version;
#[doc(hidden)]
pub use primitives::{traits::Block as BlockT, generic::BlockId, transaction_validity::TransactionValidity, ApplyResult};
use runtime_version::RuntimeVersion;
use runtime_version::{ApiId, RuntimeVersion};
use rstd::vec::Vec;
#[doc(hidden)]
pub use rstd::slice;
@@ -429,6 +429,20 @@ macro_rules! decl_apis {
};
}
/// The ApiIds for the various standard runtime APIs.
pub mod id {
use super::ApiId;
/// ApiId for the BlockBuilder trait.
pub const BLOCK_BUILDER: ApiId = *b"blkbuild";
/// ApiId for the TaggedTransactionQueue trait.
pub const TAGGED_TRANSACTION_QUEUE: ApiId = *b"validatx";
/// ApiId for the Metadata trait.
pub const METADATA: ApiId = *b"metadata";
}
decl_apis! {
/// The `Core` api trait that is mandantory for each runtime.
pub trait Core<Block: BlockT, AuthorityId> {
@@ -453,13 +467,6 @@ decl_apis! {
fn validate_transaction<TransactionValidity>(tx: <Block as BlockT>::Extrinsic) -> TransactionValidity;
}
/// The `Miscellaneous` api trait for getting miscellaneous information from the runtime.
pub trait Miscellaneous {
fn validator_count() -> u32;
fn validators<AccountId>() -> Vec<AccountId>;
fn timestamp<Moment>() -> Moment;
}
/// The `BlockBuilder` api trait that provides required functions for building a block for a runtime.
pub trait BlockBuilder<Block: BlockT> ExtraClientSide <OverlayedChanges> {
/// Initialise a block with the given header.
+1
View File
@@ -31,3 +31,4 @@ std = [
"substrate-primitives/std",
]
api-for-runtime = []
@@ -21,6 +21,8 @@ use rstd::prelude::*;
use codec::{Decode, Encode, Codec, Input};
use traits::{self, Member, DigestItem as DigestItemT};
use substrate_primitives::hash::H512 as Signature;
#[derive(PartialEq, Eq, Clone, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))]
pub struct Digest<Item> {
@@ -46,6 +48,10 @@ impl<Item> traits::Digest for Digest<Item> where
fn push(&mut self, item: Self::Item) {
self.logs.push(item);
}
fn pop(&mut self) -> Option<Self::Item> {
self.logs.pop()
}
}
/// Digest item that is able to encode/decode 'system' digest items and
@@ -60,10 +66,13 @@ pub enum DigestItem<Hash, AuthorityId> {
/// block. It is created for every block iff runtime supports changes
/// trie creation.
ChangesTrieRoot(Hash),
/// Put a Seal on it
Seal(u64, Signature),
/// Any 'non-system' digest item, opaque to the native code.
Other(Vec<u8>),
}
/// A 'referencing view' for digest item. Does not own its contents. Used by
/// final runtime implementations for encoding/decoding its log items.
#[derive(PartialEq, Eq, Clone)]
@@ -73,6 +82,9 @@ pub enum DigestItemRef<'a, Hash: 'a, AuthorityId: 'a> {
AuthoritiesChange(&'a [AuthorityId]),
/// Reference to `DigestItem::ChangesTrieRoot`.
ChangesTrieRoot(&'a Hash),
/// A sealed signature for testing
Seal(&'a u64, &'a Signature),
/// Any 'non-system' digest item, opaque to the native code.
/// Reference to `DigestItem::Other`.
Other(&'a Vec<u8>),
}
@@ -87,6 +99,7 @@ enum DigestItemType {
Other = 0,
AuthoritiesChange,
ChangesTrieRoot,
Seal,
}
impl<Hash, AuthorityId> DigestItem<Hash, AuthorityId> {
@@ -103,6 +116,7 @@ impl<Hash, AuthorityId> DigestItem<Hash, AuthorityId> {
match *self {
DigestItem::AuthoritiesChange(ref v) => DigestItemRef::AuthoritiesChange(v),
DigestItem::ChangesTrieRoot(ref v) => DigestItemRef::ChangesTrieRoot(v),
DigestItem::Seal(ref v, ref s) => DigestItemRef::Seal(v, s),
DigestItem::Other(ref v) => DigestItemRef::Other(v),
}
}
@@ -137,6 +151,10 @@ impl<Hash: Decode, AuthorityId: Decode> Decode for DigestItem<Hash, AuthorityId>
DigestItemType::ChangesTrieRoot => Some(DigestItem::ChangesTrieRoot(
Decode::decode(input)?,
)),
DigestItemType::Seal => {
let vals: (u64, Signature) = Decode::decode(input)?;
Some(DigestItem::Seal(vals.0, vals.1))
},
DigestItemType::Other => Some(DigestItem::Other(
Decode::decode(input)?,
)),
@@ -173,6 +191,10 @@ impl<'a, Hash: Encode, AuthorityId: Encode> Encode for DigestItemRef<'a, Hash, A
DigestItemType::ChangesTrieRoot.encode_to(&mut v);
changes_trie_root.encode_to(&mut v);
},
DigestItemRef::Seal(val, sig) => {
DigestItemType::Seal.encode_to(&mut v);
(val, sig).encode_to(&mut v);
},
DigestItemRef::Other(val) => {
DigestItemType::Other.encode_to(&mut v);
val.encode_to(&mut v);
@@ -137,6 +137,7 @@ impl<Number, Hash, DigestItem> traits::Header for Header<Number, Hash, DigestIte
fn set_parent_hash(&mut self, hash: Self::Hash) { self.parent_hash = hash }
fn digest(&self) -> &Self::Digest { &self.digest }
fn digest_mut(&mut self) -> &mut Self::Digest { &mut self.digest }
fn set_digest(&mut self, digest: Self::Digest) { self.digest = digest }
fn new(
@@ -42,6 +42,10 @@ impl traits::Digest for Digest {
fn push(&mut self, item: Self::Item) {
self.logs.push(item);
}
fn pop(&mut self) -> Option<Self::Item> {
self.logs.pop()
}
}
#[derive(PartialEq, Eq, Clone, Serialize, Deserialize, Debug, Encode, Decode)]
@@ -74,6 +78,7 @@ impl traits::Header for Header {
fn set_parent_hash(&mut self, hash: Self::Hash) { self.parent_hash = hash }
fn digest(&self) -> &Self::Digest { &self.digest }
fn digest_mut(&mut self) -> &mut Self::Digest { &mut self.digest }
fn set_digest(&mut self, digest: Self::Digest) { self.digest = digest }
fn new(
@@ -197,11 +197,13 @@ impl<T: Default + Eq + PartialEq> Clear for T {
pub trait SimpleBitOps:
Sized + Clear +
rstd::ops::BitOr<Self, Output = Self> +
rstd::ops::BitXor<Self, Output = Self> +
rstd::ops::BitAnd<Self, Output = Self>
{}
impl<T:
Sized + Clear +
rstd::ops::BitOr<Self, Output = Self> +
rstd::ops::BitXor<Self, Output = Self> +
rstd::ops::BitAnd<Self, Output = Self>
> SimpleBitOps for T {}
@@ -429,6 +431,8 @@ pub trait Header: Clone + Send + Sync + Codec + Eq + MaybeSerializeDebug + 'stat
fn set_parent_hash(&mut self, Self::Hash);
fn digest(&self) -> &Self::Digest;
/// Get a mutable reference to the digest.
fn digest_mut(&mut self) -> &mut Self::Digest;
fn set_digest(&mut self, Self::Digest);
fn hash(&self) -> Self::Hash {
@@ -458,6 +462,10 @@ pub trait Block: Clone + Send + Sync + Codec + Eq + MaybeSerializeDebug + 'stati
pub type HashFor<B> = <<B as Block>::Header as Header>::Hashing;
/// Extract the number type for a block.
pub type NumberFor<B> = <<B as Block>::Header as Header>::Number;
/// Extract the digest type for a block.
pub type DigestFor<B> = <<B as Block>::Header as Header>::Digest;
/// Extract the digest item type for a block.
pub type DigestItemFor<B> = <DigestFor<B> as Digest>::Item;
/// A "checkable" piece of information, used by the standard Substrate Executive in order to
/// check the validity of a piece of extrinsic information, usually by verifying the signature.
@@ -516,6 +524,8 @@ pub trait Digest: Member + Default {
fn logs(&self) -> &[Self::Item];
/// Push new digest item.
fn push(&mut self, item: Self::Item);
/// Pop a digest item.
fn pop(&mut self) -> Option<Self::Item>;
/// Get reference to the first digest item that matches the passed predicate.
fn log<T, F: Fn(&Self::Item) -> Option<&T>>(&self, predicate: F) -> Option<&T> {
+4 -1
View File
@@ -64,6 +64,9 @@ pub use overlayed_changes::OverlayedChanges;
pub use trie_backend_essence::Storage;
pub use trie_backend::TrieBackend;
/// Default num of pages for the heap
const DEFAULT_HEAP_PAGES :u64 = 1024;
/// State Machine Error bound.
///
/// This should reflect WASM error type bound for future compatibility.
@@ -291,7 +294,7 @@ where
.to_vec();
let heap_pages = try_read_overlay_value(overlay, backend, well_known_keys::HEAP_PAGES)?
.and_then(|v| u64::decode(&mut &v[..])).unwrap_or(8) as usize;
.and_then(|v| u64::decode(&mut &v[..])).unwrap_or(DEFAULT_HEAP_PAGES) as usize;
// read changes trie configuration. The reason why we're doing it here instead of the
// `OverlayedChanges` constructor is that we need proofs for this read as a part of
+1
View File
@@ -7,6 +7,7 @@ authors = ["Parity Technologies <admin@parity.io>"]
substrate-client = { path = "../client" }
parity-codec = "2.1"
substrate-executor = { path = "../executor" }
substrate-consensus-common = { path = "../consensus/common" }
substrate-keyring = { path = "../../core/keyring" }
substrate-primitives = { path = "../primitives" }
substrate-test-runtime = { path = "../test-runtime" }
+12 -8
View File
@@ -16,15 +16,17 @@
//! Client extension for tests.
use client::{self, ImportBlock, Client};
use client::{self, Client};
use consensus::{ImportBlock, BlockImport, BlockOrigin};
use runtime_primitives::generic::BlockId;
use primitives::Blake2Hasher;
use runtime;
/// Extension trait for a test client.
pub trait TestClient {
pub trait TestClient: Sized {
/// Justify and import block to the chain. No finality.
fn justify_and_import(&self, origin: client::BlockOrigin, block: runtime::Block) -> client::error::Result<()>;
fn justify_and_import(&self, origin: BlockOrigin, block: runtime::Block)
-> client::error::Result<()>;
/// Finalize a block.
fn finalize_block(&self, id: BlockId<runtime::Block>) -> client::error::Result<()>;
@@ -36,21 +38,23 @@ pub trait TestClient {
impl<B, E> TestClient for Client<B, E, runtime::Block>
where
B: client::backend::Backend<runtime::Block, Blake2Hasher>,
E: client::CallExecutor<runtime::Block, Blake2Hasher>
E: client::CallExecutor<runtime::Block, Blake2Hasher>,
Self: BlockImport<runtime::Block, Error=client::error::Error>
{
fn justify_and_import(&self, origin: client::BlockOrigin, block: runtime::Block) -> client::error::Result<()> {
fn justify_and_import(&self, origin: BlockOrigin, block: runtime::Block)
-> client::error::Result<()>
{
let import = ImportBlock {
origin,
header: block.header,
external_justification: vec![],
internal_justification: vec![],
post_runtime_digests: vec![],
body: Some(block.extrinsics),
finalized: false,
auxiliary: Vec::new(),
};
self.import_block(import, None)?;
Ok(())
self.import_block(import, None).map(|_| ())
}
fn finalize_block(&self, id: BlockId<runtime::Block>) -> client::error::Result<()> {
+1
View File
@@ -28,6 +28,7 @@ extern crate sr_primitives as runtime_primitives;
pub extern crate substrate_client as client;
pub extern crate substrate_keyring as keyring;
pub extern crate substrate_test_runtime as runtime;
pub extern crate substrate_consensus_common as consensus;
pub mod client_ext;
pub mod trait_tests;
@@ -21,7 +21,7 @@
use std::sync::Arc;
use keyring::Keyring;
use client::BlockOrigin;
use consensus::BlockOrigin;
use primitives::Blake2Hasher;
use ::TestClient;
use runtime_primitives::traits::Block as BlockT;