diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index 865627b668..e2f79397e2 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -160,6 +160,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" @@ -1786,6 +1791,7 @@ dependencies = [ "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)", "polkadot-cli 0.2.0", + "vergen 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1811,29 +1817,19 @@ dependencies = [ name = "polkadot-cli" version = "0.2.0" dependencies = [ - "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "app_dirs 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "backtrace 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "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)", - "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.21 (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)", - "names 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "polkadot-primitives 0.1.0", "polkadot-runtime 0.1.0", "polkadot-service 0.2.0", "polkadot-transaction-pool 0.1.0", - "regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", "slog 2.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-cli 0.2.0", "substrate-client 0.1.0", "substrate-codec 0.1.0", "substrate-extrinsic-pool 0.1.0", @@ -1845,9 +1841,7 @@ dependencies = [ "substrate-service 0.2.0", "substrate-state-machine 0.1.0", "substrate-telemetry 0.2.0", - "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "triehash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2636,6 +2630,35 @@ dependencies = [ "tokio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "substrate-cli" +version = "0.2.0" +dependencies = [ + "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "app_dirs 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "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)", + "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.21 (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)", + "names 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.0.2 (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-extrinsic-pool 0.1.0", + "substrate-network 0.1.0", + "substrate-runtime-primitives 0.1.0", + "substrate-service 0.2.0", + "substrate-telemetry 0.2.0", + "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "substrate-client" version = "0.1.0" @@ -3145,14 +3168,18 @@ dependencies = [ "slog 2.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-client 0.1.0", "substrate-client-db 0.1.0", + "substrate-codec 0.1.0", "substrate-executor 0.1.0", "substrate-extrinsic-pool 0.1.0", "substrate-keystore 0.1.0", "substrate-network 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-runtime-primitives 0.1.0", "substrate-telemetry 0.2.0", + "target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -3257,6 +3284,11 @@ name = "take_mut" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "target_info" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "tempdir" version = "0.3.7" @@ -3710,6 +3742,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" @@ -3881,6 +3922,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" @@ -4114,6 +4156,7 @@ dependencies = [ "checksum syn 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6dfd71b2be5a58ee30a6f8ea355ba8290d397131c00dfa55c3d34e6e13db5101" "checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" "checksum take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" +"checksum target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c63f48baada5c52e65a29eef93ab4f8982681b67f9e8d29c7b05abcfec2b9ffe" "checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" "checksum tempfile 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "11ce2fe9db64b842314052e2421ac61a73ce41b898dc8e3750398b219c5fc1e0" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" @@ -4163,6 +4206,7 @@ dependencies = [ "checksum varint 0.1.0 (git+https://github.com/tomaka/libp2p-rs?rev=c537102bb39f3ec6590befbfe9094cf411387f77)" = "" "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/substrate/Cargo.toml b/substrate/Cargo.toml index ffd639ca88..39a2e757b7 100644 --- a/substrate/Cargo.toml +++ b/substrate/Cargo.toml @@ -6,6 +6,7 @@ path = "polkadot/src/main.rs" name = "polkadot" version = "0.2.0" authors = ["Parity Technologies "] +build = "build.rs" [dependencies] error-chain = "0.12" @@ -13,6 +14,9 @@ polkadot-cli = { path = "polkadot/cli" } futures = "0.1" ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" } +[build-dependencies] +vergen = "0.1" + [workspace] members = [ "polkadot/api", @@ -29,6 +33,7 @@ members = [ "polkadot/service", "substrate/bft", + "substrate/cli", "substrate/client", "substrate/client/db", "substrate/codec", diff --git a/substrate/build.rs b/substrate/build.rs new file mode 100644 index 0000000000..493d25315c --- /dev/null +++ b/substrate/build.rs @@ -0,0 +1,25 @@ +// Copyright 2015-2018 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +extern crate vergen; + +use vergen::{vergen, OutputFns}; + +const ERROR_MSG: &'static str = "Failed to generate metadata files"; + +fn main() { + vergen(OutputFns::all()).expect(ERROR_MSG); +} diff --git a/substrate/polkadot/cli/Cargo.toml b/substrate/polkadot/cli/Cargo.toml index 276855c887..cc2e87190e 100644 --- a/substrate/polkadot/cli/Cargo.toml +++ b/substrate/polkadot/cli/Cargo.toml @@ -3,30 +3,18 @@ name = "polkadot-cli" version = "0.2.0" authors = ["Parity Technologies "] description = "Polkadot node implementation in Rust." -build = "build.rs" [dependencies] clap = { version = "~2.32", features = ["yaml"] } -backtrace = "0.3" -env_logger = "0.4" error-chain = "0.12" log = "0.3" -atty = "0.2" -regex = "1" -time = "0.1" slog = "^2" -ansi_term = "0.10" lazy_static = "1.0" -triehash = "0.1" -ed25519 = { path = "../../substrate/ed25519" } -app_dirs = "1.2" tokio = "0.1.7" futures = "0.1.17" -fdlimit = "0.1" parking_lot = "0.4" -serde_json = "1.0" -serde = "1.0" exit-future = "0.1" +substrate-cli = { path = "../../substrate/cli" } substrate-client = { path = "../../substrate/client" } substrate-codec = { path = "../../substrate/codec" } substrate-extrinsic-pool = { path = "../../substrate/extrinsic-pool" } @@ -42,7 +30,4 @@ polkadot-primitives = { path = "../primitives" } polkadot-runtime = { path = "../runtime" } polkadot-service = { path = "../service" } polkadot-transaction-pool = { path = "../transaction-pool" } -names = "0.11.0" -[build-dependencies] -clap = "~2.32" diff --git a/substrate/polkadot/cli/doc/shell-completion.adoc b/substrate/polkadot/cli/doc/shell-completion.adoc deleted file mode 100644 index 2486bed09e..0000000000 --- a/substrate/polkadot/cli/doc/shell-completion.adoc +++ /dev/null @@ -1,41 +0,0 @@ - -== Shell completion - -The Polkadot cli command supports shell auto-completion. For this to work, you will need to run the completion script matching you build and system. - -Assuming you built a release version using `cargo build --release` and use `bash` run the following: - -[source, shell] -source target/release/completion-scripts/polkadot.bash - -You can find completion scripts for: -- bash -- fish -- zsh -- elvish -- powershell - -To make this change persistent, you can proceed as follow: - -.First install - -[source, shell] ----- -COMPL_DIR=$HOME/.completion -mkdir -p $COMPL_DIR -cp -f target/release/completion-scripts/polkadot.bash $COMPL_DIR/ -echo "source $COMPL_DIR/polkadot.bash" >> $HOME/.bash_profile -source $HOME/.bash_profile ----- - -.Update - -When you build a new version of Polkadot, the following will ensure you auto-completion script matches the current binary: - -[source, shell] ----- -COMPL_DIR=$HOME/.completion -mkdir -p $COMPL_DIR -cp -f target/release/completion-scripts/polkadot.bash $COMPL_DIR/ -source $HOME/.bash_profile ----- diff --git a/substrate/polkadot/cli/src/chain_spec.rs b/substrate/polkadot/cli/src/chain_spec.rs index 0224e24bdc..7298a4cdff 100644 --- a/substrate/polkadot/cli/src/chain_spec.rs +++ b/substrate/polkadot/cli/src/chain_spec.rs @@ -17,7 +17,6 @@ //! Predefined chains. use service; -use std::path::PathBuf; /// The chain specification (this should eventually be replaced by a more general JSON-based chain /// specification). @@ -31,8 +30,6 @@ pub enum ChainSpec { KrummeLanke, /// Whatever the current runtime is with the "global testnet" defaults. StagingTestnet, - /// Custom Genesis file. - Custom(String), } /// Get a chain config from a spec setting. @@ -43,32 +40,18 @@ impl ChainSpec { ChainSpec::Development => service::chain_spec::development_config(), ChainSpec::LocalTestnet => service::chain_spec::local_testnet_config(), ChainSpec::StagingTestnet => service::chain_spec::staging_testnet_config(), - ChainSpec::Custom(f) => service::ChainSpec::from_json_file(PathBuf::from(f))?, }) } -} -impl<'a> From<&'a str> for ChainSpec { - fn from(s: &'a str) -> Self { + pub(crate) fn from(s: &str) -> Option { match s { - "dev" => ChainSpec::Development, - "local" => ChainSpec::LocalTestnet, - "poc-1" => ChainSpec::KrummeLanke, - "krummelanke" => ChainSpec::KrummeLanke, - "staging" => ChainSpec::StagingTestnet, - s => ChainSpec::Custom(s.into()), + "dev" => Some(ChainSpec::Development), + "local" => Some(ChainSpec::LocalTestnet), + "poc-1" => Some(ChainSpec::KrummeLanke), + "" | "krummelanke" => Some(ChainSpec::KrummeLanke), + "staging" => Some(ChainSpec::StagingTestnet), + _ => None, } } } -impl From for String { - fn from(s: ChainSpec) -> String { - match s { - ChainSpec::Development => "dev".into(), - ChainSpec::LocalTestnet => "local".into(), - ChainSpec::KrummeLanke => "krummelanke".into(), - ChainSpec::StagingTestnet => "staging".into(), - ChainSpec::Custom(f) => format!("custom ({})", f), - } - } -} diff --git a/substrate/polkadot/cli/src/lib.rs b/substrate/polkadot/cli/src/lib.rs index de8872472f..ba29a39ecc 100644 --- a/substrate/polkadot/cli/src/lib.rs +++ b/substrate/polkadot/cli/src/lib.rs @@ -17,119 +17,34 @@ //! Polkadot CLI library. #![warn(missing_docs)] +#![warn(unused_extern_crates)] -extern crate app_dirs; -extern crate env_logger; -extern crate atty; -extern crate ansi_term; -extern crate regex; -extern crate time; -extern crate fdlimit; extern crate futures; extern crate tokio; -extern crate ed25519; -extern crate triehash; -extern crate parking_lot; -extern crate serde; -extern crate serde_json; -extern crate names; -extern crate backtrace; -extern crate substrate_client as client; -extern crate substrate_network as network; -extern crate substrate_codec as codec; -extern crate substrate_primitives; -extern crate substrate_rpc; -extern crate substrate_rpc_servers as rpc; -extern crate substrate_runtime_primitives as runtime_primitives; -extern crate substrate_state_machine as state_machine; -extern crate substrate_extrinsic_pool; -extern crate substrate_service; -extern crate polkadot_primitives; -extern crate polkadot_runtime; +extern crate substrate_cli as cli; extern crate polkadot_service as service; -#[macro_use] -extern crate slog; // needed until we can reexport `slog_info` from `substrate_telemetry` -#[macro_use] -extern crate substrate_telemetry; -extern crate polkadot_transaction_pool as txpool; extern crate exit_future; -#[macro_use] -extern crate lazy_static; -#[macro_use] -extern crate clap; -#[macro_use] -extern crate error_chain; #[macro_use] extern crate log; -pub mod error; -mod informant; mod chain_spec; -mod panic_hook; -pub use chain_spec::ChainSpec; -pub use client::error::Error as ClientError; -pub use client::backend::Backend as ClientBackend; -pub use state_machine::Backend as StateMachineBackend; -pub use polkadot_primitives::Block as PolkadotBlock; -pub use service::{Components as ServiceComponents, Service, CustomConfiguration}; +pub use cli::error; -use std::io::{self, Write, Read, stdin, stdout}; -use std::fs::File; -use std::net::SocketAddr; -use std::path::{Path, PathBuf}; -use substrate_telemetry::{init_telemetry, TelemetryConfig}; -use polkadot_primitives::BlockId; -use codec::{Decode, Encode}; -use client::BlockOrigin; -use runtime_primitives::generic::SignedBlock; -use names::{Generator, Name}; -use regex::Regex; +use chain_spec::ChainSpec; use futures::Future; use tokio::runtime::Runtime; -use service::PruningMode; +pub use service::{Components as ServiceComponents, Service, CustomConfiguration}; +pub use cli::VersionInfo; -const DEFAULT_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; - -#[derive(Clone)] -struct SystemConfiguration { - chain_name: String, -} - -impl substrate_rpc::system::SystemApi for SystemConfiguration { - fn system_name(&self) -> substrate_rpc::system::error::Result { - Ok("parity-polkadot".into()) - } - - fn system_version(&self) -> substrate_rpc::system::error::Result { - Ok(crate_version!().into()) - } - - fn system_chain(&self) -> substrate_rpc::system::error::Result { - Ok(self.chain_name.clone()) - } -} - -fn load_spec(matches: &clap::ArgMatches) -> Result<(service::ChainSpec, bool), String> { - let chain_spec = matches.value_of("chain") - .map(ChainSpec::from) - .unwrap_or_else(|| if matches.is_present("dev") { ChainSpec::Development } else { ChainSpec::KrummeLanke }); - let is_global = match chain_spec { - ChainSpec::KrummeLanke => true, - _ => false, - }; - let spec = chain_spec.load()?; - info!("Chain specification: {}", spec.name()); - Ok((spec, is_global)) -} - -fn base_path(matches: &clap::ArgMatches) -> PathBuf { - matches.value_of("base-path") - .map(|x| Path::new(x).to_owned()) - .unwrap_or_else(default_base_path) +fn load_spec(id: &str) -> Result, String> { + Ok(match ChainSpec::from(id) { + Some(spec) => Some(spec.load()?), + None => None, + }) } /// Additional worker making use of the node, to run asynchronously before shutdown. @@ -147,39 +62,16 @@ pub trait Worker { /// Return configuration for the polkadot node. // TODO: make this the full configuration, so embedded nodes don't need // string CLI args - fn configuration(&self) -> CustomConfiguration { Default::default() } + fn configuration(&self) -> service::CustomConfiguration { Default::default() } /// Don't work, but schedule an exit. - fn exit_only(self) -> Self::Exit; + fn exit_only(&self) -> Self::Exit; /// Do work and schedule exit. - fn work(self, service: &Service) -> Self::Work; + fn work(self, service: &service::Service) -> Self::Work; } -/// Check whether a node name is considered as valid -fn is_node_name_valid(_name: &str) -> Result<(), &str> { - const MAX_NODE_NAME_LENGTH: usize = 32; - let name = _name.to_string(); - if name.chars().count() >= MAX_NODE_NAME_LENGTH { - return Err("Node name too long"); -} - - let invalid_chars = r"[\\.@]"; - let re = Regex::new(invalid_chars).unwrap(); - if re.is_match(&name) { - return Err("Node name should not contain invalid chars such as '.' and '@'"); - } - - let invalid_patterns = r"(https?:\\/+)?(www)+"; - let re = Regex::new(invalid_patterns).unwrap(); - if re.is_match(&name) { - return Err("Node name should not contain urls"); - } - - Ok(()) -} - -/// Parse command line arguments and start the node. +/// Parse command line arguments into service configuration. /// /// IANA unassigned port ranges that we could use: /// 6717-6766 Unassigned @@ -187,342 +79,37 @@ fn is_node_name_valid(_name: &str) -> Result<(), &str> { /// 9556-9591 Unassigned /// 9803-9874 Unassigned /// 9926-9949 Unassigned -pub fn run(args: I, worker: W) -> error::Result<()> where +pub fn run(args: I, worker: W, version: cli::VersionInfo) -> error::Result<()> where I: IntoIterator, T: Into + Clone, W: Worker, { - panic_hook::set(); - let yaml = load_yaml!("./cli.yml"); - let matches = match clap::App::from_yaml(yaml) - .version(&(crate_version!().to_owned() + "\n")[..]) - .get_matches_from_safe(args) { - Ok(m) => m, - Err(e) => e.exit(), - }; - - // TODO [ToDr] Split parameters parsing from actual execution. - let log_pattern = matches.value_of("log").unwrap_or(""); - init_logger(log_pattern); - fdlimit::raise_fd_limit(); - - info!("Parity ·:· Polkadot"); - info!(" version {}", crate_version!()); - info!(" by Parity Technologies, 2017, 2018"); - - if let Some(matches) = matches.subcommand_matches("build-spec") { - return build_spec(matches); - } - - if let Some(matches) = matches.subcommand_matches("export-blocks") { - return export_blocks(matches, worker.exit_only()); - } - - if let Some(matches) = matches.subcommand_matches("import-blocks") { - return import_blocks(matches, worker.exit_only()); - } - - if let Some(matches) = matches.subcommand_matches("revert") { - return revert_chain(matches); - } - - let (spec, is_global) = load_spec(&matches)?; - let mut config = service::Configuration::default_with_spec(spec); - - config.name = match matches.value_of("name") { - None => Generator::with_naming(Name::Numbered).next().unwrap(), - Some(name) => name.into(), - }; - match is_node_name_valid(&config.name) { - Ok(_) => info!("Node name: {}", config.name), - Err(msg) => return Err(error::ErrorKind::Input( - format!("Invalid node name '{}'. Reason: {}. If unsure, use none.", config.name, msg)).into()) - } - - let base_path = base_path(&matches); - - config.keystore_path = matches.value_of("keystore") - .map(|x| Path::new(x).to_owned()) - .unwrap_or_else(|| keystore_path(&base_path, config.chain_spec.id())) - .to_string_lossy() - .into(); - - config.database_path = db_path(&base_path, config.chain_spec.id()).to_string_lossy().into(); - - config.pruning = match matches.value_of("pruning") { - Some("archive") => PruningMode::ArchiveAll, - None => PruningMode::default(), - Some(s) => PruningMode::keep_blocks(s.parse() - .map_err(|_| error::ErrorKind::Input("Invalid pruning mode specified".to_owned()))?), - }; - - let role = - if matches.is_present("light") { - info!("Starting (light)"); - config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible; - service::Roles::LIGHT - } else if matches.is_present("validator") || matches.is_present("dev") { - info!("Starting validator"); - config.execution_strategy = service::ExecutionStrategy::Both; - service::Roles::AUTHORITY - } else { - info!("Starting (heavy)"); - config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible; - service::Roles::FULL - }; - - if let Some(v) = matches.value_of("min-heap-pages") { - config.min_heap_pages = v.parse().map_err(|_| "Invalid --min-heap-pages argument")?; - } - if let Some(v) = matches.value_of("max-heap-pages") { - config.max_heap_pages = v.parse().map_err(|_| "Invalid --max-heap-pages argument")?; - } - - if let Some(s) = matches.value_of("execution") { - config.execution_strategy = match s { - "both" => service::ExecutionStrategy::Both, - "native" => service::ExecutionStrategy::NativeWhenPossible, - "wasm" => service::ExecutionStrategy::AlwaysWasm, - _ => return Err(error::ErrorKind::Input("Invalid execution mode specified".to_owned()).into()), - }; - } - - config.roles = role; - { - config.network.boot_nodes.extend(matches - .values_of("bootnodes") - .map_or(Default::default(), |v| v.map(|n| n.to_owned()).collect::>())); - config.network.config_path = Some(network_path(&base_path, config.chain_spec.id()).to_string_lossy().into()); - config.network.net_config_path = config.network.config_path.clone(); - - let port = match matches.value_of("port") { - Some(port) => port.parse().map_err(|_| "Invalid p2p port value specified.")?, - None => 30333, - }; - - config.network.listen_address = Some(SocketAddr::new("0.0.0.0".parse().unwrap(), port)); - config.network.public_address = None; - config.network.client_version = format!("parity-polkadot/{}", crate_version!()); - config.network.use_secret = match matches.value_of("node-key").map(|s| s.parse()) { - Some(Ok(secret)) => Some(secret), - Some(Err(err)) => return Err(format!("Error parsing node key: {}", err).into()), - None => None, - }; - } - - config.custom = worker.configuration(); - - config.keys = matches.values_of("key").unwrap_or_default().map(str::to_owned).collect(); - if matches.is_present("dev") { - config.keys.push("Alice".into()); - } - - let sys_conf = SystemConfiguration { - chain_name: config.chain_spec.name().to_owned(), - }; - - let mut runtime = Runtime::new()?; - let executor = runtime.executor(); - - let telemetry_enabled = - matches.is_present("telemetry") - || matches.value_of("telemetry-url").is_some() - || (is_global && !matches.is_present("no-telemetry")); - let _guard = if telemetry_enabled { - let name = config.name.clone(); - let chain_name = config.chain_spec.name().to_owned(); - Some(init_telemetry(TelemetryConfig { - url: matches.value_of("telemetry-url").unwrap_or(DEFAULT_TELEMETRY_URL).into(), - on_connect: Box::new(move || { - telemetry!("system.connected"; - "name" => name.clone(), - "implementation" => "parity-polkadot", - "version" => crate_version!(), - "config" => "", - "chain" => chain_name.clone(), - ); - }), - })) - } else { - None - }; - - match role == service::Roles::LIGHT { - true => run_until_exit(&mut runtime, service::new_light(config, executor)?, &matches, sys_conf, worker)?, - false => run_until_exit(&mut runtime, service::new_full(config, executor)?, &matches, sys_conf, worker)?, - } - - // TODO: hard exit if this stalls? - runtime.shutdown_on_idle().wait().expect("failed to shut down event loop"); - Ok(()) -} - -fn build_spec(matches: &clap::ArgMatches) -> error::Result<()> { - let (spec, _) = load_spec(&matches)?; - info!("Building chain spec"); - let json = spec.to_json(matches.is_present("raw"))?; - print!("{}", json); - Ok(()) -} - -fn export_blocks(matches: &clap::ArgMatches, exit: E) -> error::Result<()> - where E: Future + Send + 'static -{ - let base_path = base_path(matches); - let (spec, _) = load_spec(&matches)?; - let mut config = service::Configuration::default_with_spec(spec); - config.database_path = db_path(&base_path, config.chain_spec.id()).to_string_lossy().into(); - info!("DB path: {}", config.database_path); - let client = service::new_client(config)?; - let (exit_send, exit_recv) = std::sync::mpsc::channel(); - ::std::thread::spawn(move || { - let _ = exit.wait(); - let _ = exit_send.send(()); - }); - let mut from_block: u32 = match matches.value_of("from") { - Some(v) => v.parse().map_err(|_| "Invalid --from argument")?, - None => 1, - }; - - if from_block < 1 { - from_block = 1; - } - - let to_block = match matches.value_of("to") { - Some(v) => v.parse().map_err(|_| "Invalid --to argument")?, - None => client.info()?.chain.best_number as u32, - }; - info!("Exporting blocks from #{} to #{}", from_block, to_block); - - if to_block < from_block { - return Err("Invalid block range specified".into()); - } - - let json = matches.is_present("json"); - - let mut file: Box = match matches.value_of("OUTPUT") { - Some(filename) => Box::new(File::create(filename)?), - None => Box::new(stdout()), - }; - - if !json { - file.write(&(to_block - from_block + 1).encode())?; - } - - loop { - if exit_recv.try_recv().is_ok() { - break; - } - match client.block(&BlockId::number(from_block as u64))? { - Some(from_block) => { - if json { - serde_json::to_writer(&mut *file, &from_block).map_err(|e| format!("Eror writing JSON: {}", e))?; - } else { - file.write(&from_block.encode())?; - } - }, - None => break, - } - if from_block % 10000 == 0 { - info!("#{}", from_block); - } - if from_block == to_block { - break; - } - from_block += 1; - } - Ok(()) -} - -fn import_blocks(matches: &clap::ArgMatches, exit: E) -> error::Result<()> - where E: Future + Send + 'static -{ - let (spec, _) = load_spec(&matches)?; - let base_path = base_path(matches); - let mut config = service::Configuration::default_with_spec(spec); - config.database_path = db_path(&base_path, config.chain_spec.id()).to_string_lossy().into(); - - if let Some(v) = matches.value_of("min-heap-pages") { - config.min_heap_pages = v.parse().map_err(|_| "Invalid --min-heap-pages argument")?; - } - if let Some(v) = matches.value_of("max-heap-pages") { - config.max_heap_pages = v.parse().map_err(|_| "Invalid --max-heap-pages argument")?; - } - - if let Some(s) = matches.value_of("execution") { - config.execution_strategy = match s { - "both" => service::ExecutionStrategy::Both, - "native" => service::ExecutionStrategy::NativeWhenPossible, - "wasm" => service::ExecutionStrategy::AlwaysWasm, - _ => return Err(error::ErrorKind::Input("Invalid execution mode specified".to_owned()).into()), - }; - } - - let client = service::new_client(config)?; - let (exit_send, exit_recv) = std::sync::mpsc::channel(); - - ::std::thread::spawn(move || { - let _ = exit.wait(); - let _ = exit_send.send(()); - }); - - let mut file: Box = match matches.value_of("INPUT") { - Some(filename) => Box::new(File::open(filename)?), - None => Box::new(stdin()), - }; - - let count: u32 = Decode::decode(&mut file).ok_or("Error reading file")?; - info!("Importing {} blocks", count); - let mut block = 0; - for _ in 0 .. count { - if exit_recv.try_recv().is_ok() { - break; - } - match SignedBlock::decode(&mut file) { - Some(block) => { - let header = client.check_justification(block.block.header, block.justification.into())?; - client.import_block(BlockOrigin::File, header, Some(block.block.extrinsics))?; - }, - None => { - warn!("Error reading block data."); - break; + match cli::prepare_execution::(args, worker.exit_only(), version, load_spec, "parity-polkadot")? { + cli::Action::ExecutedInternally => (), + cli::Action::RunService(mut config) => { + info!("Parity ·:· Polkadot"); + 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); + config.custom = worker.configuration(); + 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)?, worker)?, + false => run_until_exit(&mut runtime, service::new_full(config, executor)?, worker)?, } - } - block += 1; - if block % 1000 == 0 { - info!("#{}", block); + // TODO: hard exit if this stalls? + runtime.shutdown_on_idle().wait().expect("failed to shut down event loop"); } } - info!("Imported {} blocks. Best: #{}", block, client.info()?.chain.best_number); - Ok(()) } - -fn revert_chain(matches: &clap::ArgMatches) -> error::Result<()> { - let (spec, _) = load_spec(&matches)?; - let base_path = base_path(matches); - let mut config = service::Configuration::default_with_spec(spec); - config.database_path = db_path(&base_path, config.chain_spec.id()).to_string_lossy().into(); - - let client = service::new_client(config)?; - - let blocks = match matches.value_of("NUM") { - Some(v) => v.parse().map_err(|_| "Invalid block count specified")?, - None => 256, - }; - - let reverted = client.revert(blocks)?; - let info = client.info()?.chain; - info!("Reverted {} blocks. Best: #{} ({})", reverted, info.best_number, info.best_hash); - Ok(()) -} - fn run_until_exit( runtime: &mut Runtime, service: service::Service, - matches: &clap::ArgMatches, - sys_conf: SystemConfiguration, worker: W, ) -> error::Result<()> where @@ -532,163 +119,9 @@ fn run_until_exit( let (exit_send, exit) = exit_future::signal(); let executor = runtime.executor(); - informant::start(&service, exit.clone(), executor.clone()); - - let _rpc_servers = { - let http_address = parse_address("127.0.0.1:9933", "rpc-port", matches)?; - let ws_address = parse_address("127.0.0.1:9944", "ws-port", matches)?; - - let handler = || { - let client = substrate_service::Service::client(&service); - let chain = rpc::apis::chain::Chain::new(client.clone(), executor.clone()); - let author = rpc::apis::author::Author::new(client.clone(), service.extrinsic_pool(), executor.clone()); - rpc::rpc_handler::, _, _, _, _>( - client, - chain, - author, - sys_conf.clone(), - ) - }; - ( - start_server(http_address, |address| rpc::start_http(address, handler())), - start_server(ws_address, |address| rpc::start_ws(address, handler())), - ) - }; + cli::informant::start(&service, exit.clone(), executor.clone()); let _ = runtime.block_on(worker.work(&service)); exit_send.fire(); Ok(()) } - -fn start_server(mut address: SocketAddr, start: F) -> Result where - F: Fn(&SocketAddr) -> Result, -{ - start(&address) - .or_else(|e| match e.kind() { - io::ErrorKind::AddrInUse | - io::ErrorKind::PermissionDenied => { - warn!("Unable to bind server to {}. Trying random port.", address); - address.set_port(0); - start(&address) - }, - _ => Err(e), - }) -} - -fn parse_address(default: &str, port_param: &str, matches: &clap::ArgMatches) -> Result { - let mut address: SocketAddr = default.parse().ok().ok_or(format!("Invalid address specified for --{}.", port_param))?; - if let Some(port) = matches.value_of(port_param) { - let port: u16 = port.parse().ok().ok_or(format!("Invalid port for --{} specified.", port_param))?; - address.set_port(port); - } - - Ok(address) -} - -fn keystore_path(base_path: &Path, chain_id: &str) -> PathBuf { - let mut path = base_path.to_owned(); - path.push("chains"); - path.push(chain_id); - path.push("keystore"); - path -} - -fn db_path(base_path: &Path, chain_id: &str) -> PathBuf { - let mut path = base_path.to_owned(); - path.push("chains"); - path.push(chain_id); - path.push("db"); - path -} - -fn network_path(base_path: &Path, chain_id: &str) -> PathBuf { - let mut path = base_path.to_owned(); - path.push("chains"); - path.push(chain_id); - path.push("network"); - path -} - -fn default_base_path() -> PathBuf { - use app_dirs::{AppInfo, AppDataType}; - - let app_info = AppInfo { - name: "Polkadot", - author: "Parity Technologies", - }; - - app_dirs::get_app_root( - AppDataType::UserData, - &app_info, - ).expect("app directories exist on all supported platforms; qed") -} - -fn init_logger(pattern: &str) { - use ansi_term::Colour; - - let mut builder = env_logger::LogBuilder::new(); - // Disable info logging by default for some modules: - builder.filter(Some("ws"), log::LogLevelFilter::Warn); - 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); - let isatty = atty::is(atty::Stream::Stderr); - let enable_color = isatty; - - let format = move |record: &log::LogRecord| { - let timestamp = time::strftime("%Y-%m-%d %H:%M:%S", &time::now()).expect("Error formatting log timestamp"); - - let mut output = if log::max_log_level() <= log::LogLevelFilter::Info { - format!("{} {}", Colour::Black.bold().paint(timestamp), record.args()) - } else { - let name = ::std::thread::current().name().map_or_else(Default::default, |x| format!("{}", Colour::Blue.bold().paint(x))); - format!("{} {} {} {} {}", Colour::Black.bold().paint(timestamp), name, record.level(), record.target(), record.args()) - }; - - if !enable_color { - output = kill_color(output.as_ref()); - } - - if !isatty && record.level() <= log::LogLevel::Info && atty::is(atty::Stream::Stdout) { - // duplicate INFO/WARN output to console - println!("{}", output); - } - output - }; - builder.format(format); - - builder.init().expect("Logger initialized only once."); -} - -fn kill_color(s: &str) -> String { - lazy_static! { - static ref RE: Regex = Regex::new("\x1b\\[[^m]+m").expect("Error initializing color regex"); - } - RE.replace_all(s, "").to_string() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn tests_node_name_good() { - assert!(is_node_name_valid("short name").is_ok()); - } - - #[test] - fn tests_node_name_bad() { - assert!(is_node_name_valid("long names are not very cool for the ui").is_err()); - assert!(is_node_name_valid("Dots.not.Ok").is_err()); - assert!(is_node_name_valid("http://visit.me").is_err()); - assert!(is_node_name_valid("https://visit.me").is_err()); - assert!(is_node_name_valid("www.visit.me").is_err()); - assert!(is_node_name_valid("email@domain").is_err()); - } -} diff --git a/substrate/polkadot/collator/src/lib.rs b/substrate/polkadot/collator/src/lib.rs index 7e4b36cc48..0acee408b6 100644 --- a/substrate/polkadot/collator/src/lib.rs +++ b/substrate/polkadot/collator/src/lib.rs @@ -68,7 +68,7 @@ use client::BlockchainEvents; use polkadot_api::PolkadotApi; use polkadot_primitives::{AccountId, BlockId, SessionKey}; use polkadot_primitives::parachain::{self, BlockData, DutyRoster, HeadData, ConsolidatedIngress, Message, Id as ParaId}; -use polkadot_cli::{ServiceComponents, Service, CustomConfiguration}; +use polkadot_cli::{ServiceComponents, Service, CustomConfiguration, VersionInfo}; use polkadot_cli::Worker; use tokio::timer::Deadline; @@ -213,7 +213,7 @@ struct CollationNode { impl Worker for CollationNode where P: ParachainContext + Send + 'static, - E: Future + Send + 'static + E: Future + Send + Clone + 'static { type Work = Box + Send>; type Exit = E; @@ -227,8 +227,8 @@ impl Worker for CollationNode where config } - fn exit_only(self) -> Self::Exit { - self.exit + fn exit_only(&self) -> Self::Exit { + self.exit.clone() } fn work(self, service: &Service) -> Self::Work { @@ -323,14 +323,15 @@ pub fn run_collator( para_id: ParaId, exit: E, key: Arc, - args: Vec<::std::ffi::OsString> + args: Vec<::std::ffi::OsString>, + version: VersionInfo, ) -> polkadot_cli::error::Result<()> where P: ParachainContext + Send + 'static, E: IntoFuture, - E::Future: Send + 'static, + E::Future: Send + Clone + 'static, { let node_logic = CollationNode { parachain_context, exit: exit.into_future(), para_id, key }; - polkadot_cli::run(args, node_logic) + polkadot_cli::run(args, node_logic, version) } #[cfg(test)] diff --git a/substrate/polkadot/service/res/krummelanke.json b/substrate/polkadot/service/res/krummelanke.json index 756106e78b..510c1e467c 100644 --- a/substrate/polkadot/service/res/krummelanke.json +++ b/substrate/polkadot/service/res/krummelanke.json @@ -46,5 +46,6 @@ "/ip4/104.211.48.247/tcp/30333/p2p/QmYPx99i3H8EKXrvYHTBwqz3jjFC1kBfkvmSKd2h9zwQFr", "/ip4/40.114.120.164/tcp/30333/p2p/QmWzYU5X1NpFrprD1YZF5Lcj9aE5WF4QEg5FpvQx5XGWG7", "/ip4/40.117.153.33/tcp/30333/p2p/QmSz8qCADMmi92QB8dTqMPu56JYQQKZBAHz7y8KXjvqcvW" -] +], +"telemetryUrl": "wss://telemetry.polkadot.io/submit/" } diff --git a/substrate/polkadot/service/src/chain_spec.rs b/substrate/polkadot/service/src/chain_spec.rs index 37b7bae35e..0c734f5712 100644 --- a/substrate/polkadot/service/src/chain_spec.rs +++ b/substrate/polkadot/service/src/chain_spec.rs @@ -22,6 +22,8 @@ use polkadot_runtime::{GenesisConfig, ConsensusConfig, CouncilConfig, DemocracyC SessionConfig, StakingConfig, TimestampConfig}; use service::ChainSpec; +const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; + pub fn poc_1_testnet_config() -> Result, String> { ChainSpec::from_embedded(include_bytes!("../res/krummelanke.json")) } @@ -94,7 +96,13 @@ fn staging_testnet_config_genesis() -> GenesisConfig { /// 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) + 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 { @@ -169,7 +177,7 @@ fn development_config_genesis() -> GenesisConfig { /// Development config (single validator Alice) pub fn development_config() -> ChainSpec { - ChainSpec::from_genesis("Development", "development", development_config_genesis, vec![]) + ChainSpec::from_genesis("Development", "development", development_config_genesis, vec![], None) } fn local_testnet_genesis() -> GenesisConfig { @@ -181,5 +189,5 @@ fn local_testnet_genesis() -> GenesisConfig { /// Local testnet config (multivalidator Alice + Bob) pub fn local_testnet_config() -> ChainSpec { - ChainSpec::from_genesis("Local Testnet", "local_testnet", local_testnet_genesis, vec![]) + ChainSpec::from_genesis("Local Testnet", "local_testnet", local_testnet_genesis, vec![], None) } diff --git a/substrate/polkadot/src/main.rs b/substrate/polkadot/src/main.rs index 8780126fcd..4e3fba054f 100644 --- a/substrate/polkadot/src/main.rs +++ b/substrate/polkadot/src/main.rs @@ -25,19 +25,24 @@ extern crate futures; #[macro_use] extern crate error_chain; -use cli::{ServiceComponents, Service}; +use cli::{ServiceComponents, Service, VersionInfo}; use futures::sync::oneshot; use futures::{future, Future}; use std::cell::RefCell; +mod vergen { + #![allow(unused)] + include!(concat!(env!("OUT_DIR"), "/version.rs")); +} + // the regular polkadot worker simply does nothing until ctrl-c struct Worker; impl cli::Worker for Worker { type Work = Self::Exit; type Exit = future::MapErr, fn(oneshot::Canceled) -> ()>; - fn exit_only(self) -> Self::Exit { + fn exit_only(&self) -> Self::Exit { // can't use signal directly here because CtrlC takes only `Fn`. let (exit_send, exit) = oneshot::channel(); @@ -59,5 +64,12 @@ impl cli::Worker for Worker { quick_main!(run); fn run() -> cli::error::Result<()> { - cli::run(::std::env::args(), Worker) + let version = VersionInfo { + commit: vergen::short_sha(), + version: env!("CARGO_PKG_VERSION"), + executable_name: "polkadot", + author: "Parity Team ", + description: "Polkadot Node Rust Implementation", + }; + cli::run(::std::env::args(), Worker, version) } diff --git a/substrate/substrate/cli/Cargo.toml b/substrate/substrate/cli/Cargo.toml new file mode 100644 index 0000000000..a551e2ff94 --- /dev/null +++ b/substrate/substrate/cli/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "substrate-cli" +version = "0.2.0" +authors = ["Parity Technologies "] +description = "Substrate CLI interface." +build = "build.rs" + +[dependencies] +clap = { version = "~2.32", features = ["yaml"] } +backtrace = "0.3" +env_logger = "0.4" +error-chain = "0.12" +log = "0.3" +atty = "0.2" +regex = "1" +time = "0.1" +slog = "^2" +ansi_term = "0.10" +lazy_static = "1.0" +app_dirs = "1.2" +tokio = "0.1.7" +futures = "0.1.17" +fdlimit = "0.1" +exit-future = "0.1" +substrate-client = { path = "../../substrate/client" } +substrate-extrinsic-pool = { path = "../../substrate/extrinsic-pool" } +substrate-network = { path = "../../substrate/network" } +substrate-runtime-primitives = { path = "../../substrate/runtime/primitives" } +substrate-service = { path = "../../substrate/service" } +substrate-telemetry = { path = "../../substrate/telemetry" } +names = "0.11.0" + +[build-dependencies] +clap = "~2.32" diff --git a/substrate/polkadot/cli/README.adoc b/substrate/substrate/cli/README.adoc similarity index 87% rename from substrate/polkadot/cli/README.adoc rename to substrate/substrate/cli/README.adoc index 54fa5f3f32..1ad1d01eb1 100644 --- a/substrate/polkadot/cli/README.adoc +++ b/substrate/substrate/cli/README.adoc @@ -1,5 +1,5 @@ -= Polkadot CLI += Substrate CLI == Summary diff --git a/substrate/polkadot/cli/build.rs b/substrate/substrate/cli/build.rs similarity index 58% rename from substrate/polkadot/cli/build.rs rename to substrate/substrate/cli/build.rs index 9b878725ee..645e98d5e8 100644 --- a/substrate/polkadot/cli/build.rs +++ b/substrate/substrate/cli/build.rs @@ -1,3 +1,19 @@ +// Copyright 2017 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + #[macro_use] extern crate clap; diff --git a/substrate/substrate/cli/doc/shell-completion.adoc b/substrate/substrate/cli/doc/shell-completion.adoc new file mode 100644 index 0000000000..8afbd37adb --- /dev/null +++ b/substrate/substrate/cli/doc/shell-completion.adoc @@ -0,0 +1,41 @@ + +== Shell completion + +The Substrate cli command supports shell auto-completion. For this to work, you will need to run the completion script matching you build and system. + +Assuming you built a release version using `cargo build --release` and use `bash` run the following: + +[source, shell] +source target/release/completion-scripts/substrate.bash + +You can find completion scripts for: +- bash +- fish +- zsh +- elvish +- powershell + +To make this change persistent, you can proceed as follow: + +.First install + +[source, shell] +---- +COMPL_DIR=$HOME/.completion +mkdir -p $COMPL_DIR +cp -f target/release/completion-scripts/substrate.bash $COMPL_DIR/ +echo "source $COMPL_DIR/substrate.bash" >> $HOME/.bash_profile +source $HOME/.bash_profile +---- + +.Update + +When you build a new version of Substrate, the following will ensure you auto-completion script matches the current binary: + +[source, shell] +---- +COMPL_DIR=$HOME/.completion +mkdir -p $COMPL_DIR +cp -f target/release/completion-scripts/substrate.bash $COMPL_DIR/ +source $HOME/.bash_profile +---- diff --git a/substrate/polkadot/cli/src/cli.yml b/substrate/substrate/cli/src/cli.yml similarity index 98% rename from substrate/polkadot/cli/src/cli.yml rename to substrate/substrate/cli/src/cli.yml index d8e2797970..995f773400 100644 --- a/substrate/polkadot/cli/src/cli.yml +++ b/substrate/substrate/cli/src/cli.yml @@ -1,6 +1,6 @@ -name: polkadot -author: "Parity Team " -about: Polkadot Node Rust Implementation +name: {name} +author: {author} +about: {description} args: - log: short: l diff --git a/substrate/polkadot/cli/src/error.rs b/substrate/substrate/cli/src/error.rs similarity index 73% rename from substrate/polkadot/cli/src/error.rs rename to substrate/substrate/cli/src/error.rs index d7c690276c..2c44083afe 100644 --- a/substrate/polkadot/cli/src/error.rs +++ b/substrate/substrate/cli/src/error.rs @@ -1,18 +1,18 @@ // Copyright 2017 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . //! Initialization errors. @@ -22,7 +22,7 @@ error_chain! { foreign_links { Io(::std::io::Error) #[doc="IO error"]; Cli(::clap::Error) #[doc="CLI error"]; - Service(::service::Error) #[doc="Polkadot service error"]; + Service(::service::Error) #[doc="Substrate service error"]; } links { Client(client::error::Error, client::error::ErrorKind) #[doc="Client error"]; diff --git a/substrate/polkadot/cli/src/informant.rs b/substrate/substrate/cli/src/informant.rs similarity index 86% rename from substrate/polkadot/cli/src/informant.rs rename to substrate/substrate/cli/src/informant.rs index a624bce626..37eed5d3c2 100644 --- a/substrate/polkadot/cli/src/informant.rs +++ b/substrate/substrate/cli/src/informant.rs @@ -1,18 +1,18 @@ // Copyright 2017 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . //! Console informant. Prints sync progress and block events. Runs on the calling thread. @@ -52,7 +52,7 @@ pub fn start(service: &Service, exit: ::exit_future::Exit, handle: TaskExe }; let txpool_status = txpool.light_status(); let best_number: u64 = best_block.number().as_(); - info!(target: "polkadot", "{} ({} peers), best: #{} ({})", status, sync_status.num_peers, best_number, hash); + info!(target: "substrate", "{} ({} peers), best: #{} ({})", status, sync_status.num_peers, best_number, hash); telemetry!("system.interval"; "status" => status, "peers" => num_peers, "height" => best_number, "best" => ?hash, "txcount" => txpool_status.transaction_count); } else { warn!("Error getting best block information"); @@ -62,7 +62,7 @@ pub fn start(service: &Service, exit: ::exit_future::Exit, handle: TaskExe let client = service.client(); let display_block_import = client.import_notification_stream().for_each(|n| { - info!(target: "polkadot", "Imported #{} ({})", n.header.number, n.hash); + info!(target: "substrate", "Imported #{} ({})", n.header.number(), n.hash); Ok(()) }); diff --git a/substrate/substrate/cli/src/lib.rs b/substrate/substrate/cli/src/lib.rs new file mode 100644 index 0000000000..814a4994e1 --- /dev/null +++ b/substrate/substrate/cli/src/lib.rs @@ -0,0 +1,502 @@ +// Copyright 2017 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Substrate CLI library. + +#![warn(missing_docs)] +#![warn(unused_extern_crates)] + +extern crate app_dirs; +extern crate env_logger; +extern crate atty; +extern crate ansi_term; +extern crate regex; +extern crate time; +extern crate fdlimit; +extern crate futures; +extern crate tokio; +extern crate names; +extern crate backtrace; + +extern crate substrate_client as client; +extern crate substrate_network as network; +extern crate substrate_runtime_primitives as runtime_primitives; +extern crate substrate_extrinsic_pool; +extern crate substrate_service as service; +#[macro_use] +extern crate slog; // needed until we can reexport `slog_info` from `substrate_telemetry` +#[macro_use] +extern crate substrate_telemetry; +extern crate exit_future; + +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate clap; +#[macro_use] +extern crate error_chain; +#[macro_use] +extern crate log; + +pub mod error; +pub mod informant; +mod panic_hook; + +use runtime_primitives::traits::As; +use service::{ + ServiceFactory, FactoryFullConfiguration, RuntimeGenesis, + FactoryGenesis, PruningMode, ChainSpec, +}; + +use std::io::{Write, Read, stdin, stdout}; +use std::fs::File; +use std::net::SocketAddr; +use std::path::{Path, PathBuf}; +use names::{Generator, Name}; +use regex::Regex; + +use futures::Future; + +/// Executable version. Used to pass version information from the root crate. +pub struct VersionInfo { + /// Implementation version. + pub version: &'static str, + /// SCM Commit hash. + pub commit: &'static str, + /// Executable file name. + pub executable_name: &'static str, + /// Executable file description. + pub description: &'static str, + /// Executable file author. + pub author: &'static str, +} + +/// CLI Action +pub enum Action { + /// Substrate handled the command. No need to do anything. + ExecutedInternally, + /// Service mode requested. Caller should start the service. + RunService(FactoryFullConfiguration), +} + +fn load_spec(matches: &clap::ArgMatches, factory: F) -> Result, String> + where G: RuntimeGenesis, F: FnOnce(&str) -> Result>, String>, +{ + let chain_key = matches.value_of("chain").unwrap_or_else(|| if matches.is_present("dev") { "dev" } else { "" }); + let spec = match factory(chain_key)? { + Some(spec) => spec, + None => ChainSpec::from_json_file(PathBuf::from(chain_key))? + }; + Ok(spec) +} + +fn base_path(matches: &clap::ArgMatches) -> PathBuf { + matches.value_of("base-path") + .map(|x| Path::new(x).to_owned()) + .unwrap_or_else(default_base_path) +} + +/// Check whether a node name is considered as valid +fn is_node_name_valid(_name: &str) -> Result<(), &str> { + const MAX_NODE_NAME_LENGTH: usize = 32; + let name = _name.to_string(); + if name.chars().count() >= MAX_NODE_NAME_LENGTH { + return Err("Node name too long"); +} + + let invalid_chars = r"[\\.@]"; + let re = Regex::new(invalid_chars).unwrap(); + if re.is_match(&name) { + return Err("Node name should not contain invalid chars such as '.' and '@'"); + } + + let invalid_patterns = r"(https?:\\/+)?(www)+"; + let re = Regex::new(invalid_patterns).unwrap(); + if re.is_match(&name) { + return Err("Node name should not contain urls"); + } + + Ok(()) +} + +/// Parse command line arguments and execute commands or return service configuration. +/// +/// 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 prepare_execution( + args: I, + exit: E, + version: VersionInfo, + spec_factory: S, + impl_name: &'static str, +) -> error::Result> +where + I: IntoIterator, + T: Into + Clone, + E: Future + Send + 'static, + F: ServiceFactory, + S: FnOnce(&str) -> Result>>, String>, +{ + panic_hook::set(); + + let yaml = format!(include_str!("./cli.yml"), + name = version.executable_name, + description = version.description, + author = version.author, + ); + let yaml = &clap::YamlLoader::load_from_str(&yaml).expect("Invalid yml file")[0]; + let matches = match clap::App::from_yaml(yaml) + .version(&(crate_version!().to_owned() + "\n")[..]) + .get_matches_from_safe(args) { + Ok(m) => m, + Err(e) => e.exit(), + }; + + // TODO [ToDr] Split parameters parsing from actual execution. + let log_pattern = matches.value_of("log").unwrap_or(""); + init_logger(log_pattern); + fdlimit::raise_fd_limit(); + + if let Some(matches) = matches.subcommand_matches("build-spec") { + let spec = load_spec(&matches, spec_factory)?; + build_spec::(matches, spec)?; + return Ok(Action::ExecutedInternally); + } + + if let Some(matches) = matches.subcommand_matches("export-blocks") { + let spec = load_spec(&matches, spec_factory)?; + export_blocks::(matches, spec, exit)?; + return Ok(Action::ExecutedInternally); + } + + if let Some(matches) = matches.subcommand_matches("import-blocks") { + let spec = load_spec(&matches, spec_factory)?; + import_blocks::(matches, spec, exit)?; + return Ok(Action::ExecutedInternally); + } + + if let Some(matches) = matches.subcommand_matches("revert") { + let spec = load_spec(&matches, spec_factory)?; + revert_chain::(matches, spec)?; + return Ok(Action::ExecutedInternally); + } + + let spec = load_spec(&matches, spec_factory)?; + let mut config = service::Configuration::default_with_spec(spec); + + config.impl_name = impl_name; + config.impl_commit = version.commit; + config.impl_version = version.version; + + config.name = match matches.value_of("name") { + None => Generator::with_naming(Name::Numbered).next().unwrap(), + Some(name) => name.into(), + }; + match is_node_name_valid(&config.name) { + Ok(_) => (), + Err(msg) => return Err(error::ErrorKind::Input( + format!("Invalid node name '{}'. Reason: {}. If unsure, use none.", config.name, msg)).into()) + } + + let base_path = base_path(&matches); + + config.keystore_path = matches.value_of("keystore") + .map(|x| Path::new(x).to_owned()) + .unwrap_or_else(|| keystore_path(&base_path, config.chain_spec.id())) + .to_string_lossy() + .into(); + + config.database_path = db_path(&base_path, config.chain_spec.id()).to_string_lossy().into(); + + config.pruning = match matches.value_of("pruning") { + Some("archive") => PruningMode::ArchiveAll, + None => PruningMode::default(), + Some(s) => PruningMode::keep_blocks(s.parse() + .map_err(|_| error::ErrorKind::Input("Invalid pruning mode specified".to_owned()))?), + }; + + let role = + if matches.is_present("light") { + config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible; + service::Roles::LIGHT + } else if matches.is_present("validator") || matches.is_present("dev") { + config.execution_strategy = service::ExecutionStrategy::Both; + service::Roles::AUTHORITY + } else { + config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible; + service::Roles::FULL + }; + + if let Some(v) = matches.value_of("min-heap-pages") { + config.min_heap_pages = v.parse().map_err(|_| "Invalid --min-heap-pages argument")?; + } + if let Some(v) = matches.value_of("max-heap-pages") { + config.max_heap_pages = v.parse().map_err(|_| "Invalid --max-heap-pages argument")?; + } + + if let Some(s) = matches.value_of("execution") { + config.execution_strategy = match s { + "both" => service::ExecutionStrategy::Both, + "native" => service::ExecutionStrategy::NativeWhenPossible, + "wasm" => service::ExecutionStrategy::AlwaysWasm, + _ => return Err(error::ErrorKind::Input("Invalid execution mode specified".to_owned()).into()), + }; + } + + config.roles = role; + { + config.network.boot_nodes.extend(matches + .values_of("bootnodes") + .map_or(Default::default(), |v| v.map(|n| n.to_owned()).collect::>())); + config.network.config_path = Some(network_path(&base_path, config.chain_spec.id()).to_string_lossy().into()); + config.network.net_config_path = config.network.config_path.clone(); + + let port = match matches.value_of("port") { + Some(port) => port.parse().map_err(|_| "Invalid p2p port value specified.")?, + None => 30333, + }; + + config.network.listen_address = Some(SocketAddr::new("0.0.0.0".parse().unwrap(), port)); + config.network.public_address = None; + config.network.client_version = config.client_id(); + config.network.use_secret = match matches.value_of("node-key").map(|s| s.parse()) { + Some(Ok(secret)) => Some(secret), + Some(Err(err)) => return Err(format!("Error parsing node key: {}", err).into()), + None => None, + }; + } + + config.keys = matches.values_of("key").unwrap_or_default().map(str::to_owned).collect(); + if matches.is_present("dev") { + config.keys.push("Alice".into()); + } + + config.rpc_http = Some(parse_address("127.0.0.1:9933", "rpc-port", &matches)?); + config.rpc_ws = Some(parse_address("127.0.0.1:9944", "ws-port", &matches)?); + + // Override telemetry + if matches.is_present("no-telemetry") { + config.telemetry_url = None; + } else if let Some(url) = matches.value_of("telemetry-url") { + config.telemetry_url = Some(url.to_owned()); + } + + Ok(Action::RunService(config)) +} + +fn build_spec(matches: &clap::ArgMatches, spec: ChainSpec>) -> error::Result<()> + where F: ServiceFactory, +{ + info!("Building chain spec"); + let raw = matches.is_present("raw"); + let json = service::chain_ops::build_spec::>(spec, raw)?; + print!("{}", json); + Ok(()) +} + +fn export_blocks(matches: &clap::ArgMatches, spec: ChainSpec>, exit: E) -> error::Result<()> + where F: ServiceFactory, E: Future + Send + 'static, +{ + let base_path = base_path(matches); + let mut config = service::Configuration::default_with_spec(spec); + config.database_path = db_path(&base_path, config.chain_spec.id()).to_string_lossy().into(); + info!("DB path: {}", config.database_path); + let from: u64 = match matches.value_of("from") { + Some(v) => v.parse().map_err(|_| "Invalid --from argument")?, + None => 1, + }; + + let to: Option = match matches.value_of("to") { + Some(v) => Some(v.parse().map_err(|_| "Invalid --to argument")?), + None => None, + }; + let json = matches.is_present("json"); + + let file: Box = match matches.value_of("OUTPUT") { + Some(filename) => Box::new(File::create(filename)?), + None => Box::new(stdout()), + }; + + Ok(service::chain_ops::export_blocks::(config, exit, file, As::sa(from), to.map(As::sa), json)?) +} + +fn import_blocks(matches: &clap::ArgMatches, spec: ChainSpec>, exit: E) -> error::Result<()> + where F: ServiceFactory, E: Future + Send + 'static, +{ + let base_path = base_path(matches); + let mut config = service::Configuration::default_with_spec(spec); + config.database_path = db_path(&base_path, config.chain_spec.id()).to_string_lossy().into(); + + if let Some(v) = matches.value_of("min-heap-pages") { + config.min_heap_pages = v.parse().map_err(|_| "Invalid --min-heap-pages argument")?; + } + if let Some(v) = matches.value_of("max-heap-pages") { + config.max_heap_pages = v.parse().map_err(|_| "Invalid --max-heap-pages argument")?; + } + + if let Some(s) = matches.value_of("execution") { + config.execution_strategy = match s { + "both" => service::ExecutionStrategy::Both, + "native" => service::ExecutionStrategy::NativeWhenPossible, + "wasm" => service::ExecutionStrategy::AlwaysWasm, + _ => return Err(error::ErrorKind::Input("Invalid execution mode specified".to_owned()).into()), + }; + } + + let file: Box = match matches.value_of("INPUT") { + Some(filename) => Box::new(File::open(filename)?), + None => Box::new(stdin()), + }; + + Ok(service::chain_ops::import_blocks::(config, exit, file)?) +} + +fn revert_chain(matches: &clap::ArgMatches, spec: ChainSpec>) -> error::Result<()> + where F: ServiceFactory, +{ + let base_path = base_path(matches); + let mut config = service::Configuration::default_with_spec(spec); + config.database_path = db_path(&base_path, config.chain_spec.id()).to_string_lossy().into(); + + let blocks = match matches.value_of("NUM") { + Some(v) => v.parse().map_err(|_| "Invalid block count specified")?, + None => 256, + }; + + Ok(service::chain_ops::revert_chain::(config, As::sa(blocks))?) +} + +fn parse_address(default: &str, port_param: &str, matches: &clap::ArgMatches) -> Result { + let mut address: SocketAddr = default.parse().ok().ok_or(format!("Invalid address specified for --{}.", port_param))?; + if let Some(port) = matches.value_of(port_param) { + let port: u16 = port.parse().ok().ok_or(format!("Invalid port for --{} specified.", port_param))?; + address.set_port(port); + } + + Ok(address) +} + +fn keystore_path(base_path: &Path, chain_id: &str) -> PathBuf { + let mut path = base_path.to_owned(); + path.push("chains"); + path.push(chain_id); + path.push("keystore"); + path +} + +fn db_path(base_path: &Path, chain_id: &str) -> PathBuf { + let mut path = base_path.to_owned(); + path.push("chains"); + path.push(chain_id); + path.push("db"); + path +} + +fn network_path(base_path: &Path, chain_id: &str) -> PathBuf { + let mut path = base_path.to_owned(); + path.push("chains"); + path.push(chain_id); + path.push("network"); + path +} + +fn default_base_path() -> PathBuf { + use app_dirs::{AppInfo, AppDataType}; + + let app_info = AppInfo { + name: "Polkadot", + author: "Parity Technologies", + }; + + app_dirs::get_app_root( + AppDataType::UserData, + &app_info, + ).expect("app directories exist on all supported platforms; qed") +} + +fn init_logger(pattern: &str) { + use ansi_term::Colour; + + let mut builder = env_logger::LogBuilder::new(); + // Disable info logging by default for some modules: + builder.filter(Some("ws"), log::LogLevelFilter::Warn); + 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); + let isatty = atty::is(atty::Stream::Stderr); + let enable_color = isatty; + + let format = move |record: &log::LogRecord| { + let timestamp = time::strftime("%Y-%m-%d %H:%M:%S", &time::now()).expect("Error formatting log timestamp"); + + let mut output = if log::max_log_level() <= log::LogLevelFilter::Info { + format!("{} {}", Colour::Black.bold().paint(timestamp), record.args()) + } else { + let name = ::std::thread::current().name().map_or_else(Default::default, |x| format!("{}", Colour::Blue.bold().paint(x))); + format!("{} {} {} {} {}", Colour::Black.bold().paint(timestamp), name, record.level(), record.target(), record.args()) + }; + + if !enable_color { + output = kill_color(output.as_ref()); + } + + if !isatty && record.level() <= log::LogLevel::Info && atty::is(atty::Stream::Stdout) { + // duplicate INFO/WARN output to console + println!("{}", output); + } + output + }; + builder.format(format); + + builder.init().expect("Logger initialized only once."); +} + +fn kill_color(s: &str) -> String { + lazy_static! { + static ref RE: Regex = Regex::new("\x1b\\[[^m]+m").expect("Error initializing color regex"); + } + RE.replace_all(s, "").to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn tests_node_name_good() { + assert!(is_node_name_valid("short name").is_ok()); + } + + #[test] + fn tests_node_name_bad() { + assert!(is_node_name_valid("long names are not very cool for the ui").is_err()); + assert!(is_node_name_valid("Dots.not.Ok").is_err()); + assert!(is_node_name_valid("http://visit.me").is_err()); + assert!(is_node_name_valid("https://visit.me").is_err()); + assert!(is_node_name_valid("www.visit.me").is_err()); + assert!(is_node_name_valid("email@domain").is_err()); + } +} diff --git a/substrate/polkadot/cli/src/panic_hook.rs b/substrate/substrate/cli/src/panic_hook.rs similarity index 87% rename from substrate/polkadot/cli/src/panic_hook.rs rename to substrate/substrate/cli/src/panic_hook.rs index fa48ce08cb..1364cafce4 100644 --- a/substrate/polkadot/cli/src/panic_hook.rs +++ b/substrate/substrate/cli/src/panic_hook.rs @@ -1,18 +1,18 @@ // Copyright 2017 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . //! Custom panic hook with bug report link diff --git a/substrate/substrate/rpc-servers/src/lib.rs b/substrate/substrate/rpc-servers/src/lib.rs index 571e9bcd60..006345e68e 100644 --- a/substrate/substrate/rpc-servers/src/lib.rs +++ b/substrate/substrate/rpc-servers/src/lib.rs @@ -34,6 +34,8 @@ use substrate_runtime_primitives::traits::Block as BlockT; type Metadata = apis::metadata::Metadata; type RpcHandler = pubsub::PubSubHandler; +pub type HttpServer = http::Server; +pub type WsServer = ws::Server; /// Construct rpc `IoHandler` pub fn rpc_handler( diff --git a/substrate/substrate/service/Cargo.toml b/substrate/substrate/service/Cargo.toml index 91985a550b..e10f7db9f8 100644 --- a/substrate/substrate/service/Cargo.toml +++ b/substrate/substrate/service/Cargo.toml @@ -15,6 +15,7 @@ exit-future = "0.1" serde = "1.0" serde_json = "1.0" serde_derive = "1.0" +target_info = "0.1" substrate-keystore = { path = "../../substrate/keystore" } substrate-runtime-io = { path = "../../substrate/runtime-io" } substrate-runtime-primitives = { path = "../../substrate/runtime/primitives" } @@ -22,6 +23,9 @@ substrate-primitives = { path = "../../substrate/primitives" } substrate-network = { path = "../../substrate/network" } substrate-client = { path = "../../substrate/client" } substrate-client-db = { path = "../../substrate/client/db" } +substrate-codec = { path = "../../substrate/codec" } substrate-executor = { path = "../../substrate/executor" } substrate-extrinsic-pool = { path = "../../substrate/extrinsic-pool" } +substrate-rpc = { path = "../../substrate/rpc" } +substrate-rpc-servers = { path = "../../substrate/rpc-servers" } substrate-telemetry = { path = "../../substrate/telemetry" } diff --git a/substrate/substrate/service/src/chain_ops.rs b/substrate/substrate/service/src/chain_ops.rs new file mode 100644 index 0000000000..aeefda6a67 --- /dev/null +++ b/substrate/substrate/service/src/chain_ops.rs @@ -0,0 +1,139 @@ +// Copyright 2017 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Chain utilities. + +use std::{self, io::{Read, Write}}; +use futures::Future; +use serde_json; + +use client::BlockOrigin; +use runtime_primitives::generic::{SignedBlock, BlockId}; +use runtime_primitives::traits::{As}; +use components::{ServiceFactory, FactoryFullConfiguration, FactoryBlockNumber, RuntimeGenesis}; +use new_client; +use codec::{Decode, Encode}; +use error; +use chain_spec::ChainSpec; + +/// Export a range of blocks to a binary stream. +pub fn export_blocks(config: FactoryFullConfiguration, exit: E, mut output: W, from: FactoryBlockNumber, to: Option>, json: bool) -> error::Result<()> + where F: ServiceFactory, E: Future + Send + 'static, W: Write, +{ + let client = new_client::(config)?; + let mut block = from; + + let last = match to { + Some(v) if v == As::sa(0) => As::sa(1), + Some(v) => v, + None => client.info()?.chain.best_number, + }; + + if last < block { + return Err("Invalid block range specified".into()); + } + + let (exit_send, exit_recv) = std::sync::mpsc::channel(); + ::std::thread::spawn(move || { + let _ = exit.wait(); + let _ = exit_send.send(()); + }); + info!("Exporting blocks from #{} to #{}", block, last); + if !json { + output.write(&(last - block + As::sa(1)).encode())?; + } + + loop { + if exit_recv.try_recv().is_ok() { + break; + } + match client.block(&BlockId::number(block))? { + Some(block) => { + if json { + serde_json::to_writer(&mut output, &block).map_err(|e| format!("Eror writing JSON: {}", e))?; + } else { + output.write(&block.encode())?; + } + }, + None => break, + } + if block.as_() % 10000 == 0 { + info!("#{}", block); + } + if block == last { + break; + } + block += As::sa(1); + } + Ok(()) +} + +/// Import blocks from a binary stream. +pub fn import_blocks(config: FactoryFullConfiguration, exit: E, mut input: R) -> error::Result<()> + where F: ServiceFactory, E: Future + Send + 'static, R: Read, +{ + let client = new_client::(config)?; + + let (exit_send, exit_recv) = std::sync::mpsc::channel(); + ::std::thread::spawn(move || { + let _ = exit.wait(); + let _ = exit_send.send(()); + }); + + let count: u32 = Decode::decode(&mut input).ok_or("Error reading file")?; + info!("Importing {} blocks", count); + let mut block = 0; + for _ in 0 .. count { + if exit_recv.try_recv().is_ok() { + break; + } + match SignedBlock::decode(&mut input) { + Some(block) => { + let header = client.check_justification(block.block.header, block.justification.into())?; + client.import_block(BlockOrigin::File, header, Some(block.block.extrinsics))?; + }, + None => { + warn!("Error reading block data."); + break; + } + } + block += 1; + if block % 1000 == 0 { + info!("#{}", block); + } + } + info!("Imported {} blocks. Best: #{}", block, client.info()?.chain.best_number); + + Ok(()) +} + +/// Revert the chain. +pub fn revert_chain(config: FactoryFullConfiguration, blocks: FactoryBlockNumber) -> error::Result<()> + where F: ServiceFactory, +{ + let client = new_client::(config)?; + let reverted = client.revert(blocks)?; + let info = client.info()?.chain; + info!("Reverted {} blocks. Best: #{} ({})", reverted, info.best_number, info.best_hash); + Ok(()) +} + +/// Build a chain spec json +pub fn build_spec(spec: ChainSpec, raw: bool) -> error::Result + where G: RuntimeGenesis, +{ + Ok(spec.to_json(raw)?) +} diff --git a/substrate/substrate/service/src/chain_spec.rs b/substrate/substrate/service/src/chain_spec.rs index cc550bc989..6ccd0545b4 100644 --- a/substrate/substrate/service/src/chain_spec.rs +++ b/substrate/substrate/service/src/chain_spec.rs @@ -75,6 +75,7 @@ struct ChainSpecFile { pub name: String, pub id: String, pub boot_nodes: Vec, + pub telemetry_url: Option, } /// A configuration of a chain. Can be used to build a genesis block. @@ -96,6 +97,10 @@ impl ChainSpec { &self.spec.id } + pub fn telemetry_url(&self) -> Option<&str> { + self.spec.telemetry_url.as_ref().map(String::as_str) + } + /// Parse json content into a `ChainSpec` pub fn from_embedded(json: &'static [u8]) -> Result { let spec = json::from_slice(json).map_err(|e| format!("Error parsing spec file: {}", e))?; @@ -116,11 +121,19 @@ impl ChainSpec { } /// Create hardcoded spec. - pub fn from_genesis(name: &str, id: &str, constructor: fn() -> G, boot_nodes: Vec) -> Self { + pub fn from_genesis( + name: &str, + id: &str, + constructor: fn() -> G, + boot_nodes: Vec, + telemetry_url: Option<&str> + ) -> Self + { let spec = ChainSpecFile { name: name.to_owned(), id: id.to_owned(), boot_nodes: boot_nodes, + telemetry_url: telemetry_url.map(str::to_owned), }; ChainSpec { spec, diff --git a/substrate/substrate/service/src/components.rs b/substrate/substrate/service/src/components.rs index 7c6086b3f9..fd96dc7c31 100644 --- a/substrate/substrate/service/src/components.rs +++ b/substrate/substrate/service/src/components.rs @@ -26,7 +26,7 @@ use error; use network::{self, OnDemand}; use substrate_executor::{NativeExecutor, NativeExecutionDispatch}; use extrinsic_pool::{txpool::Options as ExtrinsicPoolOptions, api::ExtrinsicPool as ExtrinsicPoolApi}; -use runtime_primitives::{traits::Block as BlockT, generic::BlockId, BuildStorage}; +use runtime_primitives::{traits::Block as BlockT, traits::Header as HeaderT, generic::BlockId, BuildStorage}; use config::Configuration; // Type aliases. @@ -80,6 +80,9 @@ pub type FactoryGenesis = ::Genesis; /// `Block` type for a factory. pub type FactoryBlock = ::Block; +/// `Number` type for a factory. +pub type FactoryBlockNumber = < as BlockT>::Header as HeaderT>::Number; + /// Full `Configuration` type for a factory. pub type FactoryFullConfiguration = Configuration<::Configuration, FactoryGenesis>; diff --git a/substrate/substrate/service/src/config.rs b/substrate/substrate/service/src/config.rs index 5ad1d51f78..b3e42c9b6c 100644 --- a/substrate/substrate/service/src/config.rs +++ b/substrate/substrate/service/src/config.rs @@ -16,6 +16,7 @@ //! Service configuration. +use std::net::SocketAddr; use extrinsic_pool; use chain_spec::ChainSpec; pub use client::ExecutionStrategy; @@ -24,9 +25,16 @@ pub use network::NetworkConfiguration; pub use client_db::PruningMode; use runtime_primitives::BuildStorage; use serde::{Serialize, de::DeserializeOwned}; +use target_info::Target; /// Service configuration. pub struct Configuration { + /// Implementation name + pub impl_name: &'static str, + /// Implementation version + pub impl_version: &'static str, + /// Git commit if any. + pub impl_commit: &'static str, /// Node roles. pub roles: Roles, /// Extrinsic pool configuration. @@ -55,12 +63,21 @@ pub struct Configuration { pub min_heap_pages: usize, /// Maximum number of heap pages to allocate for Wasm execution. pub max_heap_pages: usize, + /// RPC over HTTP binding address. `None` if disabled. + pub rpc_http: Option, + /// RPC over Websockets binding address. `None` if disabled. + pub rpc_ws: Option, + /// Telemetry service URL. `None` if disabled. + pub telemetry_url: Option, } impl Configuration { /// Create default config for given chain spec. pub fn default_with_spec(chain_spec: ChainSpec) -> Self { let mut configuration = Configuration { + impl_name: "parity-substrate", + impl_version: "0.0.0", + impl_commit: "", chain_spec, name: Default::default(), roles: Roles::FULL, @@ -75,8 +92,30 @@ impl Configuration String { + let env = Target::env(); + let env_dash = if env.is_empty() { "" } else { "-" }; + format!("{}-{}{}{}", Target::arch(), Target::os(), env_dash, env) + } + + /// Returns full version string. + pub fn full_version(&self) -> String { + let commit_dash = if self.impl_commit.is_empty() { "" } else { "-" }; + format!("{}{}{}-{}", self.impl_version, commit_dash, self.impl_commit, Self::platform()) + } + + /// Implementation id and version. + pub fn client_id(&self) -> String { + format!("{}/v{}", self.impl_name, self.full_version()) + } } diff --git a/substrate/substrate/service/src/error.rs b/substrate/substrate/service/src/error.rs index f3c3ff3811..abc09cb0c3 100644 --- a/substrate/substrate/service/src/error.rs +++ b/substrate/substrate/service/src/error.rs @@ -21,6 +21,9 @@ use network; use keystore; error_chain! { + foreign_links { + Io(::std::io::Error) #[doc="IO error"]; + } links { Client(client::error::Error, client::error::ErrorKind) #[doc="Client error"]; Network(network::error::Error, network::error::ErrorKind) #[doc="Network error"]; diff --git a/substrate/substrate/service/src/lib.rs b/substrate/substrate/service/src/lib.rs index 0ba8c3c704..fca2c4d058 100644 --- a/substrate/substrate/service/src/lib.rs +++ b/substrate/substrate/service/src/lib.rs @@ -30,11 +30,15 @@ extern crate substrate_network as network; extern crate substrate_executor; extern crate substrate_client as client; extern crate substrate_client_db as client_db; +extern crate substrate_codec as codec; extern crate substrate_extrinsic_pool as extrinsic_pool; +extern crate substrate_rpc; +extern crate substrate_rpc_servers as rpc; +extern crate target_info; extern crate tokio; #[macro_use] -extern crate substrate_telemetry; +extern crate substrate_telemetry as tel; #[macro_use] extern crate error_chain; #[macro_use] @@ -48,7 +52,10 @@ mod components; mod error; mod config; mod chain_spec; +pub mod chain_ops; +use std::io; +use std::net::SocketAddr; use std::sync::Arc; use futures::prelude::*; use keystore::Store as Keystore; @@ -64,11 +71,13 @@ pub use config::{Configuration, Roles, PruningMode}; pub use chain_spec::ChainSpec; pub use extrinsic_pool::txpool::{Options as ExtrinsicPoolOptions}; pub use extrinsic_pool::api::{ExtrinsicPool as ExtrinsicPoolApi}; +pub use client::ExecutionStrategy; pub use components::{ServiceFactory, FullBackend, FullExecutor, LightBackend, LightExecutor, ExtrinsicPool, Components, PoolApi, ComponentClient, ComponentBlock, FullClient, LightClient, FullComponents, LightComponents, - CodeExecutor, NetworkService, FactoryChainSpec, FactoryBlock, FactoryFullConfiguration, RuntimeGenesis, + CodeExecutor, NetworkService, FactoryChainSpec, FactoryBlock, + FactoryFullConfiguration, RuntimeGenesis, FactoryGenesis, }; /// Substrate service. @@ -78,6 +87,9 @@ pub struct Service { extrinsic_pool: Arc, keystore: Keystore, signal: Option, + _rpc_http: Option, + _rpc_ws: Option, + _telemetry: Option, } /// Creates bare client without any networking. @@ -121,6 +133,7 @@ impl Service let (client, on_demand) = Components::build_client(&config, executor)?; let best_header = client.best_block_header()?; + let version = config.full_version(); info!("Best block: #{}", best_header.number()); telemetry!("node.start"; "height" => best_header.number().as_(), "best" => ?best_header.hash()); @@ -178,12 +191,63 @@ impl Service task_executor.spawn(events); } + // RPC + let rpc_config = RpcConfig { + chain_name: config.chain_spec.name().to_string(), + impl_name: config.impl_name, + impl_version: config.impl_version, + }; + + let (rpc_http, rpc_ws) = { + let handler = || { + let client = client.clone(); + let chain = rpc::apis::chain::Chain::new(client.clone(), task_executor.clone()); + let author = rpc::apis::author::Author::new(client.clone(), extrinsic_pool.api(), task_executor.clone()); + rpc::rpc_handler::, _, _, _, _>( + client, + chain, + author, + rpc_config.clone(), + ) + }; + ( + maybe_start_server(config.rpc_http, |address| rpc::start_http(address, handler()))?, + maybe_start_server(config.rpc_ws, |address| rpc::start_ws(address, handler()))?, + ) + }; + + // Telemetry + let telemetry = match config.telemetry_url { + Some(url) => { + let name = config.name.clone(); + let impl_name = config.impl_name.to_owned(); + let version = version.clone(); + let chain_name = config.chain_spec.name().to_owned(); + Some(tel::init_telemetry(tel::TelemetryConfig { + url: url, + on_connect: Box::new(move || { + telemetry!("system.connected"; + "name" => name.clone(), + "implementation" => impl_name.clone(), + "version" => version.clone(), + "config" => "", + "chain" => chain_name.clone(), + ); + }), + })) + }, + None => None, + }; + Ok(Service { client: client, network: network, extrinsic_pool: extrinsic_pool, signal: Some(signal), keystore: keystore, + _rpc_http: rpc_http, + _rpc_ws: rpc_ws, + _telemetry: telemetry, }) } @@ -219,3 +283,42 @@ impl Drop for Service where Components: components::Comp } } } + +fn maybe_start_server(address: Option, start: F) -> Result, io::Error> where + F: Fn(&SocketAddr) -> Result, +{ + Ok(match address { + Some(mut address) => Some(start(&address) + .or_else(|e| match e.kind() { + io::ErrorKind::AddrInUse | + io::ErrorKind::PermissionDenied => { + warn!("Unable to bind server to {}. Trying random port.", address); + address.set_port(0); + start(&address) + }, + _ => Err(e), + })?), + None => None, + }) +} + +#[derive(Clone)] +struct RpcConfig { + chain_name: String, + impl_name: &'static str, + impl_version: &'static str, +} + +impl substrate_rpc::system::SystemApi for RpcConfig { + fn system_name(&self) -> substrate_rpc::system::error::Result { + Ok(self.impl_name.into()) + } + + fn system_version(&self) -> substrate_rpc::system::error::Result { + Ok(self.impl_version.into()) + } + + fn system_chain(&self) -> substrate_rpc::system::error::Result { + Ok(self.chain_name.clone()) + } +} diff --git a/substrate/substrate/telemetry/src/lib.rs b/substrate/substrate/telemetry/src/lib.rs index 62d16c1a81..4782f244e1 100644 --- a/substrate/substrate/telemetry/src/lib.rs +++ b/substrate/substrate/telemetry/src/lib.rs @@ -44,6 +44,9 @@ pub struct TelemetryConfig { pub on_connect: Box, } +/// Telemetry service guard. +pub type Telemetry = slog_scope::GlobalLoggerGuard; + /// Size of the channel for passing messages to telemetry thread. const CHANNEL_SIZE: usize = 262144;