CLI API refactoring and improvement (#4692)

It changes the way we extended the CLI functionalities of substrate to allow more flexibility. (If this was not clear, here is another version: it changes the `sc_cli` API to allow more flexibility).

This touches a few important things:
 - the startup of the async task with tokei:
    This was in node and node-template and I moved it to substrate. The idea is to have 1 time the code that handles unix signals (SIGTERM and SIGINT) properly. It is however possible to make this more generic to wait for a future instead and provide only a helper for the basic handling of SIGTERM and SIGINT.
 - increased the version of structopt and tokei
 - no more use of structopt internal's API
 - less use of generics

Related to #4643 and https://github.com/paritytech/cumulus/pull/42: the implementation of "into_configuration" and "get_config" are similar but with better flexibility so it is now possible in cumulus to have the command-line arguments only of the run command for polkadot if we want

Related to https://github.com/paritytech/cumulus/issues/24 and https://github.com/paritytech/cumulus/issues/34 : it will now be possible to make a configuration struct for polkadot with some overrides of the default parameters much more easily.
This commit is contained in:
Cecile Tonglet
2020-01-30 11:40:08 +01:00
committed by GitHub
parent 506f9c29d9
commit 605f643eed
28 changed files with 2041 additions and 2234 deletions
File diff suppressed because it is too large Load Diff
+208
View File
@@ -0,0 +1,208 @@
// 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 <http://www.gnu.org/licenses/>.
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<P>(params: NodeKeyParams, net_config_dir: &Option<P>)
-> error::Result<NodeKeyConfig>
where
P: AsRef<Path>
{
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<sc_network::config::Ed25519Secret> {
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<P>(net_config_dir: &Option<P>, name: &str) -> Option<PathBuf>
where
P: AsRef<Path>
{
net_config_dir.as_ref().map(|d| d.as_ref().join(name))
}
mod tests {
use super::*;
use sc_network::config::identity::ed25519;
#[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<String>) -> error::Result<()> {
NodeKeyType::variants().into_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<String>) -> error::Result<()> {
NodeKeyType::variants().into_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: F) -> error::Result<()>
where
F: Fn(NodeKeyParams) -> error::Result<()>
{
NodeKeyType::variants().into_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::<String>(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());
}
}
+360 -164
View File
@@ -14,10 +14,24 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use crate::traits::GetSharedParams;
use std::{str::FromStr, path::PathBuf};
use structopt::{StructOpt, StructOptInternal, clap::{arg_enum, App, AppSettings, SubCommand, Arg}};
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;
@@ -408,7 +422,7 @@ pub struct RunCmd {
/// available to relay to private nodes.
#[structopt(
long = "sentry",
conflicts_with_all = &[ "validator" ]
conflicts_with_all = &[ "validator", "light" ]
)]
pub sentry: bool,
@@ -417,10 +431,7 @@ pub struct RunCmd {
pub no_grandpa: bool,
/// Experimental: Run in light client mode.
#[structopt(
long = "light",
conflicts_with_all = &[ "validator" ]
)]
#[structopt(long = "light", conflicts_with = "sentry")]
pub light: bool,
/// Listen to all RPC interfaces.
@@ -532,9 +543,37 @@ pub struct RunCmd {
#[structopt(flatten)]
pub pool_config: TransactionPoolParams,
#[allow(missing_docs)]
#[structopt(flatten)]
pub keyring: Keyring,
/// 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")]
@@ -582,71 +621,20 @@ pub struct RunCmd {
pub password_filename: Option<PathBuf>
}
/// Stores all required Cli values for a keyring test account.
struct KeyringTestAccountCliValues {
help: String,
conflicts_with: Vec<String>,
name: String,
variant: sp_keyring::Sr25519Keyring,
}
impl RunCmd {
/// Get the `Sr25519Keyring` matching one of the flag
pub fn get_keyring(&self) -> Option<sp_keyring::Sr25519Keyring> {
use sp_keyring::Sr25519Keyring::*;
lazy_static::lazy_static! {
/// The Cli values for all test accounts.
static ref TEST_ACCOUNTS_CLI_VALUES: Vec<KeyringTestAccountCliValues> = {
sp_keyring::Sr25519Keyring::iter().map(|a| {
let help = format!(
"Shortcut for `--name {} --validator` with session keys for `{}` added to keystore.",
a,
a,
);
let conflicts_with = sp_keyring::Sr25519Keyring::iter()
.filter(|b| a != *b)
.map(|b| b.to_string().to_lowercase())
.chain(std::iter::once("name".to_string()))
.collect::<Vec<_>>();
let name = a.to_string().to_lowercase();
KeyringTestAccountCliValues {
help,
conflicts_with,
name,
variant: a,
}
}).collect()
};
}
/// Wrapper for exposing the keyring test accounts into the Cli.
#[derive(Debug, Clone)]
pub struct Keyring {
pub account: Option<sp_keyring::Sr25519Keyring>,
}
impl StructOpt for Keyring {
fn clap<'a, 'b>() -> App<'a, 'b> {
unimplemented!("Should not be called for `TestAccounts`.")
}
fn from_clap(m: &structopt::clap::ArgMatches) -> Self {
Keyring {
account: TEST_ACCOUNTS_CLI_VALUES.iter().find(|a| m.is_present(&a.name)).map(|a| a.variant),
}
}
}
impl StructOptInternal for Keyring {
fn augment_clap<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
TEST_ACCOUNTS_CLI_VALUES.iter().fold(app, |app, a| {
let conflicts_with_strs = a.conflicts_with.iter().map(|s| s.as_str()).collect::<Vec<_>>();
app.arg(
Arg::with_name(&a.name)
.long(&a.name)
.help(&a.help)
.conflicts_with_all(&conflicts_with_strs)
.takes_value(false)
)
})
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 }
}
}
@@ -862,11 +850,8 @@ pub struct PurgeChainCmd {
/// 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)]
pub enum CoreParams<CC, RP> {
/// Run a node.
Run(MergeParameters<RunCmd, RP>),
#[derive(Debug, Clone, StructOpt)]
pub enum Subcommand {
/// Build a spec.json file, outputing to stdout.
BuildSpec(BuildSpecCmd),
@@ -884,112 +869,323 @@ pub enum CoreParams<CC, RP> {
/// Remove the whole chain data.
PurgeChain(PurgeChainCmd),
/// Further custom subcommands.
Custom(CC),
}
impl<CC, RP> StructOpt for CoreParams<CC, RP> where
CC: StructOpt + GetSharedParams,
RP: StructOpt + StructOptInternal,
{
fn clap<'a, 'b>() -> App<'a, 'b> {
RP::augment_clap(
RunCmd::augment_clap(
CC::clap().unset_setting(AppSettings::SubcommandRequiredElseHelp)
)
).subcommand(
BuildSpecCmd::augment_clap(SubCommand::with_name("build-spec"))
.about("Build a spec.json file, outputting to stdout.")
)
.subcommand(
ExportBlocksCmd::augment_clap(SubCommand::with_name("export-blocks"))
.about("Export blocks to a file. This file can only be re-imported \
if it is in binary format (not JSON!)."
)
)
.subcommand(
ImportBlocksCmd::augment_clap(SubCommand::with_name("import-blocks"))
.about("Import blocks from file.")
)
.subcommand(
CheckBlockCmd::augment_clap(SubCommand::with_name("check-block"))
.about("Re-validate a known block.")
)
.subcommand(
RevertCmd::augment_clap(SubCommand::with_name("revert"))
.about("Revert chain to the previous state.")
)
.subcommand(
PurgeChainCmd::augment_clap(SubCommand::with_name("purge-chain"))
.about("Remove the whole chain data.")
)
impl Subcommand {
/// Get the shared parameters of a `CoreParams` command
pub fn get_shared_params(&self) -> &SharedParams {
use Subcommand::*;
match self {
BuildSpec(params) => &params.shared_params,
ExportBlocks(params) => &params.shared_params,
ImportBlocks(params) => &params.shared_params,
CheckBlock(params) => &params.shared_params,
Revert(params) => &params.shared_params,
PurgeChain(params) => &params.shared_params,
}
}
fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self {
match matches.subcommand() {
("build-spec", Some(matches)) =>
CoreParams::BuildSpec(BuildSpecCmd::from_clap(matches)),
("export-blocks", Some(matches)) =>
CoreParams::ExportBlocks(ExportBlocksCmd::from_clap(matches)),
("import-blocks", Some(matches)) =>
CoreParams::ImportBlocks(ImportBlocksCmd::from_clap(matches)),
("check-block", Some(matches)) =>
CoreParams::CheckBlock(CheckBlockCmd::from_clap(matches)),
("revert", Some(matches)) => CoreParams::Revert(RevertCmd::from_clap(matches)),
("purge-chain", Some(matches)) =>
CoreParams::PurgeChain(PurgeChainCmd::from_clap(matches)),
(_, None) => CoreParams::Run(MergeParameters::from_clap(matches)),
_ => CoreParams::Custom(CC::from_clap(matches)),
/// Run any `CoreParams` command
pub fn run<G, E, B, BC, BB>(
self,
config: Configuration<G, E>,
builder: B,
) -> error::Result<()>
where
B: FnOnce(Configuration<G, E>) -> Result<BC, sc_service::error::Error>,
G: RuntimeGenesis,
E: ChainSpecExtension,
BC: ServiceBuilderCommand<Block = BB> + Unpin,
BB: sp_runtime::traits::Block + Debug,
<<<BB as BlockT>::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug,
<BB as BlockT>::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),
}
}
}
/// A special commandline parameter that expands to nothing.
/// Should be used as custom subcommand/run arguments if no custom values are required.
#[derive(Clone, Debug, Default)]
pub struct NoCustom {}
impl RunCmd {
/// Run the command that runs the node
pub fn run<G, E, FNL, FNF, SL, SF>(
self,
mut config: Configuration<G, E>,
new_light: FNL,
new_full: FNF,
version: &VersionInfo,
) -> error::Result<()>
where
G: RuntimeGenesis,
E: ChainSpecExtension,
FNL: FnOnce(Configuration<G, E>) -> Result<SL, sc_service::error::Error>,
FNF: FnOnce(Configuration<G, E>) -> Result<SF, sc_service::error::Error>,
SL: AbstractService + Unpin,
SF: AbstractService + Unpin,
{
assert!(config.chain_spec.is_some(), "chain_spec must be present before continuing");
impl StructOpt for NoCustom {
fn clap<'a, 'b>() -> App<'a, 'b> {
App::new("NoCustom")
}
crate::update_config_for_running_node(
&mut config,
self,
)?;
fn from_clap(_: &::structopt::clap::ArgMatches) -> Self {
NoCustom {}
crate::run_node(config, new_light, new_full, &version)
}
}
impl StructOptInternal for NoCustom {
fn augment_clap<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
app
impl BuildSpecCmd {
/// Run the build-spec command
pub fn run<G, E>(
self,
config: Configuration<G, E>,
) -> 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 GetSharedParams for NoCustom {
fn shared_params(&self) -> Option<&SharedParams> {
None
impl ExportBlocksCmd {
/// Run the export-blocks command
pub fn run<G, E, B, BC, BB>(
self,
mut config: Configuration<G, E>,
builder: B,
) -> error::Result<()>
where
B: FnOnce(Configuration<G, E>) -> Result<BC, sc_service::error::Error>,
G: RuntimeGenesis,
E: ChainSpecExtension,
BC: ServiceBuilderCommand<Block = BB> + Unpin,
BB: sp_runtime::traits::Block + Debug,
<<<BB as BlockT>::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug,
<BB as BlockT>::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.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<dyn io::Write> = 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))
})
}
}
/// Merge all CLI parameters of `L` and `R` into the same level.
#[derive(Clone, Debug)]
pub struct MergeParameters<L, R> {
/// The left side parameters.
pub left: L,
/// The right side parameters.
pub right: R,
/// Internal trait used to cast to a dynamic type that implements Read and Seek.
trait ReadPlusSeek: Read + Seek {}
impl<T: Read + Seek> ReadPlusSeek for T {}
impl ImportBlocksCmd {
/// Run the import-blocks command
pub fn run<G, E, B, BC, BB>(
self,
mut config: Configuration<G, E>,
builder: B,
) -> error::Result<()>
where
B: FnOnce(Configuration<G, E>) -> Result<BC, sc_service::error::Error>,
G: RuntimeGenesis,
E: ChainSpecExtension,
BC: ServiceBuilderCommand<Block = BB> + Unpin,
BB: sp_runtime::traits::Block + Debug,
<<<BB as BlockT>::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug,
<BB as BlockT>::Hash: std::str::FromStr,
{
crate::fill_import_params(
&mut config,
&self.import_params,
sc_service::Roles::FULL,
self.shared_params.dev,
)?;
let file: Box<dyn ReadPlusSeek + Send> = 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<L, R> StructOpt for MergeParameters<L, R> where L: StructOpt + StructOptInternal, R: StructOpt {
fn clap<'a, 'b>() -> App<'a, 'b> {
L::augment_clap(R::clap())
}
impl CheckBlockCmd {
/// Run the check-block command
pub fn run<G, E, B, BC, BB>(
self,
mut config: Configuration<G, E>,
builder: B,
) -> error::Result<()>
where
B: FnOnce(Configuration<G, E>) -> Result<BC, sc_service::error::Error>,
G: RuntimeGenesis,
E: ChainSpecExtension,
BC: ServiceBuilderCommand<Block = BB> + Unpin,
BB: sp_runtime::traits::Block + Debug,
<<<BB as BlockT>::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug,
<BB as BlockT>::Hash: std::str::FromStr,
{
assert!(config.chain_spec.is_some(), "chain_spec must be present before continuing");
fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self {
MergeParameters {
left: L::from_clap(matches),
right: R::from_clap(matches),
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::<u32>() {
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<G, E>(
self,
mut config: Configuration<G, E>,
) -> 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.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<G, E, B, BC, BB>(
self,
mut config: Configuration<G, E>,
builder: B,
) -> error::Result<()>
where
B: FnOnce(Configuration<G, E>) -> Result<BC, sc_service::error::Error>,
G: RuntimeGenesis,
E: ChainSpecExtension,
BC: ServiceBuilderCommand<Block = BB> + Unpin,
BB: sp_runtime::traits::Block + Debug,
<<<BB as BlockT>::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug,
<BB as BlockT>::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(())
}
}
+138
View File
@@ -0,0 +1,138 @@
// 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 <http://www.gnu.org/licenses/>.
use futures::{Future, future, future::FutureExt};
use futures::select;
use futures::pin_mut;
use sc_service::{AbstractService, Configuration};
use crate::error;
use crate::informant;
#[cfg(target_family = "unix")]
async fn main<F, E>(func: F) -> Result<(), Box<dyn std::error::Error>>
where
F: Future<Output = Result<(), E>> + future::FusedFuture,
E: 'static + std::error::Error,
{
use tokio::signal::unix::{signal, SignalKind};
let mut stream_int = signal(SignalKind::interrupt())?;
let mut stream_term = signal(SignalKind::terminate())?;
let t1 = stream_int.recv().fuse();
let t2 = stream_term.recv().fuse();
let t3 = func;
pin_mut!(t1, t2, t3);
select! {
_ = t1 => {},
_ = t2 => {},
res = t3 => res?,
}
Ok(())
}
#[cfg(not(unix))]
async fn main<F, E>(func: F) -> Result<(), Box<dyn std::error::Error>>
where
F: Future<Output = Result<(), E>> + future::FusedFuture,
E: 'static + std::error::Error,
{
use tokio::signal::ctrl_c;
let t1 = ctrl_c().fuse();
let t2 = func;
pin_mut!(t1, t2);
select! {
_ = t1 => {},
res = t2 => res?,
}
Ok(())
}
fn build_runtime() -> Result<tokio::runtime::Runtime, std::io::Error> {
tokio::runtime::Builder::new()
.thread_name("main-tokio-")
.threaded_scheduler()
.enable_all()
.build()
}
/// A helper function that runs a future with tokio and stops if the process receives the signal
/// SIGTERM or SIGINT
pub fn run_until_exit<FUT, ERR, G, E, F>(
mut config: Configuration<G, E>,
future_builder: F,
) -> error::Result<()>
where
F: FnOnce(Configuration<G, E>) -> error::Result<FUT>,
FUT: Future<Output = Result<(), ERR>> + future::Future,
ERR: 'static + std::error::Error,
{
let mut runtime = build_runtime()?;
config.task_executor = {
let runtime_handle = runtime.handle().clone();
Some(Box::new(move |fut| { runtime_handle.spawn(fut); }))
};
let f = future_builder(config)?;
let f = f.fuse();
pin_mut!(f);
runtime.block_on(main(f)).map_err(|e| e.to_string())?;
Ok(())
}
/// A helper function that runs an `AbstractService` with tokio and stops if the process receives
/// the signal SIGTERM or SIGINT
pub fn run_service_until_exit<T, G, E, F>(
mut config: Configuration<G, E>,
service_builder: F,
) -> error::Result<()>
where
F: FnOnce(Configuration<G, E>) -> Result<T, sc_service::error::Error>,
T: AbstractService + Unpin,
{
let mut runtime = build_runtime()?;
config.task_executor = {
let runtime_handle = runtime.handle().clone();
Some(Box::new(move |fut| { runtime_handle.spawn(fut); }))
};
let service = service_builder(config)?;
let informant_future = informant::build(&service);
let _informant_handle = runtime.spawn(informant_future);
// we eagerly drop the service so that the internal exit future is fired,
// but we need to keep holding a reference to the global telemetry guard
let _telemetry = service.telemetry();
let f = service.fuse();
pin_mut!(f);
runtime.block_on(main(f)).map_err(|e| e.to_string())?;
Ok(())
}