diff --git a/Cargo.lock b/Cargo.lock index d607ec0e868da..6754966e11dfc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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)" = "" +"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)" = "" "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" diff --git a/Cargo.toml b/Cargo.toml index fd43193e9c58e..a0550303dcccd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 = [ diff --git a/demo/Cargo.toml b/demo/Cargo.toml new file mode 100644 index 0000000000000..326844dc9002f --- /dev/null +++ b/demo/Cargo.toml @@ -0,0 +1,18 @@ +[[bin]] +name = "substrate" +path = "src/main.rs" + +[package] +name = "substrate" +version = "0.1.0" +authors = ["Parity Technologies "] +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" diff --git a/demo/api/Cargo.toml b/demo/api/Cargo.toml new file mode 100644 index 0000000000000..b03eb27a72541 --- /dev/null +++ b/demo/api/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "demo-api" +version = "0.1.0" +authors = ["Parity Technologies "] + +[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" } diff --git a/demo/api/src/lib.rs b/demo/api/src/lib.rs new file mode 100644 index 0000000000000..fbcba1cf9acee --- /dev/null +++ b/demo/api/src/lib.rs @@ -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 . + +//! 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; +} + +/// 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>; + + /// Get validators at a given block. + fn validators(&self, at: &BlockId) -> Result>; + + /// Get the value of the randomness beacon at a given block. + fn random_seed(&self, at: &BlockId) -> Result; + + /// Get the timestamp registered at a block. + fn timestamp(&self, at: &BlockId) -> Result; + + /// Get the nonce (né index) of an account at a block. + fn index(&self, at: &BlockId, account: AccountId) -> Result; + + /// Get the account id of an address at a block. + fn lookup(&self, at: &BlockId, address: Address) -> Result>; + + /// 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; + + /// Build a block on top of the given, with inherent extrinsics pre-pushed. + fn build_block(&self, at: &BlockId, inherent_data: InherentData) -> Result; + + /// 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>; +} + +impl BlockBuilder for ClientBlockBuilder +where + B: Backend, + E: CallExecutor+ 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 { + ClientBlockBuilder::bake(self).map_err(Into::into) + } +} + +impl Api for Client +where + B: Backend, + E: CallExecutor + Clone, +{ + type BlockBuilder = ClientBlockBuilder; + + fn session_keys(&self, at: &BlockId) -> Result> { + Ok(self.authorities_at(at)?) + } + + fn validators(&self, at: &BlockId) -> Result> { + self.call_api(at, "validators", &()) + } + + fn random_seed(&self, at: &BlockId) -> Result { + self.call_api(at, "random_seed", &()) + } + + fn timestamp(&self, at: &BlockId) -> Result { + self.call_api(at, "timestamp", &()) + } + + fn evaluate_block(&self, at: &BlockId, block: Block) -> Result { + 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 { + self.call_api(at, "account_nonce", &account) + } + + fn lookup(&self, at: &BlockId, address: Address) -> Result> { + self.call_api(at, "lookup_address", &address) + } + + fn build_block(&self, at: &BlockId, inherent_data: InherentData) -> Result { + 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> { + let runtime_version = self.runtime_version_at(at)?; + self.call_api(at, "inherent_extrinsics", &(inherent_data, runtime_version.spec_version)) + } +} + diff --git a/demo/build.rs b/demo/build.rs new file mode 100644 index 0000000000000..0f2f3b9bdcaf8 --- /dev/null +++ b/demo/build.rs @@ -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 . + +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"); +} diff --git a/demo/cli/Cargo.toml b/demo/cli/Cargo.toml index 01493751b55c6..5c5b0f7a36f41 100644 --- a/demo/cli/Cargo.toml +++ b/demo/cli/Cargo.toml @@ -5,25 +5,8 @@ authors = ["Parity Technologies "] 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" } diff --git a/demo/cli/src/error.rs b/demo/cli/src/error.rs index 8de348c4ef1b2..c8b4fdedde099 100644 --- a/demo/cli/src/error.rs +++ b/demo/cli/src/error.rs @@ -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 diff --git a/demo/cli/src/lib.rs b/demo/cli/src/lib.rs index f416a0122f702..09c09e1cc6ccb 100644 --- a/demo/cli/src/lib.rs +++ b/demo/cli/src/lib.rs @@ -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}; - -#[derive(Debug, Clone)] -struct VerifiedExtrinsic { - sender: AccountId, - hash: Hash, +pub use service::{Components as ServiceComponents, Service, CustomConfiguration}; +pub use cli::{VersionInfo, IntoExit}; + +/// 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 { + 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 { + 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) -> Result { - unimplemented!() - } - - fn ready(&self) -> Self::Ready { } - - fn is_ready(&self, _at: &BlockId, _ready: &mut Self::Ready, _xt: &VerifiedFor) -> Readiness { - unimplemented!() - } - - fn compare(_old: &VerifiedFor, _other: &VerifiedFor) -> ::std::cmp::Ordering { - unimplemented!() - } - - fn choose(_old: &VerifiedFor, _new: &VerifiedFor) -> scoring::Choice { - unimplemented!() - } - - fn update_scores( - _xts: &[extrinsic_pool::Transaction>], - _scores: &mut [Self::Score], - _change: scoring::Change<()> - ) { - unimplemented!() - } - - fn should_replace(_old: &VerifiedFor, _new: &VerifiedFor) -> scoring::Choice { - unimplemented!() - } +fn load_spec(id: &str) -> Result, 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 { - Ok("substrate-demo".into()) - } - fn system_version(&self) -> substrate_rpc::system::error::Result { - Ok(crate_version!().into()) - } - fn system_chain(&self) -> substrate_rpc::system::error::Result { - 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(args: I) -> error::Result<()> where +/// Parse command line arguments into service configuration. +pub fn run(args: I, exit: E, version: cli::VersionInfo) -> error::Result<()> where I: IntoIterator, T: Into + 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::, 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::(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::(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); - - if let Ok(lvl) = std::env::var("RUST_LOG") { - builder.parse(&lvl); - } - - builder.parse(pattern); +fn run_until_exit( + runtime: &mut Runtime, + service: service::Service, + e: E, +) -> error::Result<()> + where + C: service::Components, + E: IntoExit, +{ + let (exit_send, exit) = exit_future::signal(); + let executor = runtime.executor(); + cli::informant::start(&service, exit.clone(), executor.clone()); - builder.init().expect("Logger initialized only once."); + let _ = runtime.block_on(e.into_exit()); + exit_send.fire(); + Ok(()) } diff --git a/demo/consensus/Cargo.toml b/demo/consensus/Cargo.toml new file mode 100644 index 0000000000000..1dc8eb8367711 --- /dev/null +++ b/demo/consensus/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "demo-consensus" +version = "0.1.0" +authors = ["Parity Technologies "] + +[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" } diff --git a/demo/consensus/README.adoc b/demo/consensus/README.adoc new file mode 100644 index 0000000000000..a3ac5f631c38c --- /dev/null +++ b/demo/consensus/README.adoc @@ -0,0 +1,5 @@ + += Polkadot Consensus + +placeholder +//TODO Write content :) diff --git a/demo/consensus/src/error.rs b/demo/consensus/src/error.rs new file mode 100644 index 0000000000000..e8b60c847c34d --- /dev/null +++ b/demo/consensus/src/error.rs @@ -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 . + +//! 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() + } +} diff --git a/demo/consensus/src/evaluation.rs b/demo/consensus/src/evaluation.rs new file mode 100644 index 0000000000000..0d073aa52109c --- /dev/null +++ b/demo/consensus/src/evaluation.rs @@ -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 . + +//! 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 { + 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) +} diff --git a/demo/consensus/src/lib.rs b/demo/consensus/src/lib.rs new file mode 100644 index 0000000000000..7f55bc1e68fef --- /dev/null +++ b/demo/consensus/src/lib.rs @@ -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 . + +//! 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>; + +// 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,Error=Error>; + /// The output sink of BFT messages. Messages sent here should eventually pass to all + /// current authorities. + type Output: Sink,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 + where + P: Api + Send + Sync + 'static +{ + /// The client instance. + pub client: Arc

, + /// The transaction pool. + pub transaction_pool: Arc>, + /// The backing network handle. + pub network: N, + /// handle to remote task executor + pub handle: TaskExecutor, + /// Offline-tracker. + pub offline: SharedOfflineTracker, +} + +impl bft::Environment for ProposerFactory + where + N: Network, + P: Api + Send + Sync + 'static, +{ + type Proposer = Proposer

; + type Input = N::Input; + type Output = N::Output; + type Error = Error; + + fn init( + &self, + parent_header: &Header, + authorities: &[AuthorityId], + sign_with: Arc, + ) -> 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 { + client: Arc, + start: Instant, + local_key: Arc, + parent_hash: Hash, + parent_id: BlockId, + parent_number: BlockNumber, + random_seed: Hash, + transaction_pool: Arc>, + offline: SharedOfflineTracker, + validators: Vec, + minimum_timestamp: u64, +} + +impl Proposer { + 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 bft::Proposer for Proposer + where + C: Api + Send + Sync, +{ + type Create = Result; + type Error = Error; + type Evaluate = Box>; + + fn propose(&self) -> Result { + 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::>(), + ) + } + + 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::>() + .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)>) { + 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() +} diff --git a/demo/consensus/src/offline_tracker.rs b/demo/consensus/src/offline_tracker.rs new file mode 100644 index 0000000000000..243b801bcec0c --- /dev/null +++ b/demo/consensus/src/offline_tracker.rs @@ -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 . + +//! 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, +} + +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 { + 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]); + } +} diff --git a/demo/consensus/src/service.rs b/demo/consensus/src/service.rs new file mode 100644 index 0000000000000..e70bc78ab2154 --- /dev/null +++ b/demo/consensus/src/service.rs @@ -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 . + +//! 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( + header: Header, + bft_service: Arc>, +) where + F: bft::Environment + 'static, + C: bft::BlockImport + bft::Authorities + 'static, + F::Error: ::std::fmt::Debug, + >::Error: ::std::fmt::Display + Into, + >::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>, + exit_signal: Option<::exit_future::Signal>, +} + +impl Service { + /// Create and start a new instance. + pub fn new( + client: Arc, + api: Arc, + network: N, + transaction_pool: Arc>, + thread_pool: ThreadPoolHandle, + key: ed25519::Pair, + ) -> Service + where + A: Api + Send + Sync + 'static, + C: BlockchainEvents + ChainHead + BlockBody, + C: bft::BlockImport + bft::Authorities + 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"); + } + } +} diff --git a/demo/executor/Cargo.toml b/demo/executor/Cargo.toml index fd3de56684d14..5bf56e9a8f0c4 100644 --- a/demo/executor/Cargo.toml +++ b/demo/executor/Cargo.toml @@ -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" } diff --git a/demo/executor/src/lib.rs b/demo/executor/src/lib.rs index 8c7941f3fc77e..17ae8b47387d9 100644 --- a/demo/executor/src/lib.rs +++ b/demo/executor/src/lib.rs @@ -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::>(); - let extrinsics_root = ordered_trie_root::(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 { diff --git a/demo/network/Cargo.toml b/demo/network/Cargo.toml new file mode 100644 index 0000000000000..3329f779b4719 --- /dev/null +++ b/demo/network/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "demo-network" +version = "0.1.0" +authors = ["Parity Technologies "] +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" diff --git a/demo/network/src/consensus.rs b/demo/network/src/consensus.rs new file mode 100644 index 0000000000000..d7c5fd92c570a --- /dev/null +++ b/demo/network/src/consensus.rs @@ -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 . + +//! 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 { + network: Arc, + parent_hash: Hash, + _marker: ::std::marker::PhantomData, +} + +impl Sink for BftSink { + type SinkItem = bft::Communication; + // TODO: replace this with the ! type when that's stabilized + type SinkError = E; + + fn start_send(&mut self, message: bft::Communication) + -> ::futures::StartSend, 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, + local_id: &SessionKey, + authorities: &[SessionKey] + ) -> Result>, 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::(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::(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>, + bft_messages: mpsc::UnboundedSender>, + validators: Vec, + local_id: SessionKey, +} + +impl MessageProcessTask { + fn process_message(&self, msg: ConsensusMessage) -> Option> { + 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>, +} + +impl Stream for InputAdapter { + type Item = bft::Communication; + type Error = ::demo_consensus::Error; + + fn poll(&mut self) -> Poll, 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

{ + network: Arc, + api: Arc

, +} + +impl

ConsensusNetwork

{ + /// Create a new consensus networking object. + pub fn new(network: Arc, api: Arc

) -> Self { + ConsensusNetwork { network, api } + } +} + +impl

Clone for ConsensusNetwork

{ + 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 Network for ConsensusNetwork

{ + /// 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; diff --git a/demo/network/src/lib.rs b/demo/network/src/lib.rs new file mode 100644 index 0000000000000..f32c039669f3a --- /dev/null +++ b/demo/network/src/lib.rs @@ -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 . + +//! 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; + +/// Specialization of the network service for the demo protocol. +pub type NetworkService = ::substrate_network::Service; + + +/// Demo protocol attachment for substrate. +pub struct Protocol { + consensus_gossip: ConsensusGossip, + live_consensus: Option, +} + +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 for Protocol { + fn status(&self) -> Vec { + Vec::new() + } + + fn on_connect(&mut self, ctx: &mut Context, who: NodeIndex, status: FullStatus) { + self.consensus_gossip.new_peer(ctx, who, status.roles); + } + + fn on_disconnect(&mut self, ctx: &mut Context, who: NodeIndex) { + self.consensus_gossip.peer_disconnected(ctx, who); + } + + fn on_message(&mut self, ctx: &mut Context, who: NodeIndex, message: message::Message) { + 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) { + } + + fn on_block_imported(&mut self, _ctx: &mut Context, _hash: Hash, _header: &Header) { + } +} + diff --git a/demo/primitives/Cargo.toml b/demo/primitives/Cargo.toml index 1a359e06e7463..b517210d1951b 100644 --- a/demo/primitives/Cargo.toml +++ b/demo/primitives/Cargo.toml @@ -7,6 +7,7 @@ authors = ["Parity Technologies "] 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", diff --git a/demo/primitives/src/lib.rs b/demo/primitives/src/lib.rs index 858002bb98872..eacaf7185aa87 100644 --- a/demo/primitives/src/lib.rs +++ b/demo/primitives/src/lib.rs @@ -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; +/// Block type. +pub type Block = generic::Block; +/// Block ID. +pub type BlockId = generic::BlockId; + +/// 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); + +//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); +/// +/// 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, +} diff --git a/demo/runtime/src/checked_block.rs b/demo/runtime/src/checked_block.rs new file mode 100644 index 0000000000000..281a9e4136fca --- /dev/null +++ b/demo/runtime/src/checked_block.rs @@ -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 . + +//! 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 { + 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!()) + } +} diff --git a/demo/runtime/src/lib.rs b/demo/runtime/src/lib.rs index c07e5104764b6..8a93fef97e782 100644 --- a/demo/runtime/src/lib.rs +++ b/demo/runtime/src/lib.rs @@ -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; 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; 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; /// Block header type as expected by this runtime. @@ -254,18 +268,43 @@ pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; /// Executive: handles dispatch to the various modules. pub type Executive = executive::Executive; + (((((((), 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 { + 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 +} diff --git a/demo/runtime/wasm/Cargo.lock b/demo/runtime/wasm/Cargo.lock index 56c05ce783ee7..333c02b898cb9 100644 --- a/demo/runtime/wasm/Cargo.lock +++ b/demo/runtime/wasm/Cargo.lock @@ -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", diff --git a/demo/runtime/wasm/genesis.wasm b/demo/runtime/wasm/genesis.wasm deleted file mode 100644 index bc0cb89235c57..0000000000000 Binary files a/demo/runtime/wasm/genesis.wasm and /dev/null differ diff --git a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm index 15128a6727a2d..6c8c6bb1c82ee 100644 Binary files a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm and b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm differ diff --git a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm index 190dc49be1ab5..783b2e6f41721 100755 Binary files a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm and b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm differ diff --git a/demo/service/Cargo.toml b/demo/service/Cargo.toml new file mode 100644 index 0000000000000..8b38012870e17 --- /dev/null +++ b/demo/service/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "demo-service" +version = "0.1.0" +authors = ["Parity Technologies "] + +[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" } diff --git a/demo/service/src/chain_spec.rs b/demo/service/src/chain_spec.rs new file mode 100644 index 0000000000000..5cabbac58c593 --- /dev/null +++ b/demo/service/src/chain_spec.rs @@ -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 . + +//! 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, 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 { + 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) -> 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 { + 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 { + ChainSpec::from_genesis("Local Testnet", "local_testnet", local_testnet_genesis, vec![], None) +} diff --git a/demo/service/src/lib.rs b/demo/service/src/lib.rs new file mode 100644 index 0000000000000..14cd91d643acf --- /dev/null +++ b/demo/service/src/lib.rs @@ -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 . + +#![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; +/// Client type for specialised `Components`. +pub type ComponentClient = Client<::Backend, ::Executor, Block>; +pub type NetworkService = network::Service::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; + /// Client executor. + type Executor: 'static + client::CallExecutor + Send + Sync; +} + +impl Components for service::LightComponents { + type Api = service::LightClient; + type Executor = service::LightExecutor; + type Backend = service::LightBackend; +} + +impl Components for service::FullComponents { + type Api = service::FullClient; + type Executor = service::FullExecutor; + type Backend = service::FullBackend; +} + +/// All configuration for the node. +pub type Configuration = FactoryFullConfiguration; + +/// 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>; + type LightExtrinsicPoolApi = transaction_pool::ChainApi>; + 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>) + -> Result>, Error> + { + Ok(TransactionPool::new(config, transaction_pool::ChainApi::new(client))) + } + + fn build_light_extrinsic_pool(config: ExtrinsicPoolOptions, client: Arc>) + -> Result>, Error> + { + Ok(TransactionPool::new(config, transaction_pool::ChainApi::new(client))) + } + + fn build_network_protocol(_config: &Configuration) + -> Result + { + Ok(DemoProtocol::new()) + } +} + +/// Demo service. +pub struct Service { + inner: service::Service, + client: Arc>, + network: Arc, + api: Arc<::Api>, + _consensus: Option, +} + +impl Service { + pub fn client(&self) -> Arc> { + self.client.clone() + } + + pub fn network(&self) -> Arc { + self.network.clone() + } + + pub fn api(&self) -> Arc<::Api> { + self.api.clone() + } +} + +/// Creates light client and register protocol with the network service +pub fn new_light(config: Configuration, executor: TaskExecutor) + -> Result>, Error> +{ + let service = service::Service::>::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>, Error> +{ + let is_validator = (config.roles & Roles::AUTHORITY) == Roles::AUTHORITY; + let service = service::Service::>::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>>, Error> +{ + service::new_client::(config) +} + +impl ::std::ops::Deref for Service { + type Target = service::Service; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} diff --git a/demo/src/main.rs b/demo/src/main.rs index 82e48dfc70cf8..7b219728c38c7 100644 --- a/demo/src/main.rs +++ b/demo/src/main.rs @@ -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, 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 ", + description: "Generic substrate node", + }; + cli::run(::std::env::args(), Exit, version) } diff --git a/demo/transaction-pool/Cargo.toml b/demo/transaction-pool/Cargo.toml new file mode 100644 index 0000000000000..94eaae111ae7d --- /dev/null +++ b/demo/transaction-pool/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "demo-transaction-pool" +version = "0.1.0" +authors = ["Parity Technologies "] + +[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" } diff --git a/demo/transaction-pool/src/error.rs b/demo/transaction-pool/src/error.rs new file mode 100644 index 0000000000000..2303372084e13 --- /dev/null +++ b/demo/transaction-pool/src/error.rs @@ -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 . + +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 { + match self { + Error(ErrorKind::Pool(e), c) => Ok(extrinsic_pool::Error(e, c)), + e => Err(e), + } + } +} diff --git a/demo/transaction-pool/src/lib.rs b/demo/transaction-pool/src/lib.rs new file mode 100644 index 0000000000000..637356a098fb5 --- /dev/null +++ b/demo/transaction-pool/src/lib.rs @@ -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 . + +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 = std::result::Result>>::Checked; + +/// Type alias for the transaction pool. +pub type TransactionPool = extrinsic_pool::Pool>; + +/// 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 { + api: Arc, +} + +impl ChainApi where + A: Api, +{ + /// Create a new instance. + pub fn new(api: Arc) -> Self { + ChainApi { + api, + } + } +} + + +impl extrinsic_pool::ChainApi for ChainApi where + A: Api + Send + Sync, +{ + type Block = Block; + type Hash = Hash; + type Sender = AccountId; + type VEx = VerifiedTransaction; + type Ready = HashMap; + type Error = Error; + type Score = u64; + type Event = (); + + fn verify_transaction(&self, _at: &BlockId, xt: &ExtrinsicFor) -> Result { + 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) -> 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, other: &VerifiedFor) -> Ordering { + old.verified.index().cmp(&other.verified.index()) + } + + fn choose(old: &VerifiedFor, new: &VerifiedFor) -> Choice { + if old.verified.index() == new.verified.index() { + return Choice::ReplaceOld; + } + Choice::InsertNew + } + + fn update_scores( + xts: &[extrinsic_pool::Transaction>], + 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, _new: &VerifiedFor) -> Choice { + // Don't allow new transactions if we are reaching the limit. + Choice::RejectNew + } +} + diff --git a/substrate/bft/src/lib.rs b/substrate/bft/src/lib.rs index 9e465f852cec7..be6da011be295 100644 --- a/substrate/bft/src/lib.rs +++ b/substrate/bft/src/lib.rs @@ -235,10 +235,12 @@ impl> BftInstance { 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 BftService 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 } diff --git a/substrate/client/src/client.rs b/substrate/client/src/client.rs index d74c8ef304da5..c736ed2af7cf1 100644 --- a/substrate/client/src/client.rs +++ b/substrate/client/src/client.rs @@ -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 Client where block_builder::BlockBuilder::at_block(parent, &self) } + /// Call a runtime function at given block. + pub fn call_api(&self, at: &BlockId, function: &'static str, args: &A) -> error::Result + where + A: Encode, + R: Decode, + { + let parent = at; + let header = <::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, diff --git a/substrate/client/src/light/call_executor.rs b/substrate/client/src/light/call_executor.rs index ae36a5e6d683d..354a2dbe33e71 100644 --- a/substrate/client/src/light/call_executor.rs +++ b/substrate/client/src/light/call_executor.rs @@ -47,6 +47,17 @@ pub struct RemoteCallExecutor { _codec: PhantomData, } +impl Clone for RemoteCallExecutor { + fn clone(&self) -> Self { + RemoteCallExecutor { + blockchain: self.blockchain.clone(), + fetcher: self.fetcher.clone(), + _hasher: Default::default(), + _codec: Default::default(), + } + } +} + impl RemoteCallExecutor { /// Creates new instance of remote call executor. pub fn new(blockchain: Arc, fetcher: Arc) -> Self { diff --git a/substrate/primitives/src/hasher.rs b/substrate/primitives/src/hasher.rs index 16b14ef25fc39..7cc00a917e743 100644 --- a/substrate/primitives/src/hasher.rs +++ b/substrate/primitives/src/hasher.rs @@ -85,4 +85,4 @@ pub mod keccak { keccak256(x).into() } } -} \ No newline at end of file +} diff --git a/substrate/service/src/lib.rs b/substrate/service/src/lib.rs index e8319d8fd88ce..e1cd2acc48d80 100644 --- a/substrate/service/src/lib.rs +++ b/substrate/service/src/lib.rs @@ -423,4 +423,4 @@ impl network::TransactionPool, ComponentBlock< fn on_broadcasted(&self, propagations: HashMap, Vec>) { self.pool.on_broadcasted(propagations) } -} \ No newline at end of file +}