diff --git a/substrate/core/cli/src/lib.rs b/substrate/core/cli/src/lib.rs index 9204408eb3..2fe72c583d 100644 --- a/substrate/core/cli/src/lib.rs +++ b/substrate/core/cli/src/lib.rs @@ -167,38 +167,29 @@ fn is_node_name_valid(_name: &str) -> Result<(), &str> { Ok(()) } -/// Parse command line interface arguments and executes the desired command. +/// Parse command line interface arguments and prepares the command for execution. /// -/// # Return value -/// -/// A result that indicates if any error occurred. -/// If no error occurred and a custom subcommand was found, the subcommand is returned. -/// The user needs to handle this subcommand on its own. +/// Before returning, this function performs various initializations, such as initializing the +/// panic handler and the logger, or increasing the limit for file descriptors. /// /// # Remarks /// /// `CC` is a custom subcommand. This needs to be an `enum`! If no custom subcommand is required, /// `NoCustom` can be used as type here. +/// /// `RP` are custom parameters for the run command. This needs to be a `struct`! The custom /// parameters are visible to the user as if they were normal run command parameters. If no custom /// parameters are required, `NoCustom` can be used as type here. -pub fn parse_and_execute<'a, F, CC, RP, S, RS, E, I, T>( - spec_factory: S, - version: &VersionInfo, +pub fn parse_and_prepare<'a, CC, RP, I>( + version: &'a VersionInfo, impl_name: &'static str, args: I, - exit: E, - run_service: RS, -) -> error::Result> +) -> ParseAndPrepare<'a, CC, RP> where - F: ServiceFactory, - S: FnOnce(&str) -> Result>>, String>, CC: StructOpt + Clone + GetLogFilter, RP: StructOpt + Clone + AugmentClap, - E: IntoExit, - RS: FnOnce(E, RunCmd, RP, FactoryFullConfiguration) -> Result<(), String>, - I: IntoIterator, - T: Into + Clone, + I: IntoIterator, + ::Item: Into + Clone, { panic_handler::set(version.support_url); @@ -222,23 +213,294 @@ where fdlimit::raise_fd_limit(); match cli_args { - params::CoreParams::Run(params) => run_node( - params, spec_factory, exit, run_service, impl_name, version, - ).map(|_| None), - params::CoreParams::BuildSpec(params) => - build_spec(params, spec_factory, version).map(|_| None), - params::CoreParams::ExportBlocks(params) => - export_blocks::(params, spec_factory, exit, version).map(|_| None), - params::CoreParams::ImportBlocks(params) => - import_blocks::(params, spec_factory, exit, version).map(|_| None), - params::CoreParams::PurgeChain(params) => - purge_chain(params, spec_factory, version).map(|_| None), - params::CoreParams::Revert(params) => - revert_chain::(params, spec_factory, version).map(|_| None), - params::CoreParams::Custom(params) => Ok(Some(params)), + params::CoreParams::Run(params) => ParseAndPrepare::Run( + ParseAndPrepareRun { params, impl_name, version } + ), + params::CoreParams::BuildSpec(params) => ParseAndPrepare::BuildSpec( + ParseAndPrepareBuildSpec { params, version } + ), + params::CoreParams::ExportBlocks(params) => ParseAndPrepare::ExportBlocks( + ParseAndPrepareExport { params, version } + ), + params::CoreParams::ImportBlocks(params) => ParseAndPrepare::ImportBlocks( + ParseAndPrepareImport { params, version } + ), + params::CoreParams::PurgeChain(params) => ParseAndPrepare::PurgeChain( + ParseAndPreparePurge { params, version } + ), + params::CoreParams::Revert(params) => ParseAndPrepare::RevertChain( + ParseAndPrepareRevert { params, version } + ), + params::CoreParams::Custom(params) => ParseAndPrepare::CustomCommand(params), } } +/// Output of calling `parse_and_prepare`. +#[must_use] +pub enum ParseAndPrepare<'a, CC, RP> { + /// Command ready to run the main client. + Run(ParseAndPrepareRun<'a, RP>), + /// Command ready to build chain specs. + BuildSpec(ParseAndPrepareBuildSpec<'a>), + /// Command ready to export the chain. + ExportBlocks(ParseAndPrepareExport<'a>), + /// Command ready to import the chain. + ImportBlocks(ParseAndPrepareImport<'a>), + /// Command ready to purge the chain. + PurgeChain(ParseAndPreparePurge<'a>), + /// Command ready to revert the chain. + RevertChain(ParseAndPrepareRevert<'a>), + /// An additional custom command passed to `parse_and_prepare`. + CustomCommand(CC), +} + +/// Command ready to run the main client. +pub struct ParseAndPrepareRun<'a, RP> { + params: MergeParameters, + impl_name: &'static str, + version: &'a VersionInfo, +} + +impl<'a, RP> ParseAndPrepareRun<'a, RP> { + /// Runs the command and runs the main client. + pub fn run( + self, + spec_factory: S, + exit: E, + run_service: RS, + ) -> error::Result<()> + where S: FnOnce(&str) -> Result>, String>, + RP: StructOpt + Clone, + C: Default, + G: RuntimeGenesis, + E: IntoExit, + RS: FnOnce(E, RunCmd, RP, Configuration) -> Result<(), String> + { + let config = create_run_node_config(self.params.left.clone(), spec_factory, self.impl_name, self.version)?; + + run_service(exit, self.params.left, self.params.right, config).map_err(Into::into) + } +} + +/// Command ready to build chain specs. +pub struct ParseAndPrepareBuildSpec<'a> { + params: BuildSpecCmd, + version: &'a VersionInfo, +} + +impl<'a> ParseAndPrepareBuildSpec<'a> { + /// Runs the command and build the chain specs. + pub fn run( + self, + spec_factory: S + ) -> error::Result<()> + where S: FnOnce(&str) -> Result>, String>, + G: RuntimeGenesis + { + info!("Building chain spec"); + let raw_output = self.params.raw; + let mut spec = load_spec(&self.params.shared_params, spec_factory)?; + with_default_boot_node(&mut spec, self.params, self.version)?; + let json = service::chain_ops::build_spec(spec, raw_output)?; + + print!("{}", json); + + Ok(()) + } +} + +/// Command ready to export the chain. +pub struct ParseAndPrepareExport<'a> { + params: ExportBlocksCmd, + version: &'a VersionInfo, +} + +impl<'a> ParseAndPrepareExport<'a> { + /// Runs the command and exports from the chain. + pub fn run( + self, + spec_factory: S, + exit: E, + ) -> error::Result<()> + where S: FnOnce(&str) -> Result>>, String>, + F: ServiceFactory, + E: IntoExit + { + let config = create_config_with_db_path(spec_factory, &self.params.shared_params, self.version)?; + + info!("DB path: {}", config.database_path.display()); + let from = self.params.from.unwrap_or(1); + let to = self.params.to; + let json = self.params.json; + + let file: Box = match self.params.output { + Some(filename) => Box::new(File::create(filename)?), + None => Box::new(stdout()), + }; + + service::chain_ops::export_blocks::( + config, exit.into_exit(), file, from.into(), to.map(Into::into), json + ).map_err(Into::into) + } +} + +/// Command ready to import the chain. +pub struct ParseAndPrepareImport<'a> { + params: ImportBlocksCmd, + version: &'a VersionInfo, +} + +impl<'a> ParseAndPrepareImport<'a> { + /// Runs the command and imports to the chain. + pub fn run( + self, + spec_factory: S, + exit: E, + ) -> error::Result<()> + where S: FnOnce(&str) -> Result>>, String>, + F: ServiceFactory, + E: IntoExit + { + let mut config = create_config_with_db_path(spec_factory, &self.params.shared_params, self.version)?; + config.execution_strategies = ExecutionStrategies { + importing: self.params.execution.into(), + other: self.params.execution.into(), + ..Default::default() + }; + + let file: Box = match self.params.input { + Some(filename) => Box::new(File::open(filename)?), + None => { + let mut buffer = Vec::new(); + stdin().read_to_end(&mut buffer)?; + Box::new(Cursor::new(buffer)) + }, + }; + + let fut = service::chain_ops::import_blocks::(config, exit.into_exit(), file)?; + tokio::run(fut); + Ok(()) + } +} + +/// Command ready to purge the chain. +pub struct ParseAndPreparePurge<'a> { + params: PurgeChainCmd, + version: &'a VersionInfo, +} + +impl<'a> ParseAndPreparePurge<'a> { + /// Runs the command and purges the chain. + pub fn run( + self, + spec_factory: S + ) -> error::Result<()> + where S: FnOnce(&str) -> Result>, String>, + G: RuntimeGenesis + { + let config = create_config_with_db_path::<(), _, _>(spec_factory, &self.params.shared_params, self.version)?; + let db_path = config.database_path; + + if !self.params.yes { + print!("Are you sure to remove {:?}? (y/n)", &db_path); + stdout().flush().expect("failed to flush stdout"); + + let mut input = String::new(); + 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) { + Result::Ok(_) => { + println!("{:?} removed.", &db_path); + Ok(()) + }, + Result::Err(ref err) if err.kind() == ErrorKind::NotFound => { + println!("{:?} did not exist.", &db_path); + Ok(()) + }, + Result::Err(err) => Result::Err(err.into()) + } + } +} + +/// Command ready to revert the chain. +pub struct ParseAndPrepareRevert<'a> { + params: RevertCmd, + version: &'a VersionInfo, +} + +impl<'a> ParseAndPrepareRevert<'a> { + /// Runs the command and reverts the chain. + pub fn run( + self, + spec_factory: S + ) -> error::Result<()> + where S: FnOnce(&str) -> Result>>, String>, + F: ServiceFactory { + let config = create_config_with_db_path(spec_factory, &self.params.shared_params, self.version)?; + let blocks = self.params.num; + Ok(service::chain_ops::revert_chain::(config, blocks.into())?) + } +} + +/// Parse command line interface arguments and executes the desired command. +/// +/// # Return value +/// +/// A result that indicates if any error occurred. +/// If no error occurred and a custom subcommand was found, the subcommand is returned. +/// The user needs to handle this subcommand on its own. +/// +/// # Remarks +/// +/// `CC` is a custom subcommand. This needs to be an `enum`! If no custom subcommand is required, +/// `NoCustom` can be used as type here. +/// `RP` are custom parameters for the run command. This needs to be a `struct`! The custom +/// parameters are visible to the user as if they were normal run command parameters. If no custom +/// parameters are required, `NoCustom` can be used as type here. +#[deprecated( + note = "Use parse_and_prepare instead; see the source code of parse_and_execute for how to transition" +)] +pub fn parse_and_execute<'a, F, CC, RP, S, RS, E, I, T>( + spec_factory: S, + version: &VersionInfo, + impl_name: &'static str, + args: I, + exit: E, + run_service: RS, +) -> error::Result> +where + F: ServiceFactory, + S: FnOnce(&str) -> Result>>, String>, + CC: StructOpt + Clone + GetLogFilter, + RP: StructOpt + Clone + AugmentClap, + E: IntoExit, + RS: FnOnce(E, RunCmd, RP, FactoryFullConfiguration) -> Result<(), String>, + I: IntoIterator, + T: Into + Clone, +{ + match parse_and_prepare::(version, impl_name, args) { + ParseAndPrepare::Run(cmd) => cmd.run(spec_factory, exit, run_service), + ParseAndPrepare::BuildSpec(cmd) => cmd.run(spec_factory), + ParseAndPrepare::ExportBlocks(cmd) => cmd.run::(spec_factory, exit), + ParseAndPrepare::ImportBlocks(cmd) => cmd.run::(spec_factory, exit), + ParseAndPrepare::PurgeChain(cmd) => cmd.run(spec_factory), + ParseAndPrepare::RevertChain(cmd) => cmd.run::(spec_factory), + ParseAndPrepare::CustomCommand(cmd) => return Ok(Some(cmd)) + }?; + + Ok(None) +} + /// Create a `NodeKeyConfig` from the given `NodeKeyParams` in the context /// of an optional network config storage directory. fn node_key_config

(params: NodeKeyParams, net_config_dir: &Option

) @@ -522,27 +784,6 @@ where Ok(config) } -fn run_node( - cli: MergeParameters, - spec_factory: S, - exit: E, - run_service: RS, - impl_name: &'static str, - version: &VersionInfo, -) -> error::Result<()> -where - RP: StructOpt + Clone, - C: Default, - G: RuntimeGenesis, - E: IntoExit, - S: FnOnce(&str) -> Result>, String>, - RS: FnOnce(E, RunCmd, RP, Configuration) -> Result<(), String>, - { - let config = create_run_node_config(cli.left.clone(), spec_factory, impl_name, version)?; - - run_service(exit, cli.left, cli.right, config).map_err(Into::into) -} - // // IANA unassigned port ranges that we could use: // 6717-6766 Unassigned @@ -575,26 +816,6 @@ where Ok(()) } -fn build_spec( - cli: BuildSpecCmd, - spec_factory: S, - version: &VersionInfo, -) -> error::Result<()> -where - G: RuntimeGenesis, - S: FnOnce(&str) -> Result>, String>, -{ - info!("Building chain spec"); - let raw_output = cli.raw; - let mut spec = load_spec(&cli.shared_params, spec_factory)?; - with_default_boot_node(&mut spec, cli, version)?; - let json = service::chain_ops::build_spec(spec, raw_output)?; - - print!("{}", json); - - Ok(()) -} - /// Creates a configuration including the database path. pub fn create_config_with_db_path( spec_factory: S, cli: &SharedParams, version: &VersionInfo, @@ -613,127 +834,11 @@ where Ok(config) } -fn export_blocks( - cli: ExportBlocksCmd, - spec_factory: S, - exit: E, - version: &VersionInfo, -) -> error::Result<()> -where - F: ServiceFactory, - E: IntoExit, - S: FnOnce(&str) -> Result>>, String>, -{ - let config = create_config_with_db_path(spec_factory, &cli.shared_params, version)?; - - info!("DB path: {}", config.database_path.display()); - let from = cli.from.unwrap_or(1); - let to = cli.to; - let json = cli.json; - - let file: Box = match cli.output { - Some(filename) => Box::new(File::create(filename)?), - None => Box::new(stdout()), - }; - - service::chain_ops::export_blocks::( - config, exit.into_exit(), file, from.into(), to.map(Into::into), json - ).map_err(Into::into) -} - /// Internal trait used to cast to a dynamic type that implements Read and Seek. trait ReadPlusSeek: Read + Seek {} impl ReadPlusSeek for T {} -fn import_blocks( - cli: ImportBlocksCmd, - spec_factory: S, - exit: E, - version: &VersionInfo, -) -> error::Result<()> -where - F: ServiceFactory, - E: IntoExit, - S: FnOnce(&str) -> Result>>, String>, -{ - let mut config = create_config_with_db_path(spec_factory, &cli.shared_params, version)?; - config.execution_strategies = ExecutionStrategies { - importing: cli.execution.into(), - other: cli.execution.into(), - ..Default::default() - }; - - let file: Box = match cli.input { - Some(filename) => Box::new(File::open(filename)?), - None => { - let mut buffer = Vec::new(); - stdin().read_to_end(&mut buffer)?; - Box::new(Cursor::new(buffer)) - }, - }; - - let fut = service::chain_ops::import_blocks::(config, exit.into_exit(), file)?; - tokio::run(fut); - Ok(()) -} - -fn revert_chain( - cli: RevertCmd, - spec_factory: S, - version: &VersionInfo, -) -> error::Result<()> -where - F: ServiceFactory, - S: FnOnce(&str) -> Result>>, String>, -{ - let config = create_config_with_db_path(spec_factory, &cli.shared_params, version)?; - let blocks = cli.num; - Ok(service::chain_ops::revert_chain::(config, blocks.into())?) -} - -fn purge_chain( - cli: PurgeChainCmd, - spec_factory: S, - version: &VersionInfo, -) -> error::Result<()> -where - G: RuntimeGenesis, - S: FnOnce(&str) -> Result>, String>, -{ - let config = create_config_with_db_path::<(), _, _>(spec_factory, &cli.shared_params, version)?; - let db_path = config.database_path; - - if cli.yes == false { - print!("Are you sure to remove {:?}? (y/n)", &db_path); - stdout().flush().expect("failed to flush stdout"); - - let mut input = String::new(); - 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) { - Result::Ok(_) => { - println!("{:?} removed.", &db_path); - Ok(()) - }, - Result::Err(ref err) if err.kind() == ErrorKind::NotFound => { - println!("{:?} did not exist.", &db_path); - Ok(()) - }, - Result::Err(err) => Result::Err(err.into()) - } -} - fn parse_address( address: &str, port: Option, diff --git a/substrate/node-template/src/cli.rs b/substrate/node-template/src/cli.rs index b799a5d9ae..5f94afddbc 100644 --- a/substrate/node-template/src/cli.rs +++ b/substrate/node-template/src/cli.rs @@ -3,7 +3,7 @@ use futures::{future, Future, sync::oneshot}; use std::cell::RefCell; use tokio::runtime::Runtime; pub use substrate_cli::{VersionInfo, IntoExit, error}; -use substrate_cli::{informant, parse_and_execute, NoCustom}; +use substrate_cli::{informant, parse_and_prepare, ParseAndPrepare, NoCustom}; use substrate_service::{ServiceFactory, Roles as ServiceRoles}; use crate::chain_spec; use std::ops::Deref; @@ -15,9 +15,8 @@ pub fn run(args: I, exit: E, version: VersionInfo) -> error::Result<()> T: Into + Clone, E: IntoExit, { - parse_and_execute::( - load_spec, &version, "substrate-node", args, exit, - |exit, _cli_args, _custom_args, config| { + match parse_and_prepare::(&version, "substrate-node", args) { + ParseAndPrepare::Run(cmd) => cmd.run(load_spec, exit, |exit, _cli_args, _custom_args, config| { info!("{}", version.name); info!(" version {}", config.full_version()); info!(" by {}, 2017, 2018", version.author); @@ -37,8 +36,16 @@ pub fn run(args: I, exit: E, version: VersionInfo) -> error::Result<()> exit ), }.map_err(|e| format!("{:?}", e)) - } - ).map_err(Into::into).map(|_| ()) + }), + ParseAndPrepare::BuildSpec(cmd) => cmd.run(load_spec), + ParseAndPrepare::ExportBlocks(cmd) => cmd.run::(load_spec, exit), + ParseAndPrepare::ImportBlocks(cmd) => cmd.run::(load_spec, exit), + ParseAndPrepare::PurgeChain(cmd) => cmd.run(load_spec), + ParseAndPrepare::RevertChain(cmd) => cmd.run::(load_spec), + ParseAndPrepare::CustomCommand(_) => Ok(()) + }?; + + Ok(()) } fn load_spec(id: &str) -> Result, String> { diff --git a/substrate/node/cli/src/lib.rs b/substrate/node/cli/src/lib.rs index 71cb19001d..6639c4ad0b 100644 --- a/substrate/node/cli/src/lib.rs +++ b/substrate/node/cli/src/lib.rs @@ -31,7 +31,7 @@ use substrate_service::{ServiceFactory, Roles as ServiceRoles}; use std::ops::Deref; use log::info; use structopt::{StructOpt, clap::App}; -use cli::{AugmentClap, GetLogFilter}; +use cli::{AugmentClap, GetLogFilter, parse_and_prepare, ParseAndPrepare}; use crate::factory_impl::FactoryState; use transaction_factory::RuntimeAdapter; use client::ExecutionStrategies; @@ -158,9 +158,8 @@ pub fn run(args: I, exit: E, version: cli::VersionInfo) -> error::Resul T: Into + Clone, E: IntoExit, { - let ret = cli::parse_and_execute::( - load_spec, &version, "substrate-node", args, exit, - |exit, _cli_args, _custom_args, config| { + match parse_and_prepare::(&version, "substrate-node", args) { + ParseAndPrepare::Run(cmd) => cmd.run(load_spec, exit, |exit, _cli_args, _custom_args, config| { info!("{}", version.name); info!(" version {}", config.full_version()); info!(" by Parity Technologies, 2017-2019"); @@ -181,11 +180,13 @@ pub fn run(args: I, exit: E, version: cli::VersionInfo) -> error::Resul exit ), }.map_err(|e| format!("{:?}", e)) - } - ); - - match &ret { - Ok(Some(CustomSubcommands::Factory(cli_args))) => { + }), + ParseAndPrepare::BuildSpec(cmd) => cmd.run(load_spec), + ParseAndPrepare::ExportBlocks(cmd) => cmd.run::(load_spec, exit), + ParseAndPrepare::ImportBlocks(cmd) => cmd.run::(load_spec, exit), + ParseAndPrepare::PurgeChain(cmd) => cmd.run(load_spec), + ParseAndPrepare::RevertChain(cmd) => cmd.run::(load_spec), + ParseAndPrepare::CustomCommand(CustomSubcommands::Factory(cli_args)) => { let mut config = cli::create_config_with_db_path( load_spec, &cli_args.shared_params, @@ -214,8 +215,7 @@ pub fn run(args: I, exit: E, version: cli::VersionInfo) -> error::Resul ).map_err(|e| format!("Error in transaction factory: {}", e))?; Ok(()) - }, - _ => ret.map_err(Into::into).map(|_| ()) + } } }