diff --git a/Cargo.lock b/Cargo.lock index 96f724fcdc..6adb7b2697 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -599,7 +599,6 @@ version = "0.119.0-pre" dependencies = [ "ckb-build-info", "ckb-chain-spec", - "ckb-constant", "ckb-jsonrpc-types", "ckb-logger", "ckb-logger-config", @@ -608,13 +607,11 @@ dependencies = [ "ckb-resource", "ckb-systemtime", "ckb-types", - "clap", "path-clean", "rand 0.8.5", "sentry", "serde", "serde_json", - "serde_plain", "tempfile", "tentacle-multiaddr", "tentacle-secio", @@ -671,6 +668,7 @@ dependencies = [ "ckb-chain-iter", "ckb-chain-spec", "ckb-channel", + "ckb-constant", "ckb-instrument", "ckb-jsonrpc-types", "ckb-launcher", @@ -5594,9 +5592,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -5615,9 +5613,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", diff --git a/Makefile b/Makefile index e9ef3cd9dd..966a1090a8 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ doc-test: ## Run doc tests .PHONY: cli-test cli-test: prod # Run ckb command line usage bats test - ./util/app-config/src/tests/bats_tests/cli_test.sh + ./ckb-bin/src/tests/bats_tests/cli_test.sh .PHONY: test test: ## Run all tests, including some tests can be time-consuming to execute (tagged with [ignore]) diff --git a/ckb-bin/Cargo.toml b/ckb-bin/Cargo.toml index f10c4961a9..c6be0d57c7 100644 --- a/ckb-bin/Cargo.toml +++ b/ckb-bin/Cargo.toml @@ -9,7 +9,7 @@ homepage = "https://github.com/nervosnetwork/ckb" repository = "https://github.com/nervosnetwork/ckb" [dependencies] -clap = { version = "=4.4" } +clap = { version = "=4.4", features = ["string", "wrap_help"] } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0" } serde_plain = "0.3.0" @@ -30,7 +30,9 @@ ckb-miner = { path = "../miner", version = "= 0.119.0-pre" } ckb-network = { path = "../network", version = "= 0.119.0-pre" } ckb-resource = { path = "../resource", version = "= 0.119.0-pre" } ctrlc = { version = "3.1", features = ["termination"] } -ckb-instrument = { path = "../util/instrument", version = "= 0.119.0-pre", features = ["progress_bar"] } +ckb-instrument = { path = "../util/instrument", version = "= 0.119.0-pre", features = [ + "progress_bar", +] } ckb-build-info = { path = "../util/build-info", version = "= 0.119.0-pre" } ckb-memory-tracker = { path = "../util/memory-tracker", version = "= 0.119.0-pre" } ckb-chain-iter = { path = "../util/chain-iter", version = "= 0.119.0-pre" } @@ -38,6 +40,7 @@ ckb-verification-traits = { path = "../verification/traits", version = "= 0.119. ckb-async-runtime = { path = "../util/runtime", version = "= 0.119.0-pre" } ckb-migrate = { path = "../util/migrate", version = "= 0.119.0-pre" } ckb-launcher = { path = "../util/launcher", version = "= 0.119.0-pre" } +ckb-constant = { path = "../util/constant", version = "= 0.119.0-pre" } base64 = "0.21.0" tempfile.workspace = true rayon = "1.0" @@ -55,7 +58,13 @@ colored = "2.0" [features] deadlock_detection = ["ckb-util/deadlock_detection"] profiling = ["ckb-memory-tracker/profiling", "ckb-shared/stats"] -with_sentry = ["sentry", "ckb-launcher/with_sentry", "ckb-network/with_sentry", "ckb-app-config/with_sentry", "ckb-logger-service/with_sentry"] +with_sentry = [ + "sentry", + "ckb-launcher/with_sentry", + "ckb-network/with_sentry", + "ckb-app-config/with_sentry", + "ckb-logger-service/with_sentry", +] with_dns_seeding = ["ckb-network/with_dns_seeding"] portable = ["ckb-launcher/portable"] march-native = ["ckb-launcher/march-native"] diff --git a/ckb-bin/src/cli.rs b/ckb-bin/src/cli.rs new file mode 100644 index 0000000000..fbda878d75 --- /dev/null +++ b/ckb-bin/src/cli.rs @@ -0,0 +1,651 @@ +//! CKB command line arguments parser. +use ckb_build_info::Version; +use ckb_resource::{AVAILABLE_SPECS, DEFAULT_P2P_PORT, DEFAULT_RPC_PORT, DEFAULT_SPEC}; +use clap::{Arg, ArgGroup, ArgMatches, Command}; + +pub use ckb_app_config::cli::*; + +/// Subcommand `export`. +pub const CMD_EXPORT: &str = "export"; +/// Subcommand `import`. +pub const CMD_IMPORT: &str = "import"; +/// Subcommand `init`. +pub const CMD_INIT: &str = "init"; +/// Subcommand `replay`. +pub const CMD_REPLAY: &str = "replay"; +/// Subcommand `stats`. +pub const CMD_STATS: &str = "stats"; +/// Subcommand `list-hashes`. +pub const CMD_LIST_HASHES: &str = "list-hashes"; +/// Subcommand `peer-id`. +pub const CMD_PEERID: &str = "peer-id"; +/// Subcommand `gen`. +pub const CMD_GEN_SECRET: &str = "gen"; +/// Subcommand `from-secret`. +pub const CMD_FROM_SECRET: &str = "from-secret"; +/// Subcommand `migrate`. +pub const CMD_MIGRATE: &str = "migrate"; +/// Subcommand `daemon` +pub const CMD_DAEMON: &str = "daemon"; +/// Command line argument `--config-dir`. +pub const ARG_CONFIG_DIR: &str = "config-dir"; +/// Command line argument `--format`. +pub const ARG_FORMAT: &str = "format"; +/// Command line argument `--target`. +pub const ARG_TARGET: &str = "target"; +/// Command line argument `--source`. +pub const ARG_SOURCE: &str = "source"; +/// Command line argument `--data`. +pub const ARG_DATA: &str = "data"; +/// Command line argument `--list-chains`. +pub const ARG_LIST_CHAINS: &str = "list-chains"; +/// Command line argument `--interactive`. +pub const ARG_INTERACTIVE: &str = "interactive"; +/// Command line argument `--chain`. +pub const ARG_CHAIN: &str = "chain"; +/// Command line argument `--import-spec`. +pub const ARG_IMPORT_SPEC: &str = "import-spec"; +/// The argument for the genesis message. +pub const ARG_GENESIS_MESSAGE: &str = "genesis-message"; +/// Command line argument `--p2p-port`. +pub const ARG_P2P_PORT: &str = "p2p-port"; +/// Command line argument `--rpc-port`. +pub const ARG_RPC_PORT: &str = "rpc-port"; +/// Command line argument `--force`. +pub const ARG_FORCE: &str = "force"; +/// Command line argument `--include-background`. +pub const ARG_INCLUDE_BACKGROUND: &str = "include-background"; +/// Command line argument `--log-to`. +pub const ARG_LOG_TO: &str = "log-to"; +/// Command line argument `--bundled`. +pub const ARG_BUNDLED: &str = "bundled"; +/// Command line argument `--ba-code-hash`. +pub const ARG_BA_CODE_HASH: &str = "ba-code-hash"; +/// Command line argument `--ba-arg`. +pub const ARG_BA_ARG: &str = "ba-arg"; +/// Command line argument `--ba-hash-type`. +pub const ARG_BA_HASH_TYPE: &str = "ba-hash-type"; +/// Command line argument `--ba-message`. +pub const ARG_BA_MESSAGE: &str = "ba-message"; +/// Command line argument `--ba-advanced`. +pub const ARG_BA_ADVANCED: &str = "ba-advanced"; +/// Command line argument `--daemon` +pub const ARG_DAEMON: &str = "daemon"; +/// Command line argument `--indexer`. +pub const ARG_INDEXER: &str = "indexer"; +/// Command line argument `--rich-indexer`. +pub const ARG_RICH_INDEXER: &str = "rich-indexer"; +/// Command line argument `--from`. +pub const ARG_FROM: &str = "from"; +/// Command line argument `--to`. +pub const ARG_TO: &str = "to"; +/// Command line argument `--all`. +pub const ARG_ALL: &str = "all"; +/// Command line argument `--limit`. +pub const ARG_LIMIT: &str = "limit"; +/// Command line argument `--database`. +pub const ARG_DATABASE: &str = "database"; +/// Command line argument `--network`. +pub const ARG_NETWORK: &str = "network"; +/// Command line argument `--network-peer-store`. +pub const ARG_NETWORK_PEER_STORE: &str = "network-peer-store"; +/// Command line argument `--network-secret-key`. +pub const ARG_NETWORK_SECRET_KEY: &str = "network-secret-key"; +/// Command line argument `--logs`. +pub const ARG_LOGS: &str = "logs"; +/// Command line argument `--tmp-target`. +pub const ARG_TMP_TARGET: &str = "tmp-target"; +/// Command line argument `--secret-path`. +pub const ARG_SECRET_PATH: &str = "secret-path"; +/// Command line argument `--profile`. +pub const ARG_PROFILE: &str = "profile"; +/// Command line argument `--sanity-check`. +pub const ARG_SANITY_CHECK: &str = "sanity-check"; +/// Command line argument `--full-verification`. +pub const ARG_FULL_VERIFICATION: &str = "full-verification"; +/// Command line argument `--skip-spec-check`. +pub const ARG_SKIP_CHAIN_SPEC_CHECK: &str = "skip-spec-check"; +/// Present `overwrite-spec` arg to force overriding the chain spec in the database with the present configured chain spec +pub const ARG_OVERWRITE_CHAIN_SPEC: &str = "overwrite-spec"; +/// Command line argument `--assume-valid-target`. +pub const ARG_ASSUME_VALID_TARGET: &str = "assume-valid-target"; +/// Command line argument `--check`. +pub const ARG_MIGRATE_CHECK: &str = "check"; +/// Command line argument `daemon --check` +pub const ARG_DAEMON_CHECK: &str = "check"; +/// Command line argument `daemon --stop` +pub const ARG_DAEMON_STOP: &str = "stop"; + +/// Command line arguments group `ba` for block assembler. +const GROUP_BA: &str = "ba"; + +/// return root clap Command +pub fn basic_app() -> Command { + let command = Command::new(BIN_NAME) + .author("Nervos Core Dev ") + .about("Nervos CKB - The Common Knowledge Base") + .subcommand_required(true) + .arg_required_else_help(true) + .term_width(110) + .arg( + Arg::new(ARG_CONFIG_DIR) + .global(true) + .short('C') + .value_name("path") + .action(clap::ArgAction::Set) + .help( + "Run as if CKB was started in , instead of the current working directory.", + ), + ) + .subcommand(run()) + .subcommand(miner()) + .subcommand(export()) + .subcommand(import()) + .subcommand(list_hashes()) + .subcommand(init()) + .subcommand(replay()) + .subcommand(stats()) + .subcommand(reset_data()) + .subcommand(peer_id()) + .subcommand(migrate()); + + #[cfg(not(target_os = "windows"))] + let command = command.subcommand(daemon()); + + command +} + +/// Parse the command line arguments by supplying the version information. +/// +/// The version is used to generate the help message and output for `--version`. +pub fn get_bin_name_and_matches(version: &Version) -> (String, ArgMatches) { + let bin_name = std::env::args() + .next() + .unwrap_or_else(|| BIN_NAME.to_owned()); + let matches = basic_app() + .version(version.short()) + .long_version(version.long()) + .get_matches(); + (bin_name, matches) +} + +fn run() -> Command { + let command = Command::new(CMD_RUN) + .about("Run CKB node") + .arg( + Arg::new(ARG_BA_ADVANCED) + .long(ARG_BA_ADVANCED) + .action(clap::ArgAction::SetTrue) + .help("Allow any block assembler code hash and args"), + ) + .arg( + Arg::new(ARG_SKIP_CHAIN_SPEC_CHECK) + .long(ARG_SKIP_CHAIN_SPEC_CHECK) + .action(clap::ArgAction::SetTrue) + .help("Skip checking the chain spec with the hash stored in the database"), + ).arg( + Arg::new(ARG_OVERWRITE_CHAIN_SPEC) + .long(ARG_OVERWRITE_CHAIN_SPEC) + .action(clap::ArgAction::SetTrue) + .help("Overwrite the chain spec in the database with the present configured chain spec") + ).arg( + Arg::new(ARG_ASSUME_VALID_TARGET) + .long(ARG_ASSUME_VALID_TARGET) + .action(clap::ArgAction::Set) + .value_parser(is_h256) + .help(format!("This parameter specifies the hash of a block. \ +When the height does not reach this block's height, script execution will be disabled, \ +meaning it will skip the verification of the script content. \n\n\ +Please note that when this option is enabled, the header will be synchronized to \ +the highest block currently found. During this period, if the assume valid target is found, \ +the block download starts; \ +If the assume valid target is either absent or has a timestamp within 24 hours of the current time, \ +the target considered invalid, and the block download proceeds with full verification. \n\n\n\ +default(MainNet): {}\n +default(TestNet): {}\n\n +You can explicitly set the value to 0x0000000000000000000000000000000000000000000000000000000000000000 \ +to disable the default behavior and execute full verification for all blocks, \ +", + ckb_constant::default_assume_valid_target::mainnet::DEFAULT_ASSUME_VALID_TARGET, + ckb_constant::default_assume_valid_target::testnet::DEFAULT_ASSUME_VALID_TARGET)) + ).arg( + Arg::new(ARG_INDEXER) + .long(ARG_INDEXER) + .action(clap::ArgAction::SetTrue) + .help("Start the built-in indexer service"), + ) + .arg( + Arg::new(ARG_RICH_INDEXER) + .long(ARG_RICH_INDEXER) + .action(clap::ArgAction::SetTrue) + .help("Start the built-in rich-indexer service"), + ); + + #[cfg(not(target_os = "windows"))] + let command = command.arg( + Arg::new(ARG_DAEMON) + .long(ARG_DAEMON) + .action(clap::ArgAction::SetTrue) + .help( + "Starts ckb as a daemon, \ + which will run in the background and output logs to the specified log file", + ), + ); + command +} + +fn miner() -> Command { + Command::new(CMD_MINER).about("Runs ckb miner").arg( + Arg::new(ARG_LIMIT) + .short('l') + .long(ARG_LIMIT) + .action(clap::ArgAction::Set) + .value_parser(clap::value_parser!(u128)) + .default_value("0") + .help( + "Exit after finding this specific number of nonces; \ + 0 means the miner will never exit. [default: 0]", + ), + ) +} + +fn reset_data() -> Command { + Command::new(CMD_RESET_DATA) + .about( + "Truncate the database directory\n\ + Example:\n\ + ckb reset-data --force --database", + ) + .arg( + Arg::new(ARG_FORCE) + .short('f') + .long(ARG_FORCE) + .action(clap::ArgAction::SetTrue) + .help("Delete data without interactive prompt"), + ) + .arg( + Arg::new(ARG_ALL) + .long(ARG_ALL) + .action(clap::ArgAction::SetTrue) + .help("Delete the whole data directory"), + ) + .arg( + Arg::new(ARG_DATABASE) + .long(ARG_DATABASE) + .action(clap::ArgAction::SetTrue) + .help("Delete only `data/db`"), + ) + .arg( + Arg::new(ARG_INDEXER) + .long(ARG_INDEXER) + .action(clap::ArgAction::SetTrue) + .help("Delete only `data/indexer/store`"), + ) + .arg( + Arg::new(ARG_RICH_INDEXER) + .long(ARG_RICH_INDEXER) + .action(clap::ArgAction::SetTrue) + .help("Delete only `data/indexer/sqlite`"), + ) + .arg( + Arg::new(ARG_NETWORK) + .long(ARG_NETWORK) + .action(clap::ArgAction::SetTrue) + .help("Delete both peer store and secret key"), + ) + .arg( + Arg::new(ARG_NETWORK_PEER_STORE) + .long(ARG_NETWORK_PEER_STORE) + .action(clap::ArgAction::SetTrue) + .help("Delete only `data/network/peer_store`"), + ) + .arg( + Arg::new(ARG_NETWORK_SECRET_KEY) + .long(ARG_NETWORK_SECRET_KEY) + .action(clap::ArgAction::SetTrue) + .help("Delete only `data/network/secret_key`"), + ) + .arg( + Arg::new(ARG_LOGS) + .long(ARG_LOGS) + .action(clap::ArgAction::SetTrue) + .help("Delete only `data/logs`"), + ) +} + +pub(crate) fn stats() -> Command { + Command::new(CMD_STATS) + .about( + "Chain stats\n\ + Example:\n\ + ckb -C stats --from 1 --to 500", + ) + .arg( + Arg::new(ARG_FROM) + .long(ARG_FROM) + .value_parser(clap::value_parser!(u64)) + .action(clap::ArgAction::Set) + .help("Specify from block number"), + ) + .arg( + Arg::new(ARG_TO) + .long(ARG_TO) + .value_parser(clap::value_parser!(u64)) + .action(clap::ArgAction::Set) + .help("Specify to block number"), + ) +} + +fn replay() -> Command { + Command::new(CMD_REPLAY) + .about("Replay CKB process block") + .override_help(" + --tmp-target --profile 1 10,\n + --tmp-target --sanity-check,\n + ") + .arg(Arg::new(ARG_TMP_TARGET).long(ARG_TMP_TARGET).value_parser(clap::builder::PathBufValueParser::new()).action(clap::ArgAction::Set).required(true).help( + "Specify a target path. The profile command makes a temporary directory within the specified target path. This temporary directory will be automatically deleted when the command completes.", + )) + .arg(Arg::new(ARG_PROFILE).long(ARG_PROFILE).action(clap::ArgAction::SetTrue).help( + "Enable profile", + )) + .arg( + Arg::new(ARG_FROM) + .value_parser(clap::value_parser!(u64)) + .help("Specify profile from block number"), + ) + .arg( + Arg::new(ARG_TO) + .value_parser(clap::value_parser!(u64)) + .help("Specify profile to block number"), + ) + .arg( + Arg::new(ARG_SANITY_CHECK).long(ARG_SANITY_CHECK).action(clap::ArgAction::SetTrue).help("Enable sanity check") + ) + .arg( + Arg::new(ARG_FULL_VERIFICATION).long(ARG_FULL_VERIFICATION).action(clap::ArgAction::SetTrue).help("Enable sanity check") + ) + .group( + ArgGroup::new("mode") + .args([ARG_PROFILE, ARG_SANITY_CHECK]) + .required(true) + ) +} + +fn export() -> Command { + Command::new(CMD_EXPORT).about("Export CKB data").arg( + Arg::new(ARG_TARGET) + .short('t') + .long(ARG_TARGET) + .value_name("path") + .value_parser(clap::builder::PathBufValueParser::new()) + .required(true) + .help("Specify the export target path"), + ) +} + +fn import() -> Command { + Command::new(CMD_IMPORT).about("Import CKB data").arg( + Arg::new(ARG_SOURCE) + .index(1) + .value_name("path") + .value_parser(clap::builder::PathBufValueParser::new()) + .required(true) + .help("Specify the exported data path"), + ) +} + +fn migrate() -> Command { + Command::new(CMD_MIGRATE) + .about("Run CKB migration") + .arg( + Arg::new(ARG_MIGRATE_CHECK) + .long(ARG_MIGRATE_CHECK) + .action(clap::ArgAction::SetTrue) + .help( + "Perform database version check without migrating. \ + If migration is in need, ExitCode(0) is returned; \ + otherwise ExitCode(64) is returned", + ), + ) + .arg( + Arg::new(ARG_FORCE) + .long(ARG_FORCE) + .action(clap::ArgAction::SetTrue) + .conflicts_with(ARG_MIGRATE_CHECK) + .help("Migrate without interactive prompt"), + ) + .arg( + Arg::new(ARG_INCLUDE_BACKGROUND) + .long(ARG_INCLUDE_BACKGROUND) + .action(clap::ArgAction::SetTrue) + .help("Whether include background migrations"), + ) +} + +#[cfg(not(target_os = "windows"))] +fn daemon() -> Command { + Command::new(CMD_DAEMON) + .about("Runs ckb daemon command") + .arg( + Arg::new(ARG_DAEMON_CHECK) + .long(ARG_DAEMON_CHECK) + .action(clap::ArgAction::SetTrue) + .help("Check the daemon status"), + ) + .arg( + Arg::new(ARG_DAEMON_STOP) + .long(ARG_DAEMON_STOP) + .action(clap::ArgAction::SetTrue) + .conflicts_with(ARG_DAEMON_CHECK) + .help("Stop the daemon process, both the miner and the node"), + ) +} + +fn list_hashes() -> Command { + Command::new(CMD_LIST_HASHES) + .about("List well known hashes") + .arg( + Arg::new(ARG_BUNDLED) + .short('b') + .long(ARG_BUNDLED) + .action(clap::ArgAction::SetTrue) + .help( + "List hashes of the bundled chain specs, instead of the current effective ones.", + ), + ) + .arg( + Arg::new(ARG_FORMAT) + .short('f') + .long(ARG_FORMAT) + .value_parser(["json", "toml"]) + .default_value("toml") + .help("Set the format of the printed hashes"), + ) +} + +fn init() -> Command { + Command::new(CMD_INIT) + .about("Create a CKB directory or re-initialize an existing one") + .arg( + Arg::new(ARG_INTERACTIVE) + .short('i') + .long(ARG_INTERACTIVE) + .action(clap::ArgAction::SetTrue) + .help("Interactive mode"), + ) + .arg( + Arg::new(ARG_LIST_CHAINS) + .short('l') + .long(ARG_LIST_CHAINS) + .action(clap::ArgAction::SetTrue) + .help("List available options for --chain"), + ) + .arg( + Arg::new(ARG_CHAIN) + .short('c') + .long(ARG_CHAIN) + .value_parser( + AVAILABLE_SPECS + .iter() + .map(|v| v.to_string()) + .collect::>(), + ) + .default_value(DEFAULT_SPEC) + .help("Initialize CKB directory for "), + ) + .arg( + Arg::new(ARG_IMPORT_SPEC) + .long(ARG_IMPORT_SPEC) + .action(clap::ArgAction::Set) + .help( + "Use the specified file as the chain spec. Specially, \ + The dash \"-\" denotes importing the spec from stdin encoded in base64", + ), + ) + .arg( + Arg::new(ARG_LOG_TO) + .long(ARG_LOG_TO) + .value_parser(["file", "stdout", "both"]) + .default_value("both") + .help("Configure where the logs should be printed"), + ) + .arg( + Arg::new(ARG_FORCE) + .short('f') + .long(ARG_FORCE) + .action(clap::ArgAction::SetTrue) + .help("Enforce overwriting existing files"), + ) + .arg( + Arg::new(ARG_RPC_PORT) + .long(ARG_RPC_PORT) + .default_value(DEFAULT_RPC_PORT) + .help("Replace CKB RPC port in the created config file"), + ) + .arg( + Arg::new(ARG_P2P_PORT) + .long(ARG_P2P_PORT) + .default_value(DEFAULT_P2P_PORT) + .help("Replace CKB P2P port in the created config file"), + ) + .arg( + Arg::new(ARG_BA_CODE_HASH) + .long(ARG_BA_CODE_HASH) + .value_name("code_hash") + .value_parser(is_h256) + .action(clap::ArgAction::Set) + .help( + "Set code_hash in [block_assembler] \ + [default: secp256k1 if --ba-arg is present]", + ), + ) + .arg( + Arg::new(ARG_BA_ARG) + .long(ARG_BA_ARG) + .value_name("arg") + .action(clap::ArgAction::Append) + .value_parser(is_hex) + .help("Set args in [block_assembler]"), + ) + .arg( + Arg::new(ARG_BA_HASH_TYPE) + .long(ARG_BA_HASH_TYPE) + .value_name("hash_type") + .action(clap::ArgAction::Set) + .value_parser(["data", "type", "data1"]) + .default_value("type") + .help("Set hash type in [block_assembler]"), + ) + .group( + ArgGroup::new(GROUP_BA) + .args([ARG_BA_CODE_HASH, ARG_BA_ARG]) + .multiple(true), + ) + .arg( + Arg::new(ARG_BA_MESSAGE) + .long(ARG_BA_MESSAGE) + .value_name("message") + .value_parser(is_hex) + .requires(GROUP_BA) + .help("Set message in [block_assembler]"), + ) + .arg(Arg::new("export-specs").long("export-specs").hide(true)) + .arg(Arg::new("list-specs").long("list-specs").hide(true)) + .arg( + Arg::new("spec") + .short('s') + .long("spec") + .action(clap::ArgAction::Set) + .hide(true), + ) + .arg( + Arg::new(ARG_GENESIS_MESSAGE) + .long(ARG_GENESIS_MESSAGE) + .value_name(ARG_GENESIS_MESSAGE) + .action(clap::ArgAction::Set) + .help( + "Specify a string as the genesis message. \ + This only works for dev chains. \ + If no message is provided, use the current timestamp.", + ), + ) +} + +fn peer_id() -> Command { + Command::new(CMD_PEERID) + .about("About peer id, base on Secp256k1") + .subcommand_required(true) + .subcommand( + Command::new(CMD_FROM_SECRET) + .about("Generate peer id from secret file") + .arg( + Arg::new(ARG_SECRET_PATH) + .action(clap::ArgAction::Set) + .long(ARG_SECRET_PATH) + .required(true) + .help("Generate peer id from secret file path"), + ), + ) + .subcommand( + Command::new(CMD_GEN_SECRET) + .about("Generate random key to file") + .arg( + Arg::new(ARG_SECRET_PATH) + .long(ARG_SECRET_PATH) + .required(true) + .action(clap::ArgAction::Set) + .help("Generate peer id to file path"), + ), + ) +} + +fn is_hex(hex: &str) -> Result { + let tmp = hex.as_bytes(); + if tmp.len() < 2 { + Err("Must be a 0x-prefixed hexadecimal string".to_string()) + } else if tmp.len() & 1 != 0 { + Err("Hexadecimal strings must be of even length".to_string()) + } else if tmp[..2] == b"0x"[..] { + for byte in &tmp[2..] { + match byte { + b'A'..=b'F' | b'a'..=b'f' | b'0'..=b'9' => continue, + invalid_char => { + return Err(format!("Hex has invalid char: {invalid_char}")); + } + } + } + + Ok(hex.to_string()) + } else { + Err("Must 0x-prefixed hexadecimal string".to_string()) + } +} + +fn is_h256(hex: &str) -> Result { + if hex.len() != 66 { + Err("Must be 0x-prefixed hexadecimal string and string length is 66".to_owned()) + } else { + is_hex(hex) + } +} diff --git a/ckb-bin/src/lib.rs b/ckb-bin/src/lib.rs index 23c09d238b..cb62c8613b 100644 --- a/ckb-bin/src/lib.rs +++ b/ckb-bin/src/lib.rs @@ -1,16 +1,22 @@ //! CKB executable. //! //! This crate is created to reduce the link time to build CKB. +#[allow(dead_code)] +mod cli; mod helper; +mod setup; mod setup_guard; mod subcommand; -use ckb_app_config::{cli, ExitCode, Setup}; +#[cfg(test)] +mod tests; +use ckb_app_config::ExitCode; use ckb_async_runtime::new_global_runtime; use ckb_build_info::Version; use ckb_logger::{debug, info}; use ckb_network::tokio; use clap::ArgMatches; use helper::raise_fd_limit; +use setup::Setup; use setup_guard::SetupGuard; #[cfg(not(target_os = "windows"))] diff --git a/ckb-bin/src/setup.rs b/ckb-bin/src/setup.rs new file mode 100644 index 0000000000..789323d250 --- /dev/null +++ b/ckb-bin/src/setup.rs @@ -0,0 +1,476 @@ +#[cfg(not(target_os = "windows"))] +use ckb_app_config::DaemonArgs; +use ckb_app_config::{ + generate_random_key, read_secret_key, write_secret_to_file, AppConfig, CustomizeSpec, ExitCode, + ExportArgs, ImportArgs, InitArgs, MigrateArgs, MinerArgs, PeerIDArgs, ReplayArgs, + ResetDataArgs, RunArgs, StatsArgs, +}; +use ckb_chain_spec::{consensus::Consensus, ChainSpec}; +use ckb_jsonrpc_types::ScriptHashType; +use ckb_logger::info; +use ckb_types::{u256, H256, U256}; +use clap::ArgMatches; +use std::{path::PathBuf, str::FromStr}; + +use crate::cli; + +// 500_000 total difficulty +const MIN_CHAIN_WORK_500K: U256 = u256!("0x3314412053c82802a7"); + +/// A struct including all the information to start the ckb process. +pub struct Setup { + /// Subcommand name. + /// + /// For example, this is set to `run` when ckb is executed with `ckb run`. + #[cfg(feature = "with_sentry")] + pub subcommand_name: String, + /// The config file for the current subcommand. + pub config: AppConfig, + /// Whether sentry is enabled. + #[cfg(feature = "with_sentry")] + pub is_sentry_enabled: bool, +} + +impl Setup { + /// Boots the ckb process by parsing the command line arguments and loading the config file. + pub fn from_matches( + bin_name: String, + subcommand_name: &str, + matches: &ArgMatches, + ) -> Result { + let root_dir = Self::root_dir_from_matches(matches)?; + let mut config = AppConfig::load_for_subcommand(root_dir, subcommand_name)?; + config.set_bin_name(bin_name); + #[cfg(feature = "with_sentry")] + let is_sentry_enabled = is_daemon(subcommand_name) && config.sentry().is_enabled(); + + Ok(Setup { + #[cfg(feature = "with_sentry")] + subcommand_name: subcommand_name.to_string(), + config, + #[cfg(feature = "with_sentry")] + is_sentry_enabled, + }) + } + + /// Executes `ckb run`. + pub fn run(self, matches: &ArgMatches) -> Result { + let consensus = self.consensus()?; + let chain_spec_hash = self.chain_spec()?.hash; + let mut config = self.config.into_ckb()?; + + let mainnet_genesis = ckb_chain_spec::ChainSpec::load_from( + &ckb_resource::Resource::bundled("specs/mainnet.toml".to_string()), + ) + .expect("load mainnet spec fail") + .build_genesis() + .expect("build mainnet genesis fail"); + config.network.sync.min_chain_work = + if consensus.genesis_block.hash() == mainnet_genesis.hash() { + MIN_CHAIN_WORK_500K + } else { + u256!("0x0") + }; + + let arg_assume_valid_target = matches.get_one::(cli::ARG_ASSUME_VALID_TARGET); + + config.network.sync.assume_valid_target = + arg_assume_valid_target.and_then(|s| H256::from_str(&s[2..]).ok()); + if config.network.sync.assume_valid_target.is_none() { + config.network.sync.assume_valid_target = match consensus.id.as_str() { + ckb_constant::hardfork::mainnet::CHAIN_SPEC_NAME => Some( + H256::from_str(&ckb_constant::default_assume_valid_target::mainnet::DEFAULT_ASSUME_VALID_TARGET[2..]) + .expect("default assume_valid_target for mainnet must be valid"), + ), + ckb_constant::hardfork::testnet::CHAIN_SPEC_NAME => Some( + H256::from_str(&ckb_constant::default_assume_valid_target::testnet::DEFAULT_ASSUME_VALID_TARGET[2..]) + .expect("default assume_valid_target for testnet must be valid"), + ), + _ => None, + }; + } + + if let Some(ref assume_valid_target) = config.network.sync.assume_valid_target { + if assume_valid_target + == &H256::from_slice(&[0; 32]).expect("must parse Zero h256 successful") + { + info!("Disable assume valid target since assume_valid_target is zero"); + config.network.sync.assume_valid_target = None + } else { + info!("assume_valid_target set to 0x{}", assume_valid_target); + } + } + + Ok(RunArgs { + config, + consensus, + block_assembler_advanced: matches.get_flag(cli::ARG_BA_ADVANCED), + skip_chain_spec_check: matches.get_flag(cli::ARG_SKIP_CHAIN_SPEC_CHECK), + overwrite_chain_spec: matches.get_flag(cli::ARG_OVERWRITE_CHAIN_SPEC), + chain_spec_hash, + indexer: matches.get_flag(cli::ARG_INDEXER), + rich_indexer: matches.get_flag(cli::ARG_RICH_INDEXER), + #[cfg(not(target_os = "windows"))] + daemon: matches.get_flag(cli::ARG_DAEMON), + }) + } + + /// `migrate` subcommand has one `flags` arg, trigger this arg with "--check" + pub fn migrate(self, matches: &ArgMatches) -> Result { + let consensus = self.consensus()?; + let config = self.config.into_ckb()?; + let check = matches.get_flag(cli::ARG_MIGRATE_CHECK); + let force = matches.get_flag(cli::ARG_FORCE); + let include_background = matches.get_flag(cli::ARG_INCLUDE_BACKGROUND); + + Ok(MigrateArgs { + config, + consensus, + check, + force, + include_background, + }) + } + + /// Executes `ckb miner`. + pub fn miner(self, matches: &ArgMatches) -> Result { + let spec = self.chain_spec()?; + let memory_tracker = self.config.memory_tracker().to_owned(); + let config = self.config.into_miner()?; + let pow_engine = spec.pow_engine(); + let limit = *matches + .get_one::(cli::ARG_LIMIT) + .expect("has default value"); + + Ok(MinerArgs { + pow_engine, + config: config.miner, + memory_tracker, + limit, + }) + } + + /// Executes `ckb replay`. + pub fn replay(self, matches: &ArgMatches) -> Result { + let consensus = self.consensus()?; + let config = self.config.into_ckb()?; + let tmp_target = matches + .get_one::(cli::ARG_TMP_TARGET) + .ok_or_else(|| { + eprintln!("Args Error: {:?} no found", cli::ARG_TMP_TARGET); + ExitCode::Cli + })? + .clone(); + let profile = if matches.get_flag(cli::ARG_PROFILE) { + let from = matches.get_one::(cli::ARG_FROM).cloned(); + let to = matches.get_one::(cli::ARG_TO).cloned(); + Some((from, to)) + } else { + None + }; + let sanity_check = matches.get_flag(cli::ARG_SANITY_CHECK); + let full_verification = matches.get_flag(cli::ARG_FULL_VERIFICATION); + Ok(ReplayArgs { + config, + consensus, + tmp_target, + profile, + sanity_check, + full_verification, + }) + } + + /// Executes `ckb stats`. + pub fn stats(self, matches: &ArgMatches) -> Result { + let consensus = self.consensus()?; + let config = self.config.into_ckb()?; + + let from = matches.get_one::(cli::ARG_FROM).cloned(); + let to = matches.get_one::(cli::ARG_TO).cloned(); + + Ok(StatsArgs { + config, + consensus, + from, + to, + }) + } + + /// Executes `ckb import`. + pub fn import(self, matches: &ArgMatches) -> Result { + let consensus = self.consensus()?; + let config = self.config.into_ckb()?; + let source = matches + .get_one::(cli::ARG_SOURCE) + .ok_or_else(|| { + eprintln!("Args Error: {:?} no found", cli::ARG_SOURCE); + ExitCode::Cli + })? + .clone(); + + Ok(ImportArgs { + config, + consensus, + source, + }) + } + + /// Executes `ckb export`. + pub fn export(self, matches: &ArgMatches) -> Result { + let consensus = self.consensus()?; + let config = self.config.into_ckb()?; + let target = matches + .get_one::(cli::ARG_TARGET) + .ok_or_else(|| { + eprintln!("Args Error: {:?} no found", cli::ARG_TARGET); + ExitCode::Cli + })? + .clone(); + + Ok(ExportArgs { + config, + consensus, + target, + }) + } + + /// Executes `ckb daemon`. + #[cfg(not(target_os = "windows"))] + pub fn daemon(self, matches: &ArgMatches) -> Result { + let check = matches.get_flag(cli::ARG_DAEMON_CHECK); + let stop = matches.get_flag(cli::ARG_DAEMON_STOP); + let pid_file = Setup::daemon_pid_file_path(matches)?; + Ok(DaemonArgs { + check, + stop, + pid_file, + }) + } + + /// Executes `ckb init`. + pub fn init(matches: &ArgMatches) -> Result { + if matches.contains_id("list-specs") { + eprintln!( + "Deprecated: Option `--list-specs` is deprecated, use `--list-chains` instead" + ); + } + if matches.contains_id("spec") { + eprintln!("Deprecated: Option `--spec` is deprecated, use `--chain` instead"); + } + if matches.contains_id("export-specs") { + eprintln!("Deprecated: Option `--export-specs` is deprecated"); + } + + let root_dir = Self::root_dir_from_matches(matches)?; + let list_chains = + matches.get_flag(cli::ARG_LIST_CHAINS) || matches.contains_id("list-specs"); + let interactive = matches.get_flag(cli::ARG_INTERACTIVE); + let force = matches.get_flag(cli::ARG_FORCE); + let chain = if !matches.contains_id("spec") { + matches + .get_one::(cli::ARG_CHAIN) + .expect("has default value") + .to_string() + } else { + matches.get_one::("spec").unwrap().to_string() + }; + let rpc_port = matches + .get_one::(cli::ARG_RPC_PORT) + .expect("has default value") + .to_string(); + let p2p_port = matches + .get_one::(cli::ARG_P2P_PORT) + .expect("has default value") + .to_string(); + let (log_to_file, log_to_stdout) = match matches + .get_one::(cli::ARG_LOG_TO) + .map(|s| s.as_str()) + { + Some("file") => (true, false), + Some("stdout") => (false, true), + Some("both") => (true, true), + _ => unreachable!(), + }; + + let block_assembler_code_hash = matches.get_one::(cli::ARG_BA_CODE_HASH).cloned(); + let block_assembler_args: Vec<_> = matches + .get_many::(cli::ARG_BA_ARG) + .unwrap_or_default() + .map(|a| a.to_owned()) + .collect(); + let block_assembler_hash_type = matches + .get_one::(cli::ARG_BA_HASH_TYPE) + .and_then(|hash_type| serde_plain::from_str::(hash_type).ok()) + .expect("has default value"); + let block_assembler_message = matches.get_one::(cli::ARG_BA_MESSAGE).cloned(); + + let import_spec = matches.get_one::(cli::ARG_IMPORT_SPEC).cloned(); + + let customize_spec = { + let genesis_message = matches.get_one::(cli::ARG_GENESIS_MESSAGE).cloned(); + CustomizeSpec { genesis_message } + }; + + Ok(InitArgs { + interactive, + root_dir, + chain, + rpc_port, + p2p_port, + list_chains, + force, + log_to_file, + log_to_stdout, + block_assembler_code_hash, + block_assembler_args, + block_assembler_hash_type, + block_assembler_message, + import_spec, + customize_spec, + }) + } + + /// Executes `ckb reset-data`. + pub fn reset_data(self, matches: &ArgMatches) -> Result { + let config = self.config.into_ckb()?; + let data_dir = config.data_dir; + let db_path = config.db.path; + let indexer_path = config.indexer.store; + let rich_indexer_path = config + .indexer + .rich_indexer + .store + .parent() + .expect("rich-indexer store path should have parent dir") + .to_path_buf(); + let network_config = config.network; + let network_dir = network_config.path.clone(); + let network_peer_store_path = network_config.peer_store_path(); + let network_secret_key_path = network_config.secret_key_path(); + let logs_dir = Some(config.logger.log_dir); + + let force = matches.get_flag(cli::ARG_FORCE); + let all = matches.get_flag(cli::ARG_ALL); + let database = matches.get_flag(cli::ARG_DATABASE); + let indexer = matches.get_flag(cli::ARG_INDEXER); + let rich_indexer = matches.get_flag(cli::ARG_RICH_INDEXER); + let network = matches.get_flag(cli::ARG_NETWORK); + let network_peer_store = matches.get_flag(cli::ARG_NETWORK_PEER_STORE); + let network_secret_key = matches.get_flag(cli::ARG_NETWORK_SECRET_KEY); + let logs = matches.get_flag(cli::ARG_LOGS); + + Ok(ResetDataArgs { + force, + all, + database, + indexer, + rich_indexer, + network, + network_peer_store, + network_secret_key, + logs, + data_dir, + db_path, + indexer_path, + rich_indexer_path, + network_dir, + network_peer_store_path, + network_secret_key_path, + logs_dir, + }) + } + + /// Resolves the root directory for ckb from the command line arguments. + pub fn root_dir_from_matches(matches: &ArgMatches) -> Result { + let config_dir = match matches.get_one::(cli::ARG_CONFIG_DIR) { + Some(arg_config_dir) => PathBuf::from(arg_config_dir), + None => ::std::env::current_dir()?, + }; + std::fs::create_dir_all(&config_dir)?; + Ok(config_dir) + } + + /// Resolves the pid file path for ckb from the command line arguments. + #[cfg(not(target_os = "windows"))] + pub fn daemon_pid_file_path(matches: &ArgMatches) -> Result { + let root_dir = Self::root_dir_from_matches(matches)?; + Ok(root_dir.join("data/daemon/ckb-run.pid")) + } + + /// Loads the chain spec. + #[cfg(feature = "with_sentry")] + fn chain_spec(&self) -> Result { + let result = self.config.chain_spec(); + if let Ok(spec) = &result { + if self.is_sentry_enabled { + sentry::configure_scope(|scope| { + scope.set_tag("spec.name", &spec.name); + scope.set_tag("spec.pow", &spec.pow); + }); + } + } + + result + } + + #[cfg(not(feature = "with_sentry"))] + fn chain_spec(&self) -> Result { + self.config.chain_spec() + } + + /// Gets the consensus. + #[cfg(feature = "with_sentry")] + pub fn consensus(&self) -> Result { + let result = consensus_from_spec(&self.chain_spec()?); + + if let Ok(consensus) = &result { + if self.is_sentry_enabled { + sentry::configure_scope(|scope| { + scope.set_tag("genesis", consensus.genesis_hash()); + }); + } + } + + result + } + + /// Gets the consensus. + #[cfg(not(feature = "with_sentry"))] + pub fn consensus(&self) -> Result { + consensus_from_spec(&self.chain_spec()?) + } + + /// Gets the network peer id by reading the network secret key. + pub fn peer_id(matches: &ArgMatches) -> Result { + let path = matches + .get_one::(cli::ARG_SECRET_PATH) + .expect("required on command line"); + match read_secret_key(path.into()) { + Ok(Some(key)) => Ok(PeerIDArgs { + peer_id: key.peer_id(), + }), + Err(_) => Err(ExitCode::Failure), + Ok(None) => Err(ExitCode::IO), + } + } + + /// Generates the network secret key. + pub fn gen(matches: &ArgMatches) -> Result<(), ExitCode> { + let path = matches + .get_one::(cli::ARG_SECRET_PATH) + .expect("required on command line"); + write_secret_to_file(&generate_random_key(), path.into()).map_err(|_| ExitCode::IO) + } +} + +#[cfg(feature = "with_sentry")] +fn is_daemon(subcommand_name: &str) -> bool { + matches!(subcommand_name, cli::CMD_RUN | cli::CMD_MINER) +} + +fn consensus_from_spec(spec: &ChainSpec) -> Result { + spec.build_consensus().map_err(|err| { + eprintln!("chainspec error: {err}"); + ExitCode::Config + }) +} diff --git a/ckb-bin/src/setup_guard.rs b/ckb-bin/src/setup_guard.rs index 9fbe80de5a..c02d050e23 100644 --- a/ckb-bin/src/setup_guard.rs +++ b/ckb-bin/src/setup_guard.rs @@ -1,9 +1,11 @@ -use ckb_app_config::{ExitCode, Setup}; +use ckb_app_config::ExitCode; use ckb_async_runtime::Handle; use ckb_build_info::Version; use ckb_logger_service::{self, LoggerInitGuard}; use ckb_metrics_service::{self, Guard as MetricsInitGuard}; +use crate::setup::Setup; + const CKB_LOG_ENV: &str = "CKB_LOG"; pub struct SetupGuard { diff --git a/ckb-bin/src/subcommand/init.rs b/ckb-bin/src/subcommand/init.rs index 36a076c91d..063defdc3f 100644 --- a/ckb-bin/src/subcommand/init.rs +++ b/ckb-bin/src/subcommand/init.rs @@ -3,7 +3,7 @@ use std::io::{self, Read}; use crate::helper::prompt; use base64::Engine; -use ckb_app_config::{cli, AppConfig, ExitCode, InitArgs}; +use ckb_app_config::{AppConfig, ExitCode, InitArgs}; use ckb_chain_spec::ChainSpec; use ckb_jsonrpc_types::ScriptHashType; use ckb_resource::{ @@ -12,6 +12,8 @@ use ckb_resource::{ }; use ckb_types::H256; +use crate::cli; + const DEFAULT_LOCK_SCRIPT_HASH_TYPE: &str = "type"; const SECP256K1_BLAKE160_SIGHASH_ALL_ARG_LEN: usize = 20 * 2 + 2; // 42 = 20 x 2 + prefix 0x diff --git a/ckb-bin/src/subcommand/list_hashes.rs b/ckb-bin/src/subcommand/list_hashes.rs index cdff023aad..93288d3b0f 100644 --- a/ckb-bin/src/subcommand/list_hashes.rs +++ b/ckb-bin/src/subcommand/list_hashes.rs @@ -1,4 +1,4 @@ -use ckb_app_config::{cli, CKBAppConfig, ExitCode}; +use ckb_app_config::{CKBAppConfig, ExitCode}; use ckb_chain_spec::ChainSpec; use ckb_resource::{Resource, AVAILABLE_SPECS}; use ckb_types::{packed::CellOutput, H256}; @@ -7,6 +7,8 @@ use clap::ArgMatches; use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use crate::cli; + #[derive(Clone, Debug, Serialize, Deserialize)] struct SystemCell { pub path: String, diff --git a/util/app-config/src/tests/bats_tests/ckb_run_replay.bats b/ckb-bin/src/tests/bats_tests/ckb_run_replay.bats similarity index 100% rename from util/app-config/src/tests/bats_tests/ckb_run_replay.bats rename to ckb-bin/src/tests/bats_tests/ckb_run_replay.bats diff --git a/util/app-config/src/tests/bats_tests/cli.bats b/ckb-bin/src/tests/bats_tests/cli.bats similarity index 100% rename from util/app-config/src/tests/bats_tests/cli.bats rename to ckb-bin/src/tests/bats_tests/cli.bats diff --git a/util/app-config/src/tests/bats_tests/cli_test.sh b/ckb-bin/src/tests/bats_tests/cli_test.sh similarity index 92% rename from util/app-config/src/tests/bats_tests/cli_test.sh rename to ckb-bin/src/tests/bats_tests/cli_test.sh index bd5fc1318c..d25f3c8cfc 100755 --- a/util/app-config/src/tests/bats_tests/cli_test.sh +++ b/ckb-bin/src/tests/bats_tests/cli_test.sh @@ -31,9 +31,9 @@ git_clone_repo_with_retry() { trap cleanup EXIT cp target/prod/ckb ${CKB_BATS_TESTBED} -cp util/app-config/src/tests/bats_tests/*.bats ${CKB_BATS_TESTBED} -cp -r util/app-config/src/tests/bats_tests/later_bats_job ${CKB_BATS_TESTBED} -cp util/app-config/src/tests/bats_tests/*.sh ${CKB_BATS_TESTBED} +cp ckb-bin/src/tests/bats_tests/*.bats ${CKB_BATS_TESTBED} +cp -r ckb-bin/src/tests/bats_tests/later_bats_job ${CKB_BATS_TESTBED} +cp ckb-bin/src/tests/bats_tests/*.sh ${CKB_BATS_TESTBED} if [ ! -d "/tmp/ckb_bats_assets/" ]; then git_clone_repo_with_retry "main" "https://github.com/nervosnetwork/ckb-assets" "/tmp/ckb_bats_assets" diff --git a/util/app-config/src/tests/bats_tests/export_import.bats b/ckb-bin/src/tests/bats_tests/export_import.bats similarity index 100% rename from util/app-config/src/tests/bats_tests/export_import.bats rename to ckb-bin/src/tests/bats_tests/export_import.bats diff --git a/util/app-config/src/tests/bats_tests/graceful_shutdown.bats b/ckb-bin/src/tests/bats_tests/graceful_shutdown.bats similarity index 100% rename from util/app-config/src/tests/bats_tests/graceful_shutdown.bats rename to ckb-bin/src/tests/bats_tests/graceful_shutdown.bats diff --git a/util/app-config/src/tests/bats_tests/init_reset.bats b/ckb-bin/src/tests/bats_tests/init_reset.bats similarity index 100% rename from util/app-config/src/tests/bats_tests/init_reset.bats rename to ckb-bin/src/tests/bats_tests/init_reset.bats diff --git a/util/app-config/src/tests/bats_tests/later_bats_job/change_epoch.bats b/ckb-bin/src/tests/bats_tests/later_bats_job/change_epoch.bats similarity index 100% rename from util/app-config/src/tests/bats_tests/later_bats_job/change_epoch.bats rename to ckb-bin/src/tests/bats_tests/later_bats_job/change_epoch.bats diff --git a/util/app-config/src/tests/bats_tests/load_notify_config.bats b/ckb-bin/src/tests/bats_tests/load_notify_config.bats similarity index 100% rename from util/app-config/src/tests/bats_tests/load_notify_config.bats rename to ckb-bin/src/tests/bats_tests/load_notify_config.bats diff --git a/util/app-config/src/tests/bats_tests/peer_id.bats b/ckb-bin/src/tests/bats_tests/peer_id.bats similarity index 100% rename from util/app-config/src/tests/bats_tests/peer_id.bats rename to ckb-bin/src/tests/bats_tests/peer_id.bats diff --git a/util/app-config/src/tests/cli.rs b/ckb-bin/src/tests/cli.rs similarity index 100% rename from util/app-config/src/tests/cli.rs rename to ckb-bin/src/tests/cli.rs diff --git a/ckb-bin/src/tests/mod.rs b/ckb-bin/src/tests/mod.rs new file mode 100644 index 0000000000..26710c101c --- /dev/null +++ b/ckb-bin/src/tests/mod.rs @@ -0,0 +1 @@ +mod cli; diff --git a/util/app-config/Cargo.toml b/util/app-config/Cargo.toml index bbffeca527..bf57c112eb 100644 --- a/util/app-config/Cargo.toml +++ b/util/app-config/Cargo.toml @@ -9,9 +9,7 @@ homepage = "https://github.com/nervosnetwork/ckb" repository = "https://github.com/nervosnetwork/ckb" [dependencies] -clap = { version = "=4.4", features = ["string", "wrap_help"] } serde = { version = "1.0", features = ["derive"] } -serde_plain = "0.3.0" serde_json = "1.0" toml = "0.5" path-clean = "0.1.0" @@ -24,7 +22,6 @@ ckb-pow = { path = "../../pow", version = "= 0.119.0-pre" } ckb-resource = { path = "../../resource", version = "= 0.119.0-pre" } ckb-build-info = { path = "../build-info", version = "= 0.119.0-pre" } ckb-types = { path = "../types", version = "= 0.119.0-pre" } -ckb-constant = { path = "../constant", version = "= 0.119.0-pre" } secio = { version = "0.6", package = "tentacle-secio" } multiaddr = { version = "0.3.0", package = "tentacle-multiaddr" } rand = "0.8" diff --git a/util/app-config/src/cli.rs b/util/app-config/src/cli.rs index 4a31dd93a8..59df6d15a0 100644 --- a/util/app-config/src/cli.rs +++ b/util/app-config/src/cli.rs @@ -1,658 +1,10 @@ //! CKB command line arguments parser. -use ckb_build_info::Version; -use ckb_resource::{AVAILABLE_SPECS, DEFAULT_P2P_PORT, DEFAULT_RPC_PORT, DEFAULT_SPEC}; -use clap::{Arg, ArgGroup, ArgMatches, Command}; /// binary file name(ckb) pub const BIN_NAME: &str = "ckb"; - -/// Subcommand `run`. -pub const CMD_RUN: &str = "run"; /// Subcommand `miner`. pub const CMD_MINER: &str = "miner"; -/// Subcommand `export`. -pub const CMD_EXPORT: &str = "export"; -/// Subcommand `import`. -pub const CMD_IMPORT: &str = "import"; -/// Subcommand `init`. -pub const CMD_INIT: &str = "init"; -/// Subcommand `replay`. -pub const CMD_REPLAY: &str = "replay"; -/// Subcommand `stats`. -pub const CMD_STATS: &str = "stats"; -/// Subcommand `list-hashes`. -pub const CMD_LIST_HASHES: &str = "list-hashes"; /// Subcommand `reset-data`. pub const CMD_RESET_DATA: &str = "reset-data"; -/// Subcommand `peer-id`. -pub const CMD_PEERID: &str = "peer-id"; -/// Subcommand `gen`. -pub const CMD_GEN_SECRET: &str = "gen"; -/// Subcommand `from-secret`. -pub const CMD_FROM_SECRET: &str = "from-secret"; -/// Subcommand `migrate`. -pub const CMD_MIGRATE: &str = "migrate"; -/// Subcommand `daemon` -pub const CMD_DAEMON: &str = "daemon"; -/// Command line argument `--config-dir`. -pub const ARG_CONFIG_DIR: &str = "config-dir"; -/// Command line argument `--format`. -pub const ARG_FORMAT: &str = "format"; -/// Command line argument `--target`. -pub const ARG_TARGET: &str = "target"; -/// Command line argument `--source`. -pub const ARG_SOURCE: &str = "source"; -/// Command line argument `--data`. -pub const ARG_DATA: &str = "data"; -/// Command line argument `--list-chains`. -pub const ARG_LIST_CHAINS: &str = "list-chains"; -/// Command line argument `--interactive`. -pub const ARG_INTERACTIVE: &str = "interactive"; -/// Command line argument `--chain`. -pub const ARG_CHAIN: &str = "chain"; -/// Command line argument `--import-spec`. -pub const ARG_IMPORT_SPEC: &str = "import-spec"; -/// The argument for the genesis message. -pub const ARG_GENESIS_MESSAGE: &str = "genesis-message"; -/// Command line argument `--p2p-port`. -pub const ARG_P2P_PORT: &str = "p2p-port"; -/// Command line argument `--rpc-port`. -pub const ARG_RPC_PORT: &str = "rpc-port"; -/// Command line argument `--force`. -pub const ARG_FORCE: &str = "force"; -/// Command line argument `--include-background`. -pub const ARG_INCLUDE_BACKGROUND: &str = "include-background"; -/// Command line argument `--log-to`. -pub const ARG_LOG_TO: &str = "log-to"; -/// Command line argument `--bundled`. -pub const ARG_BUNDLED: &str = "bundled"; -/// Command line argument `--ba-code-hash`. -pub const ARG_BA_CODE_HASH: &str = "ba-code-hash"; -/// Command line argument `--ba-arg`. -pub const ARG_BA_ARG: &str = "ba-arg"; -/// Command line argument `--ba-hash-type`. -pub const ARG_BA_HASH_TYPE: &str = "ba-hash-type"; -/// Command line argument `--ba-message`. -pub const ARG_BA_MESSAGE: &str = "ba-message"; -/// Command line argument `--ba-advanced`. -pub const ARG_BA_ADVANCED: &str = "ba-advanced"; -/// Command line argument `--daemon` -pub const ARG_DAEMON: &str = "daemon"; -/// Command line argument `--indexer`. -pub const ARG_INDEXER: &str = "indexer"; -/// Command line argument `--rich-indexer`. -pub const ARG_RICH_INDEXER: &str = "rich-indexer"; -/// Command line argument `--from`. -pub const ARG_FROM: &str = "from"; -/// Command line argument `--to`. -pub const ARG_TO: &str = "to"; -/// Command line argument `--all`. -pub const ARG_ALL: &str = "all"; -/// Command line argument `--limit`. -pub const ARG_LIMIT: &str = "limit"; -/// Command line argument `--database`. -pub const ARG_DATABASE: &str = "database"; -/// Command line argument `--network`. -pub const ARG_NETWORK: &str = "network"; -/// Command line argument `--network-peer-store`. -pub const ARG_NETWORK_PEER_STORE: &str = "network-peer-store"; -/// Command line argument `--network-secret-key`. -pub const ARG_NETWORK_SECRET_KEY: &str = "network-secret-key"; -/// Command line argument `--logs`. -pub const ARG_LOGS: &str = "logs"; -/// Command line argument `--tmp-target`. -pub const ARG_TMP_TARGET: &str = "tmp-target"; -/// Command line argument `--secret-path`. -pub const ARG_SECRET_PATH: &str = "secret-path"; -/// Command line argument `--profile`. -pub const ARG_PROFILE: &str = "profile"; -/// Command line argument `--sanity-check`. -pub const ARG_SANITY_CHECK: &str = "sanity-check"; -/// Command line argument `--full-verification`. -pub const ARG_FULL_VERIFICATION: &str = "full-verification"; -/// Command line argument `--skip-spec-check`. -pub const ARG_SKIP_CHAIN_SPEC_CHECK: &str = "skip-spec-check"; -/// Present `overwrite-spec` arg to force overriding the chain spec in the database with the present configured chain spec -pub const ARG_OVERWRITE_CHAIN_SPEC: &str = "overwrite-spec"; -/// Command line argument `--assume-valid-target`. -pub const ARG_ASSUME_VALID_TARGET: &str = "assume-valid-target"; -/// Command line argument `--check`. -pub const ARG_MIGRATE_CHECK: &str = "check"; -/// Command line argument `daemon --check` -pub const ARG_DAEMON_CHECK: &str = "check"; -/// Command line argument `daemon --stop` -pub const ARG_DAEMON_STOP: &str = "stop"; - -/// Command line arguments group `ba` for block assembler. -const GROUP_BA: &str = "ba"; - -/// return root clap Command -pub fn basic_app() -> Command { - let command = Command::new(BIN_NAME) - .author("Nervos Core Dev ") - .about("Nervos CKB - The Common Knowledge Base") - .subcommand_required(true) - .arg_required_else_help(true) - .term_width(110) - .arg( - Arg::new(ARG_CONFIG_DIR) - .global(true) - .short('C') - .value_name("path") - .action(clap::ArgAction::Set) - .help( - "Run as if CKB was started in , instead of the current working directory.", - ), - ) - .subcommand(run()) - .subcommand(miner()) - .subcommand(export()) - .subcommand(import()) - .subcommand(list_hashes()) - .subcommand(init()) - .subcommand(replay()) - .subcommand(stats()) - .subcommand(reset_data()) - .subcommand(peer_id()) - .subcommand(migrate()); - - #[cfg(not(target_os = "windows"))] - let command = command.subcommand(daemon()); - - command -} - -/// Parse the command line arguments by supplying the version information. -/// -/// The version is used to generate the help message and output for `--version`. -pub fn get_bin_name_and_matches(version: &Version) -> (String, ArgMatches) { - let bin_name = std::env::args() - .next() - .unwrap_or_else(|| BIN_NAME.to_owned()); - let matches = basic_app() - .version(version.short()) - .long_version(version.long()) - .get_matches(); - (bin_name, matches) -} - -fn run() -> Command { - let command = Command::new(CMD_RUN) - .about("Run CKB node") - .arg( - Arg::new(ARG_BA_ADVANCED) - .long(ARG_BA_ADVANCED) - .action(clap::ArgAction::SetTrue) - .help("Allow any block assembler code hash and args"), - ) - .arg( - Arg::new(ARG_SKIP_CHAIN_SPEC_CHECK) - .long(ARG_SKIP_CHAIN_SPEC_CHECK) - .action(clap::ArgAction::SetTrue) - .help("Skip checking the chain spec with the hash stored in the database"), - ).arg( - Arg::new(ARG_OVERWRITE_CHAIN_SPEC) - .long(ARG_OVERWRITE_CHAIN_SPEC) - .action(clap::ArgAction::SetTrue) - .help("Overwrite the chain spec in the database with the present configured chain spec") - ).arg( - Arg::new(ARG_ASSUME_VALID_TARGET) - .long(ARG_ASSUME_VALID_TARGET) - .action(clap::ArgAction::Set) - .value_parser(is_h256) - .help(format!("This parameter specifies the hash of a block. \ -When the height does not reach this block's height, script execution will be disabled, \ -meaning it will skip the verification of the script content. \n\n\ -Please note that when this option is enabled, the header will be synchronized to \ -the highest block currently found. During this period, if the assume valid target is found, \ -the block download starts; \ -If the assume valid target is either absent or has a timestamp within 24 hours of the current time, \ -the target considered invalid, and the block download proceeds with full verification. \n\n\n\ -default(MainNet): {}\n -default(TestNet): {}\n\n -You can explicitly set the value to 0x0000000000000000000000000000000000000000000000000000000000000000 \ -to disable the default behavior and execute full verification for all blocks, \ -", - ckb_constant::default_assume_valid_target::mainnet::DEFAULT_ASSUME_VALID_TARGET, - ckb_constant::default_assume_valid_target::testnet::DEFAULT_ASSUME_VALID_TARGET)) - ).arg( - Arg::new(ARG_INDEXER) - .long(ARG_INDEXER) - .action(clap::ArgAction::SetTrue) - .help("Start the built-in indexer service"), - ) - .arg( - Arg::new(ARG_RICH_INDEXER) - .long(ARG_RICH_INDEXER) - .action(clap::ArgAction::SetTrue) - .help("Start the built-in rich-indexer service"), - ); - - #[cfg(not(target_os = "windows"))] - let command = command.arg( - Arg::new(ARG_DAEMON) - .long(ARG_DAEMON) - .action(clap::ArgAction::SetTrue) - .help( - "Starts ckb as a daemon, \ - which will run in the background and output logs to the specified log file", - ), - ); - command -} - -fn miner() -> Command { - Command::new(CMD_MINER).about("Runs ckb miner").arg( - Arg::new(ARG_LIMIT) - .short('l') - .long(ARG_LIMIT) - .action(clap::ArgAction::Set) - .value_parser(clap::value_parser!(u128)) - .default_value("0") - .help( - "Exit after finding this specific number of nonces; \ - 0 means the miner will never exit. [default: 0]", - ), - ) -} - -fn reset_data() -> Command { - Command::new(CMD_RESET_DATA) - .about( - "Truncate the database directory\n\ - Example:\n\ - ckb reset-data --force --database", - ) - .arg( - Arg::new(ARG_FORCE) - .short('f') - .long(ARG_FORCE) - .action(clap::ArgAction::SetTrue) - .help("Delete data without interactive prompt"), - ) - .arg( - Arg::new(ARG_ALL) - .long(ARG_ALL) - .action(clap::ArgAction::SetTrue) - .help("Delete the whole data directory"), - ) - .arg( - Arg::new(ARG_DATABASE) - .long(ARG_DATABASE) - .action(clap::ArgAction::SetTrue) - .help("Delete only `data/db`"), - ) - .arg( - Arg::new(ARG_INDEXER) - .long(ARG_INDEXER) - .action(clap::ArgAction::SetTrue) - .help("Delete only `data/indexer/store`"), - ) - .arg( - Arg::new(ARG_RICH_INDEXER) - .long(ARG_RICH_INDEXER) - .action(clap::ArgAction::SetTrue) - .help("Delete only `data/indexer/sqlite`"), - ) - .arg( - Arg::new(ARG_NETWORK) - .long(ARG_NETWORK) - .action(clap::ArgAction::SetTrue) - .help("Delete both peer store and secret key"), - ) - .arg( - Arg::new(ARG_NETWORK_PEER_STORE) - .long(ARG_NETWORK_PEER_STORE) - .action(clap::ArgAction::SetTrue) - .help("Delete only `data/network/peer_store`"), - ) - .arg( - Arg::new(ARG_NETWORK_SECRET_KEY) - .long(ARG_NETWORK_SECRET_KEY) - .action(clap::ArgAction::SetTrue) - .help("Delete only `data/network/secret_key`"), - ) - .arg( - Arg::new(ARG_LOGS) - .long(ARG_LOGS) - .action(clap::ArgAction::SetTrue) - .help("Delete only `data/logs`"), - ) -} - -pub(crate) fn stats() -> Command { - Command::new(CMD_STATS) - .about( - "Chain stats\n\ - Example:\n\ - ckb -C stats --from 1 --to 500", - ) - .arg( - Arg::new(ARG_FROM) - .long(ARG_FROM) - .value_parser(clap::value_parser!(u64)) - .action(clap::ArgAction::Set) - .help("Specify from block number"), - ) - .arg( - Arg::new(ARG_TO) - .long(ARG_TO) - .value_parser(clap::value_parser!(u64)) - .action(clap::ArgAction::Set) - .help("Specify to block number"), - ) -} - -fn replay() -> Command { - Command::new(CMD_REPLAY) - .about("Replay CKB process block") - .override_help(" - --tmp-target --profile 1 10,\n - --tmp-target --sanity-check,\n - ") - .arg(Arg::new(ARG_TMP_TARGET).long(ARG_TMP_TARGET).value_parser(clap::builder::PathBufValueParser::new()).action(clap::ArgAction::Set).required(true).help( - "Specify a target path. The profile command makes a temporary directory within the specified target path. This temporary directory will be automatically deleted when the command completes.", - )) - .arg(Arg::new(ARG_PROFILE).long(ARG_PROFILE).action(clap::ArgAction::SetTrue).help( - "Enable profile", - )) - .arg( - Arg::new(ARG_FROM) - .value_parser(clap::value_parser!(u64)) - .help("Specify profile from block number"), - ) - .arg( - Arg::new(ARG_TO) - .value_parser(clap::value_parser!(u64)) - .help("Specify profile to block number"), - ) - .arg( - Arg::new(ARG_SANITY_CHECK).long(ARG_SANITY_CHECK).action(clap::ArgAction::SetTrue).help("Enable sanity check") - ) - .arg( - Arg::new(ARG_FULL_VERIFICATION).long(ARG_FULL_VERIFICATION).action(clap::ArgAction::SetTrue).help("Enable sanity check") - ) - .group( - ArgGroup::new("mode") - .args([ARG_PROFILE, ARG_SANITY_CHECK]) - .required(true) - ) -} - -fn export() -> Command { - Command::new(CMD_EXPORT).about("Export CKB data").arg( - Arg::new(ARG_TARGET) - .short('t') - .long(ARG_TARGET) - .value_name("path") - .value_parser(clap::builder::PathBufValueParser::new()) - .required(true) - .help("Specify the export target path"), - ) -} - -fn import() -> Command { - Command::new(CMD_IMPORT).about("Import CKB data").arg( - Arg::new(ARG_SOURCE) - .index(1) - .value_name("path") - .value_parser(clap::builder::PathBufValueParser::new()) - .required(true) - .help("Specify the exported data path"), - ) -} - -fn migrate() -> Command { - Command::new(CMD_MIGRATE) - .about("Run CKB migration") - .arg( - Arg::new(ARG_MIGRATE_CHECK) - .long(ARG_MIGRATE_CHECK) - .action(clap::ArgAction::SetTrue) - .help( - "Perform database version check without migrating. \ - If migration is in need, ExitCode(0) is returned; \ - otherwise ExitCode(64) is returned", - ), - ) - .arg( - Arg::new(ARG_FORCE) - .long(ARG_FORCE) - .action(clap::ArgAction::SetTrue) - .conflicts_with(ARG_MIGRATE_CHECK) - .help("Migrate without interactive prompt"), - ) - .arg( - Arg::new(ARG_INCLUDE_BACKGROUND) - .long(ARG_INCLUDE_BACKGROUND) - .action(clap::ArgAction::SetTrue) - .help("Whether include background migrations"), - ) -} - -#[cfg(not(target_os = "windows"))] -fn daemon() -> Command { - Command::new(CMD_DAEMON) - .about("Runs ckb daemon command") - .arg( - Arg::new(ARG_DAEMON_CHECK) - .long(ARG_DAEMON_CHECK) - .action(clap::ArgAction::SetTrue) - .help("Check the daemon status"), - ) - .arg( - Arg::new(ARG_DAEMON_STOP) - .long(ARG_DAEMON_STOP) - .action(clap::ArgAction::SetTrue) - .conflicts_with(ARG_DAEMON_CHECK) - .help("Stop the daemon process, both the miner and the node"), - ) -} - -fn list_hashes() -> Command { - Command::new(CMD_LIST_HASHES) - .about("List well known hashes") - .arg( - Arg::new(ARG_BUNDLED) - .short('b') - .long(ARG_BUNDLED) - .action(clap::ArgAction::SetTrue) - .help( - "List hashes of the bundled chain specs, instead of the current effective ones.", - ), - ) - .arg( - Arg::new(ARG_FORMAT) - .short('f') - .long(ARG_FORMAT) - .value_parser(["json", "toml"]) - .default_value("toml") - .help("Set the format of the printed hashes"), - ) -} - -fn init() -> Command { - Command::new(CMD_INIT) - .about("Create a CKB directory or re-initialize an existing one") - .arg( - Arg::new(ARG_INTERACTIVE) - .short('i') - .long(ARG_INTERACTIVE) - .action(clap::ArgAction::SetTrue) - .help("Interactive mode"), - ) - .arg( - Arg::new(ARG_LIST_CHAINS) - .short('l') - .long(ARG_LIST_CHAINS) - .action(clap::ArgAction::SetTrue) - .help("List available options for --chain"), - ) - .arg( - Arg::new(ARG_CHAIN) - .short('c') - .long(ARG_CHAIN) - .value_parser( - AVAILABLE_SPECS - .iter() - .map(|v| v.to_string()) - .collect::>(), - ) - .default_value(DEFAULT_SPEC) - .help("Initialize CKB directory for "), - ) - .arg( - Arg::new(ARG_IMPORT_SPEC) - .long(ARG_IMPORT_SPEC) - .action(clap::ArgAction::Set) - .help( - "Use the specified file as the chain spec. Specially, \ - The dash \"-\" denotes importing the spec from stdin encoded in base64", - ), - ) - .arg( - Arg::new(ARG_LOG_TO) - .long(ARG_LOG_TO) - .value_parser(["file", "stdout", "both"]) - .default_value("both") - .help("Configure where the logs should be printed"), - ) - .arg( - Arg::new(ARG_FORCE) - .short('f') - .long(ARG_FORCE) - .action(clap::ArgAction::SetTrue) - .help("Enforce overwriting existing files"), - ) - .arg( - Arg::new(ARG_RPC_PORT) - .long(ARG_RPC_PORT) - .default_value(DEFAULT_RPC_PORT) - .help("Replace CKB RPC port in the created config file"), - ) - .arg( - Arg::new(ARG_P2P_PORT) - .long(ARG_P2P_PORT) - .default_value(DEFAULT_P2P_PORT) - .help("Replace CKB P2P port in the created config file"), - ) - .arg( - Arg::new(ARG_BA_CODE_HASH) - .long(ARG_BA_CODE_HASH) - .value_name("code_hash") - .value_parser(is_h256) - .action(clap::ArgAction::Set) - .help( - "Set code_hash in [block_assembler] \ - [default: secp256k1 if --ba-arg is present]", - ), - ) - .arg( - Arg::new(ARG_BA_ARG) - .long(ARG_BA_ARG) - .value_name("arg") - .action(clap::ArgAction::Append) - .value_parser(is_hex) - .help("Set args in [block_assembler]"), - ) - .arg( - Arg::new(ARG_BA_HASH_TYPE) - .long(ARG_BA_HASH_TYPE) - .value_name("hash_type") - .action(clap::ArgAction::Set) - .value_parser(["data", "type", "data1"]) - .default_value("type") - .help("Set hash type in [block_assembler]"), - ) - .group( - ArgGroup::new(GROUP_BA) - .args([ARG_BA_CODE_HASH, ARG_BA_ARG]) - .multiple(true), - ) - .arg( - Arg::new(ARG_BA_MESSAGE) - .long(ARG_BA_MESSAGE) - .value_name("message") - .value_parser(is_hex) - .requires(GROUP_BA) - .help("Set message in [block_assembler]"), - ) - .arg(Arg::new("export-specs").long("export-specs").hide(true)) - .arg(Arg::new("list-specs").long("list-specs").hide(true)) - .arg( - Arg::new("spec") - .short('s') - .long("spec") - .action(clap::ArgAction::Set) - .hide(true), - ) - .arg( - Arg::new(ARG_GENESIS_MESSAGE) - .long(ARG_GENESIS_MESSAGE) - .value_name(ARG_GENESIS_MESSAGE) - .action(clap::ArgAction::Set) - .help( - "Specify a string as the genesis message. \ - This only works for dev chains. \ - If no message is provided, use the current timestamp.", - ), - ) -} - -fn peer_id() -> Command { - Command::new(CMD_PEERID) - .about("About peer id, base on Secp256k1") - .subcommand_required(true) - .subcommand( - Command::new(CMD_FROM_SECRET) - .about("Generate peer id from secret file") - .arg( - Arg::new(ARG_SECRET_PATH) - .action(clap::ArgAction::Set) - .long(ARG_SECRET_PATH) - .required(true) - .help("Generate peer id from secret file path"), - ), - ) - .subcommand( - Command::new(CMD_GEN_SECRET) - .about("Generate random key to file") - .arg( - Arg::new(ARG_SECRET_PATH) - .long(ARG_SECRET_PATH) - .required(true) - .action(clap::ArgAction::Set) - .help("Generate peer id to file path"), - ), - ) -} - -fn is_hex(hex: &str) -> Result { - let tmp = hex.as_bytes(); - if tmp.len() < 2 { - Err("Must be a 0x-prefixed hexadecimal string".to_string()) - } else if tmp.len() & 1 != 0 { - Err("Hexadecimal strings must be of even length".to_string()) - } else if tmp[..2] == b"0x"[..] { - for byte in &tmp[2..] { - match byte { - b'A'..=b'F' | b'a'..=b'f' | b'0'..=b'9' => continue, - invalid_char => { - return Err(format!("Hex has invalid char: {invalid_char}")); - } - } - } - - Ok(hex.to_string()) - } else { - Err("Must 0x-prefixed hexadecimal string".to_string()) - } -} - -fn is_h256(hex: &str) -> Result { - if hex.len() != 66 { - Err("Must be 0x-prefixed hexadecimal string and string length is 66".to_owned()) - } else { - is_hex(hex) - } -} +/// Subcommand `run`. +pub const CMD_RUN: &str = "run"; diff --git a/util/app-config/src/configs/mod.rs b/util/app-config/src/configs/mod.rs index 95b0c79508..7bcf193128 100644 --- a/util/app-config/src/configs/mod.rs +++ b/util/app-config/src/configs/mod.rs @@ -28,4 +28,4 @@ pub use rpc::{Config as RpcConfig, Module as RpcModule}; pub use store::Config as StoreConfig; pub use tx_pool::{BlockAssemblerConfig, TxPoolConfig}; -pub(crate) use network::{generate_random_key, read_secret_key, write_secret_to_file}; +pub use network::{generate_random_key, read_secret_key, write_secret_to_file}; diff --git a/util/app-config/src/configs/network.rs b/util/app-config/src/configs/network.rs index 42106c6601..912aca0d8f 100644 --- a/util/app-config/src/configs/network.rs +++ b/util/app-config/src/configs/network.rs @@ -167,7 +167,8 @@ pub fn default_support_all_protocols() -> Vec { ] } -pub(crate) fn generate_random_key() -> [u8; 32] { +/// Generate random secp256k1 key +pub fn generate_random_key() -> [u8; 32] { loop { let mut key: [u8; 32] = [0; 32]; rand::thread_rng().fill(&mut key); @@ -177,7 +178,8 @@ pub(crate) fn generate_random_key() -> [u8; 32] { } } -pub(crate) fn write_secret_to_file(secret: &[u8], path: PathBuf) -> Result<(), Error> { +/// Secret key storage +pub fn write_secret_to_file(secret: &[u8], path: PathBuf) -> Result<(), Error> { fs::OpenOptions::new() .create(true) .write(true) @@ -198,7 +200,8 @@ pub(crate) fn write_secret_to_file(secret: &[u8], path: PathBuf) -> Result<(), E }) } -pub(crate) fn read_secret_key(path: PathBuf) -> Result, Error> { +/// Load secret key from path +pub fn read_secret_key(path: PathBuf) -> Result, Error> { let mut file = match fs::File::open(path.clone()) { Ok(file) => file, Err(_) => return Ok(None), diff --git a/util/app-config/src/exit_code.rs b/util/app-config/src/exit_code.rs index a55e845c77..6b5230886b 100644 --- a/util/app-config/src/exit_code.rs +++ b/util/app-config/src/exit_code.rs @@ -41,10 +41,3 @@ impl From for ExitCode { ExitCode::Config } } - -impl From for ExitCode { - fn from(err: clap::Error) -> ExitCode { - eprintln!("Args Error: {err:?}"); - ExitCode::Cli - } -} diff --git a/util/app-config/src/lib.rs b/util/app-config/src/lib.rs index 02110816aa..b4d3daad03 100644 --- a/util/app-config/src/lib.rs +++ b/util/app-config/src/lib.rs @@ -15,476 +15,12 @@ pub use app_config::{ AppConfig, CKBAppConfig, ChainConfig, LogConfig, MetricsConfig, MinerAppConfig, }; pub use args::{ - DaemonArgs, ExportArgs, ImportArgs, InitArgs, MigrateArgs, MinerArgs, PeerIDArgs, ReplayArgs, - ResetDataArgs, RunArgs, StatsArgs, + CustomizeSpec, DaemonArgs, ExportArgs, ImportArgs, InitArgs, MigrateArgs, MinerArgs, + PeerIDArgs, ReplayArgs, ResetDataArgs, RunArgs, StatsArgs, }; -use ckb_logger::info; + pub use configs::*; pub use exit_code::ExitCode; #[cfg(feature = "with_sentry")] pub use sentry_config::SentryConfig; pub use url::Url; - -use ckb_chain_spec::{consensus::Consensus, ChainSpec}; -use ckb_jsonrpc_types::ScriptHashType; -use ckb_types::{u256, H256, U256}; -use clap::ArgMatches; -use std::{path::PathBuf, str::FromStr}; - -// 500_000 total difficulty -const MIN_CHAIN_WORK_500K: U256 = u256!("0x3314412053c82802a7"); - -/// A struct including all the information to start the ckb process. -pub struct Setup { - /// Subcommand name. - /// - /// For example, this is set to `run` when ckb is executed with `ckb run`. - pub subcommand_name: String, - /// The config file for the current subcommand. - pub config: AppConfig, - /// Whether sentry is enabled. - #[cfg(feature = "with_sentry")] - pub is_sentry_enabled: bool, -} - -impl Setup { - /// Boots the ckb process by parsing the command line arguments and loading the config file. - pub fn from_matches( - bin_name: String, - subcommand_name: &str, - matches: &ArgMatches, - ) -> Result { - let root_dir = Self::root_dir_from_matches(matches)?; - let mut config = AppConfig::load_for_subcommand(root_dir, subcommand_name)?; - config.set_bin_name(bin_name); - #[cfg(feature = "with_sentry")] - let is_sentry_enabled = is_daemon(subcommand_name) && config.sentry().is_enabled(); - - Ok(Setup { - subcommand_name: subcommand_name.to_string(), - config, - #[cfg(feature = "with_sentry")] - is_sentry_enabled, - }) - } - - /// Executes `ckb run`. - pub fn run(self, matches: &ArgMatches) -> Result { - let consensus = self.consensus()?; - let chain_spec_hash = self.chain_spec()?.hash; - let mut config = self.config.into_ckb()?; - - let mainnet_genesis = ckb_chain_spec::ChainSpec::load_from( - &ckb_resource::Resource::bundled("specs/mainnet.toml".to_string()), - ) - .expect("load mainnet spec fail") - .build_genesis() - .expect("build mainnet genesis fail"); - config.network.sync.min_chain_work = - if consensus.genesis_block.hash() == mainnet_genesis.hash() { - MIN_CHAIN_WORK_500K - } else { - u256!("0x0") - }; - - let arg_assume_valid_target = matches.get_one::(cli::ARG_ASSUME_VALID_TARGET); - - config.network.sync.assume_valid_target = - arg_assume_valid_target.and_then(|s| H256::from_str(&s[2..]).ok()); - if config.network.sync.assume_valid_target.is_none() { - config.network.sync.assume_valid_target = match consensus.id.as_str() { - ckb_constant::hardfork::mainnet::CHAIN_SPEC_NAME => Some( - H256::from_str(&ckb_constant::default_assume_valid_target::mainnet::DEFAULT_ASSUME_VALID_TARGET[2..]) - .expect("default assume_valid_target for mainnet must be valid"), - ), - ckb_constant::hardfork::testnet::CHAIN_SPEC_NAME => Some( - H256::from_str(&ckb_constant::default_assume_valid_target::testnet::DEFAULT_ASSUME_VALID_TARGET[2..]) - .expect("default assume_valid_target for testnet must be valid"), - ), - _ => None, - }; - } - - if let Some(ref assume_valid_target) = config.network.sync.assume_valid_target { - if assume_valid_target - == &H256::from_slice(&[0; 32]).expect("must parse Zero h256 successful") - { - info!("Disable assume valid target since assume_valid_target is zero"); - config.network.sync.assume_valid_target = None - } else { - info!("assume_valid_target set to 0x{}", assume_valid_target); - } - } - - Ok(RunArgs { - config, - consensus, - block_assembler_advanced: matches.get_flag(cli::ARG_BA_ADVANCED), - skip_chain_spec_check: matches.get_flag(cli::ARG_SKIP_CHAIN_SPEC_CHECK), - overwrite_chain_spec: matches.get_flag(cli::ARG_OVERWRITE_CHAIN_SPEC), - chain_spec_hash, - indexer: matches.get_flag(cli::ARG_INDEXER), - rich_indexer: matches.get_flag(cli::ARG_RICH_INDEXER), - #[cfg(not(target_os = "windows"))] - daemon: matches.get_flag(cli::ARG_DAEMON), - }) - } - - /// `migrate` subcommand has one `flags` arg, trigger this arg with "--check" - pub fn migrate(self, matches: &ArgMatches) -> Result { - let consensus = self.consensus()?; - let config = self.config.into_ckb()?; - let check = matches.get_flag(cli::ARG_MIGRATE_CHECK); - let force = matches.get_flag(cli::ARG_FORCE); - let include_background = matches.get_flag(cli::ARG_INCLUDE_BACKGROUND); - - Ok(MigrateArgs { - config, - consensus, - check, - force, - include_background, - }) - } - - /// Executes `ckb miner`. - pub fn miner(self, matches: &ArgMatches) -> Result { - let spec = self.chain_spec()?; - let memory_tracker = self.config.memory_tracker().to_owned(); - let config = self.config.into_miner()?; - let pow_engine = spec.pow_engine(); - let limit = *matches - .get_one::(cli::ARG_LIMIT) - .expect("has default value"); - - Ok(MinerArgs { - pow_engine, - config: config.miner, - memory_tracker, - limit, - }) - } - - /// Executes `ckb replay`. - pub fn replay(self, matches: &ArgMatches) -> Result { - let consensus = self.consensus()?; - let config = self.config.into_ckb()?; - let tmp_target = matches - .get_one::(cli::ARG_TMP_TARGET) - .ok_or_else(|| { - eprintln!("Args Error: {:?} no found", cli::ARG_TMP_TARGET); - ExitCode::Cli - })? - .clone(); - let profile = if matches.get_flag(cli::ARG_PROFILE) { - let from = matches.get_one::(cli::ARG_FROM).cloned(); - let to = matches.get_one::(cli::ARG_TO).cloned(); - Some((from, to)) - } else { - None - }; - let sanity_check = matches.get_flag(cli::ARG_SANITY_CHECK); - let full_verification = matches.get_flag(cli::ARG_FULL_VERIFICATION); - Ok(ReplayArgs { - config, - consensus, - tmp_target, - profile, - sanity_check, - full_verification, - }) - } - - /// Executes `ckb stats`. - pub fn stats(self, matches: &ArgMatches) -> Result { - let consensus = self.consensus()?; - let config = self.config.into_ckb()?; - - let from = matches.get_one::(cli::ARG_FROM).cloned(); - let to = matches.get_one::(cli::ARG_TO).cloned(); - - Ok(StatsArgs { - config, - consensus, - from, - to, - }) - } - - /// Executes `ckb import`. - pub fn import(self, matches: &ArgMatches) -> Result { - let consensus = self.consensus()?; - let config = self.config.into_ckb()?; - let source = matches - .get_one::(cli::ARG_SOURCE) - .ok_or_else(|| { - eprintln!("Args Error: {:?} no found", cli::ARG_SOURCE); - ExitCode::Cli - })? - .clone(); - - Ok(ImportArgs { - config, - consensus, - source, - }) - } - - /// Executes `ckb export`. - pub fn export(self, matches: &ArgMatches) -> Result { - let consensus = self.consensus()?; - let config = self.config.into_ckb()?; - let target = matches - .get_one::(cli::ARG_TARGET) - .ok_or_else(|| { - eprintln!("Args Error: {:?} no found", cli::ARG_TARGET); - ExitCode::Cli - })? - .clone(); - - Ok(ExportArgs { - config, - consensus, - target, - }) - } - - /// Executes `ckb daemon`. - pub fn daemon(self, matches: &ArgMatches) -> Result { - let check = matches.get_flag(cli::ARG_DAEMON_CHECK); - let stop = matches.get_flag(cli::ARG_DAEMON_STOP); - let pid_file = Setup::daemon_pid_file_path(matches)?; - Ok(DaemonArgs { - check, - stop, - pid_file, - }) - } - - /// Executes `ckb init`. - pub fn init(matches: &ArgMatches) -> Result { - if matches.contains_id("list-specs") { - eprintln!( - "Deprecated: Option `--list-specs` is deprecated, use `--list-chains` instead" - ); - } - if matches.contains_id("spec") { - eprintln!("Deprecated: Option `--spec` is deprecated, use `--chain` instead"); - } - if matches.contains_id("export-specs") { - eprintln!("Deprecated: Option `--export-specs` is deprecated"); - } - - let root_dir = Self::root_dir_from_matches(matches)?; - let list_chains = - matches.get_flag(cli::ARG_LIST_CHAINS) || matches.contains_id("list-specs"); - let interactive = matches.get_flag(cli::ARG_INTERACTIVE); - let force = matches.get_flag(cli::ARG_FORCE); - let chain = if !matches.contains_id("spec") { - matches - .get_one::(cli::ARG_CHAIN) - .expect("has default value") - .to_string() - } else { - matches.get_one::("spec").unwrap().to_string() - }; - let rpc_port = matches - .get_one::(cli::ARG_RPC_PORT) - .expect("has default value") - .to_string(); - let p2p_port = matches - .get_one::(cli::ARG_P2P_PORT) - .expect("has default value") - .to_string(); - let (log_to_file, log_to_stdout) = match matches - .get_one::(cli::ARG_LOG_TO) - .map(|s| s.as_str()) - { - Some("file") => (true, false), - Some("stdout") => (false, true), - Some("both") => (true, true), - _ => unreachable!(), - }; - - let block_assembler_code_hash = matches.get_one::(cli::ARG_BA_CODE_HASH).cloned(); - let block_assembler_args: Vec<_> = matches - .get_many::(cli::ARG_BA_ARG) - .unwrap_or_default() - .map(|a| a.to_owned()) - .collect(); - let block_assembler_hash_type = matches - .get_one::(cli::ARG_BA_HASH_TYPE) - .and_then(|hash_type| serde_plain::from_str::(hash_type).ok()) - .expect("has default value"); - let block_assembler_message = matches.get_one::(cli::ARG_BA_MESSAGE).cloned(); - - let import_spec = matches.get_one::(cli::ARG_IMPORT_SPEC).cloned(); - - let customize_spec = { - let genesis_message = matches.get_one::(cli::ARG_GENESIS_MESSAGE).cloned(); - args::CustomizeSpec { genesis_message } - }; - - Ok(InitArgs { - interactive, - root_dir, - chain, - rpc_port, - p2p_port, - list_chains, - force, - log_to_file, - log_to_stdout, - block_assembler_code_hash, - block_assembler_args, - block_assembler_hash_type, - block_assembler_message, - import_spec, - customize_spec, - }) - } - - /// Executes `ckb reset-data`. - pub fn reset_data(self, matches: &ArgMatches) -> Result { - let config = self.config.into_ckb()?; - let data_dir = config.data_dir; - let db_path = config.db.path; - let indexer_path = config.indexer.store; - let rich_indexer_path = config - .indexer - .rich_indexer - .store - .parent() - .expect("rich-indexer store path should have parent dir") - .to_path_buf(); - let network_config = config.network; - let network_dir = network_config.path.clone(); - let network_peer_store_path = network_config.peer_store_path(); - let network_secret_key_path = network_config.secret_key_path(); - let logs_dir = Some(config.logger.log_dir); - - let force = matches.get_flag(cli::ARG_FORCE); - let all = matches.get_flag(cli::ARG_ALL); - let database = matches.get_flag(cli::ARG_DATABASE); - let indexer = matches.get_flag(cli::ARG_INDEXER); - let rich_indexer = matches.get_flag(cli::ARG_RICH_INDEXER); - let network = matches.get_flag(cli::ARG_NETWORK); - let network_peer_store = matches.get_flag(cli::ARG_NETWORK_PEER_STORE); - let network_secret_key = matches.get_flag(cli::ARG_NETWORK_SECRET_KEY); - let logs = matches.get_flag(cli::ARG_LOGS); - - Ok(ResetDataArgs { - force, - all, - database, - indexer, - rich_indexer, - network, - network_peer_store, - network_secret_key, - logs, - data_dir, - db_path, - indexer_path, - rich_indexer_path, - network_dir, - network_peer_store_path, - network_secret_key_path, - logs_dir, - }) - } - - /// Resolves the root directory for ckb from the command line arguments. - pub fn root_dir_from_matches(matches: &ArgMatches) -> Result { - let config_dir = match matches.get_one::(cli::ARG_CONFIG_DIR) { - Some(arg_config_dir) => PathBuf::from(arg_config_dir), - None => ::std::env::current_dir()?, - }; - std::fs::create_dir_all(&config_dir)?; - Ok(config_dir) - } - - /// Resolves the pid file path for ckb from the command line arguments. - pub fn daemon_pid_file_path(matches: &ArgMatches) -> Result { - let root_dir = Self::root_dir_from_matches(matches)?; - Ok(root_dir.join("data/daemon/ckb-run.pid")) - } - - /// Loads the chain spec. - #[cfg(feature = "with_sentry")] - fn chain_spec(&self) -> Result { - let result = self.config.chain_spec(); - if let Ok(spec) = &result { - if self.is_sentry_enabled { - sentry::configure_scope(|scope| { - scope.set_tag("spec.name", &spec.name); - scope.set_tag("spec.pow", &spec.pow); - }); - } - } - - result - } - - #[cfg(not(feature = "with_sentry"))] - fn chain_spec(&self) -> Result { - self.config.chain_spec() - } - - /// Gets the consensus. - #[cfg(feature = "with_sentry")] - pub fn consensus(&self) -> Result { - let result = consensus_from_spec(&self.chain_spec()?); - - if let Ok(consensus) = &result { - if self.is_sentry_enabled { - sentry::configure_scope(|scope| { - scope.set_tag("genesis", consensus.genesis_hash()); - }); - } - } - - result - } - - /// Gets the consensus. - #[cfg(not(feature = "with_sentry"))] - pub fn consensus(&self) -> Result { - consensus_from_spec(&self.chain_spec()?) - } - - /// Gets the network peer id by reading the network secret key. - pub fn peer_id(matches: &ArgMatches) -> Result { - let path = matches - .get_one::(cli::ARG_SECRET_PATH) - .expect("required on command line"); - match read_secret_key(path.into()) { - Ok(Some(key)) => Ok(PeerIDArgs { - peer_id: key.peer_id(), - }), - Err(_) => Err(ExitCode::Failure), - Ok(None) => Err(ExitCode::IO), - } - } - - /// Generates the network secret key. - pub fn gen(matches: &ArgMatches) -> Result<(), ExitCode> { - let path = matches - .get_one::(cli::ARG_SECRET_PATH) - .expect("required on command line"); - configs::write_secret_to_file(&configs::generate_random_key(), path.into()) - .map_err(|_| ExitCode::IO) - } -} - -#[cfg(feature = "with_sentry")] -fn is_daemon(subcommand_name: &str) -> bool { - matches!(subcommand_name, cli::CMD_RUN | cli::CMD_MINER) -} - -fn consensus_from_spec(spec: &ChainSpec) -> Result { - spec.build_consensus().map_err(|err| { - eprintln!("chainspec error: {err}"); - ExitCode::Config - }) -} diff --git a/util/app-config/src/tests/mod.rs b/util/app-config/src/tests/mod.rs index 2f08a20f0b..08e5df8f17 100644 --- a/util/app-config/src/tests/mod.rs +++ b/util/app-config/src/tests/mod.rs @@ -1,3 +1,2 @@ mod app_config; -mod cli; mod legacy;