Coverage Report

Created: 2024-05-16 12:16

/__w/smoldot/smoldot/repo/full-node/bin/cli.rs
Line
Count
Source (jump to first uncovered line)
1
// Smoldot
2
// Copyright (C) 2019-2022  Parity Technologies (UK) Ltd.
3
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
4
5
// This program is free software: you can redistribute it and/or modify
6
// it under the terms of the GNU General Public License as published by
7
// the Free Software Foundation, either version 3 of the License, or
8
// (at your option) any later version.
9
10
// This program is distributed in the hope that it will be useful,
11
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
// GNU General Public License for more details.
14
15
// You should have received a copy of the GNU General Public License
16
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18
//! Provides the [`CliOptions`] struct that contains all the CLI options that can be passed to the
19
//! binary.
20
//!
21
//! See the documentation of the [`clap`] crate in order to learn more.
22
//!
23
//! # Example
24
//!
25
//! ```no_run
26
//! use clap::Parser as _;
27
//! let cli_options = full_node::CliOptions::parse();
28
//! ```
29
//!
30
// TODO: I believe this example isn't tested ^ which kills the point of having it
31
32
use smoldot::{
33
    identity::seed_phrase,
34
    libp2p::{
35
        multiaddr::{Multiaddr, Protocol},
36
        PeerId,
37
    },
38
};
39
use std::{io, net::SocketAddr, path::PathBuf};
40
41
// Note: the doc-comments applied to this struct and its field are visible when the binary is
42
// started with `--help`.
43
44
#[derive(Debug, clap::Parser)]
45
#[command(about, author, version, long_about = None)]
46
#[command(propagate_version = true)]
47
pub struct CliOptions {
48
    #[command(subcommand)]
49
    pub command: CliOptionsCommand,
50
}
51
52
#[derive(Debug, clap::Subcommand)]
53
pub enum CliOptionsCommand {
54
    /// Connects to the chain and synchronizes the local database with the network.
55
    #[command(name = "run")]
56
    Run(Box<CliOptionsRun>),
57
    /// Computes the 64 bits BLAKE2 hash of a string payload and prints the hexadecimal-encoded hash.
58
    #[command(name = "blake2-64bits-hash")]
59
    Blake264BitsHash(CliOptionsBlake264Hash),
60
    /// Computes the 256 bits BLAKE2 hash of a file and prints the hexadecimal-encoded hash.
61
    #[command(name = "blake2-256bits-hash")]
62
    Blake2256BitsHash(CliOptionsBlake2256Hash),
63
}
64
65
#[derive(Debug, clap::Parser)]
66
pub struct CliOptionsRun {
67
    /// Path to a file containing the specification of the chain to connect to.
68
    #[arg(long)]
69
0
    pub path_to_chain_spec: PathBuf,
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches20from_arg_matches_mut0B9_
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches27update_from_arg_matches_mut0B9_
70
    /// Output to stdout: auto, none, informant, logs, logs-json.
71
    #[arg(long, default_value = "auto")]
72
0
    pub output: Output,
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches20from_arg_matches_muts_0B9_
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches27update_from_arg_matches_muts_0B9_
73
    /// Level of logging: off, error, warn, info, debug, trace.
74
    #[arg(long)]
75
    pub log_level: Option<LogLevel>,
76
    /// Coloring: auto, always, never
77
    #[arg(long, default_value = "auto")]
78
0
    pub color: ColorChoice,
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches20from_arg_matches_muts0_0B9_
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches27update_from_arg_matches_muts0_0B9_
79
    /// Ed25519 private key of network identity (as a seed phrase).
80
    #[arg(long, value_parser = decode_ed25519_private_key)]
81
    pub libp2p_key: Option<Box<[u8; 32]>>,
82
    /// `Multiaddr` to listen on.
83
    #[arg(long, value_parser = decode_multiaddr)]
84
0
    pub listen_addr: Vec<Multiaddr>,
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches20from_arg_matches_muts1_0B9_
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches27update_from_arg_matches_muts1_0B9_
85
    /// `Multiaddr` of an additional node to try to connect to on startup.
86
    #[arg(long, value_parser = parse_bootnode)]
87
0
    pub additional_bootnode: Vec<Bootnode>,
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches20from_arg_matches_muts2_0B9_
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches27update_from_arg_matches_muts2_0B9_
88
    /// Bind point of the JSON-RPC server ("none" or `<ip>:<port>`).
89
    #[arg(long, default_value = "127.0.0.1:9944", value_parser = parse_json_rpc_address)]
90
0
    pub json_rpc_address: JsonRpcAddress,
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches20from_arg_matches_muts3_0B9_
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches27update_from_arg_matches_muts3_0B9_
91
    /// Maximum number of JSON-RPC clients that can be connected simultaneously. Ignored if no server.
92
    #[arg(long, default_value = "64")]
93
0
    pub json_rpc_max_clients: u32,
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches20from_arg_matches_muts4_0B9_
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches27update_from_arg_matches_muts4_0B9_
94
    /// List of secret phrases to insert in the keystore of the node. Used to author blocks.
95
    #[arg(long, value_parser = decode_sr25519_private_key)]
96
    // TODO: also automatically add the same keys through ed25519?
97
0
    pub keystore_memory: Vec<Box<[u8; 64]>>,
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches20from_arg_matches_muts5_0B9_
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches27update_from_arg_matches_muts5_0B9_
98
    /// Address of a Jaeger agent to send traces to (hint: port is typically 6831).
99
    #[arg(long)]
100
    pub jaeger: Option<SocketAddr>,
101
    /// Do not load or store anything on disk.
102
    #[arg(long)]
103
0
    pub tmp: bool,
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches20from_arg_matches_muts6_0B9_
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches27update_from_arg_matches_muts6_0B9_
104
    /// Maximum size of the cache used by the database.
105
    #[arg(long, default_value = "256M", value_parser = parse_max_bytes)]
106
0
    pub database_cache_size: MaxBytes,
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches20from_arg_matches_muts7_0B9_
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches27update_from_arg_matches_muts7_0B9_
107
    /// Maximum size of the cache used by the database for the relay chain. Ignored if the
108
    /// chain is not a parachain.
109
    #[arg(long, default_value = "256M", value_parser = parse_max_bytes)]
110
0
    pub relay_chain_database_cache_size: MaxBytes,
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches20from_arg_matches_muts8_0B9_
Unexecuted instantiation: _RNCNvXsb_NtCs3mLPJhMFEyv_9full_node3cliNtB7_13CliOptionsRunNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches27update_from_arg_matches_muts8_0B9_
111
}
112
113
#[derive(Debug, clap::Parser)]
114
pub struct CliOptionsBlake264Hash {
115
    /// Payload whose hash to compute.
116
0
    pub payload: String,
Unexecuted instantiation: _RNCNvXsg_NtCs3mLPJhMFEyv_9full_node3cliNtB7_22CliOptionsBlake264HashNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches20from_arg_matches_mut0B9_
Unexecuted instantiation: _RNCNvXsg_NtCs3mLPJhMFEyv_9full_node3cliNtB7_22CliOptionsBlake264HashNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches27update_from_arg_matches_mut0B9_
117
}
118
119
#[derive(Debug, clap::Parser)]
120
pub struct CliOptionsBlake2256Hash {
121
    /// Path of the file whose hash to compute.
122
0
    pub file: PathBuf,
Unexecuted instantiation: _RNCNvXsl_NtCs3mLPJhMFEyv_9full_node3cliNtB7_23CliOptionsBlake2256HashNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches20from_arg_matches_mut0B9_
Unexecuted instantiation: _RNCNvXsl_NtCs3mLPJhMFEyv_9full_node3cliNtB7_23CliOptionsBlake2256HashNtNtCs25TVGmaFplk_12clap_builder6derive14FromArgMatches27update_from_arg_matches_mut0B9_
123
}
124
125
#[derive(Debug, Clone)]
126
pub enum ColorChoice {
127
    Always,
128
    Never,
129
}
130
131
impl core::str::FromStr for ColorChoice {
132
    type Err = ColorChoiceParseError;
133
134
0
    fn from_str(s: &str) -> Result<Self, Self::Err> {
135
0
        if s == "always" {
136
0
            Ok(ColorChoice::Always)
137
0
        } else if s == "auto" {
138
0
            if io::IsTerminal::is_terminal(&io::stderr()) {
139
0
                Ok(ColorChoice::Always)
140
            } else {
141
0
                Ok(ColorChoice::Never)
142
            }
143
0
        } else if s == "never" {
144
0
            Ok(ColorChoice::Never)
145
        } else {
146
0
            Err(ColorChoiceParseError)
147
        }
148
0
    }
149
}
150
151
0
#[derive(Debug, derive_more::Display, derive_more::Error)]
152
#[display(fmt = "Color must be one of: always, auto, never")]
153
pub struct ColorChoiceParseError;
154
155
#[derive(Debug, Clone)]
156
pub enum LogLevel {
157
    Off,
158
    Error,
159
    Warn,
160
    Info,
161
    Debug,
162
    Trace,
163
}
164
165
impl core::str::FromStr for LogLevel {
166
    type Err = LogLevelParseError;
167
168
0
    fn from_str(s: &str) -> Result<Self, Self::Err> {
169
0
        if s.eq_ignore_ascii_case("off") {
170
0
            Ok(LogLevel::Off)
171
0
        } else if s.eq_ignore_ascii_case("error") {
172
0
            Ok(LogLevel::Error)
173
0
        } else if s.eq_ignore_ascii_case("warn") {
174
0
            Ok(LogLevel::Warn)
175
0
        } else if s.eq_ignore_ascii_case("info") {
176
0
            Ok(LogLevel::Info)
177
0
        } else if s.eq_ignore_ascii_case("debug") {
178
0
            Ok(LogLevel::Debug)
179
0
        } else if s.eq_ignore_ascii_case("trace") {
180
0
            Ok(LogLevel::Trace)
181
        } else {
182
0
            Err(LogLevelParseError)
183
        }
184
0
    }
185
}
186
187
0
#[derive(Debug, derive_more::Display, derive_more::Error)]
188
#[display(fmt = "Log level must be one of: off, error, warn, info, debug, trace")]
189
pub struct LogLevelParseError;
190
191
#[derive(Debug, Clone, clap::ValueEnum)]
192
pub enum Output {
193
    Auto,
194
    None,
195
    Informant,
196
    Logs,
197
    LogsJson,
198
}
199
200
#[derive(Debug, Clone)]
201
pub struct JsonRpcAddress(pub Option<SocketAddr>);
202
203
0
fn parse_json_rpc_address(string: &str) -> Result<JsonRpcAddress, String> {
204
0
    if string == "none" {
205
0
        return Ok(JsonRpcAddress(None));
206
0
    }
207
208
0
    if let Ok(addr) = string.parse::<SocketAddr>() {
209
0
        return Ok(JsonRpcAddress(Some(addr)));
210
0
    }
211
0
212
0
    Err("Failed to parse JSON-RPC server address".into())
213
0
}
214
215
#[derive(Debug, Clone)]
216
pub struct Bootnode {
217
    pub address: Multiaddr,
218
    pub peer_id: PeerId,
219
}
220
221
0
fn parse_bootnode(string: &str) -> Result<Bootnode, String> {
222
0
    let mut address = string.parse::<Multiaddr>().map_err(|err| err.to_string())?;
223
0
    let Some(Protocol::P2p(peer_id)) = address.iter().last() else {
224
0
        return Err("Bootnode address must end with /p2p/...".into());
225
    };
226
0
    let peer_id = PeerId::from_bytes(peer_id.into_bytes().to_vec())
227
0
        .map_err(|(err, _)| format!("Failed to parse PeerId in bootnode: {err}"))?;
228
0
    address.pop();
229
0
    Ok(Bootnode { address, peer_id })
230
0
}
231
232
#[derive(Debug, Clone)]
233
pub struct MaxBytes(pub usize);
234
235
0
fn parse_max_bytes(string: &str) -> Result<MaxBytes, String> {
236
0
    let (multiplier, num) = if let Some(s) = string.strip_suffix("Ti") {
237
0
        (
238
0
            usize::try_from(1024 * 1024 * 1024 * 1024u64).unwrap_or(usize::MAX),
239
0
            s,
240
0
        )
241
0
    } else if let Some(s) = string.strip_suffix('T') {
242
0
        (
243
0
            usize::try_from(1000 * 1000 * 1000 * 1000u64).unwrap_or(usize::MAX),
244
0
            s,
245
0
        )
246
0
    } else if let Some(s) = string.strip_suffix("Gi") {
247
0
        (
248
0
            usize::try_from(1024 * 1024 * 1024u64).unwrap_or(usize::MAX),
249
0
            s,
250
0
        )
251
0
    } else if let Some(s) = string.strip_suffix('G') {
252
0
        (
253
0
            usize::try_from(1000 * 1000 * 1000u64).unwrap_or(usize::MAX),
254
0
            s,
255
0
        )
256
0
    } else if let Some(s) = string.strip_suffix("Mi") {
257
0
        (usize::try_from(1024 * 1024u64).unwrap_or(usize::MAX), s)
258
0
    } else if let Some(s) = string.strip_suffix('M') {
259
0
        (usize::try_from(1000 * 1000u64).unwrap_or(usize::MAX), s)
260
0
    } else if let Some(s) = string.strip_suffix("ki") {
261
0
        (1024, s)
262
0
    } else if let Some(s) = string.strip_suffix('k') {
263
0
        (1000, s)
264
    } else {
265
0
        (1, string)
266
    };
267
268
0
    let Ok(num) = num.parse::<usize>() else {
269
0
        return Err("Failed to parse number of bytes".into());
270
    };
271
272
    // Because it's a maximum value it's ok to saturate rather than return an error.
273
0
    let real_value = num.saturating_mul(multiplier);
274
0
    Ok(MaxBytes(real_value))
275
0
}
276
277
// `clap` requires error types to implement the `std::error::Error` trait.
278
// For this reason, we locally define some wrappers.
279
0
fn decode_ed25519_private_key(phrase: &str) -> Result<Box<[u8; 32]>, String> {
280
0
    seed_phrase::decode_ed25519_private_key(phrase).map_err(|err| err.to_string())
281
0
}
282
0
fn decode_sr25519_private_key(phrase: &str) -> Result<Box<[u8; 64]>, String> {
283
0
    seed_phrase::decode_sr25519_private_key(phrase).map_err(|err| err.to_string())
284
0
}
285
0
fn decode_multiaddr(addr: &str) -> Result<Multiaddr, String> {
286
0
    addr.parse::<Multiaddr>().map_err(|err| err.to_string())
287
0
}