Bring substrate-demo up to date (#658)

* Updating substrate-demo

* Consenus fixes

* Reverted toolchain change

* Adjusted timeout formula

* Simplfied proposal creation

* Fixed tests

* Fixed a few small issues

* 2017->2018

* Style

* More style

* Renamed demo executable to substrate

* Style

* Fixed compilation after merge

* Style
This commit is contained in:
Arkadiy Paronyan
2018-09-10 17:54:32 +02:00
committed by Gav Wood
parent bcc26dd30a
commit fea750511e
41 changed files with 2940 additions and 312 deletions
+140 -18
View File
@@ -155,6 +155,11 @@ dependencies = [
"crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "0.9.1"
@@ -377,12 +382,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ctrlc"
version = "1.1.1"
source = "git+https://github.com/paritytech/rust-ctrlc.git#b523017108bb2d571a7a69bd97bc406e63bc7a9d"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -398,32 +402,51 @@ dependencies = [
"tempfile 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "demo-api"
version = "0.1.0"
dependencies = [
"demo-primitives 0.1.0",
"demo-runtime 0.1.0",
"substrate-client 0.1.0",
"substrate-keyring 0.1.0",
"substrate-primitives 0.1.0",
]
[[package]]
name = "demo-cli"
version = "0.1.0"
dependencies = [
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ctrlc 1.1.1 (git+https://github.com/paritytech/rust-ctrlc.git)",
"demo-executor 0.1.0",
"demo-service 0.1.0",
"exit-future 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-cli 0.3.0",
"tokio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "demo-consensus"
version = "0.1.0"
dependencies = [
"demo-api 0.1.0",
"demo-primitives 0.1.0",
"demo-runtime 0.1.0",
"demo-transaction-pool 0.1.0",
"ed25519 0.1.0",
"env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"exit-future 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
"hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"rhododendron 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-bft 0.1.0",
"substrate-client 0.1.0",
"substrate-codec 0.1.0",
"substrate-executor 0.1.0",
"substrate-extrinsic-pool 0.1.0",
"substrate-keyring 0.1.0",
"substrate-primitives 0.1.0",
"substrate-rpc 0.1.0",
"substrate-rpc-servers 0.1.0",
"substrate-runtime-io 0.1.0",
"substrate-state-machine 0.1.0",
"substrate-runtime-primitives 0.1.0",
"substrate-runtime-support 0.1.0",
"tokio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"triehash 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -446,11 +469,28 @@ dependencies = [
"substrate-runtime-staking 0.1.0",
"substrate-runtime-support 0.1.0",
"substrate-runtime-system 0.1.0",
"substrate-runtime-timestamp 0.1.0",
"substrate-runtime-treasury 0.1.0",
"substrate-state-machine 0.1.0",
"triehash 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "demo-network"
version = "0.1.0"
dependencies = [
"demo-api 0.1.0",
"demo-consensus 0.1.0",
"demo-primitives 0.1.0",
"ed25519 0.1.0",
"futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rhododendron 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-bft 0.1.0",
"substrate-network 0.1.0",
"tokio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "demo-primitives"
version = "0.1.0"
@@ -459,6 +499,7 @@ dependencies = [
"serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-codec 0.1.0",
"substrate-codec-derive 0.1.0",
"substrate-primitives 0.1.0",
"substrate-runtime-primitives 0.1.0",
"substrate-runtime-std 0.1.0",
@@ -497,6 +538,52 @@ dependencies = [
"substrate-runtime-version 0.1.0",
]
[[package]]
name = "demo-service"
version = "0.1.0"
dependencies = [
"demo-api 0.1.0",
"demo-consensus 0.1.0",
"demo-executor 0.1.0",
"demo-network 0.1.0",
"demo-primitives 0.1.0",
"demo-runtime 0.1.0",
"demo-transaction-pool 0.1.0",
"ed25519 0.1.0",
"error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"slog 2.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-client 0.1.0",
"substrate-network 0.1.0",
"substrate-primitives 0.1.0",
"substrate-runtime-io 0.1.0",
"substrate-service 0.3.0",
"substrate-telemetry 0.3.0",
"tokio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "demo-transaction-pool"
version = "0.1.0"
dependencies = [
"demo-api 0.1.0",
"demo-primitives 0.1.0",
"demo-runtime 0.1.0",
"ed25519 0.1.0",
"error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-client 0.1.0",
"substrate-codec 0.1.0",
"substrate-extrinsic-pool 0.1.0",
"substrate-keyring 0.1.0",
"substrate-primitives 0.1.0",
"substrate-runtime-primitives 0.1.0",
]
[[package]]
name = "difference"
version = "1.0.0"
@@ -1593,6 +1680,18 @@ dependencies = [
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nix"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nodrop"
version = "0.1.12"
@@ -2323,6 +2422,17 @@ dependencies = [
"substrate-primitives 0.1.0",
]
[[package]]
name = "substrate"
version = "0.1.0"
dependencies = [
"ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"demo-cli 0.1.0",
"error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
"vergen 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "substrate-bft"
version = "0.1.0"
@@ -3586,6 +3696,15 @@ name = "vec_map"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vergen"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "version_check"
version = "0.1.3"
@@ -3777,6 +3896,7 @@ dependencies = [
"checksum base64 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5032d51da2741729bfdaeb2664d9b8c6d9fd1e2b90715c660b6def36628499c2"
"checksum base64 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9263aa6a38da271eec5c91a83ce1e800f093c8535788d403d626d8d5c3f8f007"
"checksum bigint 4.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "da1dde4308822ffaa13665757273a1b787481212f3f9b1c470a864b179a01f1b"
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789"
"checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400"
@@ -3806,7 +3926,7 @@ dependencies = [
"checksum crossbeam-utils 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d636a8b3bcc1b409d7ffd3facef8f21dcb4009626adbd0c5e6c4305c07253c7b"
"checksum crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "677d453a17e8bd2b913fa38e8b9cf04bcdbb5be790aa294f2389661d72036015"
"checksum crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda"
"checksum ctrlc 1.1.1 (git+https://github.com/paritytech/rust-ctrlc.git)" = "<none>"
"checksum ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "630391922b1b893692c6334369ff528dcc3a9d8061ccf4c803aa8f83cb13db5e"
"checksum datastore 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "<none>"
"checksum difference 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3304d19798a8e067e48d8e69b2c37f0b5e9b4e462504ad9e27e9f3fce02bba8"
"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
@@ -3913,6 +4033,7 @@ dependencies = [
"checksum nan-preserving-float 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34d4f00fcc2f4c9efa8cc971db0da9e28290e28e97af47585e48691ef10ff31f"
"checksum native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f74dbadc8b43df7864539cedb7bc91345e532fdd913cfdc23ad94f4d2d40fbc0"
"checksum net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)" = "9044faf1413a1057267be51b5afba8eb1090bd2231c693664aa1db716fe1eae0"
"checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17"
"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2"
"checksum num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac0ea58d64a89d9d6b7688031b3be9358d6c919badcf7fbb0527ccfd891ee45"
"checksum num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "775393e285254d2f5004596d69bb8bc1149754570dcc08cf30cabeba67955e28"
@@ -4058,6 +4179,7 @@ dependencies = [
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
"checksum vcpkg 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7ed0f6789c8a85ca41bbc1c9d175422116a9869bd1cf31bb08e1493ecce60380"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum vergen 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c3365f36c57e5df714a34be40902b27a992eeddb9996eca52d0584611cf885d"
"checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum wabt 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "182ae543249ccf2705f324d233891c1176fca142e137b55ba43d9dbfe93f18a2"
+6
View File
@@ -40,10 +40,16 @@ members = [
"substrate/test-runtime",
"substrate/telemetry",
"substrate/keystore",
"demo",
"demo/cli",
"demo/api",
"demo/consensus",
"demo/executor",
"demo/network",
"demo/primitives",
"demo/runtime",
"demo/service",
"demo/transaction-pool",
"subkey",
]
exclude = [
+18
View File
@@ -0,0 +1,18 @@
[[bin]]
name = "substrate"
path = "src/main.rs"
[package]
name = "substrate"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
build = "build.rs"
[dependencies]
error-chain = "0.12"
demo-cli = { path = "cli" }
futures = "0.1"
ctrlc = { version = "3.0", features = ["termination"] }
[build-dependencies]
vergen = "0.1"
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "demo-api"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
[dependencies]
demo-runtime = { path = "../runtime" }
demo-primitives = { path = "../primitives" }
substrate-client = { path = "../../substrate/client" }
substrate-primitives = { path = "../../substrate/primitives" }
[dev-dependencies]
substrate-keyring = { path = "../../substrate/keyring" }
+155
View File
@@ -0,0 +1,155 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo 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 Demo 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 Demo. If not, see <http://www.gnu.org/licenses/>.
//! Strongly typed API for Substrate Demo runtime.
#![warn(missing_docs)]
#![warn(unused_extern_crates)]
extern crate demo_primitives as primitives;
extern crate demo_runtime as runtime;
extern crate substrate_client as client;
extern crate substrate_primitives;
pub use client::error::{Error, ErrorKind, Result};
use runtime::Address;
use client::backend::Backend;
use client::block_builder::BlockBuilder as ClientBlockBuilder;
use client::{Client, CallExecutor};
use primitives::{
AccountId, Block, BlockId, Hash, Index, InherentData,
SessionKey, Timestamp, UncheckedExtrinsic,
};
use substrate_primitives::{KeccakHasher, RlpCodec};
/// Build new blocks.
pub trait BlockBuilder {
/// Push an extrinsic onto the block. Fails if the extrinsic is invalid.
fn push_extrinsic(&mut self, extrinsic: UncheckedExtrinsic) -> Result<()>;
/// Bake the block with provided extrinsics.
fn bake(self) -> Result<Block>;
}
/// Trait encapsulating the demo API.
///
/// All calls should fail when the exact runtime is unknown.
pub trait Api {
/// The block builder for this API type.
type BlockBuilder: BlockBuilder;
/// Get session keys at a given block.
fn session_keys(&self, at: &BlockId) -> Result<Vec<SessionKey>>;
/// Get validators at a given block.
fn validators(&self, at: &BlockId) -> Result<Vec<AccountId>>;
/// Get the value of the randomness beacon at a given block.
fn random_seed(&self, at: &BlockId) -> Result<Hash>;
/// Get the timestamp registered at a block.
fn timestamp(&self, at: &BlockId) -> Result<Timestamp>;
/// Get the nonce (né index) of an account at a block.
fn index(&self, at: &BlockId, account: AccountId) -> Result<Index>;
/// Get the account id of an address at a block.
fn lookup(&self, at: &BlockId, address: Address) -> Result<Option<AccountId>>;
/// Evaluate a block. Returns true if the block is good, false if it is known to be bad,
/// and an error if we can't evaluate for some reason.
fn evaluate_block(&self, at: &BlockId, block: Block) -> Result<bool>;
/// Build a block on top of the given, with inherent extrinsics pre-pushed.
fn build_block(&self, at: &BlockId, inherent_data: InherentData) -> Result<Self::BlockBuilder>;
/// Attempt to produce the (encoded) inherent extrinsics for a block being built upon the given.
/// This may vary by runtime and will fail if a runtime doesn't follow the same API.
fn inherent_extrinsics(&self, at: &BlockId, inherent_data: InherentData) -> Result<Vec<UncheckedExtrinsic>>;
}
impl<B, E> BlockBuilder for ClientBlockBuilder<B, E, Block, KeccakHasher, RlpCodec>
where
B: Backend<Block, KeccakHasher, RlpCodec>,
E: CallExecutor<Block, KeccakHasher, RlpCodec>+ Clone,
{
fn push_extrinsic(&mut self, extrinsic: UncheckedExtrinsic) -> Result<()> {
self.push(extrinsic).map_err(Into::into)
}
/// Bake the block with provided extrinsics.
fn bake(self) -> Result<Block> {
ClientBlockBuilder::bake(self).map_err(Into::into)
}
}
impl<B, E> Api for Client<B, E, Block>
where
B: Backend<Block, KeccakHasher, RlpCodec>,
E: CallExecutor<Block, KeccakHasher, RlpCodec> + Clone,
{
type BlockBuilder = ClientBlockBuilder<B, E, Block, KeccakHasher, RlpCodec>;
fn session_keys(&self, at: &BlockId) -> Result<Vec<SessionKey>> {
Ok(self.authorities_at(at)?)
}
fn validators(&self, at: &BlockId) -> Result<Vec<AccountId>> {
self.call_api(at, "validators", &())
}
fn random_seed(&self, at: &BlockId) -> Result<Hash> {
self.call_api(at, "random_seed", &())
}
fn timestamp(&self, at: &BlockId) -> Result<Timestamp> {
self.call_api(at, "timestamp", &())
}
fn evaluate_block(&self, at: &BlockId, block: Block) -> Result<bool> {
let res: Result<()> = self.call_api(at, "execute_block", &block);
match res {
Ok(()) => Ok(true),
Err(err) => match err.kind() {
&client::error::ErrorKind::Execution(_) => Ok(false),
_ => Err(err)
}
}
}
fn index(&self, at: &BlockId, account: AccountId) -> Result<Index> {
self.call_api(at, "account_nonce", &account)
}
fn lookup(&self, at: &BlockId, address: Address) -> Result<Option<AccountId>> {
self.call_api(at, "lookup_address", &address)
}
fn build_block(&self, at: &BlockId, inherent_data: InherentData) -> Result<Self::BlockBuilder> {
let mut block_builder = self.new_block_at(at)?;
for inherent in self.inherent_extrinsics(at, inherent_data)? {
block_builder.push(inherent)?;
}
Ok(block_builder)
}
fn inherent_extrinsics(&self, at: &BlockId, inherent_data: InherentData) -> Result<Vec<UncheckedExtrinsic>> {
let runtime_version = self.runtime_version_at(at)?;
self.call_api(at, "inherent_extrinsics", &(inherent_data, runtime_version.spec_version))
}
}
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo 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 Demo 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 Demo. If not, see <http://www.gnu.org/licenses/>.
extern crate vergen;
const ERROR_MSG: &'static str = "Failed to generate metadata files";
fn main() {
vergen::vergen(vergen::SHORT_SHA).expect(ERROR_MSG);
println!("cargo:rerun-if-changed=../../.git/HEAD");
}
+3 -20
View File
@@ -5,25 +5,8 @@ authors = ["Parity Technologies <admin@parity.io>"]
description = "Substrate Demo node implementation in Rust."
[dependencies]
clap = { version = "2.27", features = ["yaml"] }
ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" }
ed25519 = { path = "../../substrate/ed25519" }
env_logger = "0.4"
futures = "0.1.17"
error-chain = "0.12"
hex-literal = "0.1"
log = "0.3"
tokio = "0.1.7"
triehash = "0.2"
substrate-client = { path = "../../substrate/client" }
substrate-codec = { path = "../../substrate/codec" }
substrate-extrinsic-pool = { path = "../../substrate/extrinsic-pool" }
substrate-runtime-io = { path = "../../substrate/runtime-io" }
substrate-state-machine = { path = "../../substrate/state-machine" }
substrate-executor = { path = "../../substrate/executor" }
substrate-primitives = { path = "../../substrate/primitives" }
substrate-rpc-servers = { path = "../../substrate/rpc-servers" }
substrate-rpc = { path = "../../substrate/rpc" }
demo-primitives = { path = "../primitives" }
demo-executor = { path = "../executor" }
demo-runtime = { path = "../runtime" }
exit-future = "0.1"
substrate-cli = { path = "../../substrate/cli" }
demo-service = { path = "../service" }
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo is free software: you can redistribute it and/or modify
+76 -226
View File
@@ -1,4 +1,4 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo is free software: you can redistribute it and/or modify
@@ -17,256 +17,106 @@
//! Substrate Demo CLI library.
#![warn(missing_docs)]
#![warn(unused_extern_crates)]
extern crate ctrlc;
extern crate ed25519;
extern crate env_logger;
extern crate futures;
extern crate tokio;
extern crate triehash;
extern crate substrate_client as client;
extern crate substrate_codec as codec;
extern crate substrate_primitives as primitives;
extern crate substrate_rpc;
extern crate substrate_rpc_servers as rpc;
extern crate substrate_runtime_io as runtime_io;
extern crate substrate_state_machine as state_machine;
extern crate substrate_extrinsic_pool as extrinsic_pool;
extern crate demo_executor;
extern crate demo_primitives;
extern crate demo_runtime;
#[macro_use]
extern crate hex_literal;
#[macro_use]
extern crate clap;
#[macro_use]
extern crate error_chain;
extern crate substrate_cli as cli;
extern crate demo_service as service;
extern crate exit_future;
#[macro_use]
extern crate log;
pub mod error;
pub use cli::error;
use std::sync::Arc;
use demo_primitives::{AccountId, Hash};
use demo_runtime::{Block, BlockId, GenesisConfig,
BalancesConfig, ConsensusConfig, CouncilConfig, DemocracyConfig, SessionConfig,
StakingConfig, TimestampConfig, TreasuryConfig, Permill};
use futures::{Future, Sink, Stream};
use tokio::runtime::Runtime;
use demo_executor::NativeExecutor;
use extrinsic_pool::{Pool as ExtrinsicPool, ExtrinsicFor, VerifiedFor, scoring, Readiness};
pub use service::{Components as ServiceComponents, Service, CustomConfiguration};
pub use cli::{VersionInfo, IntoExit};
#[derive(Debug, Clone)]
struct VerifiedExtrinsic {
sender: AccountId,
hash: Hash,
/// The chain specification option.
#[derive(Clone, Debug)]
pub enum ChainSpec {
/// Whatever the current runtime is, with just Alice as an auth.
Development,
/// Whatever the current runtime is, with simple Alice/Bob auths.
LocalTestnet,
/// The PoC-1 & PoC-2 era testnet.
Testnet,
/// Whatever the current runtime is with the "global testnet" defaults.
StagingTestnet,
}
impl extrinsic_pool::VerifiedTransaction for VerifiedExtrinsic {
type Hash = Hash;
type Sender = AccountId;
fn hash(&self) -> &Self::Hash {
&self.hash
/// Get a chain config from a spec setting.
impl ChainSpec {
pub(crate) fn load(self) -> Result<service::ChainSpec, String> {
Ok(match self {
ChainSpec::Testnet => service::chain_spec::testnet_config()?,
ChainSpec::Development => service::chain_spec::development_config(),
ChainSpec::LocalTestnet => service::chain_spec::local_testnet_config(),
ChainSpec::StagingTestnet => service::chain_spec::staging_testnet_config(),
})
}
fn sender(&self) -> &Self::Sender {
&self.sender
}
fn mem_usage(&self) -> usize {
0
pub(crate) fn from(s: &str) -> Option<Self> {
match s {
"dev" => Some(ChainSpec::Development),
"local" => Some(ChainSpec::LocalTestnet),
"" | "test" => Some(ChainSpec::Testnet),
"staging" => Some(ChainSpec::StagingTestnet),
_ => None,
}
}
}
struct Pool;
impl extrinsic_pool::ChainApi for Pool {
type Block = Block;
type Hash = Hash;
type Sender = AccountId;
type VEx = VerifiedExtrinsic;
type Ready = ();
type Error = extrinsic_pool::Error;
type Score = u64;
type Event = ();
fn verify_transaction(&self, _at: &BlockId, _xt: &ExtrinsicFor<Self>) -> Result<Self::VEx, Self::Error> {
unimplemented!()
}
fn ready(&self) -> Self::Ready { }
fn is_ready(&self, _at: &BlockId, _ready: &mut Self::Ready, _xt: &VerifiedFor<Self>) -> Readiness {
unimplemented!()
}
fn compare(_old: &VerifiedFor<Self>, _other: &VerifiedFor<Self>) -> ::std::cmp::Ordering {
unimplemented!()
}
fn choose(_old: &VerifiedFor<Self>, _new: &VerifiedFor<Self>) -> scoring::Choice {
unimplemented!()
}
fn update_scores(
_xts: &[extrinsic_pool::Transaction<VerifiedFor<Self>>],
_scores: &mut [Self::Score],
_change: scoring::Change<()>
) {
unimplemented!()
}
fn should_replace(_old: &VerifiedFor<Self>, _new: &VerifiedFor<Self>) -> scoring::Choice {
unimplemented!()
}
fn load_spec(id: &str) -> Result<Option<service::ChainSpec>, String> {
Ok(match ChainSpec::from(id) {
Some(spec) => Some(spec.load()?),
None => None,
})
}
struct DummySystem;
impl substrate_rpc::system::SystemApi for DummySystem {
fn system_name(&self) -> substrate_rpc::system::error::Result<String> {
Ok("substrate-demo".into())
}
fn system_version(&self) -> substrate_rpc::system::error::Result<String> {
Ok(crate_version!().into())
}
fn system_chain(&self) -> substrate_rpc::system::error::Result<String> {
Ok("default".into())
}
}
/// Parse command line arguments and start the node.
///
/// IANA unassigned port ranges that we could use:
/// 6717-6766 Unassigned
/// 8504-8553 Unassigned
/// 9556-9591 Unassigned
/// 9803-9874 Unassigned
/// 9926-9949 Unassigned
pub fn run<I, T>(args: I) -> error::Result<()> where
/// Parse command line arguments into service configuration.
pub fn run<I, T, E>(args: I, exit: E, version: cli::VersionInfo) -> error::Result<()> where
I: IntoIterator<Item = T>,
T: Into<std::ffi::OsString> + Clone,
E: IntoExit,
{
let yaml = load_yaml!("./cli.yml");
let matches = clap::App::from_yaml(yaml).version(crate_version!()).get_matches_from_safe(args)?;
// TODO [ToDr] Split parameters parsing from actual execution.
let log_pattern = matches.value_of("log").unwrap_or("");
init_logger(log_pattern);
// Create client
let executor = NativeExecutor::new();
let god_key = hex!["3d866ec8a9190c8343c2fc593d21d8a6d0c5c4763aaab2349de3a6111d64d124"];
let genesis_config = GenesisConfig {
consensus: Some(ConsensusConfig {
code: vec![], // TODO
authorities: vec![god_key.clone().into()],
}),
system: None,
balances: Some(BalancesConfig {
transaction_base_fee: 100,
transaction_byte_fee: 1,
transfer_fee: 0,
creation_fee: 0,
reclaim_rebate: 0,
existential_deposit: 500,
balances: vec![(god_key.clone().into(), 1u64 << 63)].into_iter().collect(),
}),
session: Some(SessionConfig {
validators: vec![god_key.clone().into()],
session_length: 720, // that's 1 hour per session.
}),
staking: Some(StakingConfig {
current_era: 0,
intentions: vec![],
validator_count: 12,
minimum_validator_count: 4,
sessions_per_era: 24, // 24 hours per era.
bonding_duration: 90 * 24 * 720, // 90 days per bond.
early_era_slash: 10000,
session_reward: 100,
offline_slash_grace: 0,
}),
democracy: Some(DemocracyConfig {
launch_period: 120 * 24 * 14, // 2 weeks per public referendum
voting_period: 120 * 24 * 28, // 4 weeks to discuss & vote on an active referendum
minimum_deposit: 1000, // 1000 as the minimum deposit for a referendum
}),
council: Some(CouncilConfig {
active_council: vec![],
candidacy_bond: 1000, // 1000 to become a council candidate
voter_bond: 100, // 100 down to vote for a candidate
present_slash_per_voter: 1, // slash by 1 per voter for an invalid presentation.
carry_count: 24, // carry over the 24 runners-up to the next council election
presentation_duration: 120 * 24, // one day for presenting winners.
approval_voting_period: 7 * 120 * 24, // one week period between possible council elections.
term_duration: 180 * 120 * 24, // 180 day term duration for the council.
desired_seats: 0, // start with no council: we'll raise this once the stake has been dispersed a bit.
inactive_grace_period: 1, // one addition vote should go by before an inactive voter can be reaped.
cooloff_period: 90 * 120 * 24, // 90 day cooling off period if council member vetoes a proposal.
voting_period: 7 * 120 * 24, // 7 day voting period for council members.
}),
timestamp: Some(TimestampConfig {
period: 5, // 5 second block time.
}),
treasury: Some(TreasuryConfig {
proposal_bond: Permill::from_percent(5),
proposal_bond_minimum: 1_000_000,
spend_period: 12 * 60 * 24,
burn: Permill::from_percent(50),
}),
};
let client = Arc::new(client::new_in_mem::<NativeExecutor<demo_executor::Executor>, Block, _>(executor, genesis_config)?);
let mut runtime = Runtime::new()?;
let _rpc_servers = {
let handler = || {
let state = rpc::apis::state::State::new(client.clone(), runtime.executor());
let chain = rpc::apis::chain::Chain::new(client.clone(), runtime.executor());
let author = rpc::apis::author::Author::new(client.clone(), Arc::new(ExtrinsicPool::new(Default::default(), Pool)), runtime.executor());
rpc::rpc_handler::<Block, Hash, _, _, _, _, _>(state, chain, author, DummySystem)
};
let http_address = "127.0.0.1:9933".parse().unwrap();
let ws_address = "127.0.0.1:9944".parse().unwrap();
(
rpc::start_http(&http_address, handler())?,
rpc::start_ws(&ws_address, handler())?
)
};
if let Some(_) = matches.subcommand_matches("validator") {
info!("Starting validator.");
let (exit_send, exit) = futures::sync::mpsc::channel(1);
ctrlc::CtrlC::set_handler(move || {
exit_send.clone().send(()).wait().expect("Error sending exit notification");
});
runtime.block_on(exit.into_future()).expect("Error running informant event loop");
return Ok(())
match cli::prepare_execution::<service::Factory, _, _, _, _>(args, exit, version, load_spec, "substrate-demo")? {
cli::Action::ExecutedInternally => (),
cli::Action::RunService((config, exit)) => {
info!("Parity ·:· Substrate");
info!(" version {}", config.full_version());
info!(" by Parity Technologies, 2017, 2018");
info!("Chain specification: {}", config.chain_spec.name());
info!("Node name: {}", config.name);
info!("Roles: {:?}", config.roles);
let mut runtime = Runtime::new()?;
let executor = runtime.executor();
match config.roles == service::Roles::LIGHT {
true => run_until_exit(&mut runtime, service::new_light(config, executor)?, exit)?,
false => run_until_exit(&mut runtime, service::new_full(config, executor)?, exit)?,
}
}
}
println!("No command given.\n");
let _ = clap::App::from_yaml(yaml).print_long_help();
Ok(())
}
fn init_logger(pattern: &str) {
let mut builder = env_logger::LogBuilder::new();
// Disable info logging by default for some modules:
builder.filter(Some("hyper"), log::LogLevelFilter::Warn);
// Enable info for others.
builder.filter(None, log::LogLevelFilter::Info);
fn run_until_exit<C, E>(
runtime: &mut Runtime,
service: service::Service<C>,
e: E,
) -> error::Result<()>
where
C: service::Components,
E: IntoExit,
{
let (exit_send, exit) = exit_future::signal();
if let Ok(lvl) = std::env::var("RUST_LOG") {
builder.parse(&lvl);
}
let executor = runtime.executor();
cli::informant::start(&service, exit.clone(), executor.clone());
builder.parse(pattern);
builder.init().expect("Logger initialized only once.");
let _ = runtime.block_on(e.into_exit());
exit_send.fire();
Ok(())
}
+27
View File
@@ -0,0 +1,27 @@
[package]
name = "demo-consensus"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
[dependencies]
futures = "0.1.17"
parking_lot = "0.4"
tokio = "0.1.7"
ed25519 = { path = "../../substrate/ed25519" }
error-chain = "0.12"
log = "0.3"
exit-future = "0.1"
rhododendron = "0.3"
demo-api = { path = "../api" }
demo-primitives = { path = "../primitives" }
demo-runtime = { path = "../runtime" }
demo-transaction-pool = { path = "../transaction-pool" }
substrate-bft = { path = "../../substrate/bft" }
substrate-codec = { path = "../../substrate/codec" }
substrate-primitives = { path = "../../substrate/primitives" }
substrate-runtime-support = { path = "../../substrate/runtime-support" }
substrate-client = { path = "../../substrate/client" }
substrate-runtime-primitives = { path = "../../substrate/runtime/primitives" }
[dev-dependencies]
substrate-keyring = { path = "../../substrate/keyring" }
+5
View File
@@ -0,0 +1,5 @@
= Polkadot Consensus
placeholder
//TODO Write content :)
+51
View File
@@ -0,0 +1,51 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo 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 Demo 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 Demo. If not, see <http://www.gnu.org/licenses/>.
//! Errors that can occur during the consensus process.
use primitives::AuthorityId;
error_chain! {
links {
Api(::demo_api::Error, ::demo_api::ErrorKind);
Bft(::bft::Error, ::bft::ErrorKind);
}
errors {
NotValidator(id: AuthorityId) {
description("Local account ID not a validator at this block."),
display("Local account ID ({:?}) not a validator at this block.", id),
}
PrematureDestruction {
description("Proposer destroyed before finishing proposing or evaluating"),
display("Proposer destroyed before finishing proposing or evaluating"),
}
Timer(e: ::tokio::timer::Error) {
description("Failed to register or resolve async timer."),
display("Timer failed: {}", e),
}
Executor(e: ::futures::future::ExecuteErrorKind) {
description("Unable to dispatch agreement future"),
display("Unable to dispatch agreement future: {:?}", e),
}
}
}
impl From<::bft::InputStreamConcluded> for Error {
fn from(err: ::bft::InputStreamConcluded) -> Self {
::bft::Error::from(err).into()
}
}
@@ -0,0 +1,96 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo 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 Demo 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 Demo. If not, see <http://www.gnu.org/licenses/>.
//! Block evaluation and evaluation errors.
use super::MAX_TRANSACTIONS_SIZE;
use codec::{Decode, Encode};
use demo_runtime::{Block as GenericBlock, CheckedBlock};
use demo_primitives::{Block, Hash, BlockNumber, Timestamp};
error_chain! {
links {
Api(::demo_api::Error, ::demo_api::ErrorKind);
}
errors {
BadProposalFormat {
description("Proposal provided not a block."),
display("Proposal provided not a block."),
}
TimestampInFuture {
description("Proposal had timestamp too far in the future."),
display("Proposal had timestamp too far in the future."),
}
WrongParentHash(expected: Hash, got: Hash) {
description("Proposal had wrong parent hash."),
display("Proposal had wrong parent hash. Expected {:?}, got {:?}", expected, got),
}
WrongNumber(expected: BlockNumber, got: BlockNumber) {
description("Proposal had wrong number."),
display("Proposal had wrong number. Expected {:?}, got {:?}", expected, got),
}
ProposalTooLarge(size: usize) {
description("Proposal exceeded the maximum size."),
display(
"Proposal exceeded the maximum size of {} by {} bytes.",
MAX_TRANSACTIONS_SIZE, size.saturating_sub(MAX_TRANSACTIONS_SIZE)
),
}
}
}
/// Attempt to evaluate a substrate block as a demo block, returning error
/// upon any initial validity checks failing.
pub fn evaluate_initial(
proposal: &Block,
now: Timestamp,
parent_hash: &Hash,
parent_number: BlockNumber,
) -> Result<CheckedBlock> {
const MAX_TIMESTAMP_DRIFT: Timestamp = 60;
let encoded = Encode::encode(proposal);
let proposal = GenericBlock::decode(&mut &encoded[..])
.and_then(|b| CheckedBlock::new(b).ok())
.ok_or_else(|| ErrorKind::BadProposalFormat)?;
let transactions_size = proposal.extrinsics.iter().fold(0, |a, tx| {
a + Encode::encode(tx).len()
});
if transactions_size > MAX_TRANSACTIONS_SIZE {
bail!(ErrorKind::ProposalTooLarge(transactions_size))
}
if proposal.header.parent_hash != *parent_hash {
bail!(ErrorKind::WrongParentHash(*parent_hash, proposal.header.parent_hash));
}
if proposal.header.number != parent_number + 1 {
bail!(ErrorKind::WrongNumber(parent_number + 1, proposal.header.number));
}
let block_timestamp = proposal.timestamp();
// lenient maximum -- small drifts will just be delayed using a timer.
if block_timestamp > now + MAX_TIMESTAMP_DRIFT {
bail!(ErrorKind::TimestampInFuture)
}
Ok(proposal)
}
+446
View File
@@ -0,0 +1,446 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo 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 Demo 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 Demo. If not, see <http://www.gnu.org/licenses/>.
//! This service uses BFT consensus provided by the substrate.
extern crate ed25519;
extern crate parking_lot;
extern crate demo_api;
extern crate demo_transaction_pool as transaction_pool;
extern crate demo_runtime;
extern crate demo_primitives;
extern crate substrate_bft as bft;
extern crate substrate_codec as codec;
extern crate substrate_primitives as primitives;
extern crate substrate_runtime_support as runtime_support;
extern crate substrate_runtime_primitives as runtime_primitives;
extern crate substrate_client as client;
extern crate exit_future;
extern crate tokio;
extern crate rhododendron;
#[macro_use]
extern crate error_chain;
extern crate futures;
#[macro_use]
extern crate log;
#[cfg(test)]
extern crate substrate_keyring;
use std::sync::Arc;
use std::time::{self, Duration, Instant};
use codec::{Decode, Encode};
use demo_api::Api;
use demo_primitives::{AccountId, Hash, Block, BlockId, BlockNumber, Header, Timestamp, SessionKey};
use primitives::AuthorityId;
use transaction_pool::TransactionPool;
use tokio::runtime::TaskExecutor;
use tokio::timer::Delay;
use futures::prelude::*;
use futures::future;
use parking_lot::RwLock;
pub use self::error::{ErrorKind, Error};
pub use self::offline_tracker::OfflineTracker;
pub use service::Service;
mod evaluation;
mod error;
mod offline_tracker;
mod service;
/// Shared offline validator tracker.
pub type SharedOfflineTracker = Arc<RwLock<OfflineTracker>>;
// block size limit.
const MAX_TRANSACTIONS_SIZE: usize = 4 * 1024 * 1024;
/// A long-lived network which can create BFT message routing processes on demand.
pub trait Network {
/// The input stream of BFT messages. Should never logically conclude.
type Input: Stream<Item=bft::Communication<Block>,Error=Error>;
/// The output sink of BFT messages. Messages sent here should eventually pass to all
/// current authorities.
type Output: Sink<SinkItem=bft::Communication<Block>,SinkError=Error>;
/// Instantiate input and output streams.
fn communication_for(
&self,
validators: &[SessionKey],
local_id: SessionKey,
parent_hash: Hash,
task_executor: TaskExecutor
) -> (Self::Input, Self::Output);
}
/// Proposer factory.
pub struct ProposerFactory<N, P>
where
P: Api + Send + Sync + 'static
{
/// The client instance.
pub client: Arc<P>,
/// The transaction pool.
pub transaction_pool: Arc<TransactionPool<P>>,
/// The backing network handle.
pub network: N,
/// handle to remote task executor
pub handle: TaskExecutor,
/// Offline-tracker.
pub offline: SharedOfflineTracker,
}
impl<N, P> bft::Environment<Block> for ProposerFactory<N, P>
where
N: Network,
P: Api + Send + Sync + 'static,
{
type Proposer = Proposer<P>;
type Input = N::Input;
type Output = N::Output;
type Error = Error;
fn init(
&self,
parent_header: &Header,
authorities: &[AuthorityId],
sign_with: Arc<ed25519::Pair>,
) -> Result<(Self::Proposer, Self::Input, Self::Output), Error> {
use runtime_primitives::traits::{Hash as HashT, BlakeTwo256};
// force delay in evaluation this long.
const FORCE_DELAY: Timestamp = 5;
let parent_hash = parent_header.hash().into();
let id = BlockId::hash(parent_hash);
let random_seed = self.client.random_seed(&id)?;
let random_seed = BlakeTwo256::hash(&*random_seed);
let validators = self.client.validators(&id)?;
self.offline.write().note_new_block(&validators[..]);
info!("Starting consensus session on top of parent {:?}", parent_hash);
let local_id = sign_with.public().0.into();
let (input, output) = self.network.communication_for(
authorities,
local_id,
parent_hash.clone(),
self.handle.clone(),
);
let now = Instant::now();
let proposer = Proposer {
client: self.client.clone(),
start: now,
local_key: sign_with,
parent_hash,
parent_id: id,
parent_number: parent_header.number,
random_seed,
transaction_pool: self.transaction_pool.clone(),
offline: self.offline.clone(),
validators,
minimum_timestamp: current_timestamp() + FORCE_DELAY,
};
Ok((proposer, input, output))
}
}
/// The proposer logic.
pub struct Proposer<C: Api + Send + Sync> {
client: Arc<C>,
start: Instant,
local_key: Arc<ed25519::Pair>,
parent_hash: Hash,
parent_id: BlockId,
parent_number: BlockNumber,
random_seed: Hash,
transaction_pool: Arc<TransactionPool<C>>,
offline: SharedOfflineTracker,
validators: Vec<AccountId>,
minimum_timestamp: u64,
}
impl<C: Api + Send + Sync> Proposer<C> {
fn primary_index(&self, round_number: usize, len: usize) -> usize {
use primitives::uint::U256;
let big_len = U256::from(len);
let offset = U256::from_big_endian(&self.random_seed.0) % big_len;
let offset = offset.low_u64() as usize + round_number;
offset % len
}
}
impl<C> bft::Proposer<Block> for Proposer<C>
where
C: Api + Send + Sync,
{
type Create = Result<Block, Error>;
type Error = Error;
type Evaluate = Box<Future<Item=bool, Error=Error>>;
fn propose(&self) -> Result<Block, Error> {
use demo_api::BlockBuilder;
use runtime_primitives::traits::{Hash as HashT, BlakeTwo256};
use demo_primitives::InherentData;
const MAX_VOTE_OFFLINE_SECONDS: Duration = Duration::from_secs(60);
// TODO: handle case when current timestamp behind that in state.
let timestamp = ::std::cmp::max(self.minimum_timestamp, current_timestamp());
let elapsed_since_start = self.start.elapsed();
let offline_indices = if elapsed_since_start > MAX_VOTE_OFFLINE_SECONDS {
Vec::new()
} else {
self.offline.read().reports(&self.validators[..])
};
if !offline_indices.is_empty() {
info!(
"Submitting offline validators {:?} for slash-vote",
offline_indices.iter().map(|&i| self.validators[i as usize]).collect::<Vec<_>>(),
)
}
let inherent_data = InherentData {
timestamp,
offline_indices,
};
let mut block_builder = self.client.build_block(&self.parent_id, inherent_data)?;
{
let mut unqueue_invalid = Vec::new();
let result = self.transaction_pool.cull_and_get_pending(&BlockId::hash(self.parent_hash), |pending_iterator| {
let mut pending_size = 0;
for pending in pending_iterator {
if pending_size + pending.verified.encoded_size() >= MAX_TRANSACTIONS_SIZE { break }
match block_builder.push_extrinsic(pending.original.clone()) {
Ok(()) => {
pending_size += pending.verified.encoded_size();
}
Err(e) => {
trace!(target: "transaction-pool", "Invalid transaction: {}", e);
unqueue_invalid.push(pending.verified.hash().clone());
}
}
}
});
if let Err(e) = result {
warn!("Unable to get the pending set: {:?}", e);
}
self.transaction_pool.remove(&unqueue_invalid, false);
}
let block = block_builder.bake()?;
info!("Proposing block [number: {}; hash: {}; parent_hash: {}; extrinsics: [{}]]",
block.header.number,
Hash::from(block.header.hash()),
block.header.parent_hash,
block.extrinsics.iter()
.map(|xt| format!("{}", BlakeTwo256::hash_of(xt)))
.collect::<Vec<_>>()
.join(", ")
);
let substrate_block = Decode::decode(&mut block.encode().as_slice())
.expect("blocks are defined to serialize to substrate blocks correctly; qed");
assert!(evaluation::evaluate_initial(
&substrate_block,
timestamp,
&self.parent_hash,
self.parent_number,
).is_ok());
Ok(substrate_block)
}
fn evaluate(&self, unchecked_proposal: &Block) -> Self::Evaluate {
debug!(target: "bft", "evaluating block on top of parent ({}, {:?})", self.parent_number, self.parent_hash);
let current_timestamp = current_timestamp();
// do initial serialization and structural integrity checks.
let maybe_proposal = evaluation::evaluate_initial(
unchecked_proposal,
current_timestamp,
&self.parent_hash,
self.parent_number,
);
let proposal = match maybe_proposal {
Ok(p) => p,
Err(e) => {
// TODO: these errors are easily re-checked in runtime.
debug!(target: "bft", "Invalid proposal: {:?}", e);
return Box::new(future::ok(false));
}
};
let vote_delays = {
let now = Instant::now();
// the duration until the given timestamp is current
let proposed_timestamp = ::std::cmp::max(self.minimum_timestamp, proposal.timestamp());
let timestamp_delay = if proposed_timestamp > current_timestamp {
let delay_s = proposed_timestamp - current_timestamp;
debug!(target: "bft", "Delaying evaluation of proposal for {} seconds", delay_s);
Some(now + Duration::from_secs(delay_s))
} else {
None
};
match timestamp_delay {
Some(duration) => future::Either::A(
Delay::new(duration).map_err(|e| Error::from(ErrorKind::Timer(e)))
),
None => future::Either::B(future::ok(())),
}
};
// refuse to vote if this block says a validator is offline that we
// think isn't.
let offline = proposal.noted_offline();
if !self.offline.read().check_consistency(&self.validators[..], offline) {
return Box::new(futures::empty());
}
// evaluate whether the block is actually valid.
// TODO: is it better to delay this until the delays are finished?
let evaluated = self.client
.evaluate_block(&self.parent_id, unchecked_proposal.clone())
.map_err(Into::into);
let future = future::result(evaluated).and_then(move |good| {
let end_result = future::ok(good);
if good {
// delay a "good" vote.
future::Either::A(vote_delays.and_then(|_| end_result))
} else {
// don't delay a "bad" evaluation.
future::Either::B(end_result)
}
});
Box::new(future) as Box<_>
}
fn round_proposer(&self, round_number: usize, authorities: &[AuthorityId]) -> AuthorityId {
let offset = self.primary_index(round_number, authorities.len());
let proposer = authorities[offset].clone();
trace!(target: "bft", "proposer for round {} is {}", round_number, proposer);
proposer
}
fn import_misbehavior(&self, misbehavior: Vec<(AuthorityId, bft::Misbehavior<Hash>)>) {
use rhododendron::Misbehavior as GenericMisbehavior;
use runtime_primitives::bft::{MisbehaviorKind, MisbehaviorReport};
use demo_primitives::UncheckedExtrinsic as GenericExtrinsic;
use demo_runtime::{Call, UncheckedExtrinsic, ConsensusCall};
let local_id = self.local_key.public().0.into();
let mut next_index = {
let cur_index = self.transaction_pool.cull_and_get_pending(&BlockId::hash(self.parent_hash), |pending| pending
.filter(|tx| tx.verified.sender == local_id)
.last()
.map(|tx| Ok(tx.verified.index()))
.unwrap_or_else(|| self.client.index(&self.parent_id, local_id))
);
match cur_index {
Ok(Ok(cur_index)) => cur_index + 1,
Ok(Err(e)) => {
warn!(target: "consensus", "Error computing next transaction index: {}", e);
return;
}
Err(e) => {
warn!(target: "consensus", "Error computing next transaction index: {}", e);
return;
}
}
};
for (target, misbehavior) in misbehavior {
let report = MisbehaviorReport {
parent_hash: self.parent_hash,
parent_number: self.parent_number,
target,
misbehavior: match misbehavior {
GenericMisbehavior::ProposeOutOfTurn(_, _, _) => continue,
GenericMisbehavior::DoublePropose(_, _, _) => continue,
GenericMisbehavior::DoublePrepare(round, (h1, s1), (h2, s2))
=> MisbehaviorKind::BftDoublePrepare(round as u32, (h1, s1.signature), (h2, s2.signature)),
GenericMisbehavior::DoubleCommit(round, (h1, s1), (h2, s2))
=> MisbehaviorKind::BftDoubleCommit(round as u32, (h1, s1.signature), (h2, s2.signature)),
}
};
let payload = (next_index, Call::Consensus(ConsensusCall::report_misbehavior(report)));
let signature = self.local_key.sign(&payload.encode()).into();
next_index += 1;
let local_id = self.local_key.public().0.into();
let extrinsic = UncheckedExtrinsic {
signature: Some((demo_runtime::RawAddress::Id(local_id), signature)),
index: payload.0,
function: payload.1,
};
let uxt: GenericExtrinsic = Decode::decode(&mut extrinsic.encode().as_slice()).expect("Encoded extrinsic is valid");
self.transaction_pool.submit_one(&BlockId::hash(self.parent_hash), uxt)
.expect("locally signed extrinsic is valid; qed");
}
}
fn on_round_end(&self, round_number: usize, was_proposed: bool) {
let primary_validator = self.validators[
self.primary_index(round_number, self.validators.len())
];
// alter the message based on whether we think the empty proposer was forced to skip the round.
// this is determined by checking if our local validator would have been forced to skip the round.
if !was_proposed {
let public = ::ed25519::Public::from_raw(primary_validator.0);
info!(
"Potential Offline Validator: {} failed to propose during assigned slot: {}",
public,
round_number,
);
}
self.offline.write().note_round_end(primary_validator, was_proposed);
}
}
fn current_timestamp() -> Timestamp {
time::SystemTime::now().duration_since(time::UNIX_EPOCH)
.expect("now always later than unix epoch; qed")
.as_secs()
}
@@ -0,0 +1,137 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo 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 Demo 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 Demo. If not, see <http://www.gnu.org/licenses/>.
//! Tracks offline validators.
use demo_primitives::AccountId;
use std::collections::HashMap;
use std::time::{Instant, Duration};
// time before we report a validator.
const REPORT_TIME: Duration = Duration::from_secs(60 * 5);
struct Observed {
last_round_end: Instant,
offline_since: Instant,
}
impl Observed {
fn new() -> Observed {
let now = Instant::now();
Observed {
last_round_end: now,
offline_since: now,
}
}
fn note_round_end(&mut self, was_online: bool) {
let now = Instant::now();
self.last_round_end = now;
if was_online {
self.offline_since = now;
}
}
fn is_active(&self) -> bool {
// can happen if clocks are not monotonic
if self.offline_since > self.last_round_end { return true }
self.last_round_end.duration_since(self.offline_since) < REPORT_TIME
}
}
/// Tracks offline validators and can issue a report for those offline.
pub struct OfflineTracker {
observed: HashMap<AccountId, Observed>,
}
impl OfflineTracker {
/// Create a new tracker.
pub fn new() -> Self {
OfflineTracker { observed: HashMap::new() }
}
/// Note new consensus is starting with the given set of validators.
pub fn note_new_block(&mut self, validators: &[AccountId]) {
use std::collections::HashSet;
let set: HashSet<_> = validators.iter().cloned().collect();
self.observed.retain(|k, _| set.contains(k));
}
/// Note that a round has ended.
pub fn note_round_end(&mut self, validator: AccountId, was_online: bool) {
self.observed.entry(validator)
.or_insert_with(Observed::new)
.note_round_end(was_online);
}
/// Generate a vector of indices for offline account IDs.
pub fn reports(&self, validators: &[AccountId]) -> Vec<u32> {
validators.iter()
.enumerate()
.filter_map(|(i, v)| if self.is_online(v) {
None
} else {
Some(i as u32)
})
.collect()
}
/// Whether reports on a validator set are consistent with our view of things.
pub fn check_consistency(&self, validators: &[AccountId], reports: &[u32]) -> bool {
reports.iter().cloned().all(|r| {
let v = match validators.get(r as usize) {
Some(v) => v,
None => return false,
};
// we must think all validators reported externally are offline.
let thinks_online = self.is_online(v);
!thinks_online
})
}
fn is_online(&self, v: &AccountId) -> bool {
self.observed.get(v).map(Observed::is_active).unwrap_or(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validator_offline() {
let mut tracker = OfflineTracker::new();
let v = [0; 32].into();
let v2 = [1; 32].into();
let v3 = [2; 32].into();
tracker.note_round_end(v, true);
tracker.note_round_end(v2, true);
tracker.note_round_end(v3, true);
let slash_time = REPORT_TIME + Duration::from_secs(5);
tracker.observed.get_mut(&v).unwrap().offline_since -= slash_time;
tracker.observed.get_mut(&v2).unwrap().offline_since -= slash_time;
assert_eq!(tracker.reports(&[v, v2, v3]), vec![0, 1]);
tracker.note_new_block(&[v, v3]);
assert_eq!(tracker.reports(&[v, v2, v3]), vec![0]);
}
}
+172
View File
@@ -0,0 +1,172 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo 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 Demo 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 Demo. If not, see <http://www.gnu.org/licenses/>.
//! Consensus service.
/// Consensus service. A long running service that manages BFT agreement
/// the network.
use std::thread;
use std::time::{Duration, Instant};
use std::sync::Arc;
use bft::{self, BftService};
use client::{BlockchainEvents, ChainHead, BlockBody};
use ed25519;
use futures::prelude::*;
use demo_api::Api;
use demo_primitives::{Block, Header};
use transaction_pool::TransactionPool;
use tokio::executor::current_thread::TaskExecutor as LocalThreadHandle;
use tokio::runtime::TaskExecutor as ThreadPoolHandle;
use tokio::runtime::current_thread::Runtime as LocalRuntime;
use tokio::timer::Interval;
use super::{Network, ProposerFactory};
use error;
const TIMER_DELAY_MS: u64 = 5000;
const TIMER_INTERVAL_MS: u64 = 500;
// spin up an instance of BFT agreement on the current thread's executor.
// panics if there is no current thread executor.
fn start_bft<F, C>(
header: Header,
bft_service: Arc<BftService<Block, F, C>>,
) where
F: bft::Environment<Block> + 'static,
C: bft::BlockImport<Block> + bft::Authorities<Block> + 'static,
F::Error: ::std::fmt::Debug,
<F::Proposer as bft::Proposer<Block>>::Error: ::std::fmt::Display + Into<error::Error>,
<F as bft::Environment<Block>>::Error: ::std::fmt::Display
{
let mut handle = LocalThreadHandle::current();
match bft_service.build_upon(&header) {
Ok(Some(bft_work)) => if let Err(e) = handle.spawn_local(Box::new(bft_work)) {
warn!(target: "bft", "Couldn't initialize BFT agreement: {:?}", e);
}
Ok(None) => trace!(target: "bft", "Could not start agreement on top of {}", header.hash()),
Err(e) => warn!(target: "bft", "BFT agreement error: {}", e),
}
}
/// Consensus service. Starts working when created.
pub struct Service {
thread: Option<thread::JoinHandle<()>>,
exit_signal: Option<::exit_future::Signal>,
}
impl Service {
/// Create and start a new instance.
pub fn new<A, C, N>(
client: Arc<C>,
api: Arc<A>,
network: N,
transaction_pool: Arc<TransactionPool<A>>,
thread_pool: ThreadPoolHandle,
key: ed25519::Pair,
) -> Service
where
A: Api + Send + Sync + 'static,
C: BlockchainEvents<Block> + ChainHead<Block> + BlockBody<Block>,
C: bft::BlockImport<Block> + bft::Authorities<Block> + Send + Sync + 'static,
N: Network + Send + 'static,
{
use parking_lot::RwLock;
use super::OfflineTracker;
let (signal, exit) = ::exit_future::signal();
let thread = thread::spawn(move || {
let mut runtime = LocalRuntime::new().expect("Could not create local runtime");
let key = Arc::new(key);
let factory = ProposerFactory {
client: api.clone(),
transaction_pool: transaction_pool.clone(),
network,
handle: thread_pool.clone(),
offline: Arc::new(RwLock::new(OfflineTracker::new())),
};
let bft_service = Arc::new(BftService::new(client.clone(), key, factory));
let notifications = {
let client = client.clone();
let bft_service = bft_service.clone();
client.import_notification_stream().for_each(move |notification| {
if notification.is_new_best {
start_bft(notification.header, bft_service.clone());
}
Ok(())
})
};
let interval = Interval::new(
Instant::now() + Duration::from_millis(TIMER_DELAY_MS),
Duration::from_millis(TIMER_INTERVAL_MS),
);
let mut prev_best = match client.best_block_header() {
Ok(header) => header.hash(),
Err(e) => {
warn!("Cant's start consensus service. Error reading best block header: {:?}", e);
return;
}
};
let timed = {
let c = client.clone();
let s = bft_service.clone();
interval.map_err(|e| debug!(target: "bft", "Timer error: {:?}", e)).for_each(move |_| {
if let Ok(best_block) = c.best_block_header() {
let hash = best_block.hash();
if hash == prev_best {
debug!(target: "bft", "Starting consensus round after a timeout");
start_bft(best_block, s.clone());
}
prev_best = hash;
}
Ok(())
})
};
runtime.spawn(notifications);
runtime.spawn(timed);
if let Err(e) = runtime.block_on(exit) {
debug!("BFT event loop error {:?}", e);
}
});
Service {
thread: Some(thread),
exit_signal: Some(signal),
}
}
}
impl Drop for Service {
fn drop(&mut self) {
if let Some(signal) = self.exit_signal.take() {
signal.fire();
}
if let Some(thread) = self.thread.take() {
thread.join().expect("The service thread has panicked");
}
}
}
+1
View File
@@ -25,4 +25,5 @@ substrate-runtime-session = { path = "../../substrate/runtime/session" }
substrate-runtime-staking = { path = "../../substrate/runtime/staking" }
substrate-runtime-system = { path = "../../substrate/runtime/system" }
substrate-runtime-consensus = { path = "../../substrate/runtime/consensus" }
substrate-runtime-timestamp = { path = "../../substrate/runtime/timestamp" }
substrate-runtime-treasury = { path = "../../substrate/runtime/treasury" }
+48 -21
View File
@@ -1,4 +1,4 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo is free software: you can redistribute it and/or modify
@@ -35,6 +35,7 @@ extern crate triehash;
#[cfg(test)] extern crate substrate_runtime_staking as staking;
#[cfg(test)] extern crate substrate_runtime_system as system;
#[cfg(test)] extern crate substrate_runtime_consensus as consensus;
#[cfg(test)] extern crate substrate_runtime_timestamp as timestamp;
#[cfg(test)] extern crate substrate_runtime_treasury as treasury;
#[cfg(test)] #[macro_use] extern crate hex_literal;
@@ -54,7 +55,7 @@ mod tests {
use demo_primitives::{Hash, BlockNumber, AccountId};
use runtime_primitives::traits::Header as HeaderT;
use runtime_primitives::{ApplyOutcome, ApplyError, ApplyResult};
use {balances, staking, session, system, consensus, treasury};
use {balances, staking, session, system, consensus, timestamp, treasury};
use system::{EventRecord, Phase};
use demo_runtime::{Header, Block, UncheckedExtrinsic, CheckedExtrinsic, Call, Runtime, Balances,
BuildStorage, GenesisConfig, BalancesConfig, SessionConfig, StakingConfig, System, Event};
@@ -243,7 +244,6 @@ mod tests {
use triehash::ordered_trie_root;
let extrinsics = extrinsics.into_iter().map(sign).collect::<Vec<_>>();
let extrinsics_root = ordered_trie_root::<KeccakHasher, _, _>(extrinsics.iter().map(Encode::encode)).0.into();
let header = Header {
@@ -262,12 +262,19 @@ mod tests {
construct_block(
1,
[69u8; 32].into(),
hex!("1e930ccf2a39029931fcb9173637ab99a7de9d0364e7bf57cfbcb3eb4619e0d4").into(),
vec![CheckedExtrinsic {
signed: Some(alice()),
index: 0,
function: Call::Balances(balances::Call::transfer(bob().into(), 69)),
}]
hex!("54048fe23d4e04fda6419771037922eb43d96a7ec76aa280672609711999c3ca").into(),
vec![
CheckedExtrinsic {
signed: None,
index: 0,
function: Call::Timestamp(timestamp::Call::set(42)),
},
CheckedExtrinsic {
signed: Some(alice()),
index: 0,
function: Call::Balances(balances::Call::transfer(bob().into(), 69)),
},
]
)
}
@@ -275,8 +282,13 @@ mod tests {
construct_block(
2,
block1().1,
hex!("80e45869c9a9f513695b94baf479913ddf0bc9653f1be63baa383be8553a9e13").into(),
hex!("700c76e3b6125fd9d873629ca3f3cdc2f7704587c0a71def6b152f54b6a29805").into(),
vec![
CheckedExtrinsic {
signed: None,
index: 0,
function: Call::Timestamp(timestamp::Call::set(52)),
},
CheckedExtrinsic {
signed: Some(bob()),
index: 0,
@@ -295,12 +307,19 @@ mod tests {
construct_block(
1,
[69u8; 32].into(),
hex!("58bf7cd5a908de7296bfc0524d89086384df3e8010ab83c8599be036445d6c79").into(),
vec![CheckedExtrinsic {
signed: Some(alice()),
index: 0,
function: Call::Consensus(consensus::Call::remark(vec![0; 60000000])),
}]
hex!("4428d38ae046f27254877b3a3bf0d8ec7731281aef65ebdb8bbbac86be5424a8").into(),
vec![
CheckedExtrinsic {
signed: None,
index: 0,
function: Call::Timestamp(timestamp::Call::set(42)),
},
CheckedExtrinsic {
signed: Some(alice()),
index: 0,
function: Call::Consensus(consensus::Call::remark(vec![0; 120000])),
}
]
)
}
@@ -316,10 +335,14 @@ mod tests {
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: Event::system(system::Event::ExtrinsicSuccess)
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: Event::balances(balances::RawEvent::NewAccount(bob(), 1, balances::NewAccountOutcome::NoHint))
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
phase: Phase::ApplyExtrinsic(1),
event: Event::balances(balances::RawEvent::Transfer(
hex!["d172a74cda4c865912c32ba0a80a57ae69abae410e5ccb59dee84e2f4432db4f"].into(),
hex!["d7568e5f0a7eda67a82691ff379ac4bba4f9c9b859fe779b5d46363b61ad2db9"].into(),
@@ -328,7 +351,7 @@ mod tests {
))
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
phase: Phase::ApplyExtrinsic(1),
event: Event::system(system::Event::ExtrinsicSuccess)
},
EventRecord {
@@ -354,6 +377,10 @@ mod tests {
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: Event::system(system::Event::ExtrinsicSuccess)
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: Event::balances(
balances::RawEvent::Transfer(
hex!["d7568e5f0a7eda67a82691ff379ac4bba4f9c9b859fe779b5d46363b61ad2db9"].into(),
@@ -364,11 +391,11 @@ mod tests {
)
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
phase: Phase::ApplyExtrinsic(1),
event: Event::system(system::Event::ExtrinsicSuccess)
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
phase: Phase::ApplyExtrinsic(2),
event: Event::balances(
balances::RawEvent::Transfer(
hex!["d172a74cda4c865912c32ba0a80a57ae69abae410e5ccb59dee84e2f4432db4f"].into(),
@@ -379,7 +406,7 @@ mod tests {
)
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
phase: Phase::ApplyExtrinsic(2),
event: Event::system(system::Event::ExtrinsicSuccess)
},
EventRecord {
+17
View File
@@ -0,0 +1,17 @@
[package]
name = "demo-network"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
description = "Substrate demo networking protocol"
[dependencies]
demo-api = { path = "../api" }
demo-consensus = { path = "../consensus" }
demo-primitives = { path = "../primitives" }
substrate-bft = { path = "../../substrate/bft" }
substrate-network = { path = "../../substrate/network" }
ed25519 = { path = "../../substrate/ed25519" }
futures = "0.1"
tokio = "0.1.7"
log = "0.4"
rhododendron = "0.3"
+297
View File
@@ -0,0 +1,297 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo 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 Demo 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 Demo. If not, see <http://www.gnu.org/licenses/>.
//! The "consensus" networking code built on top of the base network service.
//! This fulfills the `demo_consensus::Network` trait, providing a hook to be called
//! each time consensus begins on a new chain head.
use bft;
use ed25519;
use substrate_network::{self as net, generic_message as msg};
use substrate_network::consensus_gossip::ConsensusMessage;
use demo_api::Api;
use demo_consensus::Network;
use demo_primitives::{Block, Hash, SessionKey};
use rhododendron;
use futures::prelude::*;
use futures::sync::mpsc;
use std::sync::Arc;
use tokio::runtime::TaskExecutor;
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: "demo-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 = ::demo_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: Api + 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<::demo_consensus::Error>;
/// Get input and output streams of BFT messages.
fn communication_for(
&self, validators: &[SessionKey],
local_id: SessionKey,
parent_hash: Hash,
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.new_consensus(parent_hash);
MessageProcessTask {
inner_stream: spec.consensus_gossip.messages_for(parent_hash),
bft_messages: bft_send,
validators: validators.to_vec(),
local_id,
}
});
match process_task {
Some(task) => task_executor.spawn(task),
None => warn!(target: "demo-network", "Cannot process incoming messages: network appears to be down"),
}
(InputAdapter { input: bft_recv }, sink)
}
}
/// Error when the network appears to be down.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct NetworkDown;
+117
View File
@@ -0,0 +1,117 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo 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 Demo 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 Demo. If not, see <http://www.gnu.org/licenses/>.
//! Substrate Demo-specific network implementation.
//!
//! This manages gossip of consensus messages for BFT.
#![warn(unused_extern_crates)]
extern crate substrate_bft as bft;
extern crate substrate_network;
extern crate demo_api;
extern crate demo_consensus;
extern crate demo_primitives;
extern crate ed25519;
extern crate futures;
extern crate tokio;
extern crate rhododendron;
#[macro_use]
extern crate log;
pub mod consensus;
use demo_primitives::{Block, Hash, Header};
use substrate_network::{NodeIndex, Context, Severity};
use substrate_network::consensus_gossip::ConsensusGossip;
use substrate_network::{message, generic_message};
use substrate_network::specialization::Specialization;
use substrate_network::StatusMessage as GenericFullStatus;
/// Demo protocol id.
pub const PROTOCOL_ID: ::substrate_network::ProtocolId = *b"dot";
type FullStatus = GenericFullStatus<Block>;
/// Specialization of the network service for the demo protocol.
pub type NetworkService = ::substrate_network::Service<Block, Protocol, Hash>;
/// Demo protocol attachment for substrate.
pub struct Protocol {
consensus_gossip: ConsensusGossip<Block>,
live_consensus: Option<Hash>,
}
impl Protocol {
/// Instantiate a demo protocol handler.
pub fn new() -> Self {
Protocol {
consensus_gossip: ConsensusGossip::new(),
live_consensus: None,
}
}
/// Note new consensus session.
fn new_consensus(&mut self, parent_hash: Hash) {
let old_consensus = self.live_consensus.take();
self.live_consensus = Some(parent_hash);
self.consensus_gossip.collect_garbage(old_consensus.as_ref());
}
}
impl Specialization<Block> for Protocol {
fn status(&self) -> Vec<u8> {
Vec::new()
}
fn on_connect(&mut self, ctx: &mut Context<Block>, who: NodeIndex, status: FullStatus) {
self.consensus_gossip.new_peer(ctx, who, status.roles);
}
fn on_disconnect(&mut self, ctx: &mut Context<Block>, who: NodeIndex) {
self.consensus_gossip.peer_disconnected(ctx, who);
}
fn on_message(&mut self, ctx: &mut Context<Block>, who: NodeIndex, message: message::Message<Block>) {
match message {
generic_message::Message::BftMessage(msg) => {
trace!(target: "demo-network", "BFT message from {}: {:?}", who, msg);
// TODO: check signature here? what if relevant block is unknown?
self.consensus_gossip.on_bft_message(ctx, who, msg)
}
generic_message::Message::ChainSpecific(_) => {
trace!(target: "demo-network", "Bad message from {}", who);
ctx.report_peer(who, Severity::Bad("Invalid demo protocol message format"));
}
_ => {}
}
}
fn on_abort(&mut self) {
self.consensus_gossip.abort();
}
fn maintain_peers(&mut self, _ctx: &mut Context<Block>) {
}
fn on_block_imported(&mut self, _ctx: &mut Context<Block>, _hash: Hash, _header: &Header) {
}
}
+2
View File
@@ -7,6 +7,7 @@ authors = ["Parity Technologies <admin@parity.io>"]
serde = { version = "1.0", default_features = false }
serde_derive = { version = "1.0", optional = true }
substrate-codec = { path = "../../substrate/codec", default_features = false }
substrate-codec-derive = { path = "../../substrate/codec/derive", default_features = false }
substrate-primitives = { path = "../../substrate/primitives", default_features = false }
substrate-runtime-std = { path = "../../substrate/runtime-std", default_features = false }
substrate-runtime-primitives = { path = "../../substrate/runtime/primitives", default_features = false }
@@ -18,6 +19,7 @@ pretty_assertions = "0.4"
[features]
default = ["std"]
std = [
"substrate-codec-derive/std",
"substrate-codec/std",
"substrate-primitives/std",
"substrate-runtime-std/std",
+58 -7
View File
@@ -1,4 +1,4 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo is free software: you can redistribute it and/or modify
@@ -21,17 +21,31 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(not(feature = "std"), feature(alloc))]
#[cfg(feature = "std")] extern crate serde;
#[cfg(feature = "std")]
extern crate serde;
#[cfg(feature = "std")]
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate substrate_codec_derive;
extern crate substrate_runtime_std as rstd;
extern crate substrate_runtime_primitives as runtime_primitives;
extern crate substrate_primitives as primitives;
extern crate substrate_codec as codec;
use rstd::prelude::*;
use runtime_primitives::generic;
#[cfg(feature = "std")]
use primitives::bytes;
use runtime_primitives::traits::{BlakeTwo256, DigestItem};
/// An index to a block.
pub type BlockNumber = u64;
/// Alias to Ed25519 pubkey that identifies an account on the relay chain. This will almost
/// Alias to Ed25519 pubkey that identifies an account on the chain. This will almost
/// certainly continue to be the same as the substrate's `AuthorityId`.
pub type AccountId = ::primitives::H256;
@@ -42,15 +56,52 @@ pub type AccountIndex = u32;
/// Balance of an account.
pub type Balance = u64;
/// The Ed25519 pub key of an session that belongs to an authority of the relay chain. This is
/// The Ed25519 pub key of an session that belongs to an authority of the chain. This is
/// exactly equivalent to what the substrate calls an "authority".
pub type SessionKey = primitives::AuthorityId;
/// Index of a transaction in the relay chain.
/// Index of a transaction in the chain.
pub type Index = u64;
/// A hash of some data used by the relay chain.
/// A hash of some data used by the chain.
pub type Hash = primitives::H256;
/// Alias to 512-bit hash when used in the context of a signature on the relay chain.
/// Alias to 512-bit hash when used in the context of a signature on the chain.
pub type Signature = runtime_primitives::Ed25519Signature;
/// A timestamp: seconds since the unix epoch.
pub type Timestamp = u64;
/// Header type.
pub type Header = generic::Header<BlockNumber, BlakeTwo256, Log>;
/// Block type.
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
/// Block ID.
pub type BlockId = generic::BlockId<Block>;
/// A log entry in the block.
#[derive(PartialEq, Eq, Clone, Default, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
pub struct Log(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8>);
//TODO: remove this. Generic primitives should not require DigestItem
impl DigestItem for Log {
type AuthoritiesChange = ();
fn as_authorities_change(&self) -> Option<&()> {
unreachable!()
}
}
/// Opaque, encoded, unchecked extrinsic.
#[derive(PartialEq, Eq, Clone, Default, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
pub struct UncheckedExtrinsic(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8>);
///
/// Inherent data to include in a block.
#[derive(Encode, Decode)]
pub struct InherentData {
/// Current timestamp.
pub timestamp: Timestamp,
/// Indices of offline validators.
pub offline_indices: Vec<u32>,
}
@@ -0,0 +1,94 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo 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 Demo 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 Demo. If not, see <http://www.gnu.org/licenses/>.
//! Typesafe block interaction.
use super::{Call, Block, TIMESTAMP_SET_POSITION, NOTE_OFFLINE_POSITION};
use timestamp::Call as TimestampCall;
use consensus::Call as ConsensusCall;
/// Provides a type-safe wrapper around a structurally valid block.
pub struct CheckedBlock {
inner: Block,
file_line: Option<(&'static str, u32)>,
}
impl CheckedBlock {
/// Create a new checked block. Fails if the block is not structurally valid.
pub fn new(block: Block) -> Result<Self, Block> {
let has_timestamp = block.extrinsics.get(TIMESTAMP_SET_POSITION as usize).map_or(false, |xt| {
!xt.is_signed() && match xt.function {
Call::Timestamp(TimestampCall::set(_)) => true,
_ => false,
}
});
if !has_timestamp { return Err(block) }
Ok(CheckedBlock {
inner: block,
file_line: None,
})
}
// Creates a new checked block, asserting that it is valid.
#[doc(hidden)]
pub fn new_unchecked(block: Block, file: &'static str, line: u32) -> Self {
CheckedBlock {
inner: block,
file_line: Some((file, line)),
}
}
/// Extract the timestamp from the block.
pub fn timestamp(&self) -> ::demo_primitives::Timestamp {
let x = self.inner.extrinsics.get(TIMESTAMP_SET_POSITION as usize).and_then(|xt| match xt.function {
Call::Timestamp(TimestampCall::set(x)) => Some(x),
_ => None
});
match x {
Some(x) => x,
None => panic!("Invalid block asserted at {:?}", self.file_line),
}
}
/// Extract the noted missed proposal validator indices (if any) from the block.
pub fn noted_offline(&self) -> &[u32] {
self.inner.extrinsics.get(NOTE_OFFLINE_POSITION as usize).and_then(|xt| match xt.function {
Call::Consensus(ConsensusCall::note_offline(ref x)) => Some(&x[..]),
_ => None,
}).unwrap_or(&[])
}
/// Convert into inner block.
pub fn into_inner(self) -> Block { self.inner }
}
impl ::std::ops::Deref for CheckedBlock {
type Target = Block;
fn deref(&self) -> &Block { &self.inner }
}
/// Assert that a block is structurally valid. May lead to panic in the future
/// in case it isn't.
#[macro_export]
macro_rules! assert_demo_block {
($block: expr) => {
$crate::CheckedBlock::new_unchecked($block, file!(), line!())
}
}
+49 -10
View File
@@ -1,4 +1,4 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo is free software: you can redistribute it and/or modify
@@ -40,6 +40,7 @@ extern crate substrate_primitives;
#[macro_use]
extern crate substrate_codec_derive;
#[cfg_attr(not(feature = "std"), macro_use)]
extern crate substrate_runtime_std as rstd;
extern crate substrate_runtime_balances as balances;
extern crate substrate_runtime_consensus as consensus;
@@ -55,7 +56,11 @@ extern crate substrate_runtime_treasury as treasury;
extern crate substrate_runtime_version as version;
extern crate demo_primitives;
use demo_primitives::{AccountId, AccountIndex, Balance, BlockNumber, Hash, Index, SessionKey, Signature};
#[cfg(feature = "std")]
mod checked_block;
use rstd::prelude::*;
use demo_primitives::{AccountId, AccountIndex, Balance, BlockNumber, Hash, Index, SessionKey, Signature, InherentData};
use runtime_primitives::generic;
use runtime_primitives::traits::{Convert, BlakeTwo256, DigestItem};
use version::RuntimeVersion;
@@ -63,7 +68,15 @@ use council::motions as council_motions;
use substrate_primitives::u32_trait::{_2, _4};
#[cfg(any(feature = "std", test))]
pub use runtime_primitives::{BuildStorage, Permill};
pub use runtime_primitives::BuildStorage;
pub use consensus::Call as ConsensusCall;
pub use timestamp::Call as TimestampCall;
pub use runtime_primitives::Permill;
#[cfg(any(feature = "std", test))]
pub use checked_block::CheckedBlock;
const TIMESTAMP_SET_POSITION: u32 = 0;
const NOTE_OFFLINE_POSITION: u32 = 1;
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
#[derive(Clone, Copy, PartialEq, Eq)]
@@ -74,7 +87,7 @@ pub struct Runtime;
/// Runtime version.
pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: ver_str!("demo"),
impl_name: ver_str!("parity-demo"),
impl_name: ver_str!("substrate-demo"),
authoring_version: 1,
spec_version: 1,
impl_version: 0,
@@ -107,7 +120,7 @@ impl balances::Trait for Runtime {
pub type Balances = balances::Module<Runtime>;
impl consensus::Trait for Runtime {
const NOTE_OFFLINE_POSITION: u32 = 1;
const NOTE_OFFLINE_POSITION: u32 = NOTE_OFFLINE_POSITION;
type Log = Log;
type SessionKey = SessionKey;
type OnOfflineValidator = Staking;
@@ -117,8 +130,7 @@ impl consensus::Trait for Runtime {
pub type Consensus = consensus::Module<Runtime>;
impl timestamp::Trait for Runtime {
const TIMESTAMP_SET_POSITION: u32 = 0;
const TIMESTAMP_SET_POSITION: u32 = TIMESTAMP_SET_POSITION;
type Moment = u64;
}
@@ -240,6 +252,8 @@ impl DigestItem for Log {
}
}
/// The address format for describing accounts.
pub use balances::address::Address as RawAddress;
/// The address format for describing accounts.
pub type Address = balances::Address<Runtime>;
/// Block header type as expected by this runtime.
@@ -254,18 +268,43 @@ pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<Address, Index, Call,
pub type CheckedExtrinsic = generic::CheckedExtrinsic<AccountId, Index, Call>;
/// Executive: handles dispatch to the various modules.
pub type Executive = executive::Executive<Runtime, Block, Balances, Balances,
((((((), Treasury), Council), Democracy), Staking), Session)>;
(((((((), Treasury), Council), Democracy), Staking), Session), Timestamp)>;
pub mod api {
impl_stubs!(
version => |()| super::VERSION,
authorities => |()| super::Consensus::authorities(),
events => |()| super::System::events(),
initialise_block => |header| super::Executive::initialise_block(&header),
apply_extrinsic => |extrinsic| super::Executive::apply_extrinsic(extrinsic),
execute_block => |block| super::Executive::execute_block(block),
finalise_block => |()| super::Executive::finalise_block(),
inherent_extrinsics => |(inherent, spec_version)| super::inherent_extrinsics(inherent, spec_version),
validator_count => |()| super::Session::validator_count(),
validators => |()| super::Session::validators()
validators => |()| super::Session::validators(),
timestamp => |()| super::Timestamp::get(),
random_seed => |()| super::System::random_seed(),
account_nonce => |account| super::System::account_nonce(&account),
lookup_address => |address| super::Balances::lookup_address(address)
);
}
/// Produces the list of inherent extrinsics.
fn inherent_extrinsics(data: InherentData, _spec_version: u32) -> Vec<UncheckedExtrinsic> {
let make_inherent = |function| UncheckedExtrinsic {
signature: Default::default(),
function,
index: 0,
};
let mut inherent = vec![
make_inherent(Call::Timestamp(TimestampCall::set(data.timestamp))),
];
if !data.offline_indices.is_empty() {
inherent.push(make_inherent(
Call::Consensus(ConsensusCall::note_offline(data.offline_indices))
));
}
inherent
}
+1
View File
@@ -83,6 +83,7 @@ dependencies = [
"serde 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-codec 0.1.0",
"substrate-codec-derive 0.1.0",
"substrate-primitives 0.1.0",
"substrate-runtime-primitives 0.1.0",
"substrate-runtime-std 0.1.0",
Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
[package]
name = "demo-service"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
[dependencies]
parking_lot = "0.4"
error-chain = "0.12"
lazy_static = "1.0"
log = "0.3"
slog = "^2"
tokio = "0.1.7"
hex-literal = "0.1"
ed25519 = { path = "../../substrate/ed25519" }
demo-api = { path = "../api" }
demo-primitives = { path = "../primitives" }
demo-runtime = { path = "../runtime" }
demo-executor = { path = "../executor" }
demo-consensus = { path = "../consensus" }
demo-network = { path = "../network" }
demo-transaction-pool = { path = "../transaction-pool" }
substrate-runtime-io = { path = "../../substrate/runtime-io" }
substrate-primitives = { path = "../../substrate/primitives" }
substrate-network = { path = "../../substrate/network" }
substrate-client = { path = "../../substrate/client" }
substrate-service = { path = "../../substrate/service" }
substrate-telemetry = { path = "../../substrate/telemetry" }
+210
View File
@@ -0,0 +1,210 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo 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 Demo 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 Demo. If not, see <http://www.gnu.org/licenses/>.
//! Substrate Demo chain configurations.
use ed25519;
use primitives::AuthorityId;
use demo_runtime::{GenesisConfig, ConsensusConfig, CouncilConfig, DemocracyConfig,
SessionConfig, StakingConfig, TimestampConfig, BalancesConfig, TreasuryConfig, Permill};
use service::ChainSpec;
const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/";
pub fn testnet_config() -> Result<ChainSpec<GenesisConfig>, String> {
//ChainSpec::from_embedded(include_bytes!("../res/demo.json"))
Ok(staging_testnet_config())
}
fn staging_testnet_config_genesis() -> GenesisConfig {
let initial_authorities = vec![
hex!["82c39b31a2b79a90f8e66e7a77fdb85a4ed5517f2ae39f6a80565e8ecae85cf5"].into(),
hex!["4de37a07567ebcbf8c64568428a835269a566723687058e017b6d69db00a77e7"].into(),
hex!["063d7787ebca768b7445dfebe7d62cbb1625ff4dba288ea34488da266dd6dca5"].into(),
hex!["8101764f45778d4980dadaceee6e8af2517d3ab91ac9bec9cd1714fa5994081c"].into(),
];
let endowed_accounts = vec![
hex!["f295940fa750df68a686fcf4abd4111c8a9c5a5a5a83c4c8639c451a94a7adfd"].into(),
];
GenesisConfig {
consensus: Some(ConsensusConfig {
code: include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm").to_vec(), // TODO change
authorities: initial_authorities.clone(),
}),
system: None,
balances: Some(BalancesConfig {
transaction_base_fee: 100,
transaction_byte_fee: 1,
existential_deposit: 500,
transfer_fee: 0,
creation_fee: 0,
reclaim_rebate: 0,
balances: endowed_accounts.iter().map(|&k|(k, 1u64 << 60)).collect(),
}),
session: Some(SessionConfig {
validators: initial_authorities.iter().cloned().map(Into::into).collect(),
session_length: 60, // that's 5 minutes per session.
}),
staking: Some(StakingConfig {
current_era: 0,
intentions: initial_authorities.iter().cloned().map(Into::into).collect(),
early_era_slash: 10000,
session_reward: 100,
validator_count: 12,
sessions_per_era: 12, // 1 hour per era
bonding_duration: 24 * 60 * 12, // 1 day per bond.
offline_slash_grace: 4,
minimum_validator_count: 4,
}),
democracy: Some(DemocracyConfig {
launch_period: 12 * 60 * 24, // 1 day per public referendum
voting_period: 12 * 60 * 24 * 3, // 3 days to discuss & vote on an active referendum
minimum_deposit: 5000, // 12000 as the minimum deposit for a referendum
}),
council: Some(CouncilConfig {
active_council: vec![],
candidacy_bond: 5000, // 5000 to become a council candidate
voter_bond: 1000, // 1000 down to vote for a candidate
present_slash_per_voter: 1, // slash by 1 per voter for an invalid presentation.
carry_count: 6, // carry over the 6 runners-up to the next council election
presentation_duration: 12 * 60 * 24, // one day for presenting winners.
approval_voting_period: 12 * 60 * 24 * 2, // two days period between possible council elections.
term_duration: 12 * 60 * 24 * 24, // 24 day term duration for the council.
desired_seats: 0, // start with no council: we'll raise this once the stake has been dispersed a bit.
inactive_grace_period: 1, // one addition vote should go by before an inactive voter can be reaped.
cooloff_period: 12 * 60 * 24 * 4, // 4 day cooling off period if council member vetoes a proposal.
voting_period: 12 * 60 * 24, // 1 day voting period for council members.
}),
timestamp: Some(TimestampConfig {
period: 5, // 5 second block time.
}),
treasury: Some(TreasuryConfig {
proposal_bond: Permill::from_percent(5),
proposal_bond_minimum: 1_000_000,
spend_period: 12 * 60 * 24,
burn: Permill::from_percent(50),
}),
}
}
/// Staging testnet config.
pub fn staging_testnet_config() -> ChainSpec<GenesisConfig> {
let boot_nodes = vec![];
ChainSpec::from_genesis(
"Staging Testnet",
"staging_testnet",
staging_testnet_config_genesis,
boot_nodes,
Some(STAGING_TELEMETRY_URL.into()),
)
}
fn testnet_genesis(initial_authorities: Vec<AuthorityId>) -> GenesisConfig {
let endowed_accounts = vec![
ed25519::Pair::from_seed(b"Alice ").public().0.into(),
ed25519::Pair::from_seed(b"Bob ").public().0.into(),
ed25519::Pair::from_seed(b"Charlie ").public().0.into(),
ed25519::Pair::from_seed(b"Dave ").public().0.into(),
ed25519::Pair::from_seed(b"Eve ").public().0.into(),
ed25519::Pair::from_seed(b"Ferdie ").public().0.into(),
];
GenesisConfig {
consensus: Some(ConsensusConfig {
code: include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm").to_vec(),
authorities: initial_authorities.clone(),
}),
system: None,
balances: Some(BalancesConfig {
transaction_base_fee: 1,
transaction_byte_fee: 0,
existential_deposit: 500,
transfer_fee: 0,
creation_fee: 0,
reclaim_rebate: 0,
balances: endowed_accounts.iter().map(|&k|(k, (1u64 << 60))).collect(),
}),
session: Some(SessionConfig {
validators: initial_authorities.iter().cloned().map(Into::into).collect(),
session_length: 10,
}),
staking: Some(StakingConfig {
current_era: 0,
intentions: initial_authorities.iter().cloned().map(Into::into).collect(),
minimum_validator_count: 1,
validator_count: 2,
sessions_per_era: 5,
bonding_duration: 2 * 60 * 12,
early_era_slash: 0,
session_reward: 0,
offline_slash_grace: 0,
}),
democracy: Some(DemocracyConfig {
launch_period: 9,
voting_period: 18,
minimum_deposit: 10,
}),
council: Some(CouncilConfig {
active_council: endowed_accounts.iter()
.filter(|a| initial_authorities.iter().find(|&b| a.0 == b.0).is_none())
.map(|a| (a.clone(), 1000000)).collect(),
candidacy_bond: 10,
voter_bond: 2,
present_slash_per_voter: 1,
carry_count: 4,
presentation_duration: 10,
approval_voting_period: 20,
term_duration: 1000000,
desired_seats: (endowed_accounts.len() - initial_authorities.len()) as u32,
inactive_grace_period: 1,
cooloff_period: 75,
voting_period: 20,
}),
timestamp: Some(TimestampConfig {
period: 5, // 5 second block time.
}),
treasury: Some(TreasuryConfig {
proposal_bond: Permill::from_percent(5),
proposal_bond_minimum: 1_000_000,
spend_period: 12 * 60 * 24,
burn: Permill::from_percent(50),
}),
}
}
fn development_config_genesis() -> GenesisConfig {
testnet_genesis(vec![
ed25519::Pair::from_seed(b"Alice ").public().into(),
])
}
/// Development config (single validator Alice)
pub fn development_config() -> ChainSpec<GenesisConfig> {
ChainSpec::from_genesis("Development", "development", development_config_genesis, vec![], None)
}
fn local_testnet_genesis() -> GenesisConfig {
testnet_genesis(vec![
ed25519::Pair::from_seed(b"Alice ").public().into(),
ed25519::Pair::from_seed(b"Bob ").public().into(),
])
}
/// Local testnet config (multivalidator Alice + Bob)
pub fn local_testnet_config() -> ChainSpec<GenesisConfig> {
ChainSpec::from_genesis("Local Testnet", "local_testnet", local_testnet_genesis, vec![], None)
}
+214
View File
@@ -0,0 +1,214 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo 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 Demo 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 Demo. If not, see <http://www.gnu.org/licenses/>.
#![warn(unused_extern_crates)]
//! Substrate Demo service. Specialized wrapper over substrate service.
extern crate ed25519;
extern crate demo_api;
extern crate demo_primitives;
extern crate demo_runtime;
extern crate demo_executor;
extern crate demo_network;
extern crate demo_transaction_pool as transaction_pool;
extern crate demo_consensus as consensus;
extern crate substrate_primitives as primitives;
extern crate substrate_network as network;
extern crate substrate_client as client;
extern crate substrate_service as service;
extern crate tokio;
#[macro_use]
extern crate log;
#[macro_use]
extern crate hex_literal;
pub mod chain_spec;
use std::sync::Arc;
use transaction_pool::TransactionPool;
use demo_api::Api;
use demo_primitives::{Block, Hash};
use demo_runtime::GenesisConfig;
use client::Client;
use demo_network::{Protocol as DemoProtocol, consensus::ConsensusNetwork};
use tokio::runtime::TaskExecutor;
use service::FactoryFullConfiguration;
use primitives::{KeccakHasher, RlpCodec};
pub use service::{Roles, PruningMode, ExtrinsicPoolOptions,
ErrorKind, Error, ComponentBlock, LightComponents, FullComponents};
pub use client::ExecutionStrategy;
/// Specialised `ChainSpec`.
pub type ChainSpec = service::ChainSpec<GenesisConfig>;
/// Client type for specialised `Components`.
pub type ComponentClient<C> = Client<<C as Components>::Backend, <C as Components>::Executor, Block>;
pub type NetworkService = network::Service<Block, <Factory as service::ServiceFactory>::NetworkProtocol, Hash>;
/// A collection of type to generalise specific components over full / light client.
pub trait Components: service::Components {
/// Demo API.
type Api: 'static + Api + Send + Sync;
/// Client backend.
type Backend: 'static + client::backend::Backend<Block, KeccakHasher, RlpCodec>;
/// Client executor.
type Executor: 'static + client::CallExecutor<Block, KeccakHasher, RlpCodec> + Send + Sync;
}
impl Components for service::LightComponents<Factory> {
type Api = service::LightClient<Factory>;
type Executor = service::LightExecutor<Factory>;
type Backend = service::LightBackend<Factory>;
}
impl Components for service::FullComponents<Factory> {
type Api = service::FullClient<Factory>;
type Executor = service::FullExecutor<Factory>;
type Backend = service::FullBackend<Factory>;
}
/// All configuration for the node.
pub type Configuration = FactoryFullConfiguration<Factory>;
/// Demo-specific configuration.
#[derive(Default)]
pub struct CustomConfiguration;
/// Config for the substrate service.
pub struct Factory;
impl service::ServiceFactory for Factory {
type Block = Block;
type ExtrinsicHash = Hash;
type NetworkProtocol = DemoProtocol;
type RuntimeDispatch = demo_executor::Executor;
type FullExtrinsicPoolApi = transaction_pool::ChainApi<service::FullClient<Self>>;
type LightExtrinsicPoolApi = transaction_pool::ChainApi<service::LightClient<Self>>;
type Genesis = GenesisConfig;
type Configuration = CustomConfiguration;
const NETWORK_PROTOCOL_ID: network::ProtocolId = ::demo_network::PROTOCOL_ID;
fn build_full_extrinsic_pool(config: ExtrinsicPoolOptions, client: Arc<service::FullClient<Self>>)
-> Result<TransactionPool<service::FullClient<Self>>, Error>
{
Ok(TransactionPool::new(config, transaction_pool::ChainApi::new(client)))
}
fn build_light_extrinsic_pool(config: ExtrinsicPoolOptions, client: Arc<service::LightClient<Self>>)
-> Result<TransactionPool<service::LightClient<Self>>, Error>
{
Ok(TransactionPool::new(config, transaction_pool::ChainApi::new(client)))
}
fn build_network_protocol(_config: &Configuration)
-> Result<DemoProtocol, Error>
{
Ok(DemoProtocol::new())
}
}
/// Demo service.
pub struct Service<C: Components> {
inner: service::Service<C>,
client: Arc<ComponentClient<C>>,
network: Arc<NetworkService>,
api: Arc<<C as Components>::Api>,
_consensus: Option<consensus::Service>,
}
impl <C: Components> Service<C> {
pub fn client(&self) -> Arc<ComponentClient<C>> {
self.client.clone()
}
pub fn network(&self) -> Arc<NetworkService> {
self.network.clone()
}
pub fn api(&self) -> Arc<<C as Components>::Api> {
self.api.clone()
}
}
/// Creates light client and register protocol with the network service
pub fn new_light(config: Configuration, executor: TaskExecutor)
-> Result<Service<LightComponents<Factory>>, Error>
{
let service = service::Service::<LightComponents<Factory>>::new(config, executor.clone())?;
let api = service.client();
Ok(Service {
client: service.client(),
network: service.network(),
api: api,
inner: service,
_consensus: None,
})
}
/// Creates full client and register protocol with the network service
pub fn new_full(config: Configuration, executor: TaskExecutor)
-> Result<Service<FullComponents<Factory>>, Error>
{
let is_validator = (config.roles & Roles::AUTHORITY) == Roles::AUTHORITY;
let service = service::Service::<FullComponents<Factory>>::new(config, executor.clone())?;
// Spin consensus service if configured
let consensus = if is_validator {
// Load the first available key
let key = service.keystore().load(&service.keystore().contents()?[0], "")?;
info!("Using authority key {}", key.public());
let client = service.client();
let consensus_net = ConsensusNetwork::new(service.network(), client.clone());
Some(consensus::Service::new(
client.clone(),
client.clone(),
consensus_net,
service.extrinsic_pool(),
executor,
key,
))
} else {
None
};
Ok(Service {
client: service.client(),
network: service.network(),
api: service.client(),
inner: service,
_consensus: consensus,
})
}
/// Creates bare client without any networking.
pub fn new_client(config: Configuration)
-> Result<Arc<service::ComponentClient<FullComponents<Factory>>>, Error>
{
service::new_client::<Factory>(config)
}
impl<C: Components> ::std::ops::Deref for Service<C> {
type Target = service::Service<C>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
+41 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo is free software: you can redistribute it and/or modify
@@ -19,12 +19,51 @@
#![warn(missing_docs)]
extern crate demo_cli as cli;
extern crate ctrlc;
extern crate futures;
#[macro_use]
extern crate error_chain;
use cli::VersionInfo;
use futures::sync::oneshot;
use futures::{future, Future};
use std::cell::RefCell;
mod vergen {
#![allow(unused)]
include!(concat!(env!("OUT_DIR"), "/version.rs"));
}
// handles ctrl-c
struct Exit;
impl cli::IntoExit for Exit {
type Exit = future::MapErr<oneshot::Receiver<()>, fn(oneshot::Canceled) -> ()>;
fn into_exit(self) -> Self::Exit {
// can't use signal directly here because CtrlC takes only `Fn`.
let (exit_send, exit) = oneshot::channel();
let exit_send_cell = RefCell::new(Some(exit_send));
ctrlc::set_handler(move || {
if let Some(exit_send) = exit_send_cell.try_borrow_mut().expect("signal handler not reentrant; qed").take() {
exit_send.send(()).expect("Error sending exit notification");
}
}).expect("Error setting Ctrl-C handler");
exit.map_err(drop)
}
}
quick_main!(run);
fn run() -> cli::error::Result<()> {
cli::run(::std::env::args())
let version = VersionInfo {
commit: vergen::short_sha(),
version: env!("CARGO_PKG_VERSION"),
executable_name: "substrate",
author: "Parity Team <admin@parity.io>",
description: "Generic substrate node",
};
cli::run(::std::env::args(), Exit, version)
}
@@ -0,0 +1,19 @@
[package]
name = "demo-transaction-pool"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
[dependencies]
log = "0.3.0"
error-chain = "0.12"
parking_lot = "0.4"
demo-api = { path = "../api" }
demo-primitives = { path = "../primitives" }
demo-runtime = { path = "../runtime" }
substrate-client = { path = "../../substrate/client" }
substrate-codec = { path = "../../substrate/codec" }
substrate-keyring = { path = "../../substrate/keyring" }
substrate-extrinsic-pool = { path = "../../substrate/extrinsic-pool" }
substrate-primitives = { path = "../../substrate/primitives" }
substrate-runtime-primitives = { path = "../../substrate/runtime/primitives" }
ed25519 = { path = "../../substrate/ed25519" }
@@ -0,0 +1,73 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo 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 Demo 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 Demo. If not, see <http://www.gnu.org/licenses/>.
use extrinsic_pool;
use demo_api;
use primitives::Hash;
use runtime::{Address, UncheckedExtrinsic};
error_chain! {
links {
Pool(extrinsic_pool::Error, extrinsic_pool::ErrorKind);
Api(demo_api::Error, demo_api::ErrorKind);
}
errors {
/// Unexpected extrinsic format submitted
InvalidExtrinsicFormat {
description("Invalid extrinsic format."),
display("Invalid extrinsic format."),
}
/// Attempted to queue an inherent transaction.
IsInherent(xt: UncheckedExtrinsic) {
description("Inherent transactions cannot be queued."),
display("Inherent transactions cannot be queued."),
}
/// Attempted to queue a transaction with bad signature.
BadSignature(e: &'static str) {
description("Transaction had bad signature."),
display("Transaction had bad signature: {}", e),
}
/// Attempted to queue a transaction that is already in the pool.
AlreadyImported(hash: Hash) {
description("Transaction is already in the pool."),
display("Transaction {:?} is already in the pool.", hash),
}
/// Import error.
Import(err: Box<::std::error::Error + Send>) {
description("Error importing transaction"),
display("Error importing transaction: {}", err.description()),
}
/// Runtime failure.
UnrecognisedAddress(who: Address) {
description("Unrecognised address in extrinsic"),
display("Unrecognised address in extrinsic: {}", who),
}
/// Extrinsic too large
TooLarge(got: usize, max: usize) {
description("Extrinsic too large"),
display("Extrinsic is too large ({} > {})", got, max),
}
}
}
impl extrinsic_pool::IntoPoolError for Error {
fn into_pool_error(self) -> ::std::result::Result<extrinsic_pool::Error, Self> {
match self {
Error(ErrorKind::Pool(e), c) => Ok(extrinsic_pool::Error(e, c)),
e => Err(e),
}
}
}
+237
View File
@@ -0,0 +1,237 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate Demo.
// Substrate Demo 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 Demo 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 Demo. If not, see <http://www.gnu.org/licenses/>.
extern crate ed25519;
extern crate substrate_client as client;
extern crate substrate_codec as codec;
extern crate substrate_extrinsic_pool as extrinsic_pool;
extern crate substrate_primitives;
extern crate substrate_runtime_primitives;
extern crate demo_runtime as runtime;
extern crate demo_primitives as primitives;
extern crate demo_api;
extern crate parking_lot;
#[cfg(test)]
extern crate substrate_keyring;
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate log;
mod error;
use std::{
cmp::Ordering,
collections::HashMap,
sync::Arc,
};
use codec::{Decode, Encode};
use extrinsic_pool::{Readiness, scoring::{Change, Choice}, VerifiedFor, ExtrinsicFor};
use demo_api::Api;
use primitives::{AccountId, BlockId, Block, Hash, Index};
use runtime::{Address, UncheckedExtrinsic, RawAddress};
use substrate_runtime_primitives::traits::{Bounded, Checkable, Hash as HashT, BlakeTwo256};
pub use extrinsic_pool::{Options, Status, LightStatus, VerifiedTransaction as VerifiedTransactionOps};
pub use error::{Error, ErrorKind, Result};
/// Maximal size of a single encoded extrinsic.
const MAX_TRANSACTION_SIZE: usize = 4 * 1024 * 1024;
/// Type alias for convenience.
pub type CheckedExtrinsic = <UncheckedExtrinsic as Checkable<fn(Address) -> std::result::Result<AccountId, &'static str>>>::Checked;
/// Type alias for the transaction pool.
pub type TransactionPool<A> = extrinsic_pool::Pool<ChainApi<A>>;
/// A verified transaction which should be includable and non-inherent.
#[derive(Clone, Debug)]
pub struct VerifiedTransaction {
/// Transaction hash.
pub hash: Hash,
/// Transaction sender.
pub sender: AccountId,
/// Transaction index.
pub index: Index,
encoded_size: usize,
}
impl VerifiedTransaction {
/// Get the 256-bit hash of this transaction.
pub fn hash(&self) -> &Hash {
&self.hash
}
/// Get the account ID of the sender of this transaction.
pub fn index(&self) -> Index {
self.index
}
/// Get encoded size of the transaction.
pub fn encoded_size(&self) -> usize {
self.encoded_size
}
}
impl extrinsic_pool::VerifiedTransaction for VerifiedTransaction {
type Hash = Hash;
type Sender = AccountId;
fn hash(&self) -> &Self::Hash {
&self.hash
}
fn sender(&self) -> &Self::Sender {
&self.sender
}
fn mem_usage(&self) -> usize {
self.encoded_size // TODO
}
}
/// The transaction pool logic.
pub struct ChainApi<A> {
api: Arc<A>,
}
impl<A> ChainApi<A> where
A: Api,
{
/// Create a new instance.
pub fn new(api: Arc<A>) -> Self {
ChainApi {
api,
}
}
}
impl<A> extrinsic_pool::ChainApi for ChainApi<A> where
A: Api + Send + Sync,
{
type Block = Block;
type Hash = Hash;
type Sender = AccountId;
type VEx = VerifiedTransaction;
type Ready = HashMap<AccountId, u64>;
type Error = Error;
type Score = u64;
type Event = ();
fn verify_transaction(&self, _at: &BlockId, xt: &ExtrinsicFor<Self>) -> Result<Self::VEx> {
let encoded = xt.encode();
let uxt = UncheckedExtrinsic::decode(&mut encoded.as_slice()).ok_or_else(|| ErrorKind::InvalidExtrinsicFormat)?;
if !uxt.is_signed() {
bail!(ErrorKind::IsInherent(uxt))
}
let (encoded_size, hash) = (encoded.len(), BlakeTwo256::hash(&encoded));
if encoded_size > MAX_TRANSACTION_SIZE {
bail!(ErrorKind::TooLarge(encoded_size, MAX_TRANSACTION_SIZE));
}
debug!(target: "transaction-pool", "Transaction submitted: {}", ::substrate_primitives::hexdisplay::HexDisplay::from(&encoded));
let checked = uxt.clone().check_with(|a| {
match a {
RawAddress::Id(id) => Ok(id),
RawAddress::Index(_) => Err("Index based addresses are not supported".into()),// TODO: Make index addressing optional in substrate
}
})?;
let sender = checked.signed.expect("Only signed extrinsics are allowed at this point");
if encoded_size < 1024 {
debug!(target: "transaction-pool", "Transaction verified: {} => {:?}", hash, uxt);
} else {
debug!(target: "transaction-pool", "Transaction verified: {} ({} bytes is too large to display)", hash, encoded_size);
}
Ok(VerifiedTransaction {
index: checked.index,
sender,
hash,
encoded_size,
})
}
fn ready(&self) -> Self::Ready {
HashMap::default()
}
fn is_ready(&self, at: &BlockId, known_nonces: &mut Self::Ready, xt: &VerifiedFor<Self>) -> Readiness {
let sender = xt.verified.sender().clone();
trace!(target: "transaction-pool", "Checking readiness of {} (from {})", xt.verified.hash, sender);
// TODO: find a way to handle index error properly -- will need changes to
// transaction-pool trait.
let api = &self.api;
let next_index = known_nonces.entry(sender)
.or_insert_with(|| api.index(at, sender).ok().unwrap_or_else(Bounded::max_value));
trace!(target: "transaction-pool", "Next index for sender is {}; xt index is {}", next_index, xt.verified.index);
let result = match xt.verified.index.cmp(&next_index) {
// TODO: this won't work perfectly since accounts can now be killed, returning the nonce
// to zero.
// We should detect if the index was reset and mark all transactions as `Stale` for cull to work correctly.
// Otherwise those transactions will keep occupying the queue.
// Perhaps we could mark as stale if `index - state_index` > X?
Ordering::Greater => Readiness::Future,
Ordering::Equal => Readiness::Ready,
// TODO [ToDr] Should mark transactions referencing too old blockhash as `Stale` as well.
Ordering::Less => Readiness::Stale,
};
// remember to increment `next_index`
*next_index = next_index.saturating_add(1);
result
}
fn compare(old: &VerifiedFor<Self>, other: &VerifiedFor<Self>) -> Ordering {
old.verified.index().cmp(&other.verified.index())
}
fn choose(old: &VerifiedFor<Self>, new: &VerifiedFor<Self>) -> Choice {
if old.verified.index() == new.verified.index() {
return Choice::ReplaceOld;
}
Choice::InsertNew
}
fn update_scores(
xts: &[extrinsic_pool::Transaction<VerifiedFor<Self>>],
scores: &mut [Self::Score],
_change: Change<()>
) {
for i in 0..xts.len() {
// all the same score since there are no fees.
// TODO: prioritize things like misbehavior or fishermen reports
scores[i] = 1;
}
}
fn should_replace(_old: &VerifiedFor<Self>, _new: &VerifiedFor<Self>) -> Choice {
// Don't allow new transactions if we are reaching the limit.
Choice::RejectNew
}
}
+6 -4
View File
@@ -235,10 +235,12 @@ impl<B: Block, P: Proposer<B>> BftInstance<B, P>
{
fn round_timeout_duration(&self, round: usize) -> Duration {
const ROUND_INCREMENT_STEP: usize = 10000;
// 2^(min(6, x/8)) * 10
// Grows exponentially starting from 10 seconds, capped at 640 seconds.
const ROUND_INCREMENT_STEP: usize = 8;
let round = round / ROUND_INCREMENT_STEP;
let round = ::std::cmp::min(63, round) as u32;
let round = ::std::cmp::min(6, round) as u32;
let timeout = 1u64.checked_shl(round)
.unwrap_or_else(u64::max_value)
@@ -470,7 +472,7 @@ impl<B, P, I> BftService<B, P, I>
hash: None,
start_round: 0,
})),
round_timeout_multiplier: 4,
round_timeout_multiplier: 10,
key: key, // TODO: key changing over time.
factory,
}
@@ -866,7 +868,7 @@ mod tests {
hash: None,
start_round: 0,
})),
round_timeout_multiplier: 4,
round_timeout_multiplier: 10,
key: Arc::new(Keyring::One.into()),
factory: DummyFactory
}
+46 -1
View File
@@ -25,7 +25,7 @@ use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Zero, One,
use runtime_primitives::BuildStorage;
use primitives::{KeccakHasher, RlpCodec};
use primitives::storage::{StorageKey, StorageData};
use codec::Decode;
use codec::{Encode, Decode};
use state_machine::{
Ext, OverlayedChanges, Backend as StateBackend, CodeExecutor,
ExecutionStrategy, ExecutionManager, prove_read
@@ -316,6 +316,51 @@ impl<B, E, Block> Client<B, E, Block> where
block_builder::BlockBuilder::at_block(parent, &self)
}
/// Call a runtime function at given block.
pub fn call_api<A, R>(&self, at: &BlockId<Block>, function: &'static str, args: &A) -> error::Result<R>
where
A: Encode,
R: Decode,
{
let parent = at;
let header = <<Block as BlockT>::Header as HeaderT>::new(
self.block_number_from_id(&parent)?
.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("{:?}", parent)))? + As::sa(1),
Default::default(),
Default::default(),
self.block_hash_from_id(&parent)?
.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("{:?}", parent)))?,
Default::default()
);
self.state_at(&parent).and_then(|state| {
let mut overlay = Default::default();
let execution_manager = || ExecutionManager::Both(|wasm_result, native_result| {
warn!("Consensus error between wasm and native runtime execution at block {:?}", at);
warn!(" Function {:?}", function);
warn!(" Native result {:?}", native_result);
warn!(" Wasm result {:?}", wasm_result);
wasm_result
});
self.executor().call_at_state(
&state,
&mut overlay,
"initialise_block",
&header.encode(),
execution_manager()
)?;
let (r, _) = args.using_encoded(|input|
self.executor().call_at_state(
&state,
&mut overlay,
function,
input,
execution_manager()
))?;
Ok(R::decode(&mut &r[..])
.ok_or_else(|| error::Error::from(error::ErrorKind::CallResultDecode(function)))?)
})
}
/// Check a header's justification.
pub fn check_justification(
&self,
@@ -47,6 +47,17 @@ pub struct RemoteCallExecutor<B, F, H, C> {
_codec: PhantomData<C>,
}
impl<B, F, H, C> Clone for RemoteCallExecutor<B, F, H, C> {
fn clone(&self) -> Self {
RemoteCallExecutor {
blockchain: self.blockchain.clone(),
fetcher: self.fetcher.clone(),
_hasher: Default::default(),
_codec: Default::default(),
}
}
}
impl<B, F, H, C> RemoteCallExecutor<B, F, H, C> {
/// Creates new instance of remote call executor.
pub fn new(blockchain: Arc<B>, fetcher: Arc<F>) -> Self {
+1 -1
View File
@@ -85,4 +85,4 @@ pub mod keccak {
keccak256(x).into()
}
}
}
}
+1 -1
View File
@@ -423,4 +423,4 @@ impl<C: Components> network::TransactionPool<ComponentExHash<C>, ComponentBlock<
fn on_broadcasted(&self, propagations: HashMap<ComponentExHash<C>, Vec<String>>) {
self.pool.on_broadcasted(propagations)
}
}
}