// Smoldot // Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . //! Provides the [`CliOptions`] struct that contains all the CLI options that can be passed to the //! binary. //! //! See the documentation of the [`clap`] crate in order to learn more. //! //! # Example //! //! ```no_run //! use clap::Parser as _; //! let cli_options = full_node::CliOptions::parse(); //! ``` //! // TODO: I believe this example isn't tested ^ which kills the point of having it use smoldot::{ identity::seed_phrase, libp2p::{ multiaddr::{Multiaddr, Protocol}, PeerId, }, }; use std::{io, net::SocketAddr, path::PathBuf}; // Note: the doc-comments applied to this struct and its field are visible when the binary is // started with `--help`. #[derive(Debug, clap::Parser)] #[command(about, author, version, long_about = None)] #[command(propagate_version = true)] pub struct CliOptions { #[command(subcommand)] pub command: CliOptionsCommand, } #[derive(Debug, clap::Subcommand)] pub enum CliOptionsCommand { /// Connects to the chain and synchronizes the local database with the network. #[command(name = "run")] Run(Box), /// Computes the 64 bits BLAKE2 hash of a string payload and prints the hexadecimal-encoded hash. #[command(name = "blake2-64bits-hash")] Blake264BitsHash(CliOptionsBlake264Hash), /// Computes the 256 bits BLAKE2 hash of a file and prints the hexadecimal-encoded hash. #[command(name = "blake2-256bits-hash")] Blake2256BitsHash(CliOptionsBlake2256Hash), } #[derive(Debug, clap::Parser)] pub struct CliOptionsRun { /// Path to a file containing the specification of the chain to connect to. #[arg(long)] pub path_to_chain_spec: PathBuf, /// Output to stdout: auto, none, informant, logs, logs-json. #[arg(long, default_value = "auto")] pub output: Output, /// Level of logging: off, error, warn, info, debug, trace. #[arg(long)] pub log_level: Option, /// Coloring: auto, always, never #[arg(long, default_value = "auto")] pub color: ColorChoice, /// Ed25519 private key of network identity (as a seed phrase). #[arg(long, value_parser = decode_ed25519_private_key)] pub libp2p_key: Option>, /// `Multiaddr` to listen on. #[arg(long, value_parser = decode_multiaddr)] pub listen_addr: Vec, /// `Multiaddr` of an additional node to try to connect to on startup. #[arg(long, value_parser = parse_bootnode)] pub additional_bootnode: Vec, /// Bind point of the JSON-RPC server ("none" or `:`). #[arg(long, default_value = "127.0.0.1:9944", value_parser = parse_json_rpc_address)] pub json_rpc_address: JsonRpcAddress, /// Maximum number of JSON-RPC clients that can be connected simultaneously. Ignored if no server. #[arg(long, default_value = "64")] pub json_rpc_max_clients: u32, /// List of secret phrases to insert in the keystore of the node. Used to author blocks. #[arg(long, value_parser = decode_sr25519_private_key)] // TODO: also automatically add the same keys through ed25519? pub keystore_memory: Vec>, /// Address of a Jaeger agent to send traces to (hint: port is typically 6831). #[arg(long)] pub jaeger: Option, /// Do not load or store anything on disk. #[arg(long)] pub tmp: bool, /// Maximum size of the cache used by the database. #[arg(long, default_value = "256M", value_parser = parse_max_bytes)] pub database_cache_size: MaxBytes, /// Maximum size of the cache used by the database for the relay chain. Ignored if the /// chain is not a parachain. #[arg(long, default_value = "256M", value_parser = parse_max_bytes)] pub relay_chain_database_cache_size: MaxBytes, } #[derive(Debug, clap::Parser)] pub struct CliOptionsBlake264Hash { /// Payload whose hash to compute. pub payload: String, } #[derive(Debug, clap::Parser)] pub struct CliOptionsBlake2256Hash { /// Path of the file whose hash to compute. pub file: PathBuf, } #[derive(Debug, Clone)] pub enum ColorChoice { Always, Never, } impl core::str::FromStr for ColorChoice { type Err = ColorChoiceParseError; fn from_str(s: &str) -> Result { if s == "always" { Ok(ColorChoice::Always) } else if s == "auto" { if io::IsTerminal::is_terminal(&io::stderr()) { Ok(ColorChoice::Always) } else { Ok(ColorChoice::Never) } } else if s == "never" { Ok(ColorChoice::Never) } else { Err(ColorChoiceParseError) } } } #[derive(Debug, derive_more::Display, derive_more::Error)] #[display(fmt = "Color must be one of: always, auto, never")] pub struct ColorChoiceParseError; #[derive(Debug, Clone)] pub enum LogLevel { Off, Error, Warn, Info, Debug, Trace, } impl core::str::FromStr for LogLevel { type Err = LogLevelParseError; fn from_str(s: &str) -> Result { if s.eq_ignore_ascii_case("off") { Ok(LogLevel::Off) } else if s.eq_ignore_ascii_case("error") { Ok(LogLevel::Error) } else if s.eq_ignore_ascii_case("warn") { Ok(LogLevel::Warn) } else if s.eq_ignore_ascii_case("info") { Ok(LogLevel::Info) } else if s.eq_ignore_ascii_case("debug") { Ok(LogLevel::Debug) } else if s.eq_ignore_ascii_case("trace") { Ok(LogLevel::Trace) } else { Err(LogLevelParseError) } } } #[derive(Debug, derive_more::Display, derive_more::Error)] #[display(fmt = "Log level must be one of: off, error, warn, info, debug, trace")] pub struct LogLevelParseError; #[derive(Debug, Clone, clap::ValueEnum)] pub enum Output { Auto, None, Informant, Logs, LogsJson, } #[derive(Debug, Clone)] pub struct JsonRpcAddress(pub Option); fn parse_json_rpc_address(string: &str) -> Result { if string == "none" { return Ok(JsonRpcAddress(None)); } if let Ok(addr) = string.parse::() { return Ok(JsonRpcAddress(Some(addr))); } Err("Failed to parse JSON-RPC server address".into()) } #[derive(Debug, Clone)] pub struct Bootnode { pub address: Multiaddr, pub peer_id: PeerId, } fn parse_bootnode(string: &str) -> Result { let mut address = string.parse::().map_err(|err| err.to_string())?; let Some(Protocol::P2p(peer_id)) = address.iter().last() else { return Err("Bootnode address must end with /p2p/...".into()); }; let peer_id = PeerId::from_bytes(peer_id.into_bytes().to_vec()) .map_err(|(err, _)| format!("Failed to parse PeerId in bootnode: {err}"))?; address.pop(); Ok(Bootnode { address, peer_id }) } #[derive(Debug, Clone)] pub struct MaxBytes(pub usize); fn parse_max_bytes(string: &str) -> Result { let (multiplier, num) = if let Some(s) = string.strip_suffix("Ti") { ( usize::try_from(1024 * 1024 * 1024 * 1024u64).unwrap_or(usize::MAX), s, ) } else if let Some(s) = string.strip_suffix('T') { ( usize::try_from(1000 * 1000 * 1000 * 1000u64).unwrap_or(usize::MAX), s, ) } else if let Some(s) = string.strip_suffix("Gi") { ( usize::try_from(1024 * 1024 * 1024u64).unwrap_or(usize::MAX), s, ) } else if let Some(s) = string.strip_suffix('G') { ( usize::try_from(1000 * 1000 * 1000u64).unwrap_or(usize::MAX), s, ) } else if let Some(s) = string.strip_suffix("Mi") { (usize::try_from(1024 * 1024u64).unwrap_or(usize::MAX), s) } else if let Some(s) = string.strip_suffix('M') { (usize::try_from(1000 * 1000u64).unwrap_or(usize::MAX), s) } else if let Some(s) = string.strip_suffix("ki") { (1024, s) } else if let Some(s) = string.strip_suffix('k') { (1000, s) } else { (1, string) }; let Ok(num) = num.parse::() else { return Err("Failed to parse number of bytes".into()); }; // Because it's a maximum value it's ok to saturate rather than return an error. let real_value = num.saturating_mul(multiplier); Ok(MaxBytes(real_value)) } // `clap` requires error types to implement the `std::error::Error` trait. // For this reason, we locally define some wrappers. fn decode_ed25519_private_key(phrase: &str) -> Result, String> { seed_phrase::decode_ed25519_private_key(phrase).map_err(|err| err.to_string()) } fn decode_sr25519_private_key(phrase: &str) -> Result, String> { seed_phrase::decode_sr25519_private_key(phrase).map_err(|err| err.to_string()) } fn decode_multiaddr(addr: &str) -> Result { addr.parse::().map_err(|err| err.to_string()) }