mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 19:01:08 +00:00
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:
committed by
GitHub
parent
c0f7021427
commit
50adea6220
@@ -9,14 +9,19 @@ log = "0.4"
|
||||
tokio = "0.1.7"
|
||||
exit-future = "0.1"
|
||||
substrate-cli = { path = "../../core/cli" }
|
||||
parity-codec = { version = "2.1" }
|
||||
parking_lot = "0.4"
|
||||
slog = "^2"
|
||||
sr-io = { path = "../../core/sr-io" }
|
||||
substrate-client = { path = "../../core/client" }
|
||||
substrate-primitives = { path = "../../core/primitives" }
|
||||
node-runtime = { path = "../runtime" }
|
||||
node-primitives = { path = "../primitives" }
|
||||
node-network = { path = "../network" }
|
||||
hex-literal = "0.1"
|
||||
substrate-service = { path = "../../core/service" }
|
||||
substrate-transaction-pool = { path = "../../core/transaction-pool" }
|
||||
substrate-network = { path = "../../core/network" }
|
||||
substrate-consensus-aura = { path = "../../core/consensus/aura" }
|
||||
sr-primitives = { path = "../../core/sr-primitives" }
|
||||
node-executor = { path = "../executor" }
|
||||
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
//! Substrate chain configurations.
|
||||
|
||||
use primitives::{AuthorityId, ed25519};
|
||||
use node_primitives::AccountId;
|
||||
use node_runtime::{GenesisConfig, ConsensusConfig, CouncilSeatsConfig, CouncilVotingConfig, DemocracyConfig,
|
||||
SessionConfig, StakingConfig, TimestampConfig, BalancesConfig, TreasuryConfig,
|
||||
SessionConfig, StakingConfig, TimestampConfig, BalancesConfig, TreasuryConfig, UpgradeKeyConfig,
|
||||
ContractConfig, Permill, Perbill};
|
||||
use substrate_service;
|
||||
|
||||
@@ -45,7 +46,7 @@ fn staging_testnet_config_genesis() -> GenesisConfig {
|
||||
const CENTS: u128 = 1_000 * MILLICENTS; // assume this is worth about a cent.
|
||||
const DOLLARS: u128 = 100 * CENTS;
|
||||
|
||||
const SECS_PER_BLOCK: u64 = 5;
|
||||
const SECS_PER_BLOCK: u64 = 4;
|
||||
const MINUTES: u64 = 60 / SECS_PER_BLOCK;
|
||||
const HOURS: u64 = MINUTES * 60;
|
||||
const DAYS: u64 = HOURS * 24;
|
||||
@@ -121,6 +122,9 @@ fn staging_testnet_config_genesis() -> GenesisConfig {
|
||||
max_depth: 1024,
|
||||
block_gas_limit: 10_000_000,
|
||||
}),
|
||||
upgrade_key: Some(UpgradeKeyConfig {
|
||||
key: endowed_accounts[0].clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +143,7 @@ pub fn staging_testnet_config() -> ChainSpec {
|
||||
)
|
||||
}
|
||||
|
||||
fn testnet_genesis(initial_authorities: Vec<AuthorityId>) -> GenesisConfig {
|
||||
fn testnet_genesis(initial_authorities: Vec<AuthorityId>, upgrade_key: AccountId) -> GenesisConfig {
|
||||
let endowed_accounts = vec![
|
||||
ed25519::Pair::from_seed(b"Alice ").public().0.into(),
|
||||
ed25519::Pair::from_seed(b"Bob ").public().0.into(),
|
||||
@@ -220,13 +224,18 @@ fn testnet_genesis(initial_authorities: Vec<AuthorityId>) -> GenesisConfig {
|
||||
max_depth: 1024,
|
||||
block_gas_limit: 10_000_000,
|
||||
}),
|
||||
upgrade_key: Some(UpgradeKeyConfig {
|
||||
key: upgrade_key,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn development_config_genesis() -> GenesisConfig {
|
||||
testnet_genesis(vec![
|
||||
ed25519::Pair::from_seed(b"Alice ").public().into(),
|
||||
])
|
||||
],
|
||||
ed25519::Pair::from_seed(b"Alice ").public().0.into()
|
||||
)
|
||||
}
|
||||
|
||||
/// Development config (single validator Alice)
|
||||
@@ -238,7 +247,9 @@ fn local_testnet_genesis() -> GenesisConfig {
|
||||
testnet_genesis(vec![
|
||||
ed25519::Pair::from_seed(b"Alice ").public().into(),
|
||||
ed25519::Pair::from_seed(b"Bob ").public().into(),
|
||||
])
|
||||
],
|
||||
ed25519::Pair::from_seed(b"Alice ").public().0.into()
|
||||
)
|
||||
}
|
||||
|
||||
/// Local testnet config (multivalidator Alice + Bob)
|
||||
|
||||
@@ -30,9 +30,9 @@ extern crate hex_literal;
|
||||
#[cfg(test)]
|
||||
extern crate substrate_service_test as service_test;
|
||||
extern crate substrate_transaction_pool as transaction_pool;
|
||||
#[macro_use]
|
||||
extern crate substrate_network as network;
|
||||
extern crate node_network;
|
||||
extern crate sr_primitives as runtime_primitives;
|
||||
extern crate substrate_consensus_aura as consensus;
|
||||
extern crate node_primitives;
|
||||
#[macro_use]
|
||||
extern crate substrate_service;
|
||||
|
||||
@@ -22,38 +22,19 @@ use std::sync::Arc;
|
||||
use transaction_pool::{self, txpool::{Pool as TransactionPool}};
|
||||
use node_primitives::Block;
|
||||
use node_runtime::GenesisConfig;
|
||||
use node_network::Protocol as NodeProtocol;
|
||||
use substrate_service::{
|
||||
FactoryFullConfiguration, LightComponents, FullComponents, FullBackend,
|
||||
LightBackend, FullExecutor, LightExecutor
|
||||
FullClient, LightClient, LightBackend, FullExecutor, LightExecutor,
|
||||
Roles, TaskExecutor,
|
||||
};
|
||||
use network::import_queue::{BasicQueue, BlockOrigin, ImportBlock, Verifier};
|
||||
use runtime_primitives::{traits::Block as BlockT};
|
||||
use primitives::AuthorityId;
|
||||
use node_executor;
|
||||
use consensus::{import_queue, start_aura, Config as AuraConfig, AuraImportQueue};
|
||||
|
||||
// TODO: Remove me, when we have a functional consensus.
|
||||
/// A verifier that doesn't actually do any checks
|
||||
pub struct NoneVerifier;
|
||||
/// This Verifiyer accepts all data as valid
|
||||
impl<B: BlockT> Verifier<B> for NoneVerifier {
|
||||
fn verify(
|
||||
&self,
|
||||
origin: BlockOrigin,
|
||||
header: B::Header,
|
||||
justification: Vec<u8>,
|
||||
body: Option<Vec<B::Extrinsic>>
|
||||
) -> Result<(ImportBlock<B>, Option<Vec<AuthorityId>>), String> {
|
||||
Ok((ImportBlock {
|
||||
origin,
|
||||
header,
|
||||
body,
|
||||
finalized: true,
|
||||
external_justification: justification,
|
||||
internal_justification: vec![],
|
||||
auxiliary: Vec::new(),
|
||||
}, None))
|
||||
}
|
||||
const AURA_SLOT_DURATION: u64 = 6;
|
||||
|
||||
construct_simple_protocol! {
|
||||
/// Demo protocol attachment for substrate.
|
||||
pub struct NodeProtocol where Block = Block { }
|
||||
}
|
||||
|
||||
construct_simple_service!(Service);
|
||||
@@ -70,15 +51,48 @@ construct_service_factory! {
|
||||
Genesis = GenesisConfig,
|
||||
Configuration = (),
|
||||
FullService = Service<FullComponents<Self>>
|
||||
{ |config, executor| Service::<FullComponents<Factory>>::new(config, executor) },
|
||||
{ |config: FactoryFullConfiguration<Self>, executor: TaskExecutor| {
|
||||
let is_auth = config.roles == Roles::AUTHORITY;
|
||||
Service::<FullComponents<Factory>>::new(config, executor.clone()).map(move |service|{
|
||||
if is_auth {
|
||||
if let Ok(Some(Ok(key))) = service.keystore().contents()
|
||||
.map(|keys| keys.get(0).map(|k| service.keystore().load(k, "")))
|
||||
{
|
||||
info!("Using authority key {}", key.public());
|
||||
let task = start_aura(
|
||||
AuraConfig {
|
||||
local_key: Some(Arc::new(key)),
|
||||
slot_duration: AURA_SLOT_DURATION,
|
||||
},
|
||||
service.client(),
|
||||
service.proposer(),
|
||||
service.network(),
|
||||
);
|
||||
|
||||
executor.spawn(task);
|
||||
}
|
||||
}
|
||||
|
||||
service
|
||||
})
|
||||
}
|
||||
},
|
||||
LightService = Service<LightComponents<Self>>
|
||||
{ |config, executor| Service::<LightComponents<Factory>>::new(config, executor) },
|
||||
ImportQueue = BasicQueue<Block, NoneVerifier>
|
||||
{ |_, _| Ok(BasicQueue::new(Arc::new(NoneVerifier {}))) }
|
||||
{ |_, _| Ok(BasicQueue::new(Arc::new(NoneVerifier {}))) },
|
||||
FullImportQueue = AuraImportQueue<Self::Block, FullClient<Self>>
|
||||
{ |config, client| Ok(import_queue(AuraConfig {
|
||||
local_key: None,
|
||||
slot_duration: 5
|
||||
}, client)) },
|
||||
LightImportQueue = AuraImportQueue<Self::Block, LightClient<Self>>
|
||||
{ |config, client| Ok(import_queue(AuraConfig {
|
||||
local_key: None,
|
||||
slot_duration: 5
|
||||
}, client)) },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[cfg(feature = "rhd")]
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
[package]
|
||||
name = "node-consensus"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
error-chain = "0.12"
|
||||
exit-future = "0.1"
|
||||
futures = "0.1.17"
|
||||
log = "0.4"
|
||||
node-primitives = { path = "../primitives" }
|
||||
node-runtime = { path = "../runtime" }
|
||||
parity-codec = "2.1"
|
||||
parking_lot = "0.4"
|
||||
rhododendron = "0.3"
|
||||
sr-primitives = { path = "../../core/sr-primitives" }
|
||||
srml-system = { path = "../../srml/system" }
|
||||
substrate-client = { path = "../../core/client" }
|
||||
substrate-primitives = { path = "../../core/primitives" }
|
||||
substrate-transaction-pool = { path = "../../core/transaction-pool" }
|
||||
tokio = "0.1.7"
|
||||
|
||||
[dev-dependencies]
|
||||
substrate-keyring = { path = "../../core/keyring" }
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
= Substrate Consensus
|
||||
|
||||
placeholder
|
||||
//TODO Write content :)
|
||||
@@ -1,52 +0,0 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Errors that can occur during the consensus process.
|
||||
|
||||
use primitives::AuthorityId;
|
||||
use client;
|
||||
|
||||
error_chain! {
|
||||
links {
|
||||
Client(client::error::Error, client::error::ErrorKind);
|
||||
Bft(::bft::Error, ::bft::ErrorKind);
|
||||
}
|
||||
|
||||
errors {
|
||||
NotValidator(id: AuthorityId) {
|
||||
description("Local account ID not a validator at this block."),
|
||||
display("Local account ID ({:?}) not a validator at this block.", id),
|
||||
}
|
||||
PrematureDestruction {
|
||||
description("Proposer destroyed before finishing proposing or evaluating"),
|
||||
display("Proposer destroyed before finishing proposing or evaluating"),
|
||||
}
|
||||
Timer(e: ::tokio::timer::Error) {
|
||||
description("Failed to register or resolve async timer."),
|
||||
display("Timer failed: {}", e),
|
||||
}
|
||||
Executor(e: ::futures::future::ExecuteErrorKind) {
|
||||
description("Unable to dispatch agreement future"),
|
||||
display("Unable to dispatch agreement future: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<::bft::InputStreamConcluded> for Error {
|
||||
fn from(err: ::bft::InputStreamConcluded) -> Self {
|
||||
::bft::Error::from(err).into()
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Block evaluation and evaluation errors.
|
||||
|
||||
use super::MAX_TRANSACTIONS_SIZE;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use node_runtime::{Block as GenericBlock};
|
||||
use node_primitives::{Hash, BlockNumber};
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, As};
|
||||
|
||||
|
||||
error_chain! {
|
||||
errors {
|
||||
BadProposalFormat {
|
||||
description("Proposal provided not a block."),
|
||||
display("Proposal provided not a block."),
|
||||
}
|
||||
WrongParentHash(expected: Hash, got: Hash) {
|
||||
description("Proposal had wrong parent hash."),
|
||||
display("Proposal had wrong parent hash. Expected {:?}, got {:?}", expected, got),
|
||||
}
|
||||
WrongNumber(expected: BlockNumber, got: BlockNumber) {
|
||||
description("Proposal had wrong number."),
|
||||
display("Proposal had wrong number. Expected {:?}, got {:?}", expected, got),
|
||||
}
|
||||
ProposalTooLarge(size: usize) {
|
||||
description("Proposal exceeded the maximum size."),
|
||||
display(
|
||||
"Proposal exceeded the maximum size of {} by {} bytes.",
|
||||
MAX_TRANSACTIONS_SIZE, size.saturating_sub(MAX_TRANSACTIONS_SIZE)
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to evaluate a substrate block as a node block, returning error
|
||||
/// upon any initial validity checks failing.
|
||||
pub fn evaluate_initial<Block: BlockT, Hash>(
|
||||
proposal: &Block,
|
||||
parent_hash: &Hash,
|
||||
parent_number: <<Block as BlockT>::Header as HeaderT>::Number,
|
||||
) -> Result<()>
|
||||
where
|
||||
Hash: PartialEq<<<GenericBlock as BlockT>::Header as HeaderT>::Hash>,
|
||||
Hash: Into<self::Hash> + Clone,
|
||||
{
|
||||
let encoded = Encode::encode(proposal);
|
||||
let proposal = GenericBlock::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((*parent_hash).clone().into(), proposal.header.parent_hash));
|
||||
}
|
||||
|
||||
if parent_number.as_() + 1 != *proposal.header().number() {
|
||||
bail!(ErrorKind::WrongNumber(parent_number.as_() + 1, proposal.header.number));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,533 +0,0 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! This service uses BFT consensus provided by the substrate.
|
||||
|
||||
#![cfg(feature="rhd")]
|
||||
|
||||
extern crate node_runtime;
|
||||
extern crate node_primitives;
|
||||
|
||||
extern crate parity_codec as codec;
|
||||
extern crate sr_primitives as runtime_primitives;
|
||||
extern crate srml_system;
|
||||
extern crate substrate_bft as bft;
|
||||
extern crate substrate_client as client;
|
||||
extern crate substrate_primitives as primitives;
|
||||
extern crate substrate_transaction_pool as transaction_pool;
|
||||
|
||||
extern crate exit_future;
|
||||
extern crate futures;
|
||||
extern crate parking_lot;
|
||||
extern crate rhododendron;
|
||||
extern crate tokio;
|
||||
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate substrate_keyring;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::{self, Duration, Instant};
|
||||
|
||||
use client::{Client as SubstrateClient, CallExecutor};
|
||||
use client::runtime_api::{Core, BlockBuilder as BlockBuilderAPI, Miscellaneous, OldTxQueue};
|
||||
use codec::{Decode, Encode};
|
||||
use node_primitives::{AccountId, Timestamp, SessionKey};
|
||||
use node_runtime::{Runtime, InherentError, TimestampInherentError, InherentData};
|
||||
use primitives::{AuthorityId, ed25519, Blake2Hasher};
|
||||
use runtime_primitives::traits::{Block as BlockT, Hash as HashT, Header as HeaderT, As, BlockNumberToHash};
|
||||
use runtime_primitives::generic::{BlockId, Era};
|
||||
use srml_system::Trait as SystemT;
|
||||
use transaction_pool::txpool::{self, Pool as TransactionPool};
|
||||
use tokio::runtime::TaskExecutor;
|
||||
use tokio::timer::Delay;
|
||||
|
||||
use futures::prelude::*;
|
||||
use futures::future;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
pub use self::error::{ErrorKind, Error, Result};
|
||||
pub use self::offline_tracker::OfflineTracker;
|
||||
pub use service::Service;
|
||||
|
||||
mod evaluation;
|
||||
mod error;
|
||||
mod service;
|
||||
|
||||
/// Shared offline validator tracker.
|
||||
pub type SharedOfflineTracker = Arc<RwLock<OfflineTracker>>;
|
||||
|
||||
// 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<()>;
|
||||
}
|
||||
|
||||
/// 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>
|
||||
+ Miscellaneous<<Self as AuthoringApi>::Block, Error=<Self as AuthoringApi>::Error>
|
||||
+ OldTxQueue<<Self as AuthoringApi>::Block, 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>;
|
||||
}
|
||||
|
||||
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<()> {
|
||||
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> {
|
||||
let runtime_version = self.runtime_version_at(at)?;
|
||||
|
||||
let mut block_builder = self.new_block_at(at)?;
|
||||
if runtime_version.has_api(*b"inherent", 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// A long-lived network which can create BFT message routing processes on demand.
|
||||
pub trait Network {
|
||||
/// The block used for this API type.
|
||||
type Block: BlockT;
|
||||
/// The input stream of BFT messages. Should never logically conclude.
|
||||
type Input: Stream<Item=bft::Communication<Self::Block>,Error=Error>;
|
||||
/// The output sink of BFT messages. Messages sent here should eventually pass to all
|
||||
/// current authorities.
|
||||
type Output: Sink<SinkItem=bft::Communication<Self::Block>,SinkError=Error>;
|
||||
|
||||
/// Instantiate input and output streams.
|
||||
fn communication_for(
|
||||
&self,
|
||||
validators: &[SessionKey],
|
||||
local_id: SessionKey,
|
||||
parent_hash: <Self::Block as BlockT>::Hash,
|
||||
task_executor: TaskExecutor
|
||||
) -> (Self::Input, Self::Output);
|
||||
}
|
||||
|
||||
/// Proposer factory.
|
||||
pub struct ProposerFactory<N, C, A> where
|
||||
C: AuthoringApi,
|
||||
A: txpool::ChainApi,
|
||||
{
|
||||
/// The client instance.
|
||||
pub client: Arc<C>,
|
||||
/// The transaction pool.
|
||||
pub transaction_pool: Arc<TransactionPool<A>>,
|
||||
/// The backing network handle.
|
||||
pub network: N,
|
||||
/// handle to remote task executor
|
||||
pub handle: TaskExecutor,
|
||||
/// Offline-tracker.
|
||||
pub offline: SharedOfflineTracker,
|
||||
/// Force delay in evaluation this long.
|
||||
pub force_delay: Timestamp,
|
||||
}
|
||||
|
||||
impl<N, C, A> bft::Environment<<C as AuthoringApi>::Block> for ProposerFactory<N, C, A> where
|
||||
N: Network<Block=<C as AuthoringApi>::Block>,
|
||||
C: AuthoringApi + BlockNumberToHash,
|
||||
A: txpool::ChainApi<Block=<C as AuthoringApi>::Block>,
|
||||
<<C as AuthoringApi>::Block as BlockT>::Hash:
|
||||
Into<<Runtime as SystemT>::Hash> + PartialEq<primitives::H256> + Into<primitives::H256>,
|
||||
Error: From<<C as AuthoringApi>::Error>
|
||||
{
|
||||
type Proposer = Proposer<C, A>;
|
||||
type Input = N::Input;
|
||||
type Output = N::Output;
|
||||
type Error = Error;
|
||||
|
||||
fn init(
|
||||
&self,
|
||||
parent_header: &<<C as AuthoringApi>::Block as BlockT>::Header,
|
||||
authorities: &[AuthorityId],
|
||||
sign_with: Arc<ed25519::Pair>,
|
||||
) -> Result<(Self::Proposer, Self::Input, Self::Output)> {
|
||||
use runtime_primitives::traits::Hash as HashT;
|
||||
let parent_hash = parent_header.hash();
|
||||
|
||||
let id = BlockId::hash(parent_hash);
|
||||
let random_seed = self.client.random_seed(&id)?;
|
||||
let random_seed = <<<C as AuthoringApi>::Block as BlockT>::Header as HeaderT>::Hashing::hash(random_seed.as_ref());
|
||||
|
||||
let validators = self.client.validators(&id)?;
|
||||
self.offline.write().note_new_block(&validators[..]);
|
||||
|
||||
info!("Starting consensus session on top of parent {:?}", parent_hash);
|
||||
|
||||
let local_id = sign_with.public().0.into();
|
||||
let (input, output) = self.network.communication_for(
|
||||
authorities,
|
||||
local_id,
|
||||
parent_hash.clone(),
|
||||
self.handle.clone(),
|
||||
);
|
||||
let now = Instant::now();
|
||||
let proposer = Proposer {
|
||||
client: self.client.clone(),
|
||||
start: now,
|
||||
local_key: sign_with,
|
||||
next_index: None,
|
||||
parent_hash,
|
||||
parent_id: id,
|
||||
parent_number: *parent_header.number(),
|
||||
random_seed,
|
||||
transaction_pool: self.transaction_pool.clone(),
|
||||
offline: self.offline.clone(),
|
||||
validators,
|
||||
minimum_timestamp: current_timestamp() + self.force_delay,
|
||||
};
|
||||
|
||||
Ok((proposer, input, output))
|
||||
}
|
||||
}
|
||||
|
||||
/// The proposer logic.
|
||||
pub struct Proposer<C: AuthoringApi, A: txpool::ChainApi> {
|
||||
client: Arc<C>,
|
||||
start: Instant,
|
||||
local_key: Arc<ed25519::Pair>,
|
||||
next_index: Option<u64>,
|
||||
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,
|
||||
random_seed: <<C as AuthoringApi>::Block as BlockT>::Hash,
|
||||
transaction_pool: Arc<TransactionPool<A>>,
|
||||
offline: SharedOfflineTracker,
|
||||
validators: Vec<AccountId>,
|
||||
minimum_timestamp: u64,
|
||||
}
|
||||
|
||||
impl<C: AuthoringApi, A: txpool::ChainApi> Proposer<C, A> {
|
||||
fn primary_index(&self, round_number: usize, len: usize) -> usize {
|
||||
use primitives::uint::U256;
|
||||
|
||||
let big_len = U256::from(len);
|
||||
let offset = U256::from_big_endian(self.random_seed.as_ref()) % big_len;
|
||||
let offset = offset.low_u64() as usize + round_number;
|
||||
offset % len
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, A> bft::Proposer<<C as AuthoringApi>::Block> for Proposer<C, A> where
|
||||
C: AuthoringApi + BlockNumberToHash,
|
||||
A: txpool::ChainApi<Block=<C as AuthoringApi>::Block>,
|
||||
<<C as AuthoringApi>::Block as BlockT>::Hash:
|
||||
Into<<Runtime as SystemT>::Hash> + PartialEq<primitives::H256> + Into<primitives::H256>,
|
||||
error::Error: From<<C as AuthoringApi>::Error>
|
||||
{
|
||||
type Create = Result<<C as AuthoringApi>::Block>;
|
||||
type Error = Error;
|
||||
type Evaluate = Box<Future<Item=bool, Error=Error>>;
|
||||
|
||||
fn propose(&self) -> Result<<C as AuthoringApi>::Block> {
|
||||
use runtime_primitives::traits::BlakeTwo256;
|
||||
|
||||
const MAX_VOTE_OFFLINE_SECONDS: Duration = Duration::from_secs(60);
|
||||
|
||||
// TODO: handle case when current timestamp behind that in state.
|
||||
let timestamp = ::std::cmp::max(self.minimum_timestamp, current_timestamp());
|
||||
|
||||
let elapsed_since_start = self.start.elapsed();
|
||||
let offline_indices = if elapsed_since_start > MAX_VOTE_OFFLINE_SECONDS {
|
||||
Vec::new()
|
||||
} else {
|
||||
self.offline.read().reports(&self.validators[..])
|
||||
};
|
||||
|
||||
if !offline_indices.is_empty() {
|
||||
info!(
|
||||
"Submitting offline validators {:?} for slash-vote",
|
||||
offline_indices.iter().map(|&i| self.validators[i as usize]).collect::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
|
||||
let inherent_data = InherentData {
|
||||
timestamp,
|
||||
offline_indices,
|
||||
};
|
||||
|
||||
let block = self.client.build_block(
|
||||
&self.parent_id,
|
||||
inherent_data,
|
||||
|block_builder| {
|
||||
let mut unqueue_invalid = Vec::new();
|
||||
self.transaction_pool.ready(|pending_iterator| {
|
||||
let mut pending_size = 0;
|
||||
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 evaluate(&self, unchecked_proposal: &<C as AuthoringApi>::Block) -> Self::Evaluate {
|
||||
debug!(target: "bft", "evaluating block on top of parent ({}, {:?})", self.parent_number, self.parent_hash);
|
||||
|
||||
// do initial serialization and structural integrity checks.
|
||||
match evaluation::evaluate_initial(
|
||||
unchecked_proposal,
|
||||
&self.parent_hash,
|
||||
self.parent_number,
|
||||
) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
// TODO: these errors are easily re-checked in runtime.
|
||||
debug!(target: "bft", "Invalid proposal (initial evaluation failed): {:?}", e);
|
||||
return Box::new(future::ok(false));
|
||||
}
|
||||
};
|
||||
|
||||
let current_timestamp = current_timestamp();
|
||||
let inherent = InherentData::new(
|
||||
current_timestamp,
|
||||
self.offline.read().reports(&self.validators)
|
||||
);
|
||||
let proposed_timestamp = match self.client.check_inherents(
|
||||
&self.parent_id,
|
||||
&unchecked_proposal,
|
||||
&inherent
|
||||
) {
|
||||
Ok(Ok(())) => None,
|
||||
Ok(Err(InherentError::Timestamp(TimestampInherentError::TimestampInFuture(timestamp)))) => Some(timestamp),
|
||||
Ok(Err(e)) => {
|
||||
debug!(target: "bft", "Invalid proposal (check_inherents): {:?}", e);
|
||||
return Box::new(future::ok(false));
|
||||
},
|
||||
Err(e) => {
|
||||
debug!(target: "bft", "Could not call into runtime: {:?}", e);
|
||||
return Box::new(future::ok(false));
|
||||
}
|
||||
};
|
||||
|
||||
let vote_delays = {
|
||||
// the duration until the given timestamp is current
|
||||
let proposed_timestamp = ::std::cmp::max(self.minimum_timestamp, proposed_timestamp.unwrap_or(0));
|
||||
let timestamp_delay = if proposed_timestamp > current_timestamp {
|
||||
let delay_s = proposed_timestamp - current_timestamp;
|
||||
debug!(target: "bft", "Delaying evaluation of proposal for {} seconds", delay_s);
|
||||
Some(Instant::now() + Duration::from_secs(delay_s))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match timestamp_delay {
|
||||
Some(duration) => future::Either::A(
|
||||
Delay::new(duration).map_err(|e| ErrorKind::Timer(e).into())
|
||||
),
|
||||
None => future::Either::B(future::ok(())),
|
||||
}
|
||||
};
|
||||
|
||||
// evaluate whether the block is actually valid.
|
||||
// TODO: is it better to delay this until the delays are finished?
|
||||
let evaluated = match self.client.execute_block(&self.parent_id, &unchecked_proposal.clone()).map_err(Error::from) {
|
||||
Ok(()) => Ok(true),
|
||||
Err(err) => match err.kind() {
|
||||
error::ErrorKind::Client(client::error::ErrorKind::Execution(_)) => Ok(false),
|
||||
_ => Err(err)
|
||||
}
|
||||
};
|
||||
|
||||
let future = future::result(evaluated).and_then(move |good| {
|
||||
let end_result = future::ok(good);
|
||||
if good {
|
||||
// delay a "good" vote.
|
||||
future::Either::A(vote_delays.and_then(|_| end_result))
|
||||
} else {
|
||||
// don't delay a "bad" evaluation.
|
||||
future::Either::B(end_result)
|
||||
}
|
||||
});
|
||||
|
||||
Box::new(future) as Box<_>
|
||||
}
|
||||
|
||||
fn round_proposer(&self, round_number: usize, authorities: &[AuthorityId]) -> AuthorityId {
|
||||
let offset = self.primary_index(round_number, authorities.len());
|
||||
let proposer = authorities[offset].clone();
|
||||
trace!(target: "bft", "proposer for round {} is {}", round_number, proposer);
|
||||
|
||||
proposer
|
||||
}
|
||||
|
||||
fn import_misbehavior(&self, misbehavior: Vec<(AuthorityId, bft::Misbehavior<<<C as AuthoringApi>::Block as BlockT>::Hash>)>) {
|
||||
use rhododendron::Misbehavior as GenericMisbehavior;
|
||||
use runtime_primitives::bft::{MisbehaviorKind, MisbehaviorReport};
|
||||
use node_runtime::{Call, UncheckedExtrinsic, ConsensusCall};
|
||||
|
||||
let mut next_index = {
|
||||
let cur_index: Result<u64> = self.client
|
||||
.account_nonce(&self.parent_id, &self.local_key.public().0)
|
||||
.map_err(Error::from);
|
||||
|
||||
let cur_index = match cur_index {
|
||||
Ok(cur_index) => cur_index + 1,
|
||||
Err(e) => {
|
||||
warn!(target: "consensus", "Error computing next transaction index: {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let index = match self.next_index {
|
||||
// make sure there were no other transactions in the meantime
|
||||
Some(idx) if idx > cur_index => idx,
|
||||
_ => cur_index,
|
||||
};
|
||||
|
||||
index
|
||||
};
|
||||
|
||||
for (target, misbehavior) in misbehavior {
|
||||
let report = MisbehaviorReport {
|
||||
parent_hash: self.parent_hash.into(),
|
||||
parent_number: self.parent_number.as_(),
|
||||
target,
|
||||
misbehavior: match misbehavior {
|
||||
GenericMisbehavior::ProposeOutOfTurn(_, _, _) => continue,
|
||||
GenericMisbehavior::DoublePropose(_, _, _) => continue,
|
||||
GenericMisbehavior::DoublePrepare(round, (h1, s1), (h2, s2))
|
||||
=> MisbehaviorKind::BftDoublePrepare(round as u32, (h1.into(), s1.signature), (h2.into(), s2.signature)),
|
||||
GenericMisbehavior::DoubleCommit(round, (h1, s1), (h2, s2))
|
||||
=> MisbehaviorKind::BftDoubleCommit(round as u32, (h1.into(), s1.signature), (h2.into(), s2.signature)),
|
||||
}
|
||||
};
|
||||
let payload = (next_index, Call::Consensus(ConsensusCall::report_misbehavior(report)), Era::immortal(), self.client.genesis_hash());
|
||||
let signature = self.local_key.sign(&payload.encode()).into();
|
||||
next_index += 1;
|
||||
self.next_index = Some(next_index);
|
||||
|
||||
let local_id = self.local_key.public().0.into();
|
||||
let extrinsic = UncheckedExtrinsic {
|
||||
signature: Some((node_runtime::RawAddress::Id(local_id), signature, payload.0, Era::immortal())),
|
||||
function: payload.1,
|
||||
};
|
||||
let uxt: <<C as AuthoringApi>::Block as BlockT>::Extrinsic = Decode::decode(&mut extrinsic.encode().as_slice()).expect("Encoded extrinsic is valid");
|
||||
let hash = BlockId::<<C as AuthoringApi>::Block>::hash(self.parent_hash);
|
||||
if let Err(e) = self.transaction_pool.submit_one(&hash, uxt) {
|
||||
warn!("Error importing misbehavior report: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_round_end(&self, round_number: usize, was_proposed: bool) {
|
||||
let primary_validator = self.validators[
|
||||
self.primary_index(round_number, self.validators.len())
|
||||
];
|
||||
|
||||
|
||||
// alter the message based on whether we think the empty proposer was forced to skip the round.
|
||||
// this is determined by checking if our local validator would have been forced to skip the round.
|
||||
if !was_proposed {
|
||||
let public = ed25519::Public::from_raw(primary_validator.0);
|
||||
info!(
|
||||
"Potential Offline Validator: {} failed to propose during assigned slot: {}",
|
||||
public,
|
||||
round_number,
|
||||
);
|
||||
}
|
||||
|
||||
self.offline.write().note_round_end(primary_validator, was_proposed);
|
||||
}
|
||||
}
|
||||
|
||||
fn current_timestamp() -> Timestamp {
|
||||
time::SystemTime::now().duration_since(time::UNIX_EPOCH)
|
||||
.expect("now always later than unix epoch; qed")
|
||||
.as_secs()
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
// 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 rhd::{self, BftService};
|
||||
use client::{BlockchainEvents, ChainHead, BlockBody};
|
||||
use ed25519;
|
||||
use futures::prelude::*;
|
||||
use transaction_pool::txpool::{Pool as TransactionPool, ChainApi as PoolChainApi};
|
||||
use primitives;
|
||||
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 super::{Network, ProposerFactory, AuthoringApi};
|
||||
use error;
|
||||
|
||||
const TIMER_DELAY_MS: u64 = 5000;
|
||||
const TIMER_INTERVAL_MS: u64 = 500;
|
||||
|
||||
// spin up an instance of BFT agreement on the current thread's executor.
|
||||
// panics if there is no current thread executor.
|
||||
fn start_bft<F, C, Block>(
|
||||
header: <Block as BlockT>::Header,
|
||||
bft_service: Arc<BftService<Block, F, C>>,
|
||||
) where
|
||||
F: rhd::Environment<Block> + 'static,
|
||||
C: rhd::BlockImport<Block> + rhd::Authorities<Block> + 'static,
|
||||
F::Error: ::std::fmt::Debug,
|
||||
<F::Proposer as rhd::Proposer<Block>>::Error: ::std::fmt::Display + Into<error::Error>,
|
||||
<F as rhd::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: bft::BlockImport<<A as AuthoringApi>::Block>
|
||||
+ bft::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,
|
||||
{
|
||||
use parking_lot::RwLock;
|
||||
use super::OfflineTracker;
|
||||
|
||||
let (signal, exit) = ::exit_future::signal();
|
||||
let thread = thread::spawn(move || {
|
||||
let mut runtime = LocalRuntime::new().expect("Could not create local runtime");
|
||||
let key = Arc::new(key);
|
||||
|
||||
let factory = ProposerFactory {
|
||||
client: api.clone(),
|
||||
transaction_pool: transaction_pool.clone(),
|
||||
network,
|
||||
handle: thread_pool.clone(),
|
||||
offline: Arc::new(RwLock::new(OfflineTracker::new())),
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,6 +254,7 @@ mod tests {
|
||||
timestamp: Some(Default::default()),
|
||||
treasury: Some(Default::default()),
|
||||
contract: Some(Default::default()),
|
||||
upgrade_key: Some(Default::default()),
|
||||
}.build_storage().unwrap())
|
||||
}
|
||||
|
||||
@@ -291,9 +292,9 @@ mod tests {
|
||||
1,
|
||||
GENESIS_HASH.into(),
|
||||
if support_changes_trie {
|
||||
hex!("ffa85ed1832eae3e25e684d4f993ff0b5e8b6ac4d7ba0f40a5fb0114fda22f3d").into()
|
||||
hex!("978a3ff733a86638da39d36a349c693b5cf562bcc8db30fec6c2b6c40f925a9b").into()
|
||||
} else {
|
||||
hex!("98971908b8923d07944cdf7ee658c203d17042ef447169adbdfec8160cfabcad").into()
|
||||
hex!("7bbad534e3de3db3c8cda015c4e8ed8ba10dde7e3fca21f4fd4fbc686e6c1410").into()
|
||||
},
|
||||
if support_changes_trie {
|
||||
Some(hex!("1f8f44dcae8982350c14dee720d34b147e73279f5a2ce1f9781195a991970978").into())
|
||||
@@ -317,7 +318,7 @@ mod tests {
|
||||
construct_block(
|
||||
2,
|
||||
block1(false).1,
|
||||
hex!("788a2e8b23e4b30e1bce347ca6415fd0080e989d40741c86995b9ad539bb76b3").into(),
|
||||
hex!("7be30152ee2ee909047cffad5f0a28bf8c2b0a97c124b500aeac112f6917738e").into(),
|
||||
None,
|
||||
vec![
|
||||
CheckedExtrinsic {
|
||||
@@ -340,7 +341,7 @@ mod tests {
|
||||
construct_block(
|
||||
1,
|
||||
GENESIS_HASH.into(),
|
||||
hex!("acc03af5b3972deaf9dde4dfd99c5614a5360454313681b6fc299d1644ae8a59").into(),
|
||||
hex!("325a73726dc640af41becb42938e7152e218f130219c0695aae35b6a156f93f3").into(),
|
||||
None,
|
||||
vec![
|
||||
CheckedExtrinsic {
|
||||
@@ -622,7 +623,7 @@ mod tests {
|
||||
let b = construct_block(
|
||||
1,
|
||||
GENESIS_HASH.into(),
|
||||
hex!("21fb6fb965f012ae3c6e521b71b5b57d6df17c738c52f202ec2809ca235eb082").into(),
|
||||
hex!("d68586d5098535e04ff7a12d71a9c9dc719960f318862e636e78a8e98cf4b8d4").into(),
|
||||
None,
|
||||
vec![
|
||||
CheckedExtrinsic {
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
[package]
|
||||
name = "node-network"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
description = "Substrate node networking protocol"
|
||||
|
||||
[dependencies]
|
||||
node-consensus = { path = "../consensus" }
|
||||
node-primitives = { path = "../primitives" }
|
||||
substrate-consensus-rhd = { path = "../../core/consensus/rhd" }
|
||||
substrate-network = { path = "../../core/network" }
|
||||
substrate-primitives = { path = "../../core/primitives" }
|
||||
futures = "0.1"
|
||||
tokio = "0.1.7"
|
||||
log = "0.4"
|
||||
rhododendron = "0.3"
|
||||
@@ -1,293 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
//! The "consensus" networking code built on top of the base network service.
|
||||
//! This fulfills the `node_consensus::Network` trait, providing a hook to be called
|
||||
//! each time consensus begins on a new chain head.
|
||||
|
||||
use bft;
|
||||
use substrate_primitives::ed25519;
|
||||
use substrate_network::{self as net, generic_message as msg};
|
||||
use substrate_network::consensus_gossip::ConsensusMessage;
|
||||
use node_consensus::{AuthoringApi, Network};
|
||||
use node_primitives::{Block, Hash, SessionKey};
|
||||
use rhododendron;
|
||||
|
||||
use futures::prelude::*;
|
||||
use futures::sync::mpsc;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::runtime::TaskExecutor;
|
||||
use tokio::executor::Executor;
|
||||
|
||||
use super::NetworkService;
|
||||
|
||||
/// Sink for output BFT messages.
|
||||
pub struct BftSink<E> {
|
||||
network: Arc<NetworkService>,
|
||||
parent_hash: Hash,
|
||||
_marker: ::std::marker::PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E> Sink for BftSink<E> {
|
||||
type SinkItem = bft::Communication<Block>;
|
||||
// TODO: replace this with the ! type when that's stabilized
|
||||
type SinkError = E;
|
||||
|
||||
fn start_send(&mut self, message: bft::Communication<Block>)
|
||||
-> ::futures::StartSend<bft::Communication<Block>, E>
|
||||
{
|
||||
let network_message = net::LocalizedBftMessage {
|
||||
message: match message {
|
||||
rhododendron::Communication::Consensus(c) => msg::BftMessage::Consensus(match c {
|
||||
rhododendron::LocalizedMessage::Propose(proposal) => msg::SignedConsensusMessage::Propose(msg::SignedConsensusProposal {
|
||||
round_number: proposal.round_number as u32,
|
||||
proposal: proposal.proposal,
|
||||
digest: proposal.digest,
|
||||
sender: proposal.sender,
|
||||
digest_signature: proposal.digest_signature.signature,
|
||||
full_signature: proposal.full_signature.signature,
|
||||
}),
|
||||
rhododendron::LocalizedMessage::Vote(vote) => msg::SignedConsensusMessage::Vote(msg::SignedConsensusVote {
|
||||
sender: vote.sender,
|
||||
signature: vote.signature.signature,
|
||||
vote: match vote.vote {
|
||||
rhododendron::Vote::Prepare(r, h) => msg::ConsensusVote::Prepare(r as u32, h),
|
||||
rhododendron::Vote::Commit(r, h) => msg::ConsensusVote::Commit(r as u32, h),
|
||||
rhododendron::Vote::AdvanceRound(r) => msg::ConsensusVote::AdvanceRound(r as u32),
|
||||
}
|
||||
}),
|
||||
}),
|
||||
rhododendron::Communication::Auxiliary(justification) => {
|
||||
let unchecked: bft::UncheckedJustification<_> = justification.uncheck().into();
|
||||
msg::BftMessage::Auxiliary(unchecked.into())
|
||||
}
|
||||
},
|
||||
parent_hash: self.parent_hash,
|
||||
};
|
||||
self.network.with_spec(
|
||||
move |spec, ctx| spec.consensus_gossip.multicast_bft_message(ctx, network_message)
|
||||
);
|
||||
Ok(::futures::AsyncSink::Ready)
|
||||
}
|
||||
|
||||
fn poll_complete(&mut self) -> ::futures::Poll<(), E> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
}
|
||||
|
||||
// check signature and authority validity of message.
|
||||
fn process_bft_message(
|
||||
msg: msg::LocalizedBftMessage<Block, Hash>,
|
||||
local_id: &SessionKey,
|
||||
authorities: &[SessionKey]
|
||||
) -> Result<Option<bft::Communication<Block>>, bft::Error>
|
||||
{
|
||||
Ok(Some(match msg.message {
|
||||
msg::BftMessage::Consensus(c) => rhododendron::Communication::Consensus(match c {
|
||||
msg::SignedConsensusMessage::Propose(proposal) => rhododendron::LocalizedMessage::Propose({
|
||||
if &proposal.sender == local_id { return Ok(None) }
|
||||
let proposal = rhododendron::LocalizedProposal {
|
||||
round_number: proposal.round_number as usize,
|
||||
proposal: proposal.proposal,
|
||||
digest: proposal.digest,
|
||||
sender: proposal.sender,
|
||||
digest_signature: ed25519::LocalizedSignature {
|
||||
signature: proposal.digest_signature,
|
||||
signer: ed25519::Public(proposal.sender.into()),
|
||||
},
|
||||
full_signature: ed25519::LocalizedSignature {
|
||||
signature: proposal.full_signature,
|
||||
signer: ed25519::Public(proposal.sender.into()),
|
||||
}
|
||||
};
|
||||
bft::check_proposal(authorities, &msg.parent_hash, &proposal)?;
|
||||
|
||||
trace!(target: "bft", "importing proposal message for round {} from {}", proposal.round_number, Hash::from(proposal.sender.0));
|
||||
proposal
|
||||
}),
|
||||
msg::SignedConsensusMessage::Vote(vote) => rhododendron::LocalizedMessage::Vote({
|
||||
if &vote.sender == local_id { return Ok(None) }
|
||||
let vote = rhododendron::LocalizedVote {
|
||||
sender: vote.sender,
|
||||
signature: ed25519::LocalizedSignature {
|
||||
signature: vote.signature,
|
||||
signer: ed25519::Public(vote.sender.0),
|
||||
},
|
||||
vote: match vote.vote {
|
||||
msg::ConsensusVote::Prepare(r, h) => rhododendron::Vote::Prepare(r as usize, h),
|
||||
msg::ConsensusVote::Commit(r, h) => rhododendron::Vote::Commit(r as usize, h),
|
||||
msg::ConsensusVote::AdvanceRound(r) => rhododendron::Vote::AdvanceRound(r as usize),
|
||||
}
|
||||
};
|
||||
bft::check_vote::<Block>(authorities, &msg.parent_hash, &vote)?;
|
||||
|
||||
trace!(target: "bft", "importing vote {:?} from {}", vote.vote, Hash::from(vote.sender.0));
|
||||
vote
|
||||
}),
|
||||
}),
|
||||
msg::BftMessage::Auxiliary(a) => {
|
||||
let justification = bft::UncheckedJustification::from(a);
|
||||
// TODO: get proper error
|
||||
let justification: Result<_, bft::Error> = bft::check_prepare_justification::<Block>(authorities, msg.parent_hash, justification)
|
||||
.map_err(|_| bft::ErrorKind::InvalidJustification.into());
|
||||
rhododendron::Communication::Auxiliary(justification?)
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
// task that processes all gossipped consensus messages,
|
||||
// checking signatures
|
||||
struct MessageProcessTask {
|
||||
inner_stream: mpsc::UnboundedReceiver<ConsensusMessage<Block>>,
|
||||
bft_messages: mpsc::UnboundedSender<bft::Communication<Block>>,
|
||||
validators: Vec<SessionKey>,
|
||||
local_id: SessionKey,
|
||||
}
|
||||
|
||||
impl MessageProcessTask {
|
||||
fn process_message(&self, msg: ConsensusMessage<Block>) -> Option<Async<()>> {
|
||||
match msg {
|
||||
ConsensusMessage::Bft(msg) => {
|
||||
match process_bft_message(msg, &self.local_id, &self.validators[..]) {
|
||||
Ok(Some(msg)) => {
|
||||
if let Err(_) = self.bft_messages.unbounded_send(msg) {
|
||||
// if the BFT receiving stream has ended then
|
||||
// we should just bail.
|
||||
trace!(target: "bft", "BFT message stream appears to have closed");
|
||||
return Some(Async::Ready(()));
|
||||
}
|
||||
}
|
||||
Ok(None) => {} // ignored local message
|
||||
Err(e) => {
|
||||
debug!("Message validation failed: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
ConsensusMessage::ChainSpecific(_, _) => {
|
||||
panic!("ChainSpecific messages are not allowed by the top level message handler");
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for MessageProcessTask {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
loop {
|
||||
match self.inner_stream.poll() {
|
||||
Ok(Async::Ready(Some(val))) => if let Some(async) = self.process_message(val) {
|
||||
return Ok(async);
|
||||
},
|
||||
Ok(Async::Ready(None)) => return Ok(Async::Ready(())),
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(e) => {
|
||||
debug!(target: "node-network", "Error getting consensus message: {:?}", e);
|
||||
return Err(e);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Input stream from the consensus network.
|
||||
pub struct InputAdapter {
|
||||
input: mpsc::UnboundedReceiver<bft::Communication<Block>>,
|
||||
}
|
||||
|
||||
impl Stream for InputAdapter {
|
||||
type Item = bft::Communication<Block>;
|
||||
type Error = ::node_consensus::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
match self.input.poll() {
|
||||
Err(_) | Ok(Async::Ready(None)) => Err(bft::InputStreamConcluded.into()),
|
||||
Ok(x) => Ok(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around the network service
|
||||
pub struct ConsensusNetwork<P> {
|
||||
network: Arc<NetworkService>,
|
||||
api: Arc<P>,
|
||||
}
|
||||
|
||||
impl<P> ConsensusNetwork<P> {
|
||||
/// Create a new consensus networking object.
|
||||
pub fn new(network: Arc<NetworkService>, api: Arc<P>) -> Self {
|
||||
ConsensusNetwork { network, api }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> Clone for ConsensusNetwork<P> {
|
||||
fn clone(&self) -> Self {
|
||||
ConsensusNetwork {
|
||||
network: self.network.clone(),
|
||||
api: self.api.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A long-lived network which can create parachain statement and BFT message routing processes on demand.
|
||||
impl<P: AuthoringApi + Send + Sync + 'static> Network for ConsensusNetwork<P> {
|
||||
/// The input stream of BFT messages. Should never logically conclude.
|
||||
type Input = InputAdapter;
|
||||
/// The output sink of BFT messages. Messages sent here should eventually pass to all
|
||||
/// current validators.
|
||||
type Output = BftSink<::node_consensus::Error>;
|
||||
type Block = Block;
|
||||
|
||||
/// Get input and output streams of BFT messages.
|
||||
fn communication_for(
|
||||
&self, validators: &[SessionKey],
|
||||
local_id: SessionKey,
|
||||
parent_hash: Hash,
|
||||
mut task_executor: TaskExecutor
|
||||
) -> (Self::Input, Self::Output)
|
||||
{
|
||||
let sink = BftSink {
|
||||
network: self.network.clone(),
|
||||
parent_hash,
|
||||
_marker: Default::default(),
|
||||
};
|
||||
|
||||
let (bft_send, bft_recv) = mpsc::unbounded();
|
||||
|
||||
// spin up a task in the background that processes all incoming statements
|
||||
// TODO: propagate statements on a timer?
|
||||
let process_task = self.network.with_spec(|spec, _ctx| {
|
||||
spec.consensus_gossip.new_session(parent_hash);
|
||||
MessageProcessTask {
|
||||
inner_stream: spec.consensus_gossip.messages_for(parent_hash),
|
||||
bft_messages: bft_send,
|
||||
validators: validators.to_vec(),
|
||||
local_id,
|
||||
}
|
||||
});
|
||||
|
||||
if let Err(e) = Executor::spawn(&mut task_executor, Box::new(process_task)) {
|
||||
debug!(target: "node-network", "Cannot spawn message processing: {:?}", e)
|
||||
}
|
||||
|
||||
(InputAdapter { input: bft_recv }, sink)
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
//! Substrate-specific network implementation.
|
||||
//!
|
||||
//! This manages gossip of consensus messages for BFT.
|
||||
|
||||
#![warn(unused_extern_crates)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate substrate_network;
|
||||
extern crate node_primitives;
|
||||
|
||||
use node_primitives::{Block, Hash};
|
||||
use substrate_network::consensus_gossip::ConsensusGossip;
|
||||
|
||||
/// Specialization of the network service for the node protocol.
|
||||
pub type NetworkService = ::substrate_network::Service<Block, Protocol, Hash>;
|
||||
|
||||
construct_simple_protocol! {
|
||||
/// Demo protocol attachment for substrate.
|
||||
pub struct Protocol where Block = Block {
|
||||
consensus_gossip: ConsensusGossip<Block>,
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ srml-staking = { path = "../../srml/staking" }
|
||||
srml-system = { path = "../../srml/system" }
|
||||
srml-timestamp = { path = "../../srml/timestamp" }
|
||||
srml-treasury = { path = "../../srml/treasury" }
|
||||
srml-upgrade-key = { path = "../../srml/upgrade-key" }
|
||||
sr-version = { path = "../../core/sr-version" }
|
||||
node-primitives = { path = "../primitives" }
|
||||
|
||||
@@ -53,6 +54,7 @@ std = [
|
||||
"srml-system/std",
|
||||
"srml-timestamp/std",
|
||||
"srml-treasury/std",
|
||||
"srml-upgrade-key/std",
|
||||
"sr-version/std",
|
||||
"node-primitives/std",
|
||||
"serde_derive",
|
||||
|
||||
@@ -52,6 +52,7 @@ extern crate srml_staking as staking;
|
||||
extern crate srml_system as system;
|
||||
extern crate srml_timestamp as timestamp;
|
||||
extern crate srml_treasury as treasury;
|
||||
extern crate srml_upgrade_key as upgrade_key;
|
||||
#[macro_use]
|
||||
extern crate sr_version as version;
|
||||
extern crate node_primitives;
|
||||
@@ -62,12 +63,12 @@ use node_primitives::{
|
||||
AccountId, AccountIndex, Balance, BlockNumber, Hash, Index,
|
||||
SessionKey, Signature
|
||||
};
|
||||
use runtime_api::runtime::*;
|
||||
use runtime_api::{runtime::*, id::*};
|
||||
use runtime_primitives::ApplyResult;
|
||||
use runtime_primitives::transaction_validity::TransactionValidity;
|
||||
use runtime_primitives::generic;
|
||||
use runtime_primitives::traits::{Convert, BlakeTwo256, Block as BlockT};
|
||||
use version::{RuntimeVersion, ApiId};
|
||||
use version::RuntimeVersion;
|
||||
use council::{motions as council_motions, voting as council_voting};
|
||||
#[cfg(feature = "std")]
|
||||
use council::seats as council_seats;
|
||||
@@ -86,9 +87,6 @@ pub use srml_support::{StorageValue, RuntimeMetadata};
|
||||
const TIMESTAMP_SET_POSITION: u32 = 0;
|
||||
const NOTE_OFFLINE_POSITION: u32 = 1;
|
||||
|
||||
const INHERENT: ApiId = *b"inherent";
|
||||
const VALIDATX: ApiId = *b"validatx";
|
||||
|
||||
/// Runtime version.
|
||||
pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
spec_name: ver_str!("node"),
|
||||
@@ -96,7 +94,11 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
authoring_version: 1,
|
||||
spec_version: 1,
|
||||
impl_version: 0,
|
||||
apis: apis_vec!([(INHERENT, 1), (VALIDATX, 1)]),
|
||||
apis: apis_vec!([
|
||||
(BLOCK_BUILDER, 1),
|
||||
(TAGGED_TRANSACTION_QUEUE, 1),
|
||||
(METADATA, 1)
|
||||
]),
|
||||
};
|
||||
|
||||
/// Native version.
|
||||
@@ -191,15 +193,19 @@ impl contract::Trait for Runtime {
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
impl upgrade_key::Trait for Runtime {
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
construct_runtime!(
|
||||
pub enum Runtime with Log(InternalLog: DigestItem<Hash, SessionKey>) where
|
||||
Block = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic
|
||||
{
|
||||
System: system::{default, Log(ChangesTrieRoot)},
|
||||
Timestamp: timestamp::{Module, Call, Storage, Config<T>, Inherent},
|
||||
Consensus: consensus::{Module, Call, Storage, Config<T>, Log(AuthoritiesChange), Inherent},
|
||||
Balances: balances,
|
||||
Timestamp: timestamp::{Module, Call, Storage, Config<T>, Inherent},
|
||||
Session: session,
|
||||
Staking: staking,
|
||||
Democracy: democracy,
|
||||
@@ -209,6 +215,7 @@ construct_runtime!(
|
||||
CouncilSeats: council_seats::{Config<T>},
|
||||
Treasury: treasury,
|
||||
Contract: contract::{Module, Call, Config<T>, Event<T>},
|
||||
UpgradeKey: upgrade_key,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -278,33 +285,9 @@ impl_apis! {
|
||||
}
|
||||
}
|
||||
|
||||
impl OldTxQueue<AccountId, Index, Address, AccountId> for Runtime {
|
||||
fn account_nonce(account: AccountId) -> Index {
|
||||
System::account_nonce(&account)
|
||||
}
|
||||
|
||||
fn lookup_address(address: Address) -> Option<AccountId> {
|
||||
Balances::lookup_address(address)
|
||||
}
|
||||
}
|
||||
|
||||
impl TaggedTransactionQueue<Block, TransactionValidity> for Runtime {
|
||||
fn validate_transaction(tx: <Block as BlockT>::Extrinsic) -> TransactionValidity {
|
||||
Executive::validate_transaction(tx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Miscellaneous<AccountId, u64> for Runtime {
|
||||
fn validator_count() -> u32 {
|
||||
Session::validator_count()
|
||||
}
|
||||
|
||||
fn validators() -> Vec<AccountId> {
|
||||
Session::validators()
|
||||
}
|
||||
|
||||
fn timestamp() -> u64 {
|
||||
Timestamp::get()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+19
@@ -285,6 +285,7 @@ dependencies = [
|
||||
"srml-system 0.1.0",
|
||||
"srml-timestamp 0.1.0",
|
||||
"srml-treasury 0.1.0",
|
||||
"srml-upgrade-key 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
@@ -817,6 +818,24 @@ dependencies = [
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "srml-upgrade-key"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-codec 2.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-codec-derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sr-io 0.1.0",
|
||||
"sr-primitives 0.1.0",
|
||||
"sr-std 0.1.0",
|
||||
"srml-consensus 0.1.0",
|
||||
"srml-support 0.1.0",
|
||||
"srml-system 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.1.1"
|
||||
|
||||
@@ -28,6 +28,7 @@ srml-staking = { path = "../../../srml/staking", default-features = false }
|
||||
srml-system = { path = "../../../srml/system", default-features = false }
|
||||
srml-timestamp = { path = "../../../srml/timestamp", default-features = false }
|
||||
srml-treasury = { path = "../../../srml/treasury", default-features = false }
|
||||
srml-upgrade-key = { path = "../../../srml/upgrade-key", default-features = false }
|
||||
sr-version = { path = "../../../core/sr-version", default-features = false }
|
||||
node-primitives = { path = "../../primitives", default-features = false }
|
||||
|
||||
@@ -53,6 +54,7 @@ std = [
|
||||
"srml-system/std",
|
||||
"srml-timestamp/std",
|
||||
"srml-treasury/std",
|
||||
"srml-upgrade-key/std",
|
||||
"sr-version/std",
|
||||
"node-primitives/std",
|
||||
]
|
||||
|
||||
BIN
Binary file not shown.
Reference in New Issue
Block a user