mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 13:01:07 +00:00
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:
committed by
Gav Wood
parent
bcc26dd30a
commit
fea750511e
Generated
+140
-18
@@ -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"
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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"
|
||||
@@ -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" }
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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,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
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
= Polkadot Consensus
|
||||
|
||||
placeholder
|
||||
//TODO Write content :)
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
@@ -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;
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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!())
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Generated
+1
@@ -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.
BIN
Binary file not shown.
Binary file not shown.
@@ -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" }
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -85,4 +85,4 @@ pub mod keccak {
|
||||
keccak256(x).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user