diff --git a/Cargo.lock b/Cargo.lock index dddd6cddadc93..4174b5bbc2ff2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3412,6 +3412,7 @@ dependencies = [ "sc-tracing", "sc-transaction-pool", "serde", + "serde_json", "sp-authority-discovery", "sp-consensus", "sp-consensus-babe", diff --git a/bin/node-template/node/src/command.rs b/bin/node-template/node/src/command.rs index e7e386703deee..0f4c301dbff5b 100644 --- a/bin/node-template/node/src/command.rs +++ b/bin/node-template/node/src/command.rs @@ -15,32 +15,35 @@ // along with Substrate. If not, see . use sp_consensus_aura::sr25519::{AuthorityPair as AuraPair}; -use sc_cli::{VersionInfo, error}; +use sc_cli::VersionInfo; use crate::service; use crate::chain_spec; use crate::cli::Cli; /// Parse and run command line arguments -pub fn run(version: VersionInfo) -> error::Result<()> { +pub fn run(version: VersionInfo) -> sc_cli::Result<()> { let opt = sc_cli::from_args::(&version); - let config = sc_service::Configuration::new(&version); + let mut config = sc_service::Configuration::from_version(&version); match opt.subcommand { - Some(subcommand) => sc_cli::run_subcommand( - config, - subcommand, - chain_spec::load_spec, - |config: _| Ok(new_full_start!(config).0), - &version, - ), - None => sc_cli::run( - config, - opt.run, - service::new_light, - service::new_full, - chain_spec::load_spec, - &version, - ) + Some(subcommand) => { + subcommand.init(&version)?; + subcommand.update_config(&mut config, chain_spec::load_spec, &version)?; + subcommand.run( + config, + |config: _| Ok(new_full_start!(config).0), + ) + }, + None => { + opt.run.init(&version)?; + opt.run.update_config(&mut config, chain_spec::load_spec, &version)?; + opt.run.run( + config, + service::new_light, + service::new_full, + &version, + ) + }, } } diff --git a/bin/node-template/node/src/main.rs b/bin/node-template/node/src/main.rs index 9d0a57d77a851..91b2c257e0cd7 100644 --- a/bin/node-template/node/src/main.rs +++ b/bin/node-template/node/src/main.rs @@ -7,10 +7,8 @@ mod service; mod cli; mod command; -pub use sc_cli::{VersionInfo, error}; - -fn main() -> Result<(), error::Error> { - let version = VersionInfo { +fn main() -> sc_cli::Result<()> { + let version = sc_cli::VersionInfo { name: "Substrate Node", commit: env!("VERGEN_SHA_SHORT"), version: env!("CARGO_PKG_VERSION"), diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index d383e2c05a944..52be9e1d95fa9 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -107,6 +107,7 @@ futures = "0.3.1" tempfile = "3.1.0" assert_cmd = "0.12" nix = "0.17" +serde_json = "1.0" [build-dependencies] build-script-utils = { version = "2.0.0", package = "substrate-build-script-utils", path = "../../../utils/build-script-utils" } diff --git a/bin/node/cli/bin/main.rs b/bin/node/cli/bin/main.rs index e951c04710b92..8c4412667bace 100644 --- a/bin/node/cli/bin/main.rs +++ b/bin/node/cli/bin/main.rs @@ -18,10 +18,8 @@ #![warn(missing_docs)] -use sc_cli::VersionInfo; - -fn main() -> Result<(), sc_cli::error::Error> { - let version = VersionInfo { +fn main() -> sc_cli::Result<()> { + let version = sc_cli::VersionInfo { name: "Substrate Node", commit: env!("VERGEN_SHA_SHORT"), version: env!("CARGO_PKG_VERSION"), diff --git a/bin/node/cli/src/cli.rs b/bin/node/cli/src/cli.rs index 40f1dcf6f428d..b6db9c3deb7e3 100644 --- a/bin/node/cli/src/cli.rs +++ b/bin/node/cli/src/cli.rs @@ -19,11 +19,6 @@ use structopt::StructOpt; /// An overarching CLI command definition. #[derive(Clone, Debug, StructOpt)] -#[structopt(settings = &[ - structopt::clap::AppSettings::GlobalVersion, - structopt::clap::AppSettings::ArgsNegateSubcommands, - structopt::clap::AppSettings::SubcommandsNegateReqs, -])] pub struct Cli { /// Possible subcommand with parameters. #[structopt(subcommand)] diff --git a/bin/node/cli/src/command.rs b/bin/node/cli/src/command.rs index 5a942d964c9f9..dfdf5533f2b34 100644 --- a/bin/node/cli/src/command.rs +++ b/bin/node/cli/src/command.rs @@ -14,13 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use sc_cli::{VersionInfo, error}; +use sc_cli::VersionInfo; use sc_service::{Roles as ServiceRoles}; use node_transaction_factory::RuntimeAdapter; use crate::{Cli, service, ChainSpec, load_spec, Subcommand, factory_impl::FactoryState}; /// Parse command line arguments into service configuration. -pub fn run(args: I, version: VersionInfo) -> error::Result<()> +pub fn run(args: I, version: VersionInfo) -> sc_cli::Result<()> where I: Iterator, T: Into + Clone, @@ -28,19 +28,22 @@ where let args: Vec<_> = args.collect(); let opt = sc_cli::from_iter::(args.clone(), &version); - let mut config = sc_service::Configuration::new(&version); + let mut config = sc_service::Configuration::from_version(&version); match opt.subcommand { - None => sc_cli::run( - config, - opt.run, - service::new_light, - service::new_full, - load_spec, - &version, - ), + None => { + opt.run.init(&version)?; + opt.run.update_config(&mut config, load_spec, &version)?; + opt.run.run( + config, + service::new_light, + service::new_full, + &version, + ) + }, Some(Subcommand::Inspect(cmd)) => { - cmd.init(&mut config, load_spec, &version)?; + cmd.init(&version)?; + cmd.update_config(&mut config, load_spec, &version)?; let client = sc_service::new_full_client::< node_runtime::Block, node_runtime::RuntimeApi, node_executor::Executor, _, _, @@ -50,25 +53,27 @@ where cmd.run(inspect) }, Some(Subcommand::Benchmark(cmd)) => { - cmd.init(&mut config, load_spec, &version)?; + cmd.init(&version)?; + cmd.update_config(&mut config, load_spec, &version)?; cmd.run::<_, _, node_runtime::Block, node_executor::Executor>(config) }, Some(Subcommand::Factory(cli_args)) => { - sc_cli::init(&cli_args.shared_params, &version)?; - sc_cli::init_config(&mut config, &cli_args.shared_params, &version, load_spec)?; - sc_cli::fill_import_params( + cli_args.shared_params.init(&version)?; + cli_args.shared_params.update_config(&mut config, load_spec, &version)?; + cli_args.import_params.update_config( &mut config, - &cli_args.import_params, ServiceRoles::FULL, cli_args.shared_params.dev, )?; - sc_cli::fill_config_keystore_in_memory(&mut config)?; + config.use_in_memory_keystore()?; match ChainSpec::from(config.expect_chain_spec().id()) { Some(ref c) if c == &ChainSpec::Development || c == &ChainSpec::LocalTestnet => {}, - _ => panic!("Factory is only supported for development and local testnet."), + _ => return Err( + "Factory is only supported for development and local testnet.".into() + ), } // Setup tracing. @@ -77,7 +82,9 @@ where cli_args.import_params.tracing_receiver.into(), tracing_targets ); if let Err(e) = tracing::subscriber::set_global_default(subscriber) { - panic!("Unable to set global default subscriber {}", e); + return Err( + format!("Unable to set global default subscriber {}", e).into() + ); } } @@ -96,12 +103,13 @@ where Ok(()) }, - Some(Subcommand::Base(subcommand)) => sc_cli::run_subcommand( - config, - subcommand, - load_spec, - |config: service::NodeConfiguration| Ok(new_full_start!(config).0), - &version, - ), + Some(Subcommand::Base(subcommand)) => { + subcommand.init(&version)?; + subcommand.update_config(&mut config, load_spec, &version)?; + subcommand.run( + config, + |config: service::NodeConfiguration| Ok(new_full_start!(config).0), + ) + }, } } diff --git a/bin/node/cli/tests/build_spec_works.rs b/bin/node/cli/tests/build_spec_works.rs new file mode 100644 index 0000000000000..2eca71a5b5978 --- /dev/null +++ b/bin/node/cli/tests/build_spec_works.rs @@ -0,0 +1,37 @@ +// Copyright 2020 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 . + +use assert_cmd::cargo::cargo_bin; +use std::process::Command; +use tempfile::tempdir; + +#[test] +fn build_spec_works() { + let base_path = tempdir().expect("could not create a temp dir"); + + let output = Command::new(cargo_bin("substrate")) + .args(&["build-spec", "--dev", "-d"]) + .arg(base_path.path()) + .output() + .unwrap(); + assert!(output.status.success()); + + // Make sure that the `dev` chain folder exists, but the `db` doesn't + assert!(base_path.path().join("chains/dev/").exists()); + assert!(!base_path.path().join("chains/dev/db").exists()); + + let _value: serde_json::Value = serde_json::from_slice(output.stdout.as_slice()).unwrap(); +} diff --git a/client/cli/src/traits.rs b/bin/node/cli/tests/check_block_works.rs similarity index 57% rename from client/cli/src/traits.rs rename to bin/node/cli/tests/check_block_works.rs index 96216a172b970..e4c93c9e88528 100644 --- a/client/cli/src/traits.rs +++ b/bin/node/cli/tests/check_block_works.rs @@ -1,4 +1,4 @@ -// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// Copyright 2020 Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify @@ -14,10 +14,25 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use crate::params::SharedParams; +#![cfg(unix)] -/// Supports getting common params. -pub trait GetSharedParams { - /// Returns shared params if any. - fn shared_params(&self) -> Option<&SharedParams>; +use assert_cmd::cargo::cargo_bin; +use std::process::Command; +use tempfile::tempdir; + +mod common; + +#[test] +fn check_block_works() { + let base_path = tempdir().expect("could not create a temp dir"); + + common::run_command_for_a_while(base_path.path(), false); + + let status = Command::new(cargo_bin("substrate")) + .args(&["check-block", "-d"]) + .arg(base_path.path()) + .arg("1") + .status() + .unwrap(); + assert!(status.success()); } diff --git a/bin/node/cli/tests/common.rs b/bin/node/cli/tests/common.rs index 4044f69d08168..93a4a3e4e5789 100644 --- a/bin/node/cli/tests/common.rs +++ b/bin/node/cli/tests/common.rs @@ -14,7 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use std::{process::{Child, ExitStatus}, thread, time::Duration}; +#![cfg(unix)] +#![allow(dead_code)] + +use std::{process::{Child, ExitStatus}, thread, time::Duration, path::Path}; +use assert_cmd::cargo::cargo_bin; +use std::{convert::TryInto, process::Command}; +use nix::sys::signal::{kill, Signal::SIGINT}; +use nix::unistd::Pid; /// Wait for the given `child` the given number of `secs`. /// @@ -32,3 +39,26 @@ pub fn wait_for(child: &mut Child, secs: usize) -> Option { None } + +/// Run the node for a while (30 seconds) +pub fn run_command_for_a_while(base_path: &Path, dev: bool) { + let mut cmd = Command::new(cargo_bin("substrate")); + + if dev { + cmd.arg("--dev"); + } + + let mut cmd = cmd + .arg("-d") + .arg(base_path) + .spawn() + .unwrap(); + + // Let it produce some blocks. + thread::sleep(Duration::from_secs(30)); + assert!(cmd.try_wait().unwrap().is_none(), "the process should still be running"); + + // Stop the process + kill(Pid::from_raw(cmd.id().try_into().unwrap()), SIGINT).unwrap(); + assert!(wait_for(&mut cmd, 20).map(|x| x.success()).unwrap_or_default()); +} diff --git a/bin/node/cli/tests/factory.rs b/bin/node/cli/tests/factory.rs new file mode 100644 index 0000000000000..2930cd52e2e16 --- /dev/null +++ b/bin/node/cli/tests/factory.rs @@ -0,0 +1,40 @@ +// Copyright 2020 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 . + +#![cfg(unix)] + +use assert_cmd::cargo::cargo_bin; +use std::process::{Command, Stdio}; +use tempfile::tempdir; + +mod common; + +#[test] +fn factory_works() { + let base_path = tempdir().expect("could not create a temp dir"); + + let status = Command::new(cargo_bin("substrate")) + .stdout(Stdio::null()) + .args(&["factory", "--dev", "-d"]) + .arg(base_path.path()) + .status() + .unwrap(); + assert!(status.success()); + + // Make sure that the `dev` chain folder exists & `db` + assert!(base_path.path().join("chains/dev/").exists()); + assert!(base_path.path().join("chains/dev/db").exists()); +} diff --git a/bin/node/cli/tests/import_export_and_revert_work.rs b/bin/node/cli/tests/import_export_and_revert_work.rs new file mode 100644 index 0000000000000..e109aa279eb88 --- /dev/null +++ b/bin/node/cli/tests/import_export_and_revert_work.rs @@ -0,0 +1,59 @@ +// Copyright 2020 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 . + +#![cfg(unix)] + +use assert_cmd::cargo::cargo_bin; +use std::{process::Command, fs}; +use tempfile::tempdir; + +mod common; + +#[test] +fn import_export_and_revert_work() { + let base_path = tempdir().expect("could not create a temp dir"); + let exported_blocks = base_path.path().join("exported_blocks"); + + common::run_command_for_a_while(base_path.path(), false); + + let status = Command::new(cargo_bin("substrate")) + .args(&["export-blocks", "-d"]) + .arg(base_path.path()) + .arg(&exported_blocks) + .status() + .unwrap(); + assert!(status.success()); + + let metadata = fs::metadata(&exported_blocks).unwrap(); + assert!(metadata.len() > 0, "file exported_blocks should not be empty"); + + let _ = fs::remove_dir_all(base_path.path().join("db")); + + let status = Command::new(cargo_bin("substrate")) + .args(&["import-blocks", "-d"]) + .arg(base_path.path()) + .arg(&exported_blocks) + .status() + .unwrap(); + assert!(status.success()); + + let status = Command::new(cargo_bin("substrate")) + .args(&["revert", "-d"]) + .arg(base_path.path()) + .status() + .unwrap(); + assert!(status.success()); +} diff --git a/bin/node/cli/tests/inspect_works.rs b/bin/node/cli/tests/inspect_works.rs new file mode 100644 index 0000000000000..0bd48c3693802 --- /dev/null +++ b/bin/node/cli/tests/inspect_works.rs @@ -0,0 +1,38 @@ +// Copyright 2020 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 . + +#![cfg(unix)] + +use assert_cmd::cargo::cargo_bin; +use std::process::Command; +use tempfile::tempdir; + +mod common; + +#[test] +fn inspect_works() { + let base_path = tempdir().expect("could not create a temp dir"); + + common::run_command_for_a_while(base_path.path(), false); + + let status = Command::new(cargo_bin("substrate")) + .args(&["inspect", "-d"]) + .arg(base_path.path()) + .args(&["block", "1"]) + .status() + .unwrap(); + assert!(status.success()); +} diff --git a/bin/node/cli/tests/purge_chain_works.rs b/bin/node/cli/tests/purge_chain_works.rs index e6b71db991048..42a5bc3ce143e 100644 --- a/bin/node/cli/tests/purge_chain_works.rs +++ b/bin/node/cli/tests/purge_chain_works.rs @@ -15,39 +15,27 @@ // along with Substrate. If not, see . use assert_cmd::cargo::cargo_bin; -use std::{convert::TryInto, process::Command, thread, time::Duration, fs, path::PathBuf}; +use std::process::Command; +use tempfile::tempdir; mod common; #[test] #[cfg(unix)] fn purge_chain_works() { - use nix::sys::signal::{kill, Signal::SIGINT}; - use nix::unistd::Pid; + let base_path = tempdir().expect("could not create a temp dir"); - let base_path = "purge_chain_test"; - - let _ = fs::remove_dir_all(base_path); - let mut cmd = Command::new(cargo_bin("substrate")) - .args(&["--dev", "-d", base_path]) - .spawn() - .unwrap(); - - // Let it produce some blocks. - thread::sleep(Duration::from_secs(30)); - assert!(cmd.try_wait().unwrap().is_none(), "the process should still be running"); - - // Stop the process - kill(Pid::from_raw(cmd.id().try_into().unwrap()), SIGINT).unwrap(); - assert!(common::wait_for(&mut cmd, 30).map(|x| x.success()).unwrap_or_default()); + common::run_command_for_a_while(base_path.path(), true); let status = Command::new(cargo_bin("substrate")) - .args(&["purge-chain", "--dev", "-d", base_path, "-y"]) + .args(&["purge-chain", "--dev", "-d"]) + .arg(base_path.path()) + .arg("-y") .status() .unwrap(); assert!(status.success()); // Make sure that the `dev` chain folder exists, but the `db` is deleted. - assert!(PathBuf::from(base_path).join("chains/dev/").exists()); - assert!(!PathBuf::from(base_path).join("chains/dev/db").exists()); + assert!(base_path.path().join("chains/dev/").exists()); + assert!(!base_path.path().join("chains/dev/db").exists()); } diff --git a/bin/node/cli/tests/running_the_node_and_interrupt.rs b/bin/node/cli/tests/running_the_node_and_interrupt.rs index dbb57bdd21ab8..67efedccbe771 100644 --- a/bin/node/cli/tests/running_the_node_and_interrupt.rs +++ b/bin/node/cli/tests/running_the_node_and_interrupt.rs @@ -15,7 +15,8 @@ // along with Substrate. If not, see . use assert_cmd::cargo::cargo_bin; -use std::{convert::TryInto, process::Command, thread, time::Duration, fs}; +use std::{convert::TryInto, process::Command, thread, time::Duration}; +use tempfile::tempdir; mod common; @@ -26,13 +27,14 @@ fn running_the_node_works_and_can_be_interrupted() { use nix::unistd::Pid; fn run_command_and_kill(signal: Signal) { - let _ = fs::remove_dir_all("interrupt_test"); + let base_path = tempdir().expect("could not create a temp dir"); let mut cmd = Command::new(cargo_bin("substrate")) - .args(&["--dev", "-d", "interrupt_test"]) + .args(&["--dev", "-d"]) + .arg(base_path.path()) .spawn() .unwrap(); - thread::sleep(Duration::from_secs(30)); + thread::sleep(Duration::from_secs(20)); assert!(cmd.try_wait().unwrap().is_none(), "the process should still be running"); kill(Pid::from_raw(cmd.id().try_into().unwrap()), signal).unwrap(); assert_eq!( diff --git a/bin/node/inspect/src/cli.rs b/bin/node/inspect/src/cli.rs index 27afcfff919ee..5d51bd5848f17 100644 --- a/bin/node/inspect/src/cli.rs +++ b/bin/node/inspect/src/cli.rs @@ -16,12 +16,8 @@ //! Structs to easily compose inspect sub-command for CLI. -use std::{ - fmt::Debug, - str::FromStr, -}; -use crate::{Inspector, PrettyPrinter}; -use sc_cli::{ImportParams, SharedParams, error}; +use std::fmt::Debug; +use sc_cli::{ImportParams, SharedParams}; use structopt::StructOpt; /// The `inspect` command used to print decoded chain data. @@ -64,177 +60,3 @@ pub enum InspectSubCmd { input: String, }, } - -impl InspectCmd { - /// Parse CLI arguments and initialize given config. - pub fn init( - &self, - config: &mut sc_service::config::Configuration, - spec_factory: impl FnOnce(&str) -> Result>, String>, - version: &sc_cli::VersionInfo, - ) -> error::Result<()> where - G: sc_service::RuntimeGenesis, - E: sc_service::ChainSpecExtension, - { - sc_cli::init_config(config, &self.shared_params, version, spec_factory)?; - // make sure to configure keystore - sc_cli::fill_config_keystore_in_memory(config)?; - // and all import params (especially pruning that has to match db meta) - sc_cli::fill_import_params( - config, - &self.import_params, - sc_service::Roles::FULL, - self.shared_params.dev, - )?; - Ok(()) - } - - /// Run the inspect command, passing the inspector. - pub fn run( - self, - inspect: Inspector, - ) -> error::Result<()> where - B: sp_runtime::traits::Block, - B::Hash: FromStr, - P: PrettyPrinter, - { - match self.command { - InspectSubCmd::Block { input } => { - let input = input.parse()?; - let res = inspect.block(input) - .map_err(|e| format!("{}", e))?; - println!("{}", res); - Ok(()) - }, - InspectSubCmd::Extrinsic { input } => { - let input = input.parse()?; - let res = inspect.extrinsic(input) - .map_err(|e| format!("{}", e))?; - println!("{}", res); - Ok(()) - }, - } - } -} - - -/// A block to retrieve. -#[derive(Debug, Clone, PartialEq)] -pub enum BlockAddress { - /// Get block by hash. - Hash(Hash), - /// Get block by number. - Number(Number), - /// Raw SCALE-encoded bytes. - Bytes(Vec), -} - -impl FromStr for BlockAddress { - type Err = String; - - fn from_str(s: &str) -> Result { - // try to parse hash first - if let Ok(hash) = s.parse() { - return Ok(Self::Hash(hash)) - } - - // then number - if let Ok(number) = s.parse() { - return Ok(Self::Number(number)) - } - - // then assume it's bytes (hex-encoded) - sp_core::bytes::from_hex(s) - .map(Self::Bytes) - .map_err(|e| format!( - "Given string does not look like hash or number. It could not be parsed as bytes either: {}", - e - )) - } -} - -/// An extrinsic address to decode and print out. -#[derive(Debug, Clone, PartialEq)] -pub enum ExtrinsicAddress { - /// Extrinsic as part of existing block. - Block(BlockAddress, usize), - /// Raw SCALE-encoded extrinsic bytes. - Bytes(Vec), -} - -impl FromStr for ExtrinsicAddress { - type Err = String; - - fn from_str(s: &str) -> Result { - // first try raw bytes - if let Ok(bytes) = sp_core::bytes::from_hex(s).map(Self::Bytes) { - return Ok(bytes) - } - - // split by a bunch of different characters - let mut it = s.split(|c| c == '.' || c == ':' || c == ' '); - let block = it.next() - .expect("First element of split iterator is never empty; qed") - .parse()?; - - let index = it.next() - .ok_or_else(|| format!("Extrinsic index missing: example \"5:0\""))? - .parse() - .map_err(|e| format!("Invalid index format: {}", e))?; - - Ok(Self::Block(block, index)) - } -} - - -#[cfg(test)] -mod tests { - use super::*; - use sp_core::hash::H160 as Hash; - - #[test] - fn should_parse_block_strings() { - type BlockAddress = super::BlockAddress; - - let b0 = BlockAddress::from_str("3BfC20f0B9aFcAcE800D73D2191166FF16540258"); - let b1 = BlockAddress::from_str("1234"); - let b2 = BlockAddress::from_str("0"); - let b3 = BlockAddress::from_str("0x0012345f"); - - - assert_eq!(b0, Ok(BlockAddress::Hash( - "3BfC20f0B9aFcAcE800D73D2191166FF16540258".parse().unwrap() - ))); - assert_eq!(b1, Ok(BlockAddress::Number(1234))); - assert_eq!(b2, Ok(BlockAddress::Number(0))); - assert_eq!(b3, Ok(BlockAddress::Bytes(vec![0, 0x12, 0x34, 0x5f]))); - } - - #[test] - fn should_parse_extrinsic_address() { - type BlockAddress = super::BlockAddress; - type ExtrinsicAddress = super::ExtrinsicAddress; - - let e0 = ExtrinsicAddress::from_str("1234"); - let b0 = ExtrinsicAddress::from_str("3BfC20f0B9aFcAcE800D73D2191166FF16540258:5"); - let b1 = ExtrinsicAddress::from_str("1234:0"); - let b2 = ExtrinsicAddress::from_str("0 0"); - let b3 = ExtrinsicAddress::from_str("0x0012345f"); - - - assert_eq!(e0, Err("Extrinsic index missing: example \"5:0\"".into())); - assert_eq!(b0, Ok(ExtrinsicAddress::Block( - BlockAddress::Hash("3BfC20f0B9aFcAcE800D73D2191166FF16540258".parse().unwrap()), - 5 - ))); - assert_eq!(b1, Ok(ExtrinsicAddress::Block( - BlockAddress::Number(1234), - 0 - ))); - assert_eq!(b2, Ok(ExtrinsicAddress::Block( - BlockAddress::Number(0), - 0 - ))); - assert_eq!(b3, Ok(ExtrinsicAddress::Bytes(vec![0, 0x12, 0x34, 0x5f]))); - } -} diff --git a/bin/node/inspect/src/command.rs b/bin/node/inspect/src/command.rs new file mode 100644 index 0000000000000..71e70e3e44fd4 --- /dev/null +++ b/bin/node/inspect/src/command.rs @@ -0,0 +1,204 @@ +// Copyright 2020 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 . + +//! Command ran by the CLI + +use std::{ + fmt::Debug, + str::FromStr, +}; + +use crate::cli::{InspectCmd, InspectSubCmd}; +use crate::{Inspector, PrettyPrinter}; + +impl InspectCmd { + /// Initialize + pub fn init(&self, version: &sc_cli::VersionInfo) -> sc_cli::Result<()> { + self.shared_params.init(version) + } + + /// Parse CLI arguments and initialize given config. + pub fn update_config( + &self, + mut config: &mut sc_service::config::Configuration, + spec_factory: impl FnOnce(&str) -> Result>, String>, + version: &sc_cli::VersionInfo, + ) -> sc_cli::Result<()> where + G: sc_service::RuntimeGenesis, + E: sc_service::ChainSpecExtension, + { + self.shared_params.update_config(config, spec_factory, version)?; + + // make sure to configure keystore + config.use_in_memory_keystore()?; + + // and all import params (especially pruning that has to match db meta) + self.import_params.update_config( + &mut config, + sc_service::Roles::FULL, + self.shared_params.dev, + )?; + + Ok(()) + } + + /// Run the inspect command, passing the inspector. + pub fn run( + self, + inspect: Inspector, + ) -> sc_cli::Result<()> where + B: sp_runtime::traits::Block, + B::Hash: FromStr, + P: PrettyPrinter, + { + match self.command { + InspectSubCmd::Block { input } => { + let input = input.parse()?; + let res = inspect.block(input) + .map_err(|e| format!("{}", e))?; + println!("{}", res); + Ok(()) + }, + InspectSubCmd::Extrinsic { input } => { + let input = input.parse()?; + let res = inspect.extrinsic(input) + .map_err(|e| format!("{}", e))?; + println!("{}", res); + Ok(()) + }, + } + } +} + +/// A block to retrieve. +#[derive(Debug, Clone, PartialEq)] +pub enum BlockAddress { + /// Get block by hash. + Hash(Hash), + /// Get block by number. + Number(Number), + /// Raw SCALE-encoded bytes. + Bytes(Vec), +} + +impl FromStr for BlockAddress { + type Err = String; + + fn from_str(s: &str) -> Result { + // try to parse hash first + if let Ok(hash) = s.parse() { + return Ok(Self::Hash(hash)) + } + + // then number + if let Ok(number) = s.parse() { + return Ok(Self::Number(number)) + } + + // then assume it's bytes (hex-encoded) + sp_core::bytes::from_hex(s) + .map(Self::Bytes) + .map_err(|e| format!( + "Given string does not look like hash or number. It could not be parsed as bytes either: {}", + e + )) + } +} + +/// An extrinsic address to decode and print out. +#[derive(Debug, Clone, PartialEq)] +pub enum ExtrinsicAddress { + /// Extrinsic as part of existing block. + Block(BlockAddress, usize), + /// Raw SCALE-encoded extrinsic bytes. + Bytes(Vec), +} + +impl FromStr for ExtrinsicAddress { + type Err = String; + + fn from_str(s: &str) -> Result { + // first try raw bytes + if let Ok(bytes) = sp_core::bytes::from_hex(s).map(Self::Bytes) { + return Ok(bytes) + } + + // split by a bunch of different characters + let mut it = s.split(|c| c == '.' || c == ':' || c == ' '); + let block = it.next() + .expect("First element of split iterator is never empty; qed") + .parse()?; + + let index = it.next() + .ok_or_else(|| format!("Extrinsic index missing: example \"5:0\""))? + .parse() + .map_err(|e| format!("Invalid index format: {}", e))?; + + Ok(Self::Block(block, index)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_core::hash::H160 as Hash; + + #[test] + fn should_parse_block_strings() { + type BlockAddress = super::BlockAddress; + + let b0 = BlockAddress::from_str("3BfC20f0B9aFcAcE800D73D2191166FF16540258"); + let b1 = BlockAddress::from_str("1234"); + let b2 = BlockAddress::from_str("0"); + let b3 = BlockAddress::from_str("0x0012345f"); + + + assert_eq!(b0, Ok(BlockAddress::Hash( + "3BfC20f0B9aFcAcE800D73D2191166FF16540258".parse().unwrap() + ))); + assert_eq!(b1, Ok(BlockAddress::Number(1234))); + assert_eq!(b2, Ok(BlockAddress::Number(0))); + assert_eq!(b3, Ok(BlockAddress::Bytes(vec![0, 0x12, 0x34, 0x5f]))); + } + + #[test] + fn should_parse_extrinsic_address() { + type BlockAddress = super::BlockAddress; + type ExtrinsicAddress = super::ExtrinsicAddress; + + let e0 = ExtrinsicAddress::from_str("1234"); + let b0 = ExtrinsicAddress::from_str("3BfC20f0B9aFcAcE800D73D2191166FF16540258:5"); + let b1 = ExtrinsicAddress::from_str("1234:0"); + let b2 = ExtrinsicAddress::from_str("0 0"); + let b3 = ExtrinsicAddress::from_str("0x0012345f"); + + + assert_eq!(e0, Err("Extrinsic index missing: example \"5:0\"".into())); + assert_eq!(b0, Ok(ExtrinsicAddress::Block( + BlockAddress::Hash("3BfC20f0B9aFcAcE800D73D2191166FF16540258".parse().unwrap()), + 5 + ))); + assert_eq!(b1, Ok(ExtrinsicAddress::Block( + BlockAddress::Number(1234), + 0 + ))); + assert_eq!(b2, Ok(ExtrinsicAddress::Block( + BlockAddress::Number(0), + 0 + ))); + assert_eq!(b3, Ok(ExtrinsicAddress::Bytes(vec![0, 0x12, 0x34, 0x5f]))); + } +} diff --git a/bin/node/inspect/src/lib.rs b/bin/node/inspect/src/lib.rs index 5c4e18c0a74a6..cd32f08e9fe59 100644 --- a/bin/node/inspect/src/lib.rs +++ b/bin/node/inspect/src/lib.rs @@ -23,6 +23,7 @@ #![warn(missing_docs)] pub mod cli; +pub mod command; use std::{ fmt, @@ -37,8 +38,10 @@ use sp_runtime::{ traits::{Block, HashFor, NumberFor, Hash} }; +use command::{BlockAddress, ExtrinsicAddress}; + /// A helper type for a generic block input. -pub type BlockAddressFor = cli::BlockAddress< +pub type BlockAddressFor = BlockAddress< as Hash>::Output, NumberFor >; @@ -148,10 +151,10 @@ impl> Inspector fn get_block(&self, input: BlockAddressFor) -> Result { Ok(match input { - cli::BlockAddress::Bytes(bytes) => { + BlockAddress::Bytes(bytes) => { TBlock::decode(&mut &*bytes)? }, - cli::BlockAddress::Number(number) => { + BlockAddress::Number(number) => { let id = BlockId::number(number); let not_found = format!("Could not find block {:?}", id); let body = self.chain.block_body(&id)? @@ -160,7 +163,7 @@ impl> Inspector .ok_or_else(|| Error::NotFound(not_found.clone()))?; TBlock::new(header, body) }, - cli::BlockAddress::Hash(hash) => { + BlockAddress::Hash(hash) => { let id = BlockId::hash(hash); let not_found = format!("Could not find block {:?}", id); let body = self.chain.block_body(&id)? @@ -175,7 +178,7 @@ impl> Inspector /// Get a pretty-printed extrinsic. pub fn extrinsic( &self, - input: cli::ExtrinsicAddress< as Hash>::Output, NumberFor>, + input: ExtrinsicAddress< as Hash>::Output, NumberFor>, ) -> Result { struct ExtrinsicPrinter<'a, A: Block, B>(A::Extrinsic, &'a B); impl<'a, A: Block, B: PrettyPrinter> fmt::Display for ExtrinsicPrinter<'a, A, B> { @@ -185,7 +188,7 @@ impl> Inspector } let ext = match input { - cli::ExtrinsicAddress::Block(block, index) => { + ExtrinsicAddress::Block(block, index) => { let block = self.get_block(block)?; block.extrinsics() .get(index) @@ -194,7 +197,7 @@ impl> Inspector "Could not find extrinsic {} in block {:?}", index, block )))? }, - cli::ExtrinsicAddress::Bytes(bytes) => { + ExtrinsicAddress::Bytes(bytes) => { TBlock::Extrinsic::decode(&mut &*bytes)? } }; diff --git a/bin/node/transaction-factory/src/lib.rs b/bin/node/transaction-factory/src/lib.rs index 7dafeed40616e..a4c001145ac7e 100644 --- a/bin/node/transaction-factory/src/lib.rs +++ b/bin/node/transaction-factory/src/lib.rs @@ -83,7 +83,7 @@ pub fn factory( mut factory_state: RA, client: &Arc>, select_chain: &Sc, -) -> sc_cli::error::Result<()> +) -> sc_cli::Result<()> where Block: BlockT, Exec: sc_client::CallExecutor + Send + Sync + Clone, @@ -97,7 +97,7 @@ where RA: RuntimeAdapter, Block::Hash: From, { - let best_header: Result<::Header, sc_cli::error::Error> = + let best_header: Result<::Header, sc_cli::Error> = select_chain.best_chain().map_err(|e| format!("{:?}", e).into()); let mut best_hash = best_header?.hash(); let mut best_block_id = BlockId::::hash(best_hash); diff --git a/client/cli/src/execution_strategy.rs b/client/cli/src/arg_enums.rs similarity index 51% rename from client/cli/src/execution_strategy.rs rename to client/cli/src/arg_enums.rs index 888d7b6c4a096..384087bec0dbe 100644 --- a/client/cli/src/execution_strategy.rs +++ b/client/cli/src/arg_enums.rs @@ -14,10 +14,74 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . +// NOTE: we allow missing docs here because arg_enum! creates the function variants without doc #![allow(missing_docs)] use structopt::clap::arg_enum; +arg_enum! { + /// How to execute Wasm runtime code + #[allow(missing_docs)] + #[derive(Debug, Clone, Copy)] + pub enum WasmExecutionMethod { + // Uses an interpreter. + Interpreted, + // Uses a compiled runtime. + Compiled, + } +} + +impl WasmExecutionMethod { + /// Returns list of variants that are not disabled by feature flags. + pub fn enabled_variants() -> Vec<&'static str> { + Self::variants() + .iter() + .cloned() + .filter(|&name| cfg!(feature = "wasmtime") || name != "Compiled") + .collect() + } +} + +impl Into for WasmExecutionMethod { + fn into(self) -> sc_service::config::WasmExecutionMethod { + match self { + WasmExecutionMethod::Interpreted => sc_service::config::WasmExecutionMethod::Interpreted, + #[cfg(feature = "wasmtime")] + WasmExecutionMethod::Compiled => sc_service::config::WasmExecutionMethod::Compiled, + #[cfg(not(feature = "wasmtime"))] + WasmExecutionMethod::Compiled => panic!( + "Substrate must be compiled with \"wasmtime\" feature for compiled Wasm execution" + ), + } + } +} + +arg_enum! { + #[allow(missing_docs)] + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub enum TracingReceiver { + Log, + Telemetry, + } +} + +impl Into for TracingReceiver { + fn into(self) -> sc_tracing::TracingReceiver { + match self { + TracingReceiver::Log => sc_tracing::TracingReceiver::Log, + TracingReceiver::Telemetry => sc_tracing::TracingReceiver::Telemetry, + } + } +} + +arg_enum! { + #[allow(missing_docs)] + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub enum NodeKeyType { + Ed25519 + } +} + arg_enum! { /// How to execute blocks #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -33,6 +97,17 @@ arg_enum! { } } +impl Into for ExecutionStrategy { + fn into(self) -> sc_client_api::ExecutionStrategy { + match self { + ExecutionStrategy::Native => sc_client_api::ExecutionStrategy::NativeWhenPossible, + ExecutionStrategy::Wasm => sc_client_api::ExecutionStrategy::AlwaysWasm, + ExecutionStrategy::Both => sc_client_api::ExecutionStrategy::Both, + ExecutionStrategy::NativeElseWasm => sc_client_api::ExecutionStrategy::NativeElseWasm, + } + } +} + impl ExecutionStrategy { /// Returns the variant as `'&static str`. pub fn as_str(&self) -> &'static str { diff --git a/client/cli/src/commands/build_spec_cmd.rs b/client/cli/src/commands/build_spec_cmd.rs new file mode 100644 index 0000000000000..9b71207efabf8 --- /dev/null +++ b/client/cli/src/commands/build_spec_cmd.rs @@ -0,0 +1,104 @@ +// Copyright 2018-2020 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 . + +use structopt::StructOpt; +use log::info; +use sc_network::config::build_multiaddr; +use sc_service::{Configuration, ChainSpecExtension, RuntimeGenesis, ChainSpec}; + +use crate::error; +use crate::VersionInfo; +use crate::params::SharedParams; +use crate::params::NodeKeyParams; + +/// The `build-spec` command used to build a specification. +#[derive(Debug, StructOpt, Clone)] +pub struct BuildSpecCmd { + /// Force raw genesis storage output. + #[structopt(long = "raw")] + pub raw: bool, + + /// Disable adding the default bootnode to the specification. + /// + /// By default the `/ip4/127.0.0.1/tcp/30333/p2p/NODE_PEER_ID` bootnode is added to the + /// specification when no bootnode exists. + #[structopt(long = "disable-default-bootnode")] + pub disable_default_bootnode: bool, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub shared_params: SharedParams, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub node_key_params: NodeKeyParams, +} + +impl BuildSpecCmd { + /// Run the build-spec command + pub fn run( + self, + config: Configuration, + ) -> error::Result<()> + where + G: RuntimeGenesis, + E: ChainSpecExtension, + { + info!("Building chain spec"); + let mut spec = config.expect_chain_spec().clone(); + let raw_output = self.raw; + + if spec.boot_nodes().is_empty() && !self.disable_default_bootnode { + let keys = config.network.node_key.into_keypair()?; + let peer_id = keys.public().into_peer_id(); + let addr = build_multiaddr![ + Ip4([127, 0, 0, 1]), + Tcp(30333u16), + P2p(peer_id) + ]; + spec.add_boot_node(addr) + } + + let json = sc_service::chain_ops::build_spec(spec, raw_output)?; + + print!("{}", json); + + Ok(()) + } + + /// Update and prepare a `Configuration` with command line parameters + pub fn update_config( + &self, + mut config: &mut Configuration, + spec_factory: F, + version: &VersionInfo, + ) -> error::Result<()> where + G: RuntimeGenesis, + E: ChainSpecExtension, + F: FnOnce(&str) -> Result>, String>, + { + self.shared_params.update_config(&mut config, spec_factory, version)?; + + let net_config_path = config + .in_chain_config_dir(crate::commands::DEFAULT_NETWORK_CONFIG_PATH) + .expect("We provided a base_path"); + + self.node_key_params.update_config(&mut config, Some(&net_config_path))?; + + Ok(()) + } +} + diff --git a/client/cli/src/commands/check_block_cmd.rs b/client/cli/src/commands/check_block_cmd.rs new file mode 100644 index 0000000000000..1036be16de4eb --- /dev/null +++ b/client/cli/src/commands/check_block_cmd.rs @@ -0,0 +1,105 @@ +// Copyright 2018-2020 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 . + +use std::fmt::Debug; +use std::str::FromStr; +use structopt::StructOpt; +use sc_service::{ + Configuration, ChainSpecExtension, RuntimeGenesis, ServiceBuilderCommand, Roles, ChainSpec, +}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; +use sp_runtime::generic::BlockId; + +use crate::error; +use crate::VersionInfo; +use crate::runtime::run_until_exit; +use crate::params::SharedParams; +use crate::params::ImportParams; + +/// The `check-block` command used to validate blocks. +#[derive(Debug, StructOpt, Clone)] +pub struct CheckBlockCmd { + /// Block hash or number + #[structopt(value_name = "HASH or NUMBER")] + pub input: String, + + /// The default number of 64KB pages to ever allocate for Wasm execution. + /// + /// Don't alter this unless you know what you're doing. + #[structopt(long = "default-heap-pages", value_name = "COUNT")] + pub default_heap_pages: Option, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub shared_params: SharedParams, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub import_params: ImportParams, +} + +impl CheckBlockCmd { + /// Run the check-block command + pub fn run( + self, + config: Configuration, + builder: B, + ) -> error::Result<()> + where + B: FnOnce(Configuration) -> Result, + G: RuntimeGenesis, + E: ChainSpecExtension, + BC: ServiceBuilderCommand + Unpin, + BB: sp_runtime::traits::Block + Debug, + <<::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug, + ::Hash: std::str::FromStr, + { + let input = if self.input.starts_with("0x") { &self.input[2..] } else { &self.input[..] }; + let block_id = match FromStr::from_str(input) { + Ok(hash) => BlockId::hash(hash), + Err(_) => match self.input.parse::() { + Ok(n) => BlockId::number((n as u32).into()), + Err(_) => return Err(error::Error::Input("Invalid hash or number specified".into())), + } + }; + + let start = std::time::Instant::now(); + run_until_exit(config, |config| { + Ok(builder(config)?.check_block(block_id)) + })?; + println!("Completed in {} ms.", start.elapsed().as_millis()); + + Ok(()) + } + + /// Update and prepare a `Configuration` with command line parameters + pub fn update_config( + &self, + mut config: &mut Configuration, + spec_factory: F, + version: &VersionInfo, + ) -> error::Result<()> where + G: RuntimeGenesis, + E: ChainSpecExtension, + F: FnOnce(&str) -> Result>, String>, + { + self.shared_params.update_config(&mut config, spec_factory, version)?; + self.import_params.update_config(&mut config, Roles::FULL, self.shared_params.dev)?; + config.use_in_memory_keystore()?; + + Ok(()) + } +} diff --git a/client/cli/src/commands/export_blocks_cmd.rs b/client/cli/src/commands/export_blocks_cmd.rs new file mode 100644 index 0000000000000..8db650ae8c88c --- /dev/null +++ b/client/cli/src/commands/export_blocks_cmd.rs @@ -0,0 +1,113 @@ +// Copyright 2018-2020 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 . + +use std::io; +use std::fs; +use std::path::PathBuf; +use std::fmt::Debug; +use log::info; +use structopt::StructOpt; +use sc_service::{ + Configuration, ChainSpecExtension, RuntimeGenesis, ServiceBuilderCommand, ChainSpec, + config::DatabaseConfig, +}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; + +use crate::error; +use crate::VersionInfo; +use crate::runtime::run_until_exit; +use crate::params::SharedParams; +use crate::params::BlockNumber; + +/// The `export-blocks` command used to export blocks. +#[derive(Debug, StructOpt, Clone)] +pub struct ExportBlocksCmd { + /// Output file name or stdout if unspecified. + #[structopt(parse(from_os_str))] + pub output: Option, + + /// Specify starting block number. + /// + /// Default is 1. + #[structopt(long = "from", value_name = "BLOCK")] + pub from: Option, + + /// Specify last block number. + /// + /// Default is best block. + #[structopt(long = "to", value_name = "BLOCK")] + pub to: Option, + + /// Use JSON output rather than binary. + #[structopt(long = "json")] + pub json: bool, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub shared_params: SharedParams, +} + +impl ExportBlocksCmd { + /// Run the export-blocks command + pub fn run( + self, + config: Configuration, + builder: B, + ) -> error::Result<()> + where + B: FnOnce(Configuration) -> Result, + G: RuntimeGenesis, + E: ChainSpecExtension, + BC: ServiceBuilderCommand + Unpin, + BB: sp_runtime::traits::Block + Debug, + <<::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug, + ::Hash: std::str::FromStr, + { + if let DatabaseConfig::Path { ref path, .. } = config.expect_database() { + info!("DB path: {}", path.display()); + } + let from = self.from.as_ref().and_then(|f| f.parse().ok()).unwrap_or(1); + let to = self.to.as_ref().and_then(|t| t.parse().ok()); + + let json = self.json; + + let file: Box = match &self.output { + Some(filename) => Box::new(fs::File::create(filename)?), + None => Box::new(io::stdout()), + }; + + run_until_exit(config, |config| { + Ok(builder(config)?.export_blocks(file, from.into(), to, json)) + }) + } + + /// Update and prepare a `Configuration` with command line parameters + pub fn update_config( + &self, + mut config: &mut Configuration, + spec_factory: F, + version: &VersionInfo, + ) -> error::Result<()> where + G: RuntimeGenesis, + E: ChainSpecExtension, + F: FnOnce(&str) -> Result>, String>, + { + self.shared_params.update_config(&mut config, spec_factory, version)?; + config.use_in_memory_keystore()?; + + Ok(()) + } +} diff --git a/client/cli/src/commands/import_blocks_cmd.rs b/client/cli/src/commands/import_blocks_cmd.rs new file mode 100644 index 0000000000000..60a57ab78d1b1 --- /dev/null +++ b/client/cli/src/commands/import_blocks_cmd.rs @@ -0,0 +1,107 @@ +// Copyright 2018-2020 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 . + +use std::fmt::Debug; +use std::io::{Read, Seek, self}; +use std::fs; +use std::path::PathBuf; +use structopt::StructOpt; +use sc_service::{ + Configuration, ChainSpecExtension, RuntimeGenesis, ServiceBuilderCommand, ChainSpec, Roles, +}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; + +use crate::error; +use crate::VersionInfo; +use crate::runtime::run_until_exit; +use crate::params::SharedParams; +use crate::params::ImportParams; + +/// The `import-blocks` command used to import blocks. +#[derive(Debug, StructOpt, Clone)] +pub struct ImportBlocksCmd { + /// Input file or stdin if unspecified. + #[structopt(parse(from_os_str))] + pub input: Option, + + /// The default number of 64KB pages to ever allocate for Wasm execution. + /// + /// Don't alter this unless you know what you're doing. + #[structopt(long = "default-heap-pages", value_name = "COUNT")] + pub default_heap_pages: Option, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub shared_params: SharedParams, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub import_params: ImportParams, +} + +/// Internal trait used to cast to a dynamic type that implements Read and Seek. +trait ReadPlusSeek: Read + Seek {} + +impl ReadPlusSeek for T {} + +impl ImportBlocksCmd { + /// Run the import-blocks command + pub fn run( + self, + config: Configuration, + builder: B, + ) -> error::Result<()> + where + B: FnOnce(Configuration) -> Result, + G: RuntimeGenesis, + E: ChainSpecExtension, + BC: ServiceBuilderCommand + Unpin, + BB: sp_runtime::traits::Block + Debug, + <<::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug, + ::Hash: std::str::FromStr, + { + let file: Box = match &self.input { + Some(filename) => Box::new(fs::File::open(filename)?), + None => { + let mut buffer = Vec::new(); + io::stdin().read_to_end(&mut buffer)?; + Box::new(io::Cursor::new(buffer)) + }, + }; + + run_until_exit(config, |config| { + Ok(builder(config)?.import_blocks(file, false)) + }) + } + + /// Update and prepare a `Configuration` with command line parameters + pub fn update_config( + &self, + mut config: &mut Configuration, + spec_factory: F, + version: &VersionInfo, + ) -> error::Result<()> where + G: RuntimeGenesis, + E: ChainSpecExtension, + F: FnOnce(&str) -> Result>, String>, + { + self.shared_params.update_config(&mut config, spec_factory, version)?; + self.import_params.update_config(&mut config, Roles::FULL, self.shared_params.dev)?; + config.use_in_memory_keystore()?; + + Ok(()) + } +} diff --git a/client/cli/src/commands/mod.rs b/client/cli/src/commands/mod.rs new file mode 100644 index 0000000000000..e9f991c745883 --- /dev/null +++ b/client/cli/src/commands/mod.rs @@ -0,0 +1,145 @@ +// Copyright 2018-2020 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 . + +mod runcmd; +mod export_blocks_cmd; +mod build_spec_cmd; +mod import_blocks_cmd; +mod check_block_cmd; +mod revert_cmd; +mod purge_chain_cmd; + +use std::fmt::Debug; +use structopt::StructOpt; + +use sc_service::{ + Configuration, ChainSpecExtension, RuntimeGenesis, ServiceBuilderCommand, ChainSpec, +}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; + +use crate::error; +use crate::VersionInfo; +use crate::params::SharedParams; + +pub use crate::commands::runcmd::RunCmd; +pub use crate::commands::export_blocks_cmd::ExportBlocksCmd; +pub use crate::commands::build_spec_cmd::BuildSpecCmd; +pub use crate::commands::import_blocks_cmd::ImportBlocksCmd; +pub use crate::commands::check_block_cmd::CheckBlockCmd; +pub use crate::commands::revert_cmd::RevertCmd; +pub use crate::commands::purge_chain_cmd::PurgeChainCmd; + +/// default sub directory to store network config +const DEFAULT_NETWORK_CONFIG_PATH : &'static str = "network"; + +/// All core commands that are provided by default. +/// +/// The core commands are split into multiple subcommands and `Run` is the default subcommand. From +/// the CLI user perspective, it is not visible that `Run` is a subcommand. So, all parameters of +/// `Run` are exported as main executable parameters. +#[derive(Debug, Clone, StructOpt)] +pub enum Subcommand { + /// Build a spec.json file, outputing to stdout. + BuildSpec(build_spec_cmd::BuildSpecCmd), + + /// Export blocks to a file. + ExportBlocks(export_blocks_cmd::ExportBlocksCmd), + + /// Import blocks from file. + ImportBlocks(import_blocks_cmd::ImportBlocksCmd), + + /// Validate a single block. + CheckBlock(check_block_cmd::CheckBlockCmd), + + /// Revert chain to the previous state. + Revert(revert_cmd::RevertCmd), + + /// Remove the whole chain data. + PurgeChain(purge_chain_cmd::PurgeChainCmd), +} + +impl Subcommand { + /// Get the shared parameters of a `CoreParams` command + pub fn get_shared_params(&self) -> &SharedParams { + use Subcommand::*; + + match self { + BuildSpec(params) => ¶ms.shared_params, + ExportBlocks(params) => ¶ms.shared_params, + ImportBlocks(params) => ¶ms.shared_params, + CheckBlock(params) => ¶ms.shared_params, + Revert(params) => ¶ms.shared_params, + PurgeChain(params) => ¶ms.shared_params, + } + } + + /// Run any `CoreParams` command + pub fn run( + self, + config: Configuration, + builder: B, + ) -> error::Result<()> + where + B: FnOnce(Configuration) -> Result, + G: RuntimeGenesis, + E: ChainSpecExtension, + BC: ServiceBuilderCommand + Unpin, + BB: sp_runtime::traits::Block + Debug, + <<::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug, + ::Hash: std::str::FromStr, + { + match self { + Subcommand::BuildSpec(cmd) => cmd.run(config), + Subcommand::ExportBlocks(cmd) => cmd.run(config, builder), + Subcommand::ImportBlocks(cmd) => cmd.run(config, builder), + Subcommand::CheckBlock(cmd) => cmd.run(config, builder), + Subcommand::PurgeChain(cmd) => cmd.run(config), + Subcommand::Revert(cmd) => cmd.run(config, builder), + } + } + + /// Update and prepare a `Configuration` with command line parameters + pub fn update_config( + &self, + mut config: &mut Configuration, + spec_factory: F, + version: &VersionInfo, + ) -> error::Result<()> where + G: RuntimeGenesis, + E: ChainSpecExtension, + F: FnOnce(&str) -> Result>, String>, + { + match self { + Subcommand::BuildSpec(cmd) => cmd.update_config(&mut config, spec_factory, version), + Subcommand::ExportBlocks(cmd) => cmd.update_config(&mut config, spec_factory, version), + Subcommand::ImportBlocks(cmd) => cmd.update_config(&mut config, spec_factory, version), + Subcommand::CheckBlock(cmd) => cmd.update_config(&mut config, spec_factory, version), + Subcommand::PurgeChain(cmd) => cmd.update_config(&mut config, spec_factory, version), + Subcommand::Revert(cmd) => cmd.update_config(&mut config, spec_factory, version), + } + } + + /// Initialize substrate. This must be done only once. + /// + /// This method: + /// + /// 1. Set the panic handler + /// 2. Raise the FD limit + /// 3. Initialize the logger + pub fn init(&self, version: &VersionInfo) -> error::Result<()> { + self.get_shared_params().init(version) + } +} diff --git a/client/cli/src/commands/purge_chain_cmd.rs b/client/cli/src/commands/purge_chain_cmd.rs new file mode 100644 index 0000000000000..b7c559e5cc359 --- /dev/null +++ b/client/cli/src/commands/purge_chain_cmd.rs @@ -0,0 +1,106 @@ +// Copyright 2018-2020 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 . + +use std::fmt::Debug; +use std::io::{Write, self}; +use std::fs; +use structopt::StructOpt; +use sc_service::{ + Configuration, ChainSpecExtension, RuntimeGenesis, ChainSpec, + config::{DatabaseConfig}, +}; + +use crate::error; +use crate::VersionInfo; +use crate::params::SharedParams; + +/// The `purge-chain` command used to remove the whole chain. +#[derive(Debug, StructOpt, Clone)] +pub struct PurgeChainCmd { + /// Skip interactive prompt by answering yes automatically. + #[structopt(short = "y")] + pub yes: bool, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub shared_params: SharedParams, +} + +impl PurgeChainCmd { + /// Run the purge command + pub fn run( + self, + config: Configuration, + ) -> error::Result<()> + where + G: RuntimeGenesis, + E: ChainSpecExtension, + { + let db_path = match config.expect_database() { + DatabaseConfig::Path { path, .. } => path, + _ => { + eprintln!("Cannot purge custom database implementation"); + return Ok(()); + } + }; + + if !self.yes { + print!("Are you sure to remove {:?}? [y/N]: ", &db_path); + io::stdout().flush().expect("failed to flush stdout"); + + let mut input = String::new(); + io::stdin().read_line(&mut input)?; + let input = input.trim(); + + match input.chars().nth(0) { + Some('y') | Some('Y') => {}, + _ => { + println!("Aborted"); + return Ok(()); + }, + } + } + + match fs::remove_dir_all(&db_path) { + Ok(_) => { + println!("{:?} removed.", &db_path); + Ok(()) + }, + Err(ref err) if err.kind() == io::ErrorKind::NotFound => { + eprintln!("{:?} did not exist.", &db_path); + Ok(()) + }, + Err(err) => Result::Err(err.into()) + } + } + + /// Update and prepare a `Configuration` with command line parameters + pub fn update_config( + &self, + mut config: &mut Configuration, + spec_factory: F, + version: &VersionInfo, + ) -> error::Result<()> where + G: RuntimeGenesis, + E: ChainSpecExtension, + F: FnOnce(&str) -> Result>, String>, + { + self.shared_params.update_config(&mut config, spec_factory, version)?; + config.use_in_memory_keystore()?; + + Ok(()) + } +} diff --git a/client/cli/src/commands/revert_cmd.rs b/client/cli/src/commands/revert_cmd.rs new file mode 100644 index 0000000000000..9ab86986cdd07 --- /dev/null +++ b/client/cli/src/commands/revert_cmd.rs @@ -0,0 +1,79 @@ +// Copyright 2018-2020 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 . + +use std::fmt::Debug; +use structopt::StructOpt; +use sc_service::{ + Configuration, ChainSpecExtension, RuntimeGenesis, ServiceBuilderCommand, ChainSpec, +}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; + +use crate::error; +use crate::VersionInfo; +use crate::params::BlockNumber; +use crate::params::SharedParams; + +/// The `revert` command used revert the chain to a previous state. +#[derive(Debug, StructOpt, Clone)] +pub struct RevertCmd { + /// Number of blocks to revert. + #[structopt(default_value = "256")] + pub num: BlockNumber, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub shared_params: SharedParams, +} + +impl RevertCmd { + /// Run the revert command + pub fn run( + self, + config: Configuration, + builder: B, + ) -> error::Result<()> + where + B: FnOnce(Configuration) -> Result, + G: RuntimeGenesis, + E: ChainSpecExtension, + BC: ServiceBuilderCommand + Unpin, + BB: sp_runtime::traits::Block + Debug, + <<::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug, + ::Hash: std::str::FromStr, + { + let blocks = self.num.parse()?; + builder(config)?.revert_chain(blocks)?; + + Ok(()) + } + + /// Update and prepare a `Configuration` with command line parameters + pub fn update_config( + &self, + mut config: &mut Configuration, + spec_factory: F, + version: &VersionInfo, + ) -> error::Result<()> where + G: RuntimeGenesis, + E: ChainSpecExtension, + F: FnOnce(&str) -> Result>, String>, + { + self.shared_params.update_config(&mut config, spec_factory, version)?; + config.use_in_memory_keystore()?; + + Ok(()) + } +} diff --git a/client/cli/src/commands/runcmd.rs b/client/cli/src/commands/runcmd.rs new file mode 100644 index 0000000000000..f29bc3c743b64 --- /dev/null +++ b/client/cli/src/commands/runcmd.rs @@ -0,0 +1,737 @@ +// Copyright 2018-2020 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 . + +use std::path::PathBuf; +use std::net::SocketAddr; +use std::fs; +use log::info; +use structopt::{StructOpt, clap::arg_enum}; +use names::{Generator, Name}; +use regex::Regex; +use chrono::prelude::*; +use sc_service::{ + AbstractService, Configuration, ChainSpecExtension, RuntimeGenesis, ChainSpec, Roles, + config::KeystoreConfig, +}; +use sc_telemetry::TelemetryEndpoints; + +use crate::VersionInfo; +use crate::error; +use crate::params::ImportParams; +use crate::params::SharedParams; +use crate::params::NetworkConfigurationParams; +use crate::params::TransactionPoolParams; +use crate::runtime::run_service_until_exit; + +/// The maximum number of characters for a node name. +const NODE_NAME_MAX_LENGTH: usize = 32; + +/// default sub directory for the key store +const DEFAULT_KEYSTORE_CONFIG_PATH : &'static str = "keystore"; + +arg_enum! { + /// Whether off-chain workers are enabled. + #[allow(missing_docs)] + #[derive(Debug, Clone)] + pub enum OffchainWorkerEnabled { + Always, + Never, + WhenValidating, + } +} + +/// The `run` command used to run a node. +#[derive(Debug, StructOpt, Clone)] +pub struct RunCmd { + /// Enable validator mode. + /// + /// The node will be started with the authority role and actively + /// participate in any consensus task that it can (e.g. depending on + /// availability of local keys). + #[structopt( + long = "validator", + conflicts_with_all = &[ "sentry" ] + )] + pub validator: bool, + + /// Enable sentry mode. + /// + /// The node will be started with the authority role and participate in + /// consensus tasks as an "observer", it will never actively participate + /// regardless of whether it could (e.g. keys are available locally). This + /// mode is useful as a secure proxy for validators (which would run + /// detached from the network), since we want this node to participate in + /// the full consensus protocols in order to have all needed consensus data + /// available to relay to private nodes. + #[structopt( + long = "sentry", + conflicts_with_all = &[ "validator", "light" ] + )] + pub sentry: bool, + + /// Disable GRANDPA voter when running in validator mode, otherwise disables the GRANDPA observer. + #[structopt(long = "no-grandpa")] + pub no_grandpa: bool, + + /// Experimental: Run in light client mode. + #[structopt(long = "light", conflicts_with = "sentry")] + pub light: bool, + + /// Listen to all RPC interfaces. + /// + /// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use a RPC proxy + /// server to filter out dangerous methods. More details: https://github.com/paritytech/substrate/wiki/Public-RPC. + /// Use `--unsafe-rpc-external` to suppress the warning if you understand the risks. + #[structopt(long = "rpc-external")] + pub rpc_external: bool, + + /// Listen to all RPC interfaces. + /// + /// Same as `--rpc-external`. + #[structopt(long = "unsafe-rpc-external")] + pub unsafe_rpc_external: bool, + + /// Listen to all Websocket interfaces. + /// + /// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use a RPC proxy + /// server to filter out dangerous methods. More details: https://github.com/paritytech/substrate/wiki/Public-RPC. + /// Use `--unsafe-ws-external` to suppress the warning if you understand the risks. + #[structopt(long = "ws-external")] + pub ws_external: bool, + + /// Listen to all Websocket interfaces. + /// + /// Same as `--ws-external` but doesn't warn you about it. + #[structopt(long = "unsafe-ws-external")] + pub unsafe_ws_external: bool, + + /// Listen to all Prometheus data source interfaces. + /// + /// Default is local. + #[structopt(long = "prometheus-external")] + pub prometheus_external: bool, + + /// Specify HTTP RPC server TCP port. + #[structopt(long = "rpc-port", value_name = "PORT")] + pub rpc_port: Option, + + /// Specify WebSockets RPC server TCP port. + #[structopt(long = "ws-port", value_name = "PORT")] + pub ws_port: Option, + + /// Maximum number of WS RPC server connections. + #[structopt(long = "ws-max-connections", value_name = "COUNT")] + pub ws_max_connections: Option, + + /// Specify browser Origins allowed to access the HTTP & WS RPC servers. + /// + /// A comma-separated list of origins (protocol://domain or special `null` + /// value). Value of `all` will disable origin validation. Default is to + /// allow localhost, https://polkadot.js.org and + /// https://substrate-ui.parity.io origins. When running in --dev mode the + /// default is to allow all origins. + #[structopt(long = "rpc-cors", value_name = "ORIGINS", parse(try_from_str = parse_cors))] + pub rpc_cors: Option, + + /// Specify Prometheus data source server TCP Port. + #[structopt(long = "prometheus-port", value_name = "PORT")] + pub prometheus_port: Option, + + /// Do not expose a Prometheus metric endpoint. + /// + /// Prometheus metric endpoint is enabled by default. + #[structopt(long = "no-prometheus")] + pub no_prometheus: bool, + + /// The human-readable name for this node. + /// + /// The node name will be reported to the telemetry server, if enabled. + #[structopt(long = "name", value_name = "NAME")] + pub name: Option, + + /// Disable connecting to the Substrate telemetry server. + /// + /// Telemetry is on by default on global chains. + #[structopt(long = "no-telemetry")] + pub no_telemetry: bool, + + /// The URL of the telemetry server to connect to. + /// + /// This flag can be passed multiple times as a mean to specify multiple + /// telemetry endpoints. Verbosity levels range from 0-9, with 0 denoting + /// the least verbosity. If no verbosity level is specified the default is + /// 0. + #[structopt(long = "telemetry-url", value_name = "URL VERBOSITY", parse(try_from_str = parse_telemetry_endpoints))] + pub telemetry_endpoints: Vec<(String, u8)>, + + /// Should execute offchain workers on every block. + /// + /// By default it's only enabled for nodes that are authoring new blocks. + #[structopt( + long = "offchain-worker", + value_name = "ENABLED", + possible_values = &OffchainWorkerEnabled::variants(), + case_insensitive = true, + default_value = "WhenValidating" + )] + pub offchain_worker: OffchainWorkerEnabled, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub shared_params: SharedParams, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub import_params: ImportParams, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub network_config: NetworkConfigurationParams, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub pool_config: TransactionPoolParams, + + /// Shortcut for `--name Alice --validator` with session keys for `Alice` added to keystore. + #[structopt(long, conflicts_with_all = &["bob", "charlie", "dave", "eve", "ferdie", "one", "two"])] + pub alice: bool, + + /// Shortcut for `--name Bob --validator` with session keys for `Bob` added to keystore. + #[structopt(long, conflicts_with_all = &["alice", "charlie", "dave", "eve", "ferdie", "one", "two"])] + pub bob: bool, + + /// Shortcut for `--name Charlie --validator` with session keys for `Charlie` added to keystore. + #[structopt(long, conflicts_with_all = &["alice", "bob", "dave", "eve", "ferdie", "one", "two"])] + pub charlie: bool, + + /// Shortcut for `--name Dave --validator` with session keys for `Dave` added to keystore. + #[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "eve", "ferdie", "one", "two"])] + pub dave: bool, + + /// Shortcut for `--name Eve --validator` with session keys for `Eve` added to keystore. + #[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "ferdie", "one", "two"])] + pub eve: bool, + + /// Shortcut for `--name Ferdie --validator` with session keys for `Ferdie` added to keystore. + #[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "one", "two"])] + pub ferdie: bool, + + /// Shortcut for `--name One --validator` with session keys for `One` added to keystore. + #[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "two"])] + pub one: bool, + + /// Shortcut for `--name Two --validator` with session keys for `Two` added to keystore. + #[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "one"])] + pub two: bool, + + /// Enable authoring even when offline. + #[structopt(long = "force-authoring")] + pub force_authoring: bool, + + /// Specify custom keystore path. + #[structopt(long = "keystore-path", value_name = "PATH", parse(from_os_str))] + pub keystore_path: Option, + + /// Use interactive shell for entering the password used by the keystore. + #[structopt( + long = "password-interactive", + conflicts_with_all = &[ "password", "password-filename" ] + )] + pub password_interactive: bool, + + /// Password used by the keystore. + #[structopt( + long = "password", + conflicts_with_all = &[ "password-interactive", "password-filename" ] + )] + pub password: Option, + + /// File that contains the password used by the keystore. + #[structopt( + long = "password-filename", + value_name = "PATH", + parse(from_os_str), + conflicts_with_all = &[ "password-interactive", "password" ] + )] + pub password_filename: Option +} + +impl RunCmd { + /// Get the `Sr25519Keyring` matching one of the flag + pub fn get_keyring(&self) -> Option { + use sp_keyring::Sr25519Keyring::*; + + if self.alice { Some(Alice) } + else if self.bob { Some(Bob) } + else if self.charlie { Some(Charlie) } + else if self.dave { Some(Dave) } + else if self.eve { Some(Eve) } + else if self.ferdie { Some(Ferdie) } + else if self.one { Some(One) } + else if self.two { Some(Two) } + else { None } + } + + /// Update and prepare a `Configuration` with command line parameters of `RunCmd` and `VersionInfo` + pub fn update_config( + &self, + mut config: &mut Configuration, + spec_factory: F, + version: &VersionInfo, + ) -> error::Result<()> + where + G: RuntimeGenesis, + E: ChainSpecExtension, + F: FnOnce(&str) -> Result>, String>, + { + self.shared_params.update_config(&mut config, spec_factory, version)?; + + let password = if self.password_interactive { + #[cfg(not(target_os = "unknown"))] + { + Some(input_keystore_password()?.into()) + } + #[cfg(target_os = "unknown")] + None + } else if let Some(ref file) = self.password_filename { + Some(fs::read_to_string(file).map_err(|e| format!("{}", e))?.into()) + } else if let Some(ref password) = self.password { + Some(password.clone().into()) + } else { + None + }; + + let path = self.keystore_path.clone().or( + config.in_chain_config_dir(DEFAULT_KEYSTORE_CONFIG_PATH) + ); + + config.keystore = KeystoreConfig::Path { + path: path.ok_or_else(|| "No `base_path` provided to create keystore path!".to_string())?, + password, + }; + + let keyring = self.get_keyring(); + let is_dev = self.shared_params.dev; + let is_light = self.light; + let is_authority = (self.validator || self.sentry || is_dev || keyring.is_some()) + && !is_light; + let role = + if is_light { + sc_service::Roles::LIGHT + } else if is_authority { + sc_service::Roles::AUTHORITY + } else { + sc_service::Roles::FULL + }; + + self.import_params.update_config(&mut config, role, is_dev)?; + + config.name = match (self.name.as_ref(), keyring) { + (Some(name), _) => name.to_string(), + (_, Some(keyring)) => keyring.to_string(), + (None, None) => generate_node_name(), + }; + if let Err(msg) = is_node_name_valid(&config.name) { + return Err(error::Error::Input( + format!("Invalid node name '{}'. Reason: {}. If unsure, use none.", + config.name, + msg, + ) + )); + } + + // set sentry mode (i.e. act as an authority but **never** actively participate) + config.sentry_mode = self.sentry; + + config.offchain_worker = match (&self.offchain_worker, role) { + (OffchainWorkerEnabled::WhenValidating, sc_service::Roles::AUTHORITY) => true, + (OffchainWorkerEnabled::Always, _) => true, + (OffchainWorkerEnabled::Never, _) => false, + (OffchainWorkerEnabled::WhenValidating, _) => false, + }; + + config.roles = role; + config.disable_grandpa = self.no_grandpa; + + let client_id = config.client_id(); + let network_path = config + .in_chain_config_dir(crate::commands::DEFAULT_NETWORK_CONFIG_PATH) + .expect("We provided a basepath"); + self.network_config.update_config( + &mut config, + network_path, + client_id, + is_dev, + )?; + + self.pool_config.update_config(&mut config)?; + + config.dev_key_seed = keyring + .map(|a| format!("//{}", a)).or_else(|| { + if is_dev && !is_light { + Some("//Alice".into()) + } else { + None + } + }); + + if config.rpc_http.is_none() || self.rpc_port.is_some() { + let rpc_interface: &str = interface_str(self.rpc_external, self.unsafe_rpc_external, self.validator)?; + config.rpc_http = Some(parse_address(&format!("{}:{}", rpc_interface, 9933), self.rpc_port)?); + } + if config.rpc_ws.is_none() || self.ws_port.is_some() { + let ws_interface: &str = interface_str(self.ws_external, self.unsafe_ws_external, self.validator)?; + config.rpc_ws = Some(parse_address(&format!("{}:{}", ws_interface, 9944), self.ws_port)?); + } + + config.rpc_ws_max_connections = self.ws_max_connections; + config.rpc_cors = self.rpc_cors.clone().unwrap_or_else(|| if is_dev { + log::warn!("Running in --dev mode, RPC CORS has been disabled."); + Cors::All + } else { + Cors::List(vec![ + "http://localhost:*".into(), + "http://127.0.0.1:*".into(), + "https://localhost:*".into(), + "https://127.0.0.1:*".into(), + "https://polkadot.js.org".into(), + "https://substrate-ui.parity.io".into(), + ]) + }).into(); + + // Override telemetry + if self.no_telemetry { + config.telemetry_endpoints = None; + } else if !self.telemetry_endpoints.is_empty() { + config.telemetry_endpoints = Some( + TelemetryEndpoints::new(self.telemetry_endpoints.clone()) + ); + } + + // Override prometheus + if self.no_prometheus { + config.prometheus_port = None; + } else if config.prometheus_port.is_none() { + let prometheus_interface: &str = if self.prometheus_external { "0.0.0.0" } else { "127.0.0.1" }; + config.prometheus_port = Some( + parse_address(&format!("{}:{}", prometheus_interface, 9615), self.prometheus_port)?); + } + + config.tracing_targets = self.import_params.tracing_targets.clone().into(); + config.tracing_receiver = self.import_params.tracing_receiver.clone().into(); + + // Imply forced authoring on --dev + config.force_authoring = self.shared_params.dev || self.force_authoring; + + Ok(()) + } + + /// Run the command that runs the node + pub fn run( + self, + config: Configuration, + new_light: FNL, + new_full: FNF, + version: &VersionInfo, + ) -> error::Result<()> + where + G: RuntimeGenesis, + E: ChainSpecExtension, + FNL: FnOnce(Configuration) -> Result, + FNF: FnOnce(Configuration) -> Result, + SL: AbstractService + Unpin, + SF: AbstractService + Unpin, + { + info!("{}", version.name); + info!(" version {}", config.full_version()); + info!(" by {}, {}-{}", version.author, version.copyright_start_year, Local::today().year()); + info!("Chain specification: {}", config.expect_chain_spec().name()); + info!("Node name: {}", config.name); + info!("Roles: {}", config.display_role()); + + match config.roles { + Roles::LIGHT => run_service_until_exit( + config, + new_light, + ), + _ => run_service_until_exit( + config, + new_full, + ), + } + } + + /// Initialize substrate. This must be done only once. + /// + /// This method: + /// + /// 1. Set the panic handler + /// 2. Raise the FD limit + /// 3. Initialize the logger + pub fn init(&self, version: &VersionInfo) -> error::Result<()> { + self.shared_params.init(version) + } +} + +/// Check whether a node name is considered as valid +pub fn is_node_name_valid(_name: &str) -> Result<(), &str> { + let name = _name.to_string(); + if name.chars().count() >= NODE_NAME_MAX_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(()) +} + +#[cfg(not(target_os = "unknown"))] +fn input_keystore_password() -> Result { + rpassword::read_password_from_tty(Some("Keystore password: ")) + .map_err(|e| format!("{:?}", e)) +} + +fn generate_node_name() -> String { + let result = loop { + let node_name = Generator::with_naming(Name::Numbered).next().unwrap(); + let count = node_name.chars().count(); + + if count < NODE_NAME_MAX_LENGTH { + break node_name + } + }; + + result +} + +fn parse_address( + address: &str, + port: Option, +) -> Result { + let mut address: SocketAddr = address.parse().map_err( + |_| format!("Invalid address: {}", address) + )?; + if let Some(port) = port { + address.set_port(port); + } + + Ok(address) +} + +fn interface_str( + is_external: bool, + is_unsafe_external: bool, + is_validator: bool, +) -> Result<&'static str, error::Error> { + if is_external && is_validator { + return Err(error::Error::Input("--rpc-external and --ws-external options shouldn't be \ + used if the node is running as a validator. Use `--unsafe-rpc-external` if you understand \ + the risks. See the options description for more information.".to_owned())); + } + + if is_external || is_unsafe_external { + log::warn!("It isn't safe to expose RPC publicly without a proxy server that filters \ + available set of RPC methods."); + + Ok("0.0.0.0") + } else { + Ok("127.0.0.1") + } +} + +/// Default to verbosity level 0, if none is provided. +fn parse_telemetry_endpoints(s: &str) -> Result<(String, u8), Box> { + let pos = s.find(' '); + match pos { + None => { + Ok((s.to_owned(), 0)) + }, + Some(pos_) => { + let verbosity = s[pos_ + 1..].parse()?; + let url = s[..pos_].parse()?; + Ok((url, verbosity)) + } + } +} + +/// CORS setting +/// +/// The type is introduced to overcome `Option>` +/// handling of `structopt`. +#[derive(Clone, Debug)] +pub enum Cors { + /// All hosts allowed + All, + /// Only hosts on the list are allowed. + List(Vec), +} + +impl From for Option> { + fn from(cors: Cors) -> Self { + match cors { + Cors::All => None, + Cors::List(list) => Some(list), + } + } +} + +/// Parse cors origins +fn parse_cors(s: &str) -> Result> { + let mut is_all = false; + let mut origins = Vec::new(); + for part in s.split(',') { + match part { + "all" | "*" => { + is_all = true; + break; + }, + other => origins.push(other.to_owned()), + } + } + + Ok(if is_all { Cors::All } else { Cors::List(origins) }) +} + +#[cfg(test)] +mod tests { + use super::*; + use sc_service::config::DatabaseConfig; + + const TEST_VERSION_INFO: &'static VersionInfo = &VersionInfo { + name: "node-test", + version: "0.1.0", + commit: "some_commit", + executable_name: "node-test", + description: "description", + author: "author", + support_url: "http://example.org", + copyright_start_year: 2020, + }; + + #[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()); + } + + #[test] + fn keystore_path_is_generated_correctly() { + let chain_spec = ChainSpec::from_genesis( + "test", + "test-id", + || (), + Vec::new(), + None, + None, + None, + None::<()>, + ); + + for keystore_path in vec![None, Some("/keystore/path")] { + let args: Vec<&str> = vec![]; + let mut cli = RunCmd::from_iter(args); + cli.keystore_path = keystore_path.clone().map(PathBuf::from); + + let mut config = Configuration::default(); + config.config_dir = Some(PathBuf::from("/test/path")); + config.chain_spec = Some(chain_spec.clone()); + let chain_spec = chain_spec.clone(); + cli.update_config(&mut config, move |_| Ok(Some(chain_spec)), TEST_VERSION_INFO).unwrap(); + + let expected_path = match keystore_path { + Some(path) => PathBuf::from(path), + None => PathBuf::from("/test/path/chains/test-id/keystore"), + }; + + assert_eq!(expected_path, config.keystore.path().unwrap().to_owned()); + } + } + + #[test] + fn ensure_load_spec_provide_defaults() { + let chain_spec = ChainSpec::from_genesis( + "test", + "test-id", + || (), + vec!["boo".to_string()], + Some(TelemetryEndpoints::new(vec![("foo".to_string(), 42)])), + None, + None, + None::<()>, + ); + + let args: Vec<&str> = vec![]; + let cli = RunCmd::from_iter(args); + + let mut config = Configuration::from_version(TEST_VERSION_INFO); + cli.update_config(&mut config, |_| Ok(Some(chain_spec)), TEST_VERSION_INFO).unwrap(); + + assert!(config.chain_spec.is_some()); + assert!(!config.network.boot_nodes.is_empty()); + assert!(config.telemetry_endpoints.is_some()); + } + + #[test] + fn ensure_update_config_for_running_node_provides_defaults() { + let chain_spec = ChainSpec::from_genesis( + "test", + "test-id", + || (), + vec![], + None, + None, + None, + None::<()>, + ); + + let args: Vec<&str> = vec![]; + let cli = RunCmd::from_iter(args); + + let mut config = Configuration::from_version(TEST_VERSION_INFO); + cli.init(&TEST_VERSION_INFO).unwrap(); + cli.update_config(&mut config, |_| Ok(Some(chain_spec)), TEST_VERSION_INFO).unwrap(); + + assert!(config.config_dir.is_some()); + assert!(config.database.is_some()); + if let Some(DatabaseConfig::Path { ref cache_size, .. }) = config.database { + assert!(cache_size.is_some()); + } else { + panic!("invalid config.database variant"); + } + assert!(!config.name.is_empty()); + assert!(config.network.config_path.is_some()); + assert!(!config.network.listen_addresses.is_empty()); + } +} diff --git a/client/cli/src/error.rs b/client/cli/src/error.rs index 074cb353c3a0a..edc1adecc762c 100644 --- a/client/cli/src/error.rs +++ b/client/cli/src/error.rs @@ -49,6 +49,12 @@ impl std::convert::From for Error { } } +impl std::convert::From<&str> for Error { + fn from(s: &str) -> Error { + Error::Input(s.to_string()) + } +} + impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { diff --git a/client/cli/src/lib.rs b/client/cli/src/lib.rs index 3517dca3789bb..e28edebd60def 100644 --- a/client/cli/src/lib.rs +++ b/client/cli/src/lib.rs @@ -19,127 +19,33 @@ #![warn(missing_docs)] #![warn(unused_extern_crates)] -#[macro_use] -mod traits; mod params; -mod execution_strategy; -pub mod error; +mod arg_enums; +mod error; mod runtime; -mod node_key; - -use sc_client_api::execution_extensions::ExecutionStrategies; -use sc_service::{ - config::{Configuration, DatabaseConfig, KeystoreConfig}, - ServiceBuilderCommand, - RuntimeGenesis, ChainSpecExtension, PruningMode, ChainSpec, - AbstractService, Roles as ServiceRoles, -}; +mod commands; + pub use sc_service::config::VersionInfo; -use sc_network::{ - self, - multiaddr::Protocol, - config::{ - NetworkConfiguration, TransportConfig, NonReservedPeerMode, - }, -}; - -use std::{ - io::Write, iter, fmt::Debug, fs, - net::{Ipv4Addr, SocketAddr}, path::PathBuf, -}; + +use std::io::Write; use regex::Regex; -use structopt::{StructOpt, clap}; +use structopt::{StructOpt, clap::{self, AppSettings}}; pub use structopt; -use params::{ - NetworkConfigurationParams, TransactionPoolParams, Cors, -}; -pub use params::{ - SharedParams, ImportParams, ExecutionStrategy, Subcommand, RunCmd, BuildSpecCmd, - ExportBlocksCmd, ImportBlocksCmd, CheckBlockCmd, PurgeChainCmd, RevertCmd, - WasmExecutionMethod, -}; -pub use traits::GetSharedParams; -use app_dirs::{AppInfo, AppDataType}; +pub use params::*; +pub use commands::*; +pub use arg_enums::*; +pub use error::*; use log::info; use lazy_static::lazy_static; -use sc_telemetry::TelemetryEndpoints; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; pub use crate::runtime::{run_until_exit, run_service_until_exit}; -use execution_strategy::*; -use names::{Generator, Name}; -use chrono::prelude::*; - -/// default sub directory to store network config -const DEFAULT_NETWORK_CONFIG_PATH : &'static str = "network"; -/// default sub directory to store database -const DEFAULT_DB_CONFIG_PATH : &'static str = "db"; -/// default sub directory for the key store -const DEFAULT_KEYSTORE_CONFIG_PATH : &'static str = "keystore"; - -/// The maximum number of characters for a node name. -const NODE_NAME_MAX_LENGTH: usize = 32; - -fn get_chain_key(cli: &SharedParams) -> String { - match cli.chain { - Some(ref chain) => chain.clone(), - None => if cli.dev { "dev".into() } else { "".into() } - } -} - -fn generate_node_name() -> String { - let result = loop { - let node_name = Generator::with_naming(Name::Numbered).next().unwrap(); - let count = node_name.chars().count(); - - if count < NODE_NAME_MAX_LENGTH { - break node_name - } - }; - - result -} - -/// Load spec to `Configuration` from shared params and spec factory. -pub fn load_spec<'a, G, E, F>( - mut config: &'a mut Configuration, - cli: &SharedParams, - factory: F, -) -> error::Result<&'a ChainSpec> where - G: RuntimeGenesis, - E: ChainSpecExtension, - F: FnOnce(&str) -> Result>, String>, -{ - let chain_key = get_chain_key(cli); - let spec = match factory(&chain_key)? { - Some(spec) => spec, - None => ChainSpec::from_json_file(PathBuf::from(chain_key))? - }; - - config.network.boot_nodes = spec.boot_nodes().to_vec(); - config.telemetry_endpoints = spec.telemetry_endpoints().clone(); - - config.chain_spec = Some(spec); - - Ok(config.chain_spec.as_ref().unwrap()) -} - -fn base_path(cli: &SharedParams, version: &VersionInfo) -> PathBuf { - cli.base_path.clone() - .unwrap_or_else(|| - app_dirs::get_app_root( - AppDataType::UserData, - &AppInfo { - name: version.executable_name, - author: version.author - } - ).expect("app directories exist on all supported platforms; qed") - ) -} /// Helper function used to parse the command line arguments. This is the equivalent of -/// `structopt`'s `from_args()` except that it takes a `VersionInfo` argument to provide the name of -/// the application, author, "about" and version. +/// `structopt`'s `from_iter()` except that it takes a `VersionInfo` argument to provide the name of +/// the application, author, "about" and version. It will also set `AppSettings::GlobalVersion`. +/// +/// To allow running the node without subcommand, tt also sets a few more settings: +/// `AppSettings::ArgsNegateSubcommands` and `AppSettings::SubcommandsNegateReqs`. /// /// Gets the struct from the command line arguments. Print the /// error message and quit the program in case of failure. @@ -152,7 +58,10 @@ where /// Helper function used to parse the command line arguments. This is the equivalent of /// `structopt`'s `from_iter()` except that it takes a `VersionInfo` argument to provide the name of -/// the application, author, "about" and version. +/// the application, author, "about" and version. It will also set `AppSettings::GlobalVersion`. +/// +/// To allow running the node without subcommand, tt also sets a few more settings: +/// `AppSettings::ArgsNegateSubcommands` and `AppSettings::SubcommandsNegateReqs`. /// /// Gets the struct from any iterator such as a `Vec` of your making. /// Print the error message and quit the program in case of failure. @@ -174,14 +83,22 @@ where .name(version.executable_name) .author(version.author) .about(version.description) - .version(full_version.as_str()); + .version(full_version.as_str()) + .settings(&[ + AppSettings::GlobalVersion, + AppSettings::ArgsNegateSubcommands, + AppSettings::SubcommandsNegateReqs, + ]); T::from_clap(&app.get_matches_from(iter)) } /// Helper function used to parse the command line arguments. This is the equivalent of -/// `structopt`'s `try_from_iter()` except that it takes a `VersionInfo` argument to provide the -/// name of the application, author, "about" and version. +/// `structopt`'s `from_iter()` except that it takes a `VersionInfo` argument to provide the name of +/// the application, author, "about" and version. It will also set `AppSettings::GlobalVersion`. +/// +/// To allow running the node without subcommand, tt also sets a few more settings: +/// `AppSettings::ArgsNegateSubcommands` and `AppSettings::SubcommandsNegateReqs`. /// /// Gets the struct from any iterator such as a `Vec` of your making. /// Print the error message and quit the program in case of failure. @@ -215,53 +132,6 @@ where Ok(T::from_clap(&matches)) } -/// A helper function that initializes and runs the node -pub fn run( - mut config: Configuration, - run_cmd: RunCmd, - new_light: FNL, - new_full: FNF, - spec_factory: F, - version: &VersionInfo, -) -> error::Result<()> -where - F: FnOnce(&str) -> Result>, String>, - FNL: FnOnce(Configuration) -> Result, - FNF: FnOnce(Configuration) -> Result, - G: RuntimeGenesis, - E: ChainSpecExtension, - SL: AbstractService + Unpin, - SF: AbstractService + Unpin, -{ - init(&run_cmd.shared_params, version)?; - init_config(&mut config, &run_cmd.shared_params, version, spec_factory)?; - run_cmd.run(config, new_light, new_full, version) -} - -/// A helper function that initializes and runs any of the subcommand variants of `CoreParams`. -pub fn run_subcommand( - mut config: Configuration, - subcommand: Subcommand, - spec_factory: F, - builder: B, - version: &VersionInfo, -) -> error::Result<()> -where - F: FnOnce(&str) -> Result>, String>, - B: FnOnce(Configuration) -> Result, - G: RuntimeGenesis, - E: ChainSpecExtension, - BC: ServiceBuilderCommand + Unpin, - BB: sp_runtime::traits::Block + Debug, - <<::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug, - ::Hash: std::str::FromStr, -{ - let shared_params = subcommand.get_shared_params(); - init(shared_params, version)?; - init_config(&mut config, shared_params, version, spec_factory)?; - subcommand.run(config, builder) -} - /// Initialize substrate. This must be done only once. /// /// This method: @@ -269,7 +139,7 @@ where /// 1. Set the panic handler /// 2. Raise the FD limit /// 3. Initialize the logger -pub fn init(shared_params: &SharedParams, version: &VersionInfo) -> error::Result<()> { +pub fn init(logger_pattern: &str, version: &VersionInfo) -> error::Result<()> { let full_version = sc_service::config::full_version_from_strs( version.version, version.commit @@ -277,426 +147,11 @@ pub fn init(shared_params: &SharedParams, version: &VersionInfo) -> error::Resul sp_panic_handler::set(version.support_url, &full_version); fdlimit::raise_fd_limit(); - init_logger(shared_params.log.as_ref().map(|v| v.as_ref()).unwrap_or("")); - - Ok(()) -} - -/// Initialize the given `config`. -/// -/// This will load the chain spec, set the `config_dir` and the `database_dir`. -pub fn init_config( - config: &mut Configuration, - shared_params: &SharedParams, - version: &VersionInfo, - spec_factory: F, -) -> error::Result<()> where - F: FnOnce(&str) -> Result>, String>, - G: RuntimeGenesis, - E: ChainSpecExtension, -{ - load_spec(config, shared_params, spec_factory)?; - - if config.config_dir.is_none() { - config.config_dir = Some(base_path(&shared_params, version)); - } - - if config.database.is_none() { - config.database = Some(DatabaseConfig::Path { - path: config - .in_chain_config_dir(DEFAULT_DB_CONFIG_PATH) - .expect("We provided a base_path/config_dir."), - cache_size: None, - }); - } - - Ok(()) -} - -/// Run the node -/// -/// Builds and runs either a full or a light node, depending on the `role` within the `Configuration`. -pub fn run_node( - config: Configuration, - new_light: FNL, - new_full: FNF, - version: &VersionInfo, -) -> error::Result<()> -where - FNL: FnOnce(Configuration) -> Result, - FNF: FnOnce(Configuration) -> Result, - G: RuntimeGenesis, - E: ChainSpecExtension, - SL: AbstractService + Unpin, - SF: AbstractService + Unpin, -{ - info!("{}", version.name); - info!(" version {}", config.full_version()); - info!(" by {}, {}-{}", version.author, version.copyright_start_year, Local::today().year()); - info!("Chain specification: {}", config.expect_chain_spec().name()); - info!("Node name: {}", config.name); - info!("Roles: {}", display_role(&config)); - - match config.roles { - ServiceRoles::LIGHT => run_service_until_exit( - config, - new_light, - ), - _ => run_service_until_exit( - config, - new_full, - ), - } -} - -/// Returns a string displaying the node role, special casing the sentry mode -/// (returning `SENTRY`), since the node technically has an `AUTHORITY` role but -/// doesn't participate. -pub fn display_role(config: &Configuration) -> String { - if config.sentry_mode { - "SENTRY".to_string() - } else { - format!("{:?}", config.roles) - } -} - -/// Fill the given `PoolConfiguration` by looking at the cli parameters. -fn fill_transaction_pool_configuration( - options: &mut Configuration, - params: TransactionPoolParams, -) -> error::Result<()> { - // ready queue - options.transaction_pool.ready.count = params.pool_limit; - options.transaction_pool.ready.total_bytes = params.pool_kbytes * 1024; - - // future queue - let factor = 10; - options.transaction_pool.future.count = params.pool_limit / factor; - options.transaction_pool.future.total_bytes = params.pool_kbytes * 1024 / factor; - - Ok(()) -} - -/// Fill the given `NetworkConfiguration` by looking at the cli parameters. -fn fill_network_configuration( - cli: NetworkConfigurationParams, - config_path: PathBuf, - config: &mut NetworkConfiguration, - client_id: String, - is_dev: bool, -) -> error::Result<()> { - config.boot_nodes.extend(cli.bootnodes.into_iter()); - config.config_path = Some(config_path.to_string_lossy().into()); - config.net_config_path = config.config_path.clone(); - - config.reserved_nodes.extend(cli.reserved_nodes.into_iter()); - if cli.reserved_only { - config.non_reserved_mode = NonReservedPeerMode::Deny; - } - - config.sentry_nodes.extend(cli.sentry_nodes.into_iter()); - - for addr in cli.listen_addr.iter() { - let addr = addr.parse().ok().ok_or(error::Error::InvalidListenMultiaddress)?; - config.listen_addresses.push(addr); - } - - if config.listen_addresses.is_empty() { - let port = match cli.port { - Some(port) => port, - None => 30333, - }; - - config.listen_addresses = vec![ - iter::once(Protocol::Ip4(Ipv4Addr::new(0, 0, 0, 0))) - .chain(iter::once(Protocol::Tcp(port))) - .collect() - ]; - } - - config.client_version = client_id; - config.node_key = node_key::node_key_config(cli.node_key_params, &config.net_config_path)?; - - config.in_peers = cli.in_peers; - config.out_peers = cli.out_peers; - - config.transport = TransportConfig::Normal { - enable_mdns: !is_dev && !cli.no_mdns, - allow_private_ipv4: !cli.no_private_ipv4, - wasm_external_transport: None, - use_yamux_flow_control: cli.use_yamux_flow_control - }; - - config.max_parallel_downloads = cli.max_parallel_downloads; - - Ok(()) -} - -#[cfg(not(target_os = "unknown"))] -fn input_keystore_password() -> Result { - rpassword::read_password_from_tty(Some("Keystore password: ")) - .map_err(|e| format!("{:?}", e)) -} - -/// Use in memory keystore config when it is not required at all. -pub fn fill_config_keystore_in_memory(config: &mut sc_service::Configuration) - -> Result<(), String> -{ - match &mut config.keystore { - cfg @ KeystoreConfig::None => { *cfg = KeystoreConfig::InMemory; Ok(()) }, - _ => Err("Keystore config specified when it should not be!".into()), - } -} - -/// Fill the password field of the given config instance. -fn fill_config_keystore_password_and_path( - config: &mut sc_service::Configuration, - cli: &RunCmd, -) -> Result<(), String> { - let password = if cli.password_interactive { - #[cfg(not(target_os = "unknown"))] - { - Some(input_keystore_password()?.into()) - } - #[cfg(target_os = "unknown")] - None - } else if let Some(ref file) = cli.password_filename { - Some(fs::read_to_string(file).map_err(|e| format!("{}", e))?.into()) - } else if let Some(ref password) = cli.password { - Some(password.clone().into()) - } else { - None - }; - - let path = cli.keystore_path.clone().or( - config.in_chain_config_dir(DEFAULT_KEYSTORE_CONFIG_PATH) - ); - - config.keystore = KeystoreConfig::Path { - path: path.ok_or_else(|| "No `base_path` provided to create keystore path!")?, - password, - }; + init_logger(logger_pattern); Ok(()) } -/// Put block import CLI params into `config` object. -pub fn fill_import_params( - config: &mut Configuration, - cli: &ImportParams, - role: sc_service::Roles, - is_dev: bool, -) -> error::Result<()> -where - G: RuntimeGenesis, -{ - if let Some(DatabaseConfig::Path { ref mut cache_size, .. }) = config.database { - *cache_size = Some(cli.database_cache_size); - } - - config.state_cache_size = cli.state_cache_size; - - // by default we disable pruning if the node is an authority (i.e. - // `ArchiveAll`), otherwise we keep state for the last 256 blocks. if the - // node is an authority and pruning is enabled explicitly, then we error - // unless `unsafe_pruning` is set. - config.pruning = match &cli.pruning { - Some(ref s) if s == "archive" => PruningMode::ArchiveAll, - None if role == sc_service::Roles::AUTHORITY => PruningMode::ArchiveAll, - None => PruningMode::default(), - Some(s) => { - if role == sc_service::Roles::AUTHORITY && !cli.unsafe_pruning { - return Err(error::Error::Input( - "Validators should run with state pruning disabled (i.e. archive). \ - You can ignore this check with `--unsafe-pruning`.".to_string() - )); - } - - PruningMode::keep_blocks(s.parse() - .map_err(|_| error::Error::Input("Invalid pruning mode specified".to_string()))? - ) - }, - }; - - config.wasm_method = cli.wasm_method.into(); - - let exec = &cli.execution_strategies; - let exec_all_or = |strat: ExecutionStrategy, default: ExecutionStrategy| { - exec.execution.unwrap_or(if strat == default && is_dev { - ExecutionStrategy::Native - } else { - strat - }).into() - }; - - config.execution_strategies = ExecutionStrategies { - syncing: exec_all_or(exec.execution_syncing, DEFAULT_EXECUTION_SYNCING), - importing: exec_all_or(exec.execution_import_block, DEFAULT_EXECUTION_IMPORT_BLOCK), - block_construction: - exec_all_or(exec.execution_block_construction, DEFAULT_EXECUTION_BLOCK_CONSTRUCTION), - offchain_worker: - exec_all_or(exec.execution_offchain_worker, DEFAULT_EXECUTION_OFFCHAIN_WORKER), - other: exec_all_or(exec.execution_other, DEFAULT_EXECUTION_OTHER), - }; - Ok(()) -} - -/// Update and prepare a `Configuration` with command line parameters of `RunCmd` and `VersionInfo` -pub fn update_config_for_running_node( - mut config: &mut Configuration, - cli: RunCmd, -) -> error::Result<()> -where - G: RuntimeGenesis, -{ - fill_config_keystore_password_and_path(&mut config, &cli)?; - - let keyring = cli.get_keyring(); - let is_dev = cli.shared_params.dev; - let is_light = cli.light; - let is_authority = (cli.validator || cli.sentry || is_dev || keyring.is_some()) - && !is_light; - let role = - if is_light { - sc_service::Roles::LIGHT - } else if is_authority { - sc_service::Roles::AUTHORITY - } else { - sc_service::Roles::FULL - }; - - fill_import_params(&mut config, &cli.import_params, role, is_dev)?; - - config.name = match (cli.name.as_ref(), keyring) { - (Some(name), _) => name.to_string(), - (_, Some(keyring)) => keyring.to_string(), - (None, None) => generate_node_name(), - }; - if let Err(msg) = node_key::is_node_name_valid(&config.name) { - return Err(error::Error::Input( - format!("Invalid node name '{}'. Reason: {}. If unsure, use none.", - config.name, - msg, - ) - )); - } - - // set sentry mode (i.e. act as an authority but **never** actively participate) - config.sentry_mode = cli.sentry; - - config.offchain_worker = match (cli.offchain_worker, role) { - (params::OffchainWorkerEnabled::WhenValidating, sc_service::Roles::AUTHORITY) => true, - (params::OffchainWorkerEnabled::Always, _) => true, - (params::OffchainWorkerEnabled::Never, _) => false, - (params::OffchainWorkerEnabled::WhenValidating, _) => false, - }; - - config.roles = role; - config.disable_grandpa = cli.no_grandpa; - - let client_id = config.client_id(); - fill_network_configuration( - cli.network_config, - config.in_chain_config_dir(DEFAULT_NETWORK_CONFIG_PATH).expect("We provided a basepath"), - &mut config.network, - client_id, - is_dev, - )?; - - fill_transaction_pool_configuration(&mut config, cli.pool_config)?; - - config.dev_key_seed = keyring - .map(|a| format!("//{}", a)).or_else(|| { - if is_dev && !is_light { - Some("//Alice".into()) - } else { - None - } - }); - - if config.rpc_http.is_none() || cli.rpc_port.is_some() { - let rpc_interface: &str = interface_str(cli.rpc_external, cli.unsafe_rpc_external, cli.validator)?; - config.rpc_http = Some(parse_address(&format!("{}:{}", rpc_interface, 9933), cli.rpc_port)?); - } - if config.rpc_ws.is_none() || cli.ws_port.is_some() { - let ws_interface: &str = interface_str(cli.ws_external, cli.unsafe_ws_external, cli.validator)?; - config.rpc_ws = Some(parse_address(&format!("{}:{}", ws_interface, 9944), cli.ws_port)?); - } - - config.rpc_ws_max_connections = cli.ws_max_connections; - config.rpc_cors = cli.rpc_cors.unwrap_or_else(|| if is_dev { - log::warn!("Running in --dev mode, RPC CORS has been disabled."); - Cors::All - } else { - Cors::List(vec![ - "http://localhost:*".into(), - "http://127.0.0.1:*".into(), - "https://localhost:*".into(), - "https://127.0.0.1:*".into(), - "https://polkadot.js.org".into(), - "https://substrate-ui.parity.io".into(), - ]) - }).into(); - - // Override telemetry - if cli.no_telemetry { - config.telemetry_endpoints = None; - } else if !cli.telemetry_endpoints.is_empty() { - config.telemetry_endpoints = Some(TelemetryEndpoints::new(cli.telemetry_endpoints)); - } - // Override prometheus - if cli.no_prometheus { - config.prometheus_port = None; - } else { - let prometheus_interface: &str = if cli.prometheus_external { "0.0.0.0" } else { "127.0.0.1" }; - config.prometheus_port = Some( - parse_address(&format!("{}:{}", prometheus_interface, 9615), cli.prometheus_port)?); - } - - config.tracing_targets = cli.import_params.tracing_targets.into(); - config.tracing_receiver = cli.import_params.tracing_receiver.into(); - - // Imply forced authoring on --dev - config.force_authoring = cli.shared_params.dev || cli.force_authoring; - - Ok(()) -} - -fn interface_str( - is_external: bool, - is_unsafe_external: bool, - is_validator: bool, -) -> Result<&'static str, error::Error> { - if is_external && is_validator { - return Err(error::Error::Input("--rpc-external and --ws-external options shouldn't be \ - used if the node is running as a validator. Use `--unsafe-rpc-external` if you understand \ - the risks. See the options description for more information.".to_owned())); - } - - if is_external || is_unsafe_external { - log::warn!("It isn't safe to expose RPC publicly without a proxy server that filters \ - available set of RPC methods."); - - Ok("0.0.0.0") - } else { - Ok("127.0.0.1") - } -} - -fn parse_address( - address: &str, - port: Option, -) -> Result { - let mut address: SocketAddr = address.parse().map_err( - |_| format!("Invalid address: {}", address) - )?; - if let Some(port) = port { - address.set_port(port); - } - - Ok(address) -} - /// Initialize the logger pub fn init_logger(pattern: &str) { use ansi_term::Colour; @@ -766,116 +221,3 @@ fn kill_color(s: &str) -> String { } RE.replace_all(s, "").to_string() } - -#[cfg(test)] -mod tests { - use super::*; - - const TEST_VERSION_INFO: &'static VersionInfo = &VersionInfo { - name: "node-test", - version: "0.1.0", - commit: "some_commit", - executable_name: "node-test", - description: "description", - author: "author", - support_url: "http://example.org", - copyright_start_year: 2020, - }; - - #[test] - fn keystore_path_is_generated_correctly() { - let chain_spec = ChainSpec::from_genesis( - "test", - "test-id", - || (), - Vec::new(), - None, - None, - None, - None::<()>, - ); - - for keystore_path in vec![None, Some("/keystore/path")] { - let args: Vec<&str> = vec![]; - let mut run_cmds = RunCmd::from_iter(args); - run_cmds.keystore_path = keystore_path.clone().map(PathBuf::from); - - let mut node_config = Configuration::default(); - node_config.config_dir = Some(PathBuf::from("/test/path")); - node_config.chain_spec = Some(chain_spec.clone()); - update_config_for_running_node( - &mut node_config, - run_cmds.clone(), - ).unwrap(); - - let expected_path = match keystore_path { - Some(path) => PathBuf::from(path), - None => PathBuf::from("/test/path/chains/test-id/keystore"), - }; - - assert_eq!(expected_path, node_config.keystore.path().unwrap().to_owned()); - } - } - - #[test] - fn ensure_load_spec_provide_defaults() { - let chain_spec = ChainSpec::from_genesis( - "test", - "test-id", - || (), - vec!["boo".to_string()], - Some(TelemetryEndpoints::new(vec![("foo".to_string(), 42)])), - None, - None, - None::<()>, - ); - - let args: Vec<&str> = vec![]; - let cli = RunCmd::from_iter(args); - - let mut config = Configuration::new(TEST_VERSION_INFO); - load_spec(&mut config, &cli.shared_params, |_| Ok(Some(chain_spec))).unwrap(); - - assert!(config.chain_spec.is_some()); - assert!(!config.network.boot_nodes.is_empty()); - assert!(config.telemetry_endpoints.is_some()); - } - - #[test] - fn ensure_update_config_for_running_node_provides_defaults() { - let chain_spec = ChainSpec::from_genesis( - "test", - "test-id", - || (), - vec![], - None, - None, - None, - None::<()>, - ); - - let args: Vec<&str> = vec![]; - let cli = RunCmd::from_iter(args); - - let mut config = Configuration::new(TEST_VERSION_INFO); - init(&cli.shared_params, &TEST_VERSION_INFO).unwrap(); - init_config( - &mut config, - &cli.shared_params, - &TEST_VERSION_INFO, - |_| Ok(Some(chain_spec)), - ).unwrap(); - update_config_for_running_node(&mut config, cli).unwrap(); - - assert!(config.config_dir.is_some()); - assert!(config.database.is_some()); - if let Some(DatabaseConfig::Path { ref cache_size, .. }) = config.database { - assert!(cache_size.is_some()); - } else { - panic!("invalid config.database variant"); - } - assert!(!config.name.is_empty()); - assert!(config.network.config_path.is_some()); - assert!(!config.network.listen_addresses.is_empty()); - } -} diff --git a/client/cli/src/node_key.rs b/client/cli/src/node_key.rs deleted file mode 100644 index 4401481ca56ce..0000000000000 --- a/client/cli/src/node_key.rs +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright 2017-2020 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 . - -use sc_network::{ - self, - config::{ - NodeKeyConfig, - }, -}; -use sp_core::H256; -use regex::Regex; -use std::{path::{Path, PathBuf}, str::FromStr}; -use crate::error; -use crate::params::{NodeKeyParams, NodeKeyType}; - -/// The file name of the node's Ed25519 secret key inside the chain-specific -/// network config directory, if neither `--node-key` nor `--node-key-file` -/// is specified in combination with `--node-key-type=ed25519`. -const NODE_KEY_ED25519_FILE: &str = "secret_ed25519"; - -/// Check whether a node name is considered as valid -pub fn is_node_name_valid(_name: &str) -> Result<(), &str> { - let name = _name.to_string(); - if name.chars().count() >= crate::NODE_NAME_MAX_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(()) -} - -/// Create a `NodeKeyConfig` from the given `NodeKeyParams` in the context -/// of an optional network config storage directory. -pub fn node_key_config

(params: NodeKeyParams, net_config_dir: &Option

) - -> error::Result -where - P: AsRef -{ - match params.node_key_type { - NodeKeyType::Ed25519 => - params.node_key.as_ref().map(parse_ed25519_secret).unwrap_or_else(|| - Ok(params.node_key_file - .or_else(|| net_config_file(net_config_dir, NODE_KEY_ED25519_FILE)) - .map(sc_network::config::Secret::File) - .unwrap_or(sc_network::config::Secret::New))) - .map(NodeKeyConfig::Ed25519) - } -} - -/// Create an error caused by an invalid node key argument. -fn invalid_node_key(e: impl std::fmt::Display) -> error::Error { - error::Error::Input(format!("Invalid node key: {}", e)) -} - -/// Parse a Ed25519 secret key from a hex string into a `sc_network::Secret`. -fn parse_ed25519_secret(hex: &String) -> error::Result { - H256::from_str(&hex).map_err(invalid_node_key).and_then(|bytes| - sc_network::config::identity::ed25519::SecretKey::from_bytes(bytes) - .map(sc_network::config::Secret::Input) - .map_err(invalid_node_key)) -} - -fn net_config_file

(net_config_dir: &Option

, name: &str) -> Option -where - P: AsRef -{ - net_config_dir.as_ref().map(|d| d.as_ref().join(name)) -} - -#[cfg(test)] -mod tests { - use sc_network::config::identity::ed25519; - 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()); - } - - #[test] - fn test_node_key_config_input() { - fn secret_input(net_config_dir: Option) -> error::Result<()> { - NodeKeyType::variants().iter().try_for_each(|t| { - let node_key_type = NodeKeyType::from_str(t).unwrap(); - let sk = match node_key_type { - NodeKeyType::Ed25519 => ed25519::SecretKey::generate().as_ref().to_vec() - }; - let params = NodeKeyParams { - node_key_type, - node_key: Some(format!("{:x}", H256::from_slice(sk.as_ref()))), - node_key_file: None - }; - node_key_config(params, &net_config_dir).and_then(|c| match c { - NodeKeyConfig::Ed25519(sc_network::config::Secret::Input(ref ski)) - if node_key_type == NodeKeyType::Ed25519 && - &sk[..] == ski.as_ref() => Ok(()), - _ => Err(error::Error::Input("Unexpected node key config".into())) - }) - }) - } - - assert!(secret_input(None).is_ok()); - assert!(secret_input(Some("x".to_string())).is_ok()); - } - - #[test] - fn test_node_key_config_file() { - fn secret_file(net_config_dir: Option) -> error::Result<()> { - NodeKeyType::variants().iter().try_for_each(|t| { - let node_key_type = NodeKeyType::from_str(t).unwrap(); - let tmp = tempfile::Builder::new().prefix("alice").tempdir()?; - let file = tmp.path().join(format!("{}_mysecret", t)).to_path_buf(); - let params = NodeKeyParams { - node_key_type, - node_key: None, - node_key_file: Some(file.clone()) - }; - node_key_config(params, &net_config_dir).and_then(|c| match c { - NodeKeyConfig::Ed25519(sc_network::config::Secret::File(ref f)) - if node_key_type == NodeKeyType::Ed25519 && f == &file => Ok(()), - _ => Err(error::Error::Input("Unexpected node key config".into())) - }) - }) - } - - assert!(secret_file(None).is_ok()); - assert!(secret_file(Some("x".to_string())).is_ok()); - } - - #[test] - fn test_node_key_config_default() { - fn with_def_params(f: F) -> error::Result<()> - where - F: Fn(NodeKeyParams) -> error::Result<()> - { - NodeKeyType::variants().iter().try_for_each(|t| { - let node_key_type = NodeKeyType::from_str(t).unwrap(); - f(NodeKeyParams { - node_key_type, - node_key: None, - node_key_file: None - }) - }) - } - - fn no_config_dir() -> error::Result<()> { - with_def_params(|params| { - let typ = params.node_key_type; - node_key_config::(params, &None) - .and_then(|c| match c { - NodeKeyConfig::Ed25519(sc_network::config::Secret::New) - if typ == NodeKeyType::Ed25519 => Ok(()), - _ => Err(error::Error::Input("Unexpected node key config".into())) - }) - }) - } - - fn some_config_dir(net_config_dir: String) -> error::Result<()> { - with_def_params(|params| { - let dir = PathBuf::from(net_config_dir.clone()); - let typ = params.node_key_type; - node_key_config(params, &Some(net_config_dir.clone())) - .and_then(move |c| match c { - NodeKeyConfig::Ed25519(sc_network::config::Secret::File(ref f)) - if typ == NodeKeyType::Ed25519 && - f == &dir.join(NODE_KEY_ED25519_FILE) => Ok(()), - _ => Err(error::Error::Input("Unexpected node key config".into())) - }) - }) - } - - assert!(no_config_dir().is_ok()); - assert!(some_config_dir("x".to_string()).is_ok()); - } -} diff --git a/client/cli/src/params.rs b/client/cli/src/params.rs deleted file mode 100644 index a1a8b9c5b4fef..0000000000000 --- a/client/cli/src/params.rs +++ /dev/null @@ -1,1202 +0,0 @@ -// Copyright 2018-2020 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 . - -use std::{str::FromStr, path::PathBuf}; -use structopt::{StructOpt, clap::arg_enum}; -use sc_service::{ - AbstractService, Configuration, ChainSpecExtension, RuntimeGenesis, ServiceBuilderCommand, - config::DatabaseConfig, -}; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; -use crate::VersionInfo; -use crate::error; -use std::fmt::Debug; -use log::info; -use sc_network::config::build_multiaddr; -use std::io; -use std::fs; -use std::io::{Read, Write, Seek}; -use sp_runtime::generic::BlockId; -use crate::runtime::run_until_exit; -use crate::node_key::node_key_config; -use crate::execution_strategy::*; - -pub use crate::execution_strategy::ExecutionStrategy; - -impl Into for ExecutionStrategy { - fn into(self) -> sc_client_api::ExecutionStrategy { - match self { - ExecutionStrategy::Native => sc_client_api::ExecutionStrategy::NativeWhenPossible, - ExecutionStrategy::Wasm => sc_client_api::ExecutionStrategy::AlwaysWasm, - ExecutionStrategy::Both => sc_client_api::ExecutionStrategy::Both, - ExecutionStrategy::NativeElseWasm => sc_client_api::ExecutionStrategy::NativeElseWasm, - } - } -} - -#[allow(missing_docs)] -mod wasm_execution_method { - use super::*; - - arg_enum! { - /// How to execute Wasm runtime code - #[derive(Debug, Clone, Copy)] - pub enum WasmExecutionMethod { - // Uses an interpreter. - Interpreted, - // Uses a compiled runtime. - Compiled, - } - } - - impl WasmExecutionMethod { - /// Returns list of variants that are not disabled by feature flags. - pub fn enabled_variants() -> Vec<&'static str> { - Self::variants() - .iter() - .cloned() - .filter(|&name| cfg!(feature = "wasmtime") || name != "Compiled") - .collect() - } - } -} - -pub use wasm_execution_method::WasmExecutionMethod; - -impl Into for WasmExecutionMethod { - fn into(self) -> sc_service::config::WasmExecutionMethod { - match self { - WasmExecutionMethod::Interpreted => sc_service::config::WasmExecutionMethod::Interpreted, - #[cfg(feature = "wasmtime")] - WasmExecutionMethod::Compiled => sc_service::config::WasmExecutionMethod::Compiled, - #[cfg(not(feature = "wasmtime"))] - WasmExecutionMethod::Compiled => panic!( - "Substrate must be compiled with \"wasmtime\" feature for compiled Wasm execution" - ), - } - } -} - -arg_enum! { - /// Whether off-chain workers are enabled. - #[allow(missing_docs)] - #[derive(Debug, Clone)] - pub enum OffchainWorkerEnabled { - Always, - Never, - WhenValidating, - } -} - -/// Shared parameters used by all `CoreParams`. -#[derive(Debug, StructOpt, Clone)] -pub struct SharedParams { - /// Specify the chain specification (one of dev, local or staging). - #[structopt(long = "chain", value_name = "CHAIN_SPEC")] - pub chain: Option, - - /// Specify the development chain. - #[structopt(long = "dev")] - pub dev: bool, - - /// Specify custom base path. - #[structopt(long = "base-path", short = "d", value_name = "PATH", parse(from_os_str))] - pub base_path: Option, - - /// Sets a custom logging filter. - #[structopt(short = "l", long = "log", value_name = "LOG_PATTERN")] - pub log: Option, -} - -/// Parameters for block import. -#[derive(Debug, StructOpt, Clone)] -pub struct ImportParams { - /// Specify the state pruning mode, a number of blocks to keep or 'archive'. - /// - /// Default is to keep all block states if the node is running as a - /// validator (i.e. 'archive'), otherwise state is only kept for the last - /// 256 blocks. - #[structopt(long = "pruning", value_name = "PRUNING_MODE")] - pub pruning: Option, - - /// Force start with unsafe pruning settings. - /// - /// When running as a validator it is highly recommended to disable state - /// pruning (i.e. 'archive') which is the default. The node will refuse to - /// start as a validator if pruning is enabled unless this option is set. - #[structopt(long = "unsafe-pruning")] - pub unsafe_pruning: bool, - - /// Method for executing Wasm runtime code. - #[structopt( - long = "wasm-execution", - value_name = "METHOD", - possible_values = &WasmExecutionMethod::enabled_variants(), - case_insensitive = true, - default_value = "Interpreted" - )] - pub wasm_method: WasmExecutionMethod, - - #[allow(missing_docs)] - #[structopt(flatten)] - pub execution_strategies: ExecutionStrategies, - - /// Limit the memory the database cache can use. - #[structopt(long = "db-cache", value_name = "MiB", default_value = "1024")] - pub database_cache_size: u32, - - /// Specify the state cache size. - #[structopt(long = "state-cache-size", value_name = "Bytes", default_value = "67108864")] - pub state_cache_size: usize, - - /// Comma separated list of targets for tracing - #[structopt(long = "tracing-targets", value_name = "TARGETS")] - pub tracing_targets: Option, - - /// Receiver to process tracing messages - #[structopt( - long = "tracing-receiver", - value_name = "RECEIVER", - possible_values = &TracingReceiver::variants(), - case_insensitive = true, - default_value = "Log" - )] - pub tracing_receiver: TracingReceiver, -} - -/// Parameters used to create the network configuration. -#[derive(Debug, StructOpt, Clone)] -pub struct NetworkConfigurationParams { - /// Specify a list of bootnodes. - #[structopt(long = "bootnodes", value_name = "URL")] - pub bootnodes: Vec, - - /// Specify a list of reserved node addresses. - #[structopt(long = "reserved-nodes", value_name = "URL")] - pub reserved_nodes: Vec, - - /// Whether to only allow connections to/from reserved nodes. - /// - /// If you are a validator your node might still connect to other validator - /// nodes regardless of whether they are defined as reserved nodes. - #[structopt(long = "reserved-only")] - pub reserved_only: bool, - - /// Specify a list of sentry node public addresses. - #[structopt( - long = "sentry-nodes", - value_name = "URL", - conflicts_with_all = &[ "sentry" ] - )] - pub sentry_nodes: Vec, - - /// Listen on this multiaddress. - #[structopt(long = "listen-addr", value_name = "LISTEN_ADDR")] - pub listen_addr: Vec, - - /// Specify p2p protocol TCP port. - /// - /// Only used if --listen-addr is not specified. - #[structopt(long = "port", value_name = "PORT")] - pub port: Option, - - /// Forbid connecting to private IPv4 addresses (as specified in - /// [RFC1918](https://tools.ietf.org/html/rfc1918)), unless the address was passed with - /// `--reserved-nodes` or `--bootnodes`. - #[structopt(long = "no-private-ipv4")] - pub no_private_ipv4: bool, - - /// Specify the number of outgoing connections we're trying to maintain. - #[structopt(long = "out-peers", value_name = "COUNT", default_value = "25")] - pub out_peers: u32, - - /// Specify the maximum number of incoming connections we're accepting. - #[structopt(long = "in-peers", value_name = "COUNT", default_value = "25")] - pub in_peers: u32, - - /// Disable mDNS discovery. - /// - /// By default, the network will use mDNS to discover other nodes on the - /// local network. This disables it. Automatically implied when using --dev. - #[structopt(long = "no-mdns")] - pub no_mdns: bool, - - /// Maximum number of peers to ask the same blocks in parallel. - /// - /// This allows downlading announced blocks from multiple peers. Decrease to save - /// traffic and risk increased latency. - #[structopt(long = "max-parallel-downloads", value_name = "COUNT", default_value = "5")] - pub max_parallel_downloads: u32, - - #[allow(missing_docs)] - #[structopt(flatten)] - pub node_key_params: NodeKeyParams, - - /// Experimental feature flag. - #[structopt(long = "use-yamux-flow-control")] - pub use_yamux_flow_control: bool, -} - -arg_enum! { - #[allow(missing_docs)] - #[derive(Debug, Copy, Clone, PartialEq, Eq)] - pub enum NodeKeyType { - Ed25519 - } -} - -/// Parameters used to create the `NodeKeyConfig`, which determines the keypair -/// used for libp2p networking. -#[derive(Debug, StructOpt, Clone)] -pub struct NodeKeyParams { - /// The secret key to use for libp2p networking. - /// - /// The value is a string that is parsed according to the choice of - /// `--node-key-type` as follows: - /// - /// `ed25519`: - /// The value is parsed as a hex-encoded Ed25519 32 bytes secret key, - /// i.e. 64 hex characters. - /// - /// The value of this option takes precedence over `--node-key-file`. - /// - /// WARNING: Secrets provided as command-line arguments are easily exposed. - /// Use of this option should be limited to development and testing. To use - /// an externally managed secret key, use `--node-key-file` instead. - #[structopt(long = "node-key", value_name = "KEY")] - pub node_key: Option, - - /// The type of secret key to use for libp2p networking. - /// - /// The secret key of the node is obtained as follows: - /// - /// * If the `--node-key` option is given, the value is parsed as a secret key - /// according to the type. See the documentation for `--node-key`. - /// - /// * If the `--node-key-file` option is given, the secret key is read from the - /// specified file. See the documentation for `--node-key-file`. - /// - /// * Otherwise, the secret key is read from a file with a predetermined, - /// type-specific name from the chain-specific network config directory - /// inside the base directory specified by `--base-dir`. If this file does - /// not exist, it is created with a newly generated secret key of the - /// chosen type. - /// - /// The node's secret key determines the corresponding public key and hence the - /// node's peer ID in the context of libp2p. - #[structopt( - long = "node-key-type", - value_name = "TYPE", - possible_values = &NodeKeyType::variants(), - case_insensitive = true, - default_value = "Ed25519" - )] - pub node_key_type: NodeKeyType, - - /// The file from which to read the node's secret key to use for libp2p networking. - /// - /// The contents of the file are parsed according to the choice of `--node-key-type` - /// as follows: - /// - /// `ed25519`: - /// The file must contain an unencoded 32 bytes Ed25519 secret key. - /// - /// If the file does not exist, it is created with a newly generated secret key of - /// the chosen type. - #[structopt(long = "node-key-file", value_name = "FILE")] - pub node_key_file: Option, -} - -/// Parameters used to create the pool configuration. -#[derive(Debug, StructOpt, Clone)] -pub struct TransactionPoolParams { - /// Maximum number of transactions in the transaction pool. - #[structopt(long = "pool-limit", value_name = "COUNT", default_value = "8192")] - pub pool_limit: usize, - /// Maximum number of kilobytes of all transactions stored in the pool. - #[structopt(long = "pool-kbytes", value_name = "COUNT", default_value = "20480")] - pub pool_kbytes: usize, -} - -arg_enum! { - #[allow(missing_docs)] - #[derive(Debug, Copy, Clone, PartialEq, Eq)] - pub enum TracingReceiver { - Log, - Telemetry, - } -} - -impl Into for TracingReceiver { - fn into(self) -> sc_tracing::TracingReceiver { - match self { - TracingReceiver::Log => sc_tracing::TracingReceiver::Log, - TracingReceiver::Telemetry => sc_tracing::TracingReceiver::Telemetry, - } - } -} - -/// Execution strategies parameters. -#[derive(Debug, StructOpt, Clone)] -pub struct ExecutionStrategies { - /// The means of execution used when calling into the runtime while syncing blocks. - #[structopt( - long = "execution-syncing", - value_name = "STRATEGY", - possible_values = &ExecutionStrategy::variants(), - case_insensitive = true, - default_value = DEFAULT_EXECUTION_SYNCING.as_str(), - )] - pub execution_syncing: ExecutionStrategy, - - /// The means of execution used when calling into the runtime while importing blocks. - #[structopt( - long = "execution-import-block", - value_name = "STRATEGY", - possible_values = &ExecutionStrategy::variants(), - case_insensitive = true, - default_value = DEFAULT_EXECUTION_IMPORT_BLOCK.as_str(), - )] - pub execution_import_block: ExecutionStrategy, - - /// The means of execution used when calling into the runtime while constructing blocks. - #[structopt( - long = "execution-block-construction", - value_name = "STRATEGY", - possible_values = &ExecutionStrategy::variants(), - case_insensitive = true, - default_value = DEFAULT_EXECUTION_BLOCK_CONSTRUCTION.as_str(), - )] - pub execution_block_construction: ExecutionStrategy, - - /// The means of execution used when calling into the runtime while using an off-chain worker. - #[structopt( - long = "execution-offchain-worker", - value_name = "STRATEGY", - possible_values = &ExecutionStrategy::variants(), - case_insensitive = true, - default_value = DEFAULT_EXECUTION_OFFCHAIN_WORKER.as_str(), - )] - pub execution_offchain_worker: ExecutionStrategy, - - /// The means of execution used when calling into the runtime while not syncing, importing or constructing blocks. - #[structopt( - long = "execution-other", - value_name = "STRATEGY", - possible_values = &ExecutionStrategy::variants(), - case_insensitive = true, - default_value = DEFAULT_EXECUTION_OTHER.as_str(), - )] - pub execution_other: ExecutionStrategy, - - /// The execution strategy that should be used by all execution contexts. - #[structopt( - long = "execution", - value_name = "STRATEGY", - possible_values = &ExecutionStrategy::variants(), - case_insensitive = true, - conflicts_with_all = &[ - "execution-other", - "execution-offchain-worker", - "execution-block-construction", - "execution-import-block", - "execution-syncing", - ] - )] - pub execution: Option, -} - -/// The `run` command used to run a node. -#[derive(Debug, StructOpt, Clone)] -pub struct RunCmd { - /// Enable validator mode. - /// - /// The node will be started with the authority role and actively - /// participate in any consensus task that it can (e.g. depending on - /// availability of local keys). - #[structopt( - long = "validator", - conflicts_with_all = &[ "sentry" ] - )] - pub validator: bool, - - /// Enable sentry mode. - /// - /// The node will be started with the authority role and participate in - /// consensus tasks as an "observer", it will never actively participate - /// regardless of whether it could (e.g. keys are available locally). This - /// mode is useful as a secure proxy for validators (which would run - /// detached from the network), since we want this node to participate in - /// the full consensus protocols in order to have all needed consensus data - /// available to relay to private nodes. - #[structopt( - long = "sentry", - conflicts_with_all = &[ "validator", "light" ] - )] - pub sentry: bool, - - /// Disable GRANDPA voter when running in validator mode, otherwise disables the GRANDPA observer. - #[structopt(long = "no-grandpa")] - pub no_grandpa: bool, - - /// Experimental: Run in light client mode. - #[structopt(long = "light", conflicts_with = "sentry")] - pub light: bool, - - /// Listen to all RPC interfaces. - /// - /// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use a RPC proxy - /// server to filter out dangerous methods. More details: https://github.com/paritytech/substrate/wiki/Public-RPC. - /// Use `--unsafe-rpc-external` to suppress the warning if you understand the risks. - #[structopt(long = "rpc-external")] - pub rpc_external: bool, - - /// Listen to all RPC interfaces. - /// - /// Same as `--rpc-external`. - #[structopt(long = "unsafe-rpc-external")] - pub unsafe_rpc_external: bool, - - /// Listen to all Websocket interfaces. - /// - /// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use a RPC proxy - /// server to filter out dangerous methods. More details: https://github.com/paritytech/substrate/wiki/Public-RPC. - /// Use `--unsafe-ws-external` to suppress the warning if you understand the risks. - #[structopt(long = "ws-external")] - pub ws_external: bool, - - /// Listen to all Websocket interfaces. - /// - /// Same as `--ws-external`. - #[structopt(long = "unsafe-ws-external")] - pub unsafe_ws_external: bool, - - /// Listen to all Prometheus endpoint interfaces. - /// - /// Default is local. - #[structopt(long = "prometheus-external")] - pub prometheus_external: bool, - - /// Specify HTTP RPC server TCP port. - #[structopt(long = "rpc-port", value_name = "PORT")] - pub rpc_port: Option, - - /// Specify WebSockets RPC server TCP port. - #[structopt(long = "ws-port", value_name = "PORT")] - pub ws_port: Option, - - /// Maximum number of WS RPC server connections. - #[structopt(long = "ws-max-connections", value_name = "COUNT")] - pub ws_max_connections: Option, - - /// Specify browser Origins allowed to access the HTTP & WS RPC servers. - /// - /// A comma-separated list of origins (protocol://domain or special `null` - /// value). Value of `all` will disable origin validation. Default is to - /// allow localhost, https://polkadot.js.org and - /// https://substrate-ui.parity.io origins. When running in --dev mode the - /// default is to allow all origins. - #[structopt(long = "rpc-cors", value_name = "ORIGINS", parse(try_from_str = parse_cors))] - pub rpc_cors: Option, - - /// Specify Prometheus endpoint TCP Port. - #[structopt(long = "prometheus-port", value_name = "PORT")] - pub prometheus_port: Option, - - /// Do not expose a Prometheus metric endpoint. - /// - /// Prometheus metric endpoint is enabled by default. - #[structopt(long = "no-prometheus")] - pub no_prometheus: bool, - - /// The human-readable name for this node. - /// - /// The node name will be reported to the telemetry server, if enabled. - #[structopt(long = "name", value_name = "NAME")] - pub name: Option, - - /// Disable connecting to the Substrate telemetry server. - /// - /// Telemetry is on by default on global chains. - #[structopt(long = "no-telemetry")] - pub no_telemetry: bool, - - /// The URL of the telemetry server to connect to. - /// - /// This flag can be passed multiple times as a mean to specify multiple - /// telemetry endpoints. Verbosity levels range from 0-9, with 0 denoting - /// the least verbosity. If no verbosity level is specified the default is - /// 0. - #[structopt(long = "telemetry-url", value_name = "URL VERBOSITY", parse(try_from_str = parse_telemetry_endpoints))] - pub telemetry_endpoints: Vec<(String, u8)>, - - /// Should execute offchain workers on every block. - /// - /// By default it's only enabled for nodes that are authoring new blocks. - #[structopt( - long = "offchain-worker", - value_name = "ENABLED", - possible_values = &OffchainWorkerEnabled::variants(), - case_insensitive = true, - default_value = "WhenValidating" - )] - pub offchain_worker: OffchainWorkerEnabled, - - #[allow(missing_docs)] - #[structopt(flatten)] - pub shared_params: SharedParams, - - #[allow(missing_docs)] - #[structopt(flatten)] - pub import_params: ImportParams, - - #[allow(missing_docs)] - #[structopt(flatten)] - pub network_config: NetworkConfigurationParams, - - #[allow(missing_docs)] - #[structopt(flatten)] - pub pool_config: TransactionPoolParams, - - /// Shortcut for `--name Alice --validator` with session keys for `Alice` added to keystore. - #[structopt(long, conflicts_with_all = &["bob", "charlie", "dave", "eve", "ferdie", "one", "two"])] - pub alice: bool, - - /// Shortcut for `--name Bob --validator` with session keys for `Bob` added to keystore. - #[structopt(long, conflicts_with_all = &["alice", "charlie", "dave", "eve", "ferdie", "one", "two"])] - pub bob: bool, - - /// Shortcut for `--name Charlie --validator` with session keys for `Charlie` added to keystore. - #[structopt(long, conflicts_with_all = &["alice", "bob", "dave", "eve", "ferdie", "one", "two"])] - pub charlie: bool, - - /// Shortcut for `--name Dave --validator` with session keys for `Dave` added to keystore. - #[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "eve", "ferdie", "one", "two"])] - pub dave: bool, - - /// Shortcut for `--name Eve --validator` with session keys for `Eve` added to keystore. - #[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "ferdie", "one", "two"])] - pub eve: bool, - - /// Shortcut for `--name Ferdie --validator` with session keys for `Ferdie` added to keystore. - #[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "one", "two"])] - pub ferdie: bool, - - /// Shortcut for `--name One --validator` with session keys for `One` added to keystore. - #[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "two"])] - pub one: bool, - - /// Shortcut for `--name Two --validator` with session keys for `Two` added to keystore. - #[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "one"])] - pub two: bool, - - /// Enable authoring even when offline. - #[structopt(long = "force-authoring")] - pub force_authoring: bool, - - /// Specify custom keystore path. - #[structopt(long = "keystore-path", value_name = "PATH", parse(from_os_str))] - pub keystore_path: Option, - - /// Use interactive shell for entering the password used by the keystore. - #[structopt( - long = "password-interactive", - conflicts_with_all = &[ "password", "password-filename" ] - )] - pub password_interactive: bool, - - /// Password used by the keystore. - #[structopt( - long = "password", - conflicts_with_all = &[ "password-interactive", "password-filename" ] - )] - pub password: Option, - - /// File that contains the password used by the keystore. - #[structopt( - long = "password-filename", - value_name = "PATH", - parse(from_os_str), - conflicts_with_all = &[ "password-interactive", "password" ] - )] - pub password_filename: Option -} - -impl RunCmd { - /// Get the `Sr25519Keyring` matching one of the flag - pub fn get_keyring(&self) -> Option { - use sp_keyring::Sr25519Keyring::*; - - if self.alice { Some(Alice) } - else if self.bob { Some(Bob) } - else if self.charlie { Some(Charlie) } - else if self.dave { Some(Dave) } - else if self.eve { Some(Eve) } - else if self.ferdie { Some(Ferdie) } - else if self.one { Some(One) } - else if self.two { Some(Two) } - else { None } - } -} - -/// Default to verbosity level 0, if none is provided. -fn parse_telemetry_endpoints(s: &str) -> Result<(String, u8), Box> { - let pos = s.find(' '); - match pos { - None => { - Ok((s.to_owned(), 0)) - }, - Some(pos_) => { - let verbosity = s[pos_ + 1..].parse()?; - let url = s[..pos_].parse()?; - Ok((url, verbosity)) - } - } -} - -/// CORS setting -/// -/// The type is introduced to overcome `Option>` -/// handling of `structopt`. -#[derive(Clone, Debug)] -pub enum Cors { - /// All hosts allowed - All, - /// Only hosts on the list are allowed. - List(Vec), -} - -impl From for Option> { - fn from(cors: Cors) -> Self { - match cors { - Cors::All => None, - Cors::List(list) => Some(list), - } - } -} - -/// Parse cors origins -fn parse_cors(s: &str) -> Result> { - let mut is_all = false; - let mut origins = Vec::new(); - for part in s.split(',') { - match part { - "all" | "*" => { - is_all = true; - break; - }, - other => origins.push(other.to_owned()), - } - } - - Ok(if is_all { Cors::All } else { Cors::List(origins) }) -} - -/// The `build-spec` command used to build a specification. -#[derive(Debug, StructOpt, Clone)] -pub struct BuildSpecCmd { - /// Force raw genesis storage output. - #[structopt(long = "raw")] - pub raw: bool, - - /// Disable adding the default bootnode to the specification. - /// - /// By default the `/ip4/127.0.0.1/tcp/30333/p2p/NODE_PEER_ID` bootnode is added to the - /// specification when no bootnode exists. - #[structopt(long = "disable-default-bootnode")] - pub disable_default_bootnode: bool, - - #[allow(missing_docs)] - #[structopt(flatten)] - pub shared_params: SharedParams, - - #[allow(missing_docs)] - #[structopt(flatten)] - pub node_key_params: NodeKeyParams, -} - -/// Wrapper type of `String` that holds an unsigned integer of arbitrary size, formatted as a decimal. -#[derive(Debug, Clone)] -pub struct BlockNumber(String); - -impl FromStr for BlockNumber { - type Err = String; - - fn from_str(block_number: &str) -> Result { - if block_number.chars().any(|d| !d.is_digit(10)) { - Err(format!( - "Invalid block number: {}, expected decimal formatted unsigned integer", - block_number - )) - } else { - Ok(Self(block_number.to_owned())) - } - } -} - -impl BlockNumber { - /// Wrapper on top of `std::str::parse` but with `Error` as a `String` - /// - /// See `https://doc.rust-lang.org/std/primitive.str.html#method.parse` for more elaborate - /// documentation. - pub fn parse(&self) -> Result - where - N: FromStr, - N::Err: std::fmt::Debug, - { - self.0 - .parse() - .map_err(|e| format!("BlockNumber: {} parsing failed because of {:?}", self.0, e)) - } -} - -/// The `export-blocks` command used to export blocks. -#[derive(Debug, StructOpt, Clone)] -pub struct ExportBlocksCmd { - /// Output file name or stdout if unspecified. - #[structopt(parse(from_os_str))] - pub output: Option, - - /// Specify starting block number. - /// - /// Default is 1. - #[structopt(long = "from", value_name = "BLOCK")] - pub from: Option, - - /// Specify last block number. - /// - /// Default is best block. - #[structopt(long = "to", value_name = "BLOCK")] - pub to: Option, - - /// Use JSON output rather than binary. - #[structopt(long = "json")] - pub json: bool, - - #[allow(missing_docs)] - #[structopt(flatten)] - pub shared_params: SharedParams, -} - -/// The `import-blocks` command used to import blocks. -#[derive(Debug, StructOpt, Clone)] -pub struct ImportBlocksCmd { - /// Input file or stdin if unspecified. - #[structopt(parse(from_os_str))] - pub input: Option, - - /// The default number of 64KB pages to ever allocate for Wasm execution. - /// - /// Don't alter this unless you know what you're doing. - #[structopt(long = "default-heap-pages", value_name = "COUNT")] - pub default_heap_pages: Option, - - #[allow(missing_docs)] - #[structopt(flatten)] - pub shared_params: SharedParams, - - #[allow(missing_docs)] - #[structopt(flatten)] - pub import_params: ImportParams, -} - -/// The `check-block` command used to validate blocks. -#[derive(Debug, StructOpt, Clone)] -pub struct CheckBlockCmd { - /// Block hash or number - #[structopt(value_name = "HASH or NUMBER")] - pub input: String, - - /// The default number of 64KB pages to ever allocate for Wasm execution. - /// - /// Don't alter this unless you know what you're doing. - #[structopt(long = "default-heap-pages", value_name = "COUNT")] - pub default_heap_pages: Option, - - #[allow(missing_docs)] - #[structopt(flatten)] - pub shared_params: SharedParams, - - #[allow(missing_docs)] - #[structopt(flatten)] - pub import_params: ImportParams, -} - -/// The `revert` command used revert the chain to a previous state. -#[derive(Debug, StructOpt, Clone)] -pub struct RevertCmd { - /// Number of blocks to revert. - #[structopt(default_value = "256")] - pub num: BlockNumber, - - #[allow(missing_docs)] - #[structopt(flatten)] - pub shared_params: SharedParams, -} - -/// The `purge-chain` command used to remove the whole chain. -#[derive(Debug, StructOpt, Clone)] -pub struct PurgeChainCmd { - /// Skip interactive prompt by answering yes automatically. - #[structopt(short = "y")] - pub yes: bool, - - #[allow(missing_docs)] - #[structopt(flatten)] - pub shared_params: SharedParams, -} - -/// All core commands that are provided by default. -/// -/// The core commands are split into multiple subcommands and `Run` is the default subcommand. From -/// the CLI user perspective, it is not visible that `Run` is a subcommand. So, all parameters of -/// `Run` are exported as main executable parameters. -#[derive(Debug, Clone, StructOpt)] -pub enum Subcommand { - /// Build a spec.json file, outputing to stdout. - BuildSpec(BuildSpecCmd), - - /// Export blocks to a file. - ExportBlocks(ExportBlocksCmd), - - /// Import blocks from file. - ImportBlocks(ImportBlocksCmd), - - /// Validate a single block. - CheckBlock(CheckBlockCmd), - - /// Revert chain to the previous state. - Revert(RevertCmd), - - /// Remove the whole chain data. - PurgeChain(PurgeChainCmd), -} - -impl Subcommand { - /// Get the shared parameters of a `CoreParams` command - pub fn get_shared_params(&self) -> &SharedParams { - use Subcommand::*; - - match self { - BuildSpec(params) => ¶ms.shared_params, - ExportBlocks(params) => ¶ms.shared_params, - ImportBlocks(params) => ¶ms.shared_params, - CheckBlock(params) => ¶ms.shared_params, - Revert(params) => ¶ms.shared_params, - PurgeChain(params) => ¶ms.shared_params, - } - } - - /// Run any `CoreParams` command - pub fn run( - self, - config: Configuration, - builder: B, - ) -> error::Result<()> - where - B: FnOnce(Configuration) -> Result, - G: RuntimeGenesis, - E: ChainSpecExtension, - BC: ServiceBuilderCommand + Unpin, - BB: sp_runtime::traits::Block + Debug, - <<::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug, - ::Hash: std::str::FromStr, - { - assert!(config.chain_spec.is_some(), "chain_spec must be present before continuing"); - - match self { - Subcommand::BuildSpec(cmd) => cmd.run(config), - Subcommand::ExportBlocks(cmd) => cmd.run(config, builder), - Subcommand::ImportBlocks(cmd) => cmd.run(config, builder), - Subcommand::CheckBlock(cmd) => cmd.run(config, builder), - Subcommand::PurgeChain(cmd) => cmd.run(config), - Subcommand::Revert(cmd) => cmd.run(config, builder), - } - } -} - -impl RunCmd { - /// Run the command that runs the node - pub fn run( - self, - mut config: Configuration, - new_light: FNL, - new_full: FNF, - version: &VersionInfo, - ) -> error::Result<()> - where - G: RuntimeGenesis, - E: ChainSpecExtension, - FNL: FnOnce(Configuration) -> Result, - FNF: FnOnce(Configuration) -> Result, - SL: AbstractService + Unpin, - SF: AbstractService + Unpin, - { - assert!(config.chain_spec.is_some(), "chain_spec must be present before continuing"); - - crate::update_config_for_running_node(&mut config, self)?; - - crate::run_node(config, new_light, new_full, &version) - } -} - -impl BuildSpecCmd { - /// Run the build-spec command - pub fn run( - self, - config: Configuration, - ) -> error::Result<()> - where - G: RuntimeGenesis, - E: ChainSpecExtension, - { - assert!(config.chain_spec.is_some(), "chain_spec must be present before continuing"); - - info!("Building chain spec"); - let mut spec = config.expect_chain_spec().clone(); - let raw_output = self.raw; - - if spec.boot_nodes().is_empty() && !self.disable_default_bootnode { - let node_key = node_key_config( - self.node_key_params.clone(), - &Some(config - .in_chain_config_dir(crate::DEFAULT_NETWORK_CONFIG_PATH) - .expect("We provided a base_path")), - )?; - let keys = node_key.into_keypair()?; - let peer_id = keys.public().into_peer_id(); - let addr = build_multiaddr![ - Ip4([127, 0, 0, 1]), - Tcp(30333u16), - P2p(peer_id) - ]; - spec.add_boot_node(addr) - } - - let json = sc_service::chain_ops::build_spec(spec, raw_output)?; - - print!("{}", json); - - Ok(()) - } -} - -impl ExportBlocksCmd { - /// Run the export-blocks command - pub fn run( - self, - mut config: Configuration, - builder: B, - ) -> error::Result<()> - where - B: FnOnce(Configuration) -> Result, - G: RuntimeGenesis, - E: ChainSpecExtension, - BC: ServiceBuilderCommand + Unpin, - BB: sp_runtime::traits::Block + Debug, - <<::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug, - ::Hash: std::str::FromStr, - { - assert!(config.chain_spec.is_some(), "chain_spec must be present before continuing"); - - crate::fill_config_keystore_in_memory(&mut config)?; - - if let DatabaseConfig::Path { ref path, .. } = config.expect_database() { - info!("DB path: {}", path.display()); - } - let from = self.from.as_ref().and_then(|f| f.parse().ok()).unwrap_or(1); - let to = self.to.as_ref().and_then(|t| t.parse().ok()); - - let json = self.json; - - let file: Box = match &self.output { - Some(filename) => Box::new(fs::File::create(filename)?), - None => Box::new(io::stdout()), - }; - - run_until_exit(config, |config| { - Ok(builder(config)?.export_blocks(file, from.into(), to, json)) - }) - } -} - -/// Internal trait used to cast to a dynamic type that implements Read and Seek. -trait ReadPlusSeek: Read + Seek {} - -impl ReadPlusSeek for T {} - -impl ImportBlocksCmd { - /// Run the import-blocks command - pub fn run( - self, - mut config: Configuration, - builder: B, - ) -> error::Result<()> - where - B: FnOnce(Configuration) -> Result, - G: RuntimeGenesis, - E: ChainSpecExtension, - BC: ServiceBuilderCommand + Unpin, - BB: sp_runtime::traits::Block + Debug, - <<::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug, - ::Hash: std::str::FromStr, - { - crate::fill_import_params( - &mut config, - &self.import_params, - sc_service::Roles::FULL, - self.shared_params.dev, - )?; - - let file: Box = match &self.input { - Some(filename) => Box::new(fs::File::open(filename)?), - None => { - let mut buffer = Vec::new(); - io::stdin().read_to_end(&mut buffer)?; - Box::new(io::Cursor::new(buffer)) - }, - }; - - run_until_exit(config, |config| { - Ok(builder(config)?.import_blocks(file, false)) - }) - } -} - -impl CheckBlockCmd { - /// Run the check-block command - pub fn run( - self, - mut config: Configuration, - builder: B, - ) -> error::Result<()> - where - B: FnOnce(Configuration) -> Result, - G: RuntimeGenesis, - E: ChainSpecExtension, - BC: ServiceBuilderCommand + Unpin, - BB: sp_runtime::traits::Block + Debug, - <<::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug, - ::Hash: std::str::FromStr, - { - assert!(config.chain_spec.is_some(), "chain_spec must be present before continuing"); - - crate::fill_import_params( - &mut config, - &self.import_params, - sc_service::Roles::FULL, - self.shared_params.dev, - )?; - crate::fill_config_keystore_in_memory(&mut config)?; - - let input = if self.input.starts_with("0x") { &self.input[2..] } else { &self.input[..] }; - let block_id = match FromStr::from_str(input) { - Ok(hash) => BlockId::hash(hash), - Err(_) => match self.input.parse::() { - Ok(n) => BlockId::number((n as u32).into()), - Err(_) => return Err(error::Error::Input("Invalid hash or number specified".into())), - } - }; - - let start = std::time::Instant::now(); - run_until_exit(config, |config| { - Ok(builder(config)?.check_block(block_id)) - })?; - println!("Completed in {} ms.", start.elapsed().as_millis()); - - Ok(()) - } -} - -impl PurgeChainCmd { - /// Run the purge command - pub fn run( - self, - mut config: Configuration, - ) -> error::Result<()> - where - G: RuntimeGenesis, - E: ChainSpecExtension, - { - assert!(config.chain_spec.is_some(), "chain_spec must be present before continuing"); - - crate::fill_config_keystore_in_memory(&mut config)?; - - let db_path = match config.expect_database() { - DatabaseConfig::Path { path, .. } => path, - _ => { - eprintln!("Cannot purge custom database implementation"); - return Ok(()); - } - }; - - if !self.yes { - print!("Are you sure to remove {:?}? [y/N]: ", &db_path); - io::stdout().flush().expect("failed to flush stdout"); - - let mut input = String::new(); - io::stdin().read_line(&mut input)?; - let input = input.trim(); - - match input.chars().nth(0) { - Some('y') | Some('Y') => {}, - _ => { - println!("Aborted"); - return Ok(()); - }, - } - } - - match fs::remove_dir_all(&db_path) { - Ok(_) => { - println!("{:?} removed.", &db_path); - Ok(()) - }, - Err(ref err) if err.kind() == io::ErrorKind::NotFound => { - eprintln!("{:?} did not exist.", &db_path); - Ok(()) - }, - Err(err) => Result::Err(err.into()) - } - } -} - -impl RevertCmd { - /// Run the revert command - pub fn run( - self, - mut config: Configuration, - builder: B, - ) -> error::Result<()> - where - B: FnOnce(Configuration) -> Result, - G: RuntimeGenesis, - E: ChainSpecExtension, - BC: ServiceBuilderCommand + Unpin, - BB: sp_runtime::traits::Block + Debug, - <<::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug, - ::Hash: std::str::FromStr, - { - assert!(config.chain_spec.is_some(), "chain_spec must be present before continuing"); - - crate::fill_config_keystore_in_memory(&mut config)?; - - let blocks = self.num.parse()?; - builder(config)?.revert_chain(blocks)?; - - Ok(()) - } -} diff --git a/client/cli/src/params/import_params.rs b/client/cli/src/params/import_params.rs new file mode 100644 index 0000000000000..98809a38ae446 --- /dev/null +++ b/client/cli/src/params/import_params.rs @@ -0,0 +1,219 @@ +// Copyright 2018-2020 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 . + +use structopt::StructOpt; +use sc_service::{ + Configuration, RuntimeGenesis, + config::DatabaseConfig, PruningMode, +}; + +use crate::error; +use crate::arg_enums::{ + WasmExecutionMethod, TracingReceiver, ExecutionStrategy, DEFAULT_EXECUTION_BLOCK_CONSTRUCTION, + DEFAULT_EXECUTION_IMPORT_BLOCK, DEFAULT_EXECUTION_OFFCHAIN_WORKER, DEFAULT_EXECUTION_OTHER, + DEFAULT_EXECUTION_SYNCING +}; + +/// Parameters for block import. +#[derive(Debug, StructOpt, Clone)] +pub struct ImportParams { + /// Specify the state pruning mode, a number of blocks to keep or 'archive'. + /// + /// Default is to keep all block states if the node is running as a + /// validator (i.e. 'archive'), otherwise state is only kept for the last + /// 256 blocks. + #[structopt(long = "pruning", value_name = "PRUNING_MODE")] + pub pruning: Option, + + /// Force start with unsafe pruning settings. + /// + /// When running as a validator it is highly recommended to disable state + /// pruning (i.e. 'archive') which is the default. The node will refuse to + /// start as a validator if pruning is enabled unless this option is set. + #[structopt(long = "unsafe-pruning")] + pub unsafe_pruning: bool, + + /// Method for executing Wasm runtime code. + #[structopt( + long = "wasm-execution", + value_name = "METHOD", + possible_values = &WasmExecutionMethod::enabled_variants(), + case_insensitive = true, + default_value = "Interpreted" + )] + pub wasm_method: WasmExecutionMethod, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub execution_strategies: ExecutionStrategies, + + /// Limit the memory the database cache can use. + #[structopt(long = "db-cache", value_name = "MiB", default_value = "1024")] + pub database_cache_size: u32, + + /// Specify the state cache size. + #[structopt(long = "state-cache-size", value_name = "Bytes", default_value = "67108864")] + pub state_cache_size: usize, + + /// Comma separated list of targets for tracing + #[structopt(long = "tracing-targets", value_name = "TARGETS")] + pub tracing_targets: Option, + + /// Receiver to process tracing messages + #[structopt( + long = "tracing-receiver", + value_name = "RECEIVER", + possible_values = &TracingReceiver::variants(), + case_insensitive = true, + default_value = "Log" + )] + pub tracing_receiver: TracingReceiver, +} + +impl ImportParams { + /// Put block import CLI params into `config` object. + pub fn update_config( + &self, + config: &mut Configuration, + role: sc_service::Roles, + is_dev: bool, + ) -> error::Result<()> + where + G: RuntimeGenesis, + { + use sc_client_api::execution_extensions::ExecutionStrategies; + + if let Some(DatabaseConfig::Path { ref mut cache_size, .. }) = config.database { + *cache_size = Some(self.database_cache_size); + } + + config.state_cache_size = self.state_cache_size; + + // by default we disable pruning if the node is an authority (i.e. + // `ArchiveAll`), otherwise we keep state for the last 256 blocks. if the + // node is an authority and pruning is enabled explicitly, then we error + // unless `unsafe_pruning` is set. + config.pruning = match &self.pruning { + Some(ref s) if s == "archive" => PruningMode::ArchiveAll, + None if role == sc_service::Roles::AUTHORITY => PruningMode::ArchiveAll, + None => PruningMode::default(), + Some(s) => { + if role == sc_service::Roles::AUTHORITY && !self.unsafe_pruning { + return Err(error::Error::Input( + "Validators should run with state pruning disabled (i.e. archive). \ + You can ignore this check with `--unsafe-pruning`.".to_string() + )); + } + + PruningMode::keep_blocks(s.parse() + .map_err(|_| error::Error::Input("Invalid pruning mode specified".to_string()))? + ) + }, + }; + + config.wasm_method = self.wasm_method.into(); + + let exec = &self.execution_strategies; + let exec_all_or = |strat: ExecutionStrategy, default: ExecutionStrategy| { + exec.execution.unwrap_or(if strat == default && is_dev { + ExecutionStrategy::Native + } else { + strat + }).into() + }; + + config.execution_strategies = ExecutionStrategies { + syncing: exec_all_or(exec.execution_syncing, DEFAULT_EXECUTION_SYNCING), + importing: exec_all_or(exec.execution_import_block, DEFAULT_EXECUTION_IMPORT_BLOCK), + block_construction: + exec_all_or(exec.execution_block_construction, DEFAULT_EXECUTION_BLOCK_CONSTRUCTION), + offchain_worker: + exec_all_or(exec.execution_offchain_worker, DEFAULT_EXECUTION_OFFCHAIN_WORKER), + other: exec_all_or(exec.execution_other, DEFAULT_EXECUTION_OTHER), + }; + Ok(()) + } +} + +/// Execution strategies parameters. +#[derive(Debug, StructOpt, Clone)] +pub struct ExecutionStrategies { + /// The means of execution used when calling into the runtime while syncing blocks. + #[structopt( + long = "execution-syncing", + value_name = "STRATEGY", + possible_values = &ExecutionStrategy::variants(), + case_insensitive = true, + default_value = DEFAULT_EXECUTION_SYNCING.as_str(), + )] + pub execution_syncing: ExecutionStrategy, + + /// The means of execution used when calling into the runtime while importing blocks. + #[structopt( + long = "execution-import-block", + value_name = "STRATEGY", + possible_values = &ExecutionStrategy::variants(), + case_insensitive = true, + default_value = DEFAULT_EXECUTION_IMPORT_BLOCK.as_str(), + )] + pub execution_import_block: ExecutionStrategy, + + /// The means of execution used when calling into the runtime while constructing blocks. + #[structopt( + long = "execution-block-construction", + value_name = "STRATEGY", + possible_values = &ExecutionStrategy::variants(), + case_insensitive = true, + default_value = DEFAULT_EXECUTION_BLOCK_CONSTRUCTION.as_str(), + )] + pub execution_block_construction: ExecutionStrategy, + + /// The means of execution used when calling into the runtime while using an off-chain worker. + #[structopt( + long = "execution-offchain-worker", + value_name = "STRATEGY", + possible_values = &ExecutionStrategy::variants(), + case_insensitive = true, + default_value = DEFAULT_EXECUTION_OFFCHAIN_WORKER.as_str(), + )] + pub execution_offchain_worker: ExecutionStrategy, + + /// The means of execution used when calling into the runtime while not syncing, importing or constructing blocks. + #[structopt( + long = "execution-other", + value_name = "STRATEGY", + possible_values = &ExecutionStrategy::variants(), + case_insensitive = true, + default_value = DEFAULT_EXECUTION_OTHER.as_str(), + )] + pub execution_other: ExecutionStrategy, + + /// The execution strategy that should be used by all execution contexts. + #[structopt( + long = "execution", + value_name = "STRATEGY", + possible_values = &ExecutionStrategy::variants(), + case_insensitive = true, + conflicts_with_all = &[ + "execution-other", + "execution-offchain-worker", + "execution-block-construction", + "execution-import-block", + "execution-syncing", + ] + )] + pub execution: Option, +} diff --git a/client/cli/src/params/mod.rs b/client/cli/src/params/mod.rs new file mode 100644 index 0000000000000..75509afa42558 --- /dev/null +++ b/client/cli/src/params/mod.rs @@ -0,0 +1,65 @@ +// Copyright 2020 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 . + +mod import_params; +mod transaction_pool_params; +mod shared_params; +mod node_key_params; +mod network_configuration_params; + +use std::str::FromStr; +use std::fmt::Debug; + +pub use crate::params::import_params::*; +pub use crate::params::transaction_pool_params::*; +pub use crate::params::shared_params::*; +pub use crate::params::node_key_params::*; +pub use crate::params::network_configuration_params::*; + +/// Wrapper type of `String` that holds an unsigned integer of arbitrary size, formatted as a decimal. +#[derive(Debug, Clone)] +pub struct BlockNumber(String); + +impl FromStr for BlockNumber { + type Err = String; + + fn from_str(block_number: &str) -> Result { + if block_number.chars().any(|d| !d.is_digit(10)) { + Err(format!( + "Invalid block number: {}, expected decimal formatted unsigned integer", + block_number, + )) + } else { + Ok(Self(block_number.to_owned())) + } + } +} + +impl BlockNumber { + /// Wrapper on top of `std::str::parse` but with `Error` as a `String` + /// + /// See `https://doc.rust-lang.org/std/primitive.str.html#method.parse` for more elaborate + /// documentation. + pub fn parse(&self) -> Result + where + N: FromStr, + N::Err: std::fmt::Debug, + { + self.0 + .parse() + .map_err(|e| format!("BlockNumber: {} parsing failed because of {:?}", self.0, e)) + } +} diff --git a/client/cli/src/params/network_configuration_params.rs b/client/cli/src/params/network_configuration_params.rs new file mode 100644 index 0000000000000..eef679d6a615f --- /dev/null +++ b/client/cli/src/params/network_configuration_params.rs @@ -0,0 +1,160 @@ +// Copyright 2018-2020 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 . + +use std::path::PathBuf; +use std::iter; +use std::net::Ipv4Addr; +use structopt::StructOpt; +use sc_network::{ + config::{NonReservedPeerMode, TransportConfig}, multiaddr::Protocol, +}; +use sc_service::{Configuration, RuntimeGenesis}; + +use crate::error; +use crate::params::node_key_params::NodeKeyParams; + +/// Parameters used to create the network configuration. +#[derive(Debug, StructOpt, Clone)] +pub struct NetworkConfigurationParams { + /// Specify a list of bootnodes. + #[structopt(long = "bootnodes", value_name = "URL")] + pub bootnodes: Vec, + + /// Specify a list of reserved node addresses. + #[structopt(long = "reserved-nodes", value_name = "URL")] + pub reserved_nodes: Vec, + + /// Whether to only allow connections to/from reserved nodes. + /// + /// If you are a validator your node might still connect to other validator + /// nodes regardless of whether they are defined as reserved nodes. + #[structopt(long = "reserved-only")] + pub reserved_only: bool, + + /// Specify a list of sentry node public addresses. + #[structopt( + long = "sentry-nodes", + value_name = "URL", + conflicts_with_all = &[ "sentry" ] + )] + pub sentry_nodes: Vec, + + /// Listen on this multiaddress. + #[structopt(long = "listen-addr", value_name = "LISTEN_ADDR")] + pub listen_addr: Vec, + + /// Specify p2p protocol TCP port. + /// + /// Only used if --listen-addr is not specified. + #[structopt(long = "port", value_name = "PORT")] + pub port: Option, + + /// Forbid connecting to private IPv4 addresses (as specified in + /// [RFC1918](https://tools.ietf.org/html/rfc1918)), unless the address was passed with + /// `--reserved-nodes` or `--bootnodes`. + #[structopt(long = "no-private-ipv4")] + pub no_private_ipv4: bool, + + /// Specify the number of outgoing connections we're trying to maintain. + #[structopt(long = "out-peers", value_name = "COUNT", default_value = "25")] + pub out_peers: u32, + + /// Specify the maximum number of incoming connections we're accepting. + #[structopt(long = "in-peers", value_name = "COUNT", default_value = "25")] + pub in_peers: u32, + + /// Disable mDNS discovery. + /// + /// By default, the network will use mDNS to discover other nodes on the + /// local network. This disables it. Automatically implied when using --dev. + #[structopt(long = "no-mdns")] + pub no_mdns: bool, + + /// Maximum number of peers to ask the same blocks in parallel. + /// + /// This allows downlading announced blocks from multiple peers. Decrease to save + /// traffic and risk increased latency. + #[structopt(long = "max-parallel-downloads", value_name = "COUNT", default_value = "5")] + pub max_parallel_downloads: u32, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub node_key_params: NodeKeyParams, + + /// Experimental feature flag. + #[structopt(long = "use-yamux-flow-control")] + pub use_yamux_flow_control: bool, +} + +impl NetworkConfigurationParams { + /// Fill the given `NetworkConfiguration` by looking at the cli parameters. + pub fn update_config( + &self, + mut config: &mut Configuration, + config_path: PathBuf, + client_id: String, + is_dev: bool, + ) -> error::Result<()> + where + G: RuntimeGenesis, + { + config.network.boot_nodes.extend(self.bootnodes.clone()); + config.network.config_path = Some(config_path.clone()); + config.network.net_config_path = Some(config_path.clone()); + + config.network.reserved_nodes.extend(self.reserved_nodes.clone()); + if self.reserved_only { + config.network.non_reserved_mode = NonReservedPeerMode::Deny; + } + + config.network.sentry_nodes.extend(self.sentry_nodes.clone()); + + for addr in self.listen_addr.iter() { + let addr = addr.parse().ok().ok_or(error::Error::InvalidListenMultiaddress)?; + config.network.listen_addresses.push(addr); + } + + if config.network.listen_addresses.is_empty() { + let port = match self.port { + Some(port) => port, + None => 30333, + }; + + config.network.listen_addresses = vec![ + iter::once(Protocol::Ip4(Ipv4Addr::new(0, 0, 0, 0))) + .chain(iter::once(Protocol::Tcp(port))) + .collect() + ]; + } + + config.network.client_version = client_id; + self.node_key_params.update_config(&mut config, Some(&config_path))?; + + config.network.in_peers = self.in_peers; + config.network.out_peers = self.out_peers; + + config.network.transport = TransportConfig::Normal { + enable_mdns: !is_dev && !self.no_mdns, + allow_private_ipv4: !self.no_private_ipv4, + wasm_external_transport: None, + use_yamux_flow_control: self.use_yamux_flow_control, + }; + + config.network.max_parallel_downloads = self.max_parallel_downloads; + + Ok(()) + } +} diff --git a/client/cli/src/params/node_key_params.rs b/client/cli/src/params/node_key_params.rs new file mode 100644 index 0000000000000..ddc1d6cc21af7 --- /dev/null +++ b/client/cli/src/params/node_key_params.rs @@ -0,0 +1,244 @@ +// Copyright 2017-2020 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 . + +use std::{path::PathBuf, str::FromStr}; +use structopt::StructOpt; +use sc_service::{Configuration, RuntimeGenesis}; +use sc_network::config::NodeKeyConfig; +use sp_core::H256; + +use crate::error; +use crate::arg_enums::NodeKeyType; + +/// The file name of the node's Ed25519 secret key inside the chain-specific +/// network config directory, if neither `--node-key` nor `--node-key-file` +/// is specified in combination with `--node-key-type=ed25519`. +const NODE_KEY_ED25519_FILE: &str = "secret_ed25519"; + +/// Parameters used to create the `NodeKeyConfig`, which determines the keypair +/// used for libp2p networking. +#[derive(Debug, StructOpt, Clone)] +pub struct NodeKeyParams { + /// The secret key to use for libp2p networking. + /// + /// The value is a string that is parsed according to the choice of + /// `--node-key-type` as follows: + /// + /// `ed25519`: + /// The value is parsed as a hex-encoded Ed25519 32 bytes secret key, + /// i.e. 64 hex characters. + /// + /// The value of this option takes precedence over `--node-key-file`. + /// + /// WARNING: Secrets provided as command-line arguments are easily exposed. + /// Use of this option should be limited to development and testing. To use + /// an externally managed secret key, use `--node-key-file` instead. + #[structopt(long = "node-key", value_name = "KEY")] + pub node_key: Option, + + /// The type of secret key to use for libp2p networking. + /// + /// The secret key of the node is obtained as follows: + /// + /// * If the `--node-key` option is given, the value is parsed as a secret key + /// according to the type. See the documentation for `--node-key`. + /// + /// * If the `--node-key-file` option is given, the secret key is read from the + /// specified file. See the documentation for `--node-key-file`. + /// + /// * Otherwise, the secret key is read from a file with a predetermined, + /// type-specific name from the chain-specific network config directory + /// inside the base directory specified by `--base-dir`. If this file does + /// not exist, it is created with a newly generated secret key of the + /// chosen type. + /// + /// The node's secret key determines the corresponding public key and hence the + /// node's peer ID in the context of libp2p. + #[structopt( + long = "node-key-type", + value_name = "TYPE", + possible_values = &NodeKeyType::variants(), + case_insensitive = true, + default_value = "Ed25519" + )] + pub node_key_type: NodeKeyType, + + /// The file from which to read the node's secret key to use for libp2p networking. + /// + /// The contents of the file are parsed according to the choice of `--node-key-type` + /// as follows: + /// + /// `ed25519`: + /// The file must contain an unencoded 32 bytes Ed25519 secret key. + /// + /// If the file does not exist, it is created with a newly generated secret key of + /// the chosen type. + #[structopt(long = "node-key-file", value_name = "FILE")] + pub node_key_file: Option, +} + +impl NodeKeyParams { + /// Create a `NodeKeyConfig` from the given `NodeKeyParams` in the context + /// of an optional network config storage directory. + pub fn update_config<'a, G, E>( + &self, + mut config: &'a mut Configuration, + net_config_path: Option<&PathBuf>, + ) -> error::Result<&'a NodeKeyConfig> + where + G: RuntimeGenesis, + { + config.network.node_key = match self.node_key_type { + NodeKeyType::Ed25519 => { + let secret = if let Some(node_key) = self.node_key.as_ref() { + parse_ed25519_secret(node_key)? + } else { + let path = self.node_key_file.clone() + .or_else(|| net_config_path.map(|d| d.join(NODE_KEY_ED25519_FILE))); + + if let Some(path) = path { + sc_network::config::Secret::File(path) + } else { + sc_network::config::Secret::New + } + }; + + NodeKeyConfig::Ed25519(secret) + } + }; + + Ok(&config.network.node_key) + } +} + +/// Create an error caused by an invalid node key argument. +fn invalid_node_key(e: impl std::fmt::Display) -> error::Error { + error::Error::Input(format!("Invalid node key: {}", e)) +} + +/// Parse a Ed25519 secret key from a hex string into a `sc_network::Secret`. +fn parse_ed25519_secret(hex: &str) -> error::Result { + H256::from_str(&hex).map_err(invalid_node_key).and_then(|bytes| + sc_network::config::identity::ed25519::SecretKey::from_bytes(bytes) + .map(sc_network::config::Secret::Input) + .map_err(invalid_node_key)) +} + +#[cfg(test)] +mod tests { + use sc_network::config::identity::ed25519; + use super::*; + + #[test] + fn test_node_key_config_input() { + fn secret_input(net_config_dir: Option<&PathBuf>) -> error::Result<()> { + NodeKeyType::variants().iter().try_for_each(|t| { + let mut config = Configuration::<(), ()>::default(); + let node_key_type = NodeKeyType::from_str(t).unwrap(); + let sk = match node_key_type { + NodeKeyType::Ed25519 => ed25519::SecretKey::generate().as_ref().to_vec() + }; + let params = NodeKeyParams { + node_key_type, + node_key: Some(format!("{:x}", H256::from_slice(sk.as_ref()))), + node_key_file: None + }; + params.update_config(&mut config, net_config_dir).and_then(|c| match c { + NodeKeyConfig::Ed25519(sc_network::config::Secret::Input(ref ski)) + if node_key_type == NodeKeyType::Ed25519 && + &sk[..] == ski.as_ref() => Ok(()), + _ => Err(error::Error::Input("Unexpected node key config".into())) + }) + }) + } + + assert!(secret_input(None).is_ok()); + assert!(secret_input(Some(&PathBuf::from_str("x").unwrap())).is_ok()); + } + + #[test] + fn test_node_key_config_file() { + fn secret_file(net_config_dir: Option<&PathBuf>) -> error::Result<()> { + NodeKeyType::variants().iter().try_for_each(|t| { + let mut config = Configuration::<(), ()>::default(); + let node_key_type = NodeKeyType::from_str(t).unwrap(); + let tmp = tempfile::Builder::new().prefix("alice").tempdir()?; + let file = tmp.path().join(format!("{}_mysecret", t)).to_path_buf(); + let params = NodeKeyParams { + node_key_type, + node_key: None, + node_key_file: Some(file.clone()) + }; + params.update_config(&mut config, net_config_dir).and_then(|c| match c { + NodeKeyConfig::Ed25519(sc_network::config::Secret::File(ref f)) + if node_key_type == NodeKeyType::Ed25519 && f == &file => Ok(()), + _ => Err(error::Error::Input("Unexpected node key config".into())) + }) + }) + } + + assert!(secret_file(None).is_ok()); + assert!(secret_file(Some(&PathBuf::from_str("x").unwrap())).is_ok()); + } + + #[test] + fn test_node_key_config_default() { + fn with_def_params(f: F) -> error::Result<()> + where + F: Fn(NodeKeyParams) -> error::Result<()> + { + NodeKeyType::variants().iter().try_for_each(|t| { + let node_key_type = NodeKeyType::from_str(t).unwrap(); + f(NodeKeyParams { + node_key_type, + node_key: None, + node_key_file: None + }) + }) + } + + fn no_config_dir() -> error::Result<()> { + with_def_params(|params| { + let mut config = Configuration::<(), ()>::default(); + let typ = params.node_key_type; + params.update_config(&mut config, None) + .and_then(|c| match c { + NodeKeyConfig::Ed25519(sc_network::config::Secret::New) + if typ == NodeKeyType::Ed25519 => Ok(()), + _ => Err(error::Error::Input("Unexpected node key config".into())) + }) + }) + } + + fn some_config_dir(net_config_dir: &PathBuf) -> error::Result<()> { + with_def_params(|params| { + let mut config = Configuration::<(), ()>::default(); + let dir = PathBuf::from(net_config_dir.clone()); + let typ = params.node_key_type; + params.update_config(&mut config, Some(net_config_dir)) + .and_then(move |c| match c { + NodeKeyConfig::Ed25519(sc_network::config::Secret::File(ref f)) + if typ == NodeKeyType::Ed25519 && + f == &dir.join(NODE_KEY_ED25519_FILE) => Ok(()), + _ => Err(error::Error::Input("Unexpected node key config".into())) + }) + }) + } + + assert!(no_config_dir().is_ok()); + assert!(some_config_dir(&PathBuf::from_str("x").unwrap()).is_ok()); + } +} diff --git a/client/cli/src/params/shared_params.rs b/client/cli/src/params/shared_params.rs new file mode 100644 index 0000000000000..03f44796460a2 --- /dev/null +++ b/client/cli/src/params/shared_params.rs @@ -0,0 +1,116 @@ +// Copyright 2018-2020 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 . + +use std::path::PathBuf; +use structopt::StructOpt; +use app_dirs::{AppInfo, AppDataType}; +use sc_service::{ + Configuration, ChainSpecExtension, RuntimeGenesis, + config::DatabaseConfig, ChainSpec, +}; + +use crate::VersionInfo; +use crate::error; + +/// default sub directory to store database +const DEFAULT_DB_CONFIG_PATH : &'static str = "db"; + +/// Shared parameters used by all `CoreParams`. +#[derive(Debug, StructOpt, Clone)] +pub struct SharedParams { + /// Specify the chain specification (one of dev, local or staging). + #[structopt(long = "chain", value_name = "CHAIN_SPEC")] + pub chain: Option, + + /// Specify the development chain. + #[structopt(long = "dev")] + pub dev: bool, + + /// Specify custom base path. + #[structopt(long = "base-path", short = "d", value_name = "PATH", parse(from_os_str))] + pub base_path: Option, + + /// Sets a custom logging filter. + #[structopt(short = "l", long = "log", value_name = "LOG_PATTERN")] + pub log: Option, +} + +impl SharedParams { + /// Load spec to `Configuration` from `SharedParams` and spec factory. + pub fn update_config<'a, G, E, F>( + &self, + mut config: &'a mut Configuration, + spec_factory: F, + version: &VersionInfo, + ) -> error::Result<&'a ChainSpec> where + G: RuntimeGenesis, + E: ChainSpecExtension, + F: FnOnce(&str) -> Result>, String>, + { + let chain_key = match self.chain { + Some(ref chain) => chain.clone(), + None => if self.dev { "dev".into() } else { "".into() } + }; + let spec = match spec_factory(&chain_key)? { + Some(spec) => spec, + None => ChainSpec::from_json_file(PathBuf::from(chain_key))? + }; + + config.network.boot_nodes = spec.boot_nodes().to_vec(); + config.telemetry_endpoints = spec.telemetry_endpoints().clone(); + + config.chain_spec = Some(spec); + + if config.config_dir.is_none() { + config.config_dir = Some(base_path(self, version)); + } + + if config.database.is_none() { + config.database = Some(DatabaseConfig::Path { + path: config + .in_chain_config_dir(DEFAULT_DB_CONFIG_PATH) + .expect("We provided a base_path/config_dir."), + cache_size: None, + }); + } + + Ok(config.chain_spec.as_ref().unwrap()) + } + + /// Initialize substrate. This must be done only once. + /// + /// This method: + /// + /// 1. Set the panic handler + /// 2. Raise the FD limit + /// 3. Initialize the logger + pub fn init(&self, version: &VersionInfo) -> error::Result<()> { + crate::init(self.log.as_ref().map(|v| v.as_ref()).unwrap_or(""), version) + } +} + +fn base_path(cli: &SharedParams, version: &VersionInfo) -> PathBuf { + cli.base_path.clone() + .unwrap_or_else(|| + app_dirs::get_app_root( + AppDataType::UserData, + &AppInfo { + name: version.executable_name, + author: version.author + } + ).expect("app directories exist on all supported platforms; qed") + ) +} diff --git a/client/cli/src/params/transaction_pool_params.rs b/client/cli/src/params/transaction_pool_params.rs new file mode 100644 index 0000000000000..80c591d1d2dcf --- /dev/null +++ b/client/cli/src/params/transaction_pool_params.rs @@ -0,0 +1,49 @@ +// Copyright 2018-2020 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 . + +use structopt::StructOpt; +use sc_service::Configuration; +use crate::error; + +/// Parameters used to create the pool configuration. +#[derive(Debug, StructOpt, Clone)] +pub struct TransactionPoolParams { + /// Maximum number of transactions in the transaction pool. + #[structopt(long = "pool-limit", value_name = "COUNT", default_value = "8192")] + pub pool_limit: usize, + /// Maximum number of kilobytes of all transactions stored in the pool. + #[structopt(long = "pool-kbytes", value_name = "COUNT", default_value = "20480")] + pub pool_kbytes: usize, +} + +impl TransactionPoolParams { + /// Fill the given `PoolConfiguration` by looking at the cli parameters. + pub fn update_config( + &self, + config: &mut Configuration, + ) -> error::Result<()> { + // ready queue + config.transaction_pool.ready.count = self.pool_limit; + config.transaction_pool.ready.total_bytes = self.pool_kbytes * 1024; + + // future queue + let factor = 10; + config.transaction_pool.future.count = self.pool_limit / factor; + config.transaction_pool.future.total_bytes = self.pool_kbytes * 1024 / factor; + + Ok(()) + } +} diff --git a/client/db/src/utils.rs b/client/db/src/utils.rs index 534cbb2197ebc..f26714eb5a7db 100644 --- a/client/db/src/utils.rs +++ b/client/db/src/utils.rs @@ -36,6 +36,7 @@ use crate::{DatabaseSettings, DatabaseSettingsSrc}; /// Number of columns in the db. Must be the same for both full && light dbs. /// Otherwise RocksDb will fail to open database && check its type. +#[cfg(any(feature = "kvdb-rocksdb", feature = "test-helpers", test))] pub const NUM_COLUMNS: u32 = 11; /// Meta column. The set of keys in the column is shared by full && light storages. pub const COLUMN_META: u32 = 0; diff --git a/client/network/src/config.rs b/client/network/src/config.rs index 8c97cbb872950..f6a3db4afe88e 100644 --- a/client/network/src/config.rs +++ b/client/network/src/config.rs @@ -126,6 +126,12 @@ impl Roles { } } +impl fmt::Display for Roles { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + impl codec::Encode for Roles { fn encode_to(&self, dest: &mut T) { dest.push_byte(self.bits()) @@ -247,9 +253,9 @@ impl From for ParseErr { #[derive(Clone, Debug)] pub struct NetworkConfiguration { /// Directory path to store general network configuration. None means nothing will be saved. - pub config_path: Option, + pub config_path: Option, /// Directory path to store network-specific configuration. None means nothing will be saved. - pub net_config_path: Option, + pub net_config_path: Option, /// Multiaddresses to listen for incoming connections. pub listen_addresses: Vec, /// Multiaddresses to advertise. Detected automatically if empty. diff --git a/client/service/src/config.rs b/client/service/src/config.rs index f5b187e8b35a1..002754f11ce93 100644 --- a/client/service/src/config.rs +++ b/client/service/src/config.rs @@ -207,7 +207,7 @@ impl Default for Configuration { impl Configuration { /// Create a default config using `VersionInfo` - pub fn new(version: &VersionInfo) -> Self { + pub fn from_version(version: &VersionInfo) -> Self { let mut config = Configuration::default(); config.impl_name = version.name; config.impl_version = version.version; @@ -254,6 +254,28 @@ impl Configuration { pub fn expect_database(&self) -> &DatabaseConfig { self.database.as_ref().expect("database must be specified") } + + /// Returns a string displaying the node role, special casing the sentry mode + /// (returning `SENTRY`), since the node technically has an `AUTHORITY` role but + /// doesn't participate. + pub fn display_role(&self) -> String { + if self.sentry_mode { + "SENTRY".to_string() + } else { + self.roles.to_string() + } + } + + /// Use in memory keystore config when it is not required at all. + /// + /// This function returns an error if the keystore is already set to something different than + /// `KeystoreConfig::None`. + pub fn use_in_memory_keystore(&mut self) -> Result<(), String> { + match &mut self.keystore { + cfg @ KeystoreConfig::None => { *cfg = KeystoreConfig::InMemory; Ok(()) }, + _ => Err("Keystore config specified when it should not be!".into()), + } + } } /// Returns platform info diff --git a/client/service/test/src/lib.rs b/client/service/test/src/lib.rs index 414b943594c01..5f679b82b39a4 100644 --- a/client/service/test/src/lib.rs +++ b/client/service/test/src/lib.rs @@ -141,7 +141,7 @@ fn node_config ( { let root = root.path().join(format!("node-{}", index)); - let config_path = Some(String::from(root.join("network").to_str().unwrap())); + let config_path = Some(root.join("network")); let net_config_path = config_path.clone(); let network_config = NetworkConfiguration { diff --git a/utils/frame/benchmarking-cli/src/lib.rs b/utils/frame/benchmarking-cli/src/lib.rs index a8303beb0fed1..b515e490131f6 100644 --- a/utils/frame/benchmarking-cli/src/lib.rs +++ b/utils/frame/benchmarking-cli/src/lib.rs @@ -16,9 +16,9 @@ use sp_runtime::{BuildStorage, traits::{Block as BlockT, Header as HeaderT, NumberFor}}; use sc_client::StateMachine; -use sc_cli::{ExecutionStrategy, WasmExecutionMethod}; +use sc_cli::{ExecutionStrategy, WasmExecutionMethod, VersionInfo}; use sc_client_db::BenchmarkingState; -use sc_service::{RuntimeGenesis, ChainSpecExtension}; +use sc_service::{RuntimeGenesis, ChainSpecExtension, Configuration, ChainSpec}; use sc_executor::{NativeExecutor, NativeExecutionDispatch}; use std::fmt::Debug; use codec::{Encode, Decode}; @@ -68,26 +68,16 @@ pub struct BenchmarkCmd { } impl BenchmarkCmd { - /// Parse CLI arguments and initialize given config. - pub fn init( - &self, - config: &mut sc_service::config::Configuration, - spec_factory: impl FnOnce(&str) -> Result>, String>, - version: &sc_cli::VersionInfo, - ) -> sc_cli::error::Result<()> where - G: sc_service::RuntimeGenesis, - E: sc_service::ChainSpecExtension, - { - sc_cli::init_config(config, &self.shared_params, version, spec_factory)?; - // make sure to configure keystore - sc_cli::fill_config_keystore_in_memory(config).map_err(Into::into) + /// Initialize + pub fn init(&self, version: &sc_cli::VersionInfo) -> sc_cli::Result<()> { + self.shared_params.init(version) } /// Runs the command and benchmarks the chain. pub fn run( self, - config: sc_service::Configuration, - ) -> sc_cli::error::Result<()> + config: Configuration, + ) -> sc_cli::Result<()> where G: RuntimeGenesis, E: ChainSpecExtension, @@ -149,4 +139,22 @@ impl BenchmarkCmd { Ok(()) } + + /// Update and prepare a `Configuration` with command line parameters + pub fn update_config( + &self, + mut config: &mut Configuration, + spec_factory: impl FnOnce(&str) -> Result>, String>, + version: &VersionInfo, + ) -> sc_cli::Result<()> where + G: RuntimeGenesis, + E: ChainSpecExtension, + { + self.shared_params.update_config(&mut config, spec_factory, version)?; + + // make sure to configure keystore + config.use_in_memory_keystore()?; + + Ok(()) + } }