Skip to main content

agave_validator/
cli.rs

1use {
2    crate::{commands, commands::run::args::pub_sub_config},
3    agave_snapshots::{
4        DEFAULT_ARCHIVE_COMPRESSION, SnapshotVersion,
5        snapshot_config::{
6            DEFAULT_FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS,
7            DEFAULT_INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS,
8            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
9            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
10        },
11    },
12    clap::{App, AppSettings, Arg, ArgMatches, SubCommand, crate_description, crate_name},
13    log::warn,
14    solana_accounts_db::accounts_db::{
15        DEFAULT_ACCOUNTS_SHRINK_OPTIMIZE_TOTAL_SPACE, DEFAULT_ACCOUNTS_SHRINK_RATIO,
16    },
17    solana_clap_utils::{
18        hidden_unless_forced,
19        input_validators::{
20            is_parsable, is_pubkey, is_pubkey_or_keypair, is_slot, is_url_or_moniker,
21            validate_cpu_ranges,
22        },
23    },
24    solana_clock::Slot,
25    solana_core::banking_trace::BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT,
26    solana_epoch_schedule::MINIMUM_SLOTS_PER_EPOCH,
27    solana_faucet::faucet::{self, FAUCET_PORT},
28    solana_hash::Hash,
29    solana_net_utils::{MINIMUM_VALIDATOR_PORT_RANGE_WIDTH, VALIDATOR_PORT_RANGE},
30    solana_send_transaction_service::send_transaction_service::{self},
31    solana_streamer::quic::{
32        DEFAULT_MAX_CONNECTIONS_PER_IPADDR_PER_MINUTE,
33        DEFAULT_MAX_QUIC_CONNECTIONS_PER_STAKED_PEER,
34        DEFAULT_MAX_QUIC_CONNECTIONS_PER_UNSTAKED_PEER, DEFAULT_MAX_STAKED_CONNECTIONS,
35        DEFAULT_MAX_STREAMS_PER_MS, DEFAULT_MAX_UNSTAKED_CONNECTIONS, DEFAULT_QUIC_ENDPOINTS,
36    },
37    solana_tpu_client::tpu_client::DEFAULT_VOTE_USE_QUIC,
38    std::{cmp::Ordering, path::PathBuf, str::FromStr},
39};
40
41pub mod thread_args;
42use {
43    solana_core::banking_stage::BankingStage,
44    thread_args::{DefaultThreadArgs, thread_args},
45};
46
47// The default minimal snapshot download speed (bytes/second)
48const DEFAULT_MIN_SNAPSHOT_DOWNLOAD_SPEED: u64 = 10485760;
49// The maximum times of snapshot download abort and retry
50const MAX_SNAPSHOT_DOWNLOAD_ABORT: u32 = 5;
51// We've observed missed leader slots leading to deadlocks on test validator
52// with less than 2 ticks per slot.
53const MINIMUM_TICKS_PER_SLOT: u64 = 2;
54
55pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> {
56    let app = App::new(crate_name!())
57        .about(crate_description!())
58        .version(version)
59        .global_setting(AppSettings::ColoredHelp)
60        .global_setting(AppSettings::InferSubcommands)
61        .global_setting(AppSettings::UnifiedHelpMessage)
62        .global_setting(AppSettings::VersionlessSubcommands)
63        .subcommand(commands::exit::command())
64        .subcommand(commands::authorized_voter::command())
65        .subcommand(commands::contact_info::command())
66        .subcommand(commands::repair_shred_from_peer::command())
67        .subcommand(commands::repair_whitelist::command())
68        .subcommand(
69            SubCommand::with_name("init").about("Initialize the ledger directory then exit"),
70        )
71        .subcommand(commands::monitor::command())
72        .subcommand(SubCommand::with_name("run").about("Run the validator"))
73        .subcommand(commands::plugin::command())
74        .subcommand(commands::set_identity::command())
75        .subcommand(commands::set_log_filter::command())
76        .subcommand(commands::staked_nodes_overrides::command())
77        .subcommand(commands::wait_for_restart_window::command())
78        .subcommand(commands::set_public_address::command())
79        .subcommand(commands::manage_block_production::command(default_args))
80        .subcommand(commands::blockstore::command());
81
82    commands::run::add_args(app, default_args)
83        .args(&thread_args(&default_args.thread_args))
84        .args(&get_deprecated_arguments())
85        .after_help("The default subcommand is run")
86}
87
88/// Deprecated argument description should be moved into the [`deprecated_arguments()`] function,
89/// expressed as an instance of this type.
90struct DeprecatedArg {
91    /// Deprecated argument description, moved here as is.
92    ///
93    /// `hidden` property will be modified by [`deprecated_arguments()`] to only show this argument
94    /// if [`hidden_unless_forced()`] says they should be displayed.
95    arg: Arg<'static, 'static>,
96
97    /// If simply replaced by a different argument, this is the name of the replacement.
98    ///
99    /// Content should be an argument name, as presented to users.
100    replaced_by: Option<&'static str>,
101
102    /// An explanation to be shown to the user if they still use this argument.
103    ///
104    /// Content should be a complete sentence or several, ending with a period.
105    usage_warning: Option<&'static str>,
106}
107
108fn deprecated_arguments() -> Vec<DeprecatedArg> {
109    let mut res = vec![];
110
111    // This macro reduces indentation and removes some noise from the argument declaration list.
112    macro_rules! add_arg {
113        (
114            $arg:expr
115            $( , replaced_by: $replaced_by:expr )?
116            $( , usage_warning: $usage_warning:expr )?
117            $(,)?
118        ) => {
119            let replaced_by = add_arg!(@into-option $( $replaced_by )?);
120            let usage_warning = add_arg!(@into-option $( $usage_warning )?);
121            res.push(DeprecatedArg {
122                arg: $arg,
123                replaced_by,
124                usage_warning,
125            });
126        };
127
128        (@into-option) => { None };
129        (@into-option $v:expr) => { Some($v) };
130    }
131
132    add_arg!(
133        // deprecated in v4.1.0
134        Arg::with_name("account_shrink_path")
135            .long("account-shrink-path")
136            .value_name("PATH")
137            .takes_value(true)
138            .multiple(true)
139            .help("Path to accounts shrink path which can hold a compacted account set."),
140    );
141    add_arg!(
142        // deprecated in v4.0.0
143        Arg::with_name("enable_accounts_disk_index")
144            .long("enable-accounts-disk-index")
145            .help("Enables the disk-based accounts index")
146            .conflicts_with("accounts_index_limit"),
147        replaced_by: "accounts-index-limit",
148    );
149    add_arg!(
150        // deprecated in v4.1.0
151        Arg::with_name("experimental_retransmit_xdp_cpu_cores")
152            .long("experimental-retransmit-xdp-cpu-cores")
153            .takes_value(true)
154            .value_name("CPU_LIST")
155            .conflicts_with("xdp_cpu_cores")
156            .validator(|value| {
157                validate_cpu_ranges(value, "--experimental-retransmit-xdp-cpu-cores")
158            })
159            .help(
160                "Enable XDP retransmit on the specified CPU cores. Use --xdp-cpu-cores instead",
161            ),
162        replaced_by: "xdp-cpu-cores",
163    );
164    add_arg!(
165        // deprecated in v4.1.0
166        Arg::with_name("experimental_retransmit_xdp_interface")
167            .long("experimental-retransmit-xdp-interface")
168            .takes_value(true)
169            .value_name("INTERFACE")
170            .conflicts_with("xdp_interface")
171            .requires("experimental_retransmit_xdp_cpu_cores")
172            .help("Network interface to use for XDP retransmit. Use --xdp-interface instead"),
173        replaced_by: "xdp-interface",
174    );
175    add_arg!(
176        // deprecated in v4.1.0
177        Arg::with_name("experimental_retransmit_xdp_zero_copy")
178            .long("experimental-retransmit-xdp-zero-copy")
179            .takes_value(false)
180            .conflicts_with("xdp_zero_copy")
181            .requires("experimental_retransmit_xdp_cpu_cores")
182            .help("Enable XDP zero copy. Use --xdp-zero-copy instead"),
183        replaced_by: "xdp-zero-copy",
184    );
185    add_arg!(
186        // deprecated in v4.0.0
187        Arg::with_name("tpu_connection_pool_size")
188            .long("tpu-connection-pool-size")
189            .takes_value(true)
190            .validator(is_parsable::<usize>)
191            .help("Controls the TPU connection pool size per remote address"),
192         usage_warning:"This parameter is misleading, avoid setting it",
193    );
194    res
195}
196
197// Helper to add arguments that are no longer used but are being kept around to avoid breaking
198// validator startup commands.
199fn get_deprecated_arguments() -> Vec<Arg<'static, 'static>> {
200    deprecated_arguments()
201        .into_iter()
202        .map(|info| {
203            let arg = info.arg;
204            // Hide all deprecated arguments by default.
205            arg.hidden(hidden_unless_forced())
206        })
207        .collect()
208}
209
210pub fn warn_for_deprecated_arguments(matches: &ArgMatches) {
211    for DeprecatedArg {
212        arg,
213        replaced_by,
214        usage_warning,
215    } in deprecated_arguments().into_iter()
216    {
217        if matches.is_present(arg.b.name) {
218            let mut msg = format!("--{} is deprecated", arg.b.name.replace('_', "-"));
219            if let Some(replaced_by) = replaced_by {
220                msg.push_str(&format!(", please use --{replaced_by}"));
221            }
222            msg.push('.');
223            if let Some(usage_warning) = usage_warning {
224                msg.push_str(&format!("  {usage_warning}"));
225                if !msg.ends_with('.') {
226                    msg.push('.');
227                }
228            }
229            warn!("{msg}");
230        }
231    }
232}
233
234pub struct DefaultArgs {
235    pub bind_address: String,
236    pub dynamic_port_range: String,
237    pub ledger_path: String,
238
239    pub tower_storage: String,
240    pub send_transaction_service_config: send_transaction_service::Config,
241
242    pub maximum_local_snapshot_age: String,
243    pub maximum_full_snapshot_archives_to_retain: String,
244    pub maximum_incremental_snapshot_archives_to_retain: String,
245    pub snapshot_packager_niceness_adjustment: String,
246    pub full_snapshot_archive_interval_slots: String,
247    pub incremental_snapshot_archive_interval_slots: String,
248    pub min_snapshot_download_speed: String,
249    pub max_snapshot_download_abort: String,
250
251    pub contact_debug_interval: String,
252
253    pub snapshot_version: SnapshotVersion,
254    pub snapshot_archive_format: String,
255    pub snapshot_zstd_compression_level: String,
256
257    pub accounts_shrink_optimize_total_space: String,
258    pub accounts_shrink_ratio: String,
259
260    pub tpu_max_connections_per_unstaked_peer: String,
261    pub tpu_max_connections_per_staked_peer: String,
262    pub tpu_max_connections_per_ipaddr_per_minute: String,
263    pub tpu_max_staked_connections: String,
264    pub tpu_max_unstaked_connections: String,
265    pub tpu_max_fwd_staked_connections: String,
266    pub tpu_max_fwd_unstaked_connections: String,
267    pub tpu_max_streams_per_ms: String,
268
269    pub num_quic_endpoints: String,
270    pub vote_use_quic: String,
271
272    pub banking_trace_dir_byte_limit: String,
273    pub block_production_pacing_fill_time_millis: String,
274
275    pub thread_args: DefaultThreadArgs,
276}
277
278impl DefaultArgs {
279    pub fn new() -> Self {
280        DefaultArgs {
281            bind_address: "0.0.0.0".to_string(),
282            ledger_path: "ledger".to_string(),
283            dynamic_port_range: format!("{}-{}", VALIDATOR_PORT_RANGE.0, VALIDATOR_PORT_RANGE.1),
284            maximum_local_snapshot_age: "2500".to_string(),
285            tower_storage: "file".to_string(),
286            send_transaction_service_config: send_transaction_service::Config::default(),
287            maximum_full_snapshot_archives_to_retain: DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN
288                .to_string(),
289            maximum_incremental_snapshot_archives_to_retain:
290                DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN.to_string(),
291            snapshot_packager_niceness_adjustment: "0".to_string(),
292            full_snapshot_archive_interval_slots: DEFAULT_FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS
293                .get()
294                .to_string(),
295            incremental_snapshot_archive_interval_slots:
296                DEFAULT_INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS
297                    .get()
298                    .to_string(),
299            min_snapshot_download_speed: DEFAULT_MIN_SNAPSHOT_DOWNLOAD_SPEED.to_string(),
300            max_snapshot_download_abort: MAX_SNAPSHOT_DOWNLOAD_ABORT.to_string(),
301            snapshot_archive_format: DEFAULT_ARCHIVE_COMPRESSION.to_string(),
302            snapshot_zstd_compression_level: "1".to_string(), // level 1 is optimized for speed
303            contact_debug_interval: "120000".to_string(),
304            snapshot_version: SnapshotVersion::default(),
305            accounts_shrink_optimize_total_space: DEFAULT_ACCOUNTS_SHRINK_OPTIMIZE_TOTAL_SPACE
306                .to_string(),
307            accounts_shrink_ratio: DEFAULT_ACCOUNTS_SHRINK_RATIO.to_string(),
308            tpu_max_connections_per_ipaddr_per_minute:
309                DEFAULT_MAX_CONNECTIONS_PER_IPADDR_PER_MINUTE.to_string(),
310            vote_use_quic: DEFAULT_VOTE_USE_QUIC.to_string(),
311            tpu_max_connections_per_unstaked_peer: DEFAULT_MAX_QUIC_CONNECTIONS_PER_UNSTAKED_PEER
312                .to_string(),
313            tpu_max_connections_per_staked_peer: DEFAULT_MAX_QUIC_CONNECTIONS_PER_STAKED_PEER
314                .to_string(),
315            tpu_max_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS.to_string(),
316            tpu_max_unstaked_connections: DEFAULT_MAX_UNSTAKED_CONNECTIONS.to_string(),
317            tpu_max_fwd_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS
318                .saturating_add(DEFAULT_MAX_UNSTAKED_CONNECTIONS)
319                .to_string(),
320            tpu_max_fwd_unstaked_connections: 0.to_string(),
321            tpu_max_streams_per_ms: DEFAULT_MAX_STREAMS_PER_MS.to_string(),
322            num_quic_endpoints: DEFAULT_QUIC_ENDPOINTS.to_string(),
323            banking_trace_dir_byte_limit: BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT.to_string(),
324            block_production_pacing_fill_time_millis: BankingStage::default_fill_time_millis()
325                .to_string(),
326            thread_args: DefaultThreadArgs::default(),
327        }
328    }
329}
330
331impl Default for DefaultArgs {
332    fn default() -> Self {
333        Self::new()
334    }
335}
336
337pub fn port_validator(port: String) -> Result<(), String> {
338    port.parse::<u16>()
339        .map(|_| ())
340        .map_err(|e| format!("{e:?}"))
341}
342
343pub fn port_range_validator(port_range: String) -> Result<(), String> {
344    if let Some((start, end)) = solana_net_utils::parse_port_range(&port_range) {
345        if end - start < MINIMUM_VALIDATOR_PORT_RANGE_WIDTH {
346            Err(format!(
347                "Port range is too small.  Try --dynamic-port-range {}-{}",
348                start,
349                start + MINIMUM_VALIDATOR_PORT_RANGE_WIDTH
350            ))
351        } else {
352            Ok(())
353        }
354    } else {
355        Err("Invalid port range".to_string())
356    }
357}
358
359pub(crate) fn hash_validator(hash: String) -> Result<(), String> {
360    Hash::from_str(&hash)
361        .map(|_| ())
362        .map_err(|e| format!("{e:?}"))
363}
364
365/// Test validator
366pub fn test_app<'a>(version: &'a str, default_args: &'a DefaultTestArgs) -> App<'a, 'a> {
367    App::new("solana-test-validator")
368        .about("Test Validator")
369        .version(version)
370        .arg({
371            let arg = Arg::with_name("config_file")
372                .short("C")
373                .long("config")
374                .value_name("PATH")
375                .takes_value(true)
376                .help("Configuration file to use");
377            if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE {
378                arg.default_value(config_file)
379            } else {
380                arg
381            }
382        })
383        .arg(
384            Arg::with_name("json_rpc_url")
385                .short("u")
386                .long("url")
387                .value_name("URL_OR_MONIKER")
388                .takes_value(true)
389                .validator(is_url_or_moniker)
390                .help(
391                    "URL for Solana's JSON RPC or moniker (or their first letter): [mainnet-beta, \
392                     testnet, devnet, localhost]",
393                ),
394        )
395        .arg(
396            Arg::with_name("mint_address")
397                .long("mint")
398                .value_name("PUBKEY")
399                .validator(is_pubkey)
400                .takes_value(true)
401                .help(
402                    "Address of the mint account that will receive tokens created at genesis. If \
403                     the ledger already exists then this parameter is silently ignored [default: \
404                     client keypair]",
405                ),
406        )
407        .arg(
408            Arg::with_name("ledger_path")
409                .short("l")
410                .long("ledger")
411                .value_name("DIR")
412                .takes_value(true)
413                .required(true)
414                .default_value("test-ledger")
415                .help("Use DIR as ledger location"),
416        )
417        .arg(
418            Arg::with_name("reset")
419                .short("r")
420                .long("reset")
421                .takes_value(false)
422                .help(
423                    "Reset the ledger to genesis if it exists. By default the validator will \
424                     resume an existing ledger (if present)",
425                ),
426        )
427        .arg(
428            Arg::with_name("quiet")
429                .short("q")
430                .long("quiet")
431                .takes_value(false)
432                .conflicts_with("log")
433                .help("Quiet mode: suppress normal output"),
434        )
435        .arg(
436            Arg::with_name("log")
437                .long("log")
438                .takes_value(false)
439                .conflicts_with("quiet")
440                .help("Log mode: stream the validator log"),
441        )
442        .arg(
443            Arg::with_name("account_indexes")
444                .long("account-index")
445                .takes_value(true)
446                .multiple(true)
447                .possible_values(&["program-id", "spl-token-owner", "spl-token-mint"])
448                .value_name("INDEX")
449                .help("Enable an accounts index, indexed by the selected account field"),
450        )
451        .arg(
452            Arg::with_name("faucet_port")
453                .long("faucet-port")
454                .value_name("PORT")
455                .takes_value(true)
456                .default_value(&default_args.faucet_port)
457                .validator(port_validator)
458                .help("Enable the faucet on this port"),
459        )
460        .arg(
461            Arg::with_name("rpc_port")
462                .long("rpc-port")
463                .value_name("PORT")
464                .takes_value(true)
465                .default_value(&default_args.rpc_port)
466                .validator(port_validator)
467                .help("Enable JSON RPC on this port, and the next port for the RPC websocket"),
468        )
469        .arg(
470            Arg::with_name("enable_rpc_bigtable_ledger_storage")
471                .long("enable-rpc-bigtable-ledger-storage")
472                .takes_value(false)
473                .hidden(hidden_unless_forced())
474                .help(
475                    "Fetch historical transaction info from a BigTable instance as a fallback to \
476                     local ledger data",
477                ),
478        )
479        .arg(
480            Arg::with_name("enable_bigtable_ledger_upload")
481                .long("enable-bigtable-ledger-upload")
482                .takes_value(false)
483                .hidden(hidden_unless_forced())
484                .help("Upload new confirmed blocks into a BigTable instance"),
485        )
486        .arg(
487            Arg::with_name("rpc_bigtable_instance")
488                .long("rpc-bigtable-instance")
489                .value_name("INSTANCE_NAME")
490                .takes_value(true)
491                .hidden(hidden_unless_forced())
492                .default_value("solana-ledger")
493                .help("Name of BigTable instance to target"),
494        )
495        .arg(
496            Arg::with_name("rpc_bigtable_app_profile_id")
497                .long("rpc-bigtable-app-profile-id")
498                .value_name("APP_PROFILE_ID")
499                .takes_value(true)
500                .hidden(hidden_unless_forced())
501                .default_value(solana_storage_bigtable::DEFAULT_APP_PROFILE_ID)
502                .help("Application profile id to use in Bigtable requests"),
503        )
504        .arg(
505            Arg::with_name("bpf_program")
506                .long("bpf-program")
507                .value_names(&["ADDRESS_OR_KEYPAIR", "SBF_PROGRAM.SO"])
508                .takes_value(true)
509                .number_of_values(2)
510                .multiple(true)
511                .help(
512                    "Add a SBF program to the genesis configuration with upgrades disabled. If \
513                     the ledger already exists then this parameter is silently ignored. The first \
514                     argument can be a pubkey string or path to a keypair",
515                ),
516        )
517        .arg(
518            Arg::with_name("upgradeable_program")
519                .long("upgradeable-program")
520                .value_names(&["ADDRESS_OR_KEYPAIR", "SBF_PROGRAM.SO", "UPGRADE_AUTHORITY"])
521                .takes_value(true)
522                .number_of_values(3)
523                .multiple(true)
524                .help(
525                    "Add an upgradeable SBF program to the genesis configuration. If the ledger \
526                     already exists then this parameter is silently ignored. First and third \
527                     arguments can be a pubkey string or path to a keypair. Upgrade authority set \
528                     to \"none\" disables upgrades",
529                ),
530        )
531        .arg(
532            Arg::with_name("account")
533                .long("account")
534                .value_names(&["ADDRESS", "DUMP.JSON"])
535                .takes_value(true)
536                .number_of_values(2)
537                .allow_hyphen_values(true)
538                .multiple(true)
539                .help(
540                    "Load an account from the provided JSON file (see `solana account --help` on \
541                     how to dump an account to file). Files are searched for relatively to CWD \
542                     and tests/fixtures. If ADDRESS is omitted via the `-` placeholder, the one \
543                     in the file will be used. If the ledger already exists then this parameter \
544                     is silently ignored",
545                ),
546        )
547        .arg(
548            Arg::with_name("account_dir")
549                .long("account-dir")
550                .value_name("DIRECTORY")
551                .validator(|value| {
552                    value
553                        .parse::<PathBuf>()
554                        .map_err(|err| format!("error parsing '{value}': {err}"))
555                        .and_then(|path| {
556                            if path.exists() && path.is_dir() {
557                                Ok(())
558                            } else {
559                                Err(format!(
560                                    "path does not exist or is not a directory: {value}"
561                                ))
562                            }
563                        })
564                })
565                .takes_value(true)
566                .multiple(true)
567                .help(
568                    "Load all the accounts from the JSON files found in the specified DIRECTORY \
569                     (see also the `--account` flag). If the ledger already exists then this \
570                     parameter is silently ignored",
571                ),
572        )
573        .arg(
574            Arg::with_name("ticks_per_slot")
575                .long("ticks-per-slot")
576                .value_name("TICKS")
577                .validator(|value| {
578                    value
579                        .parse::<u64>()
580                        .map_err(|err| format!("error parsing '{value}': {err}"))
581                        .and_then(|ticks| {
582                            if ticks < MINIMUM_TICKS_PER_SLOT {
583                                Err(format!("value must be >= {MINIMUM_TICKS_PER_SLOT}"))
584                            } else {
585                                Ok(())
586                            }
587                        })
588                })
589                .takes_value(true)
590                .help("The number of ticks in a slot"),
591        )
592        .arg(
593            Arg::with_name("slots_per_epoch")
594                .long("slots-per-epoch")
595                .value_name("SLOTS")
596                .validator(|value| {
597                    value
598                        .parse::<Slot>()
599                        .map_err(|err| format!("error parsing '{value}': {err}"))
600                        .and_then(|slot| {
601                            if slot < MINIMUM_SLOTS_PER_EPOCH {
602                                Err(format!("value must be >= {MINIMUM_SLOTS_PER_EPOCH}"))
603                            } else {
604                                Ok(())
605                            }
606                        })
607                })
608                .takes_value(true)
609                .help(
610                    "Override the number of slots in an epoch. If the ledger already exists then \
611                     this parameter is silently ignored",
612                ),
613        )
614        .arg(
615            Arg::with_name("inflation_fixed")
616                .long("inflation-fixed")
617                .value_name("RATE")
618                .validator(|value| {
619                    value
620                        .parse::<f64>()
621                        .map_err(|err| format!("error parsing '{value}': {err}"))
622                        .and_then(|rate| match rate.partial_cmp(&0.0) {
623                            Some(Ordering::Greater) | Some(Ordering::Equal) => Ok(()),
624                            Some(Ordering::Less) | None => Err(String::from("value must be >= 0")),
625                        })
626                })
627                .takes_value(true)
628                .allow_hyphen_values(true)
629                .help(
630                    "Override default inflation with fixed rate. If the ledger already exists \
631                     then this parameter is silently ignored",
632                ),
633        )
634        .arg(
635            Arg::with_name("gossip_port")
636                .long("gossip-port")
637                .value_name("PORT")
638                .takes_value(true)
639                .help("Gossip port number for the validator"),
640        )
641        .arg(
642            Arg::with_name("dynamic_port_range")
643                .long("dynamic-port-range")
644                .value_name("MIN_PORT-MAX_PORT")
645                .takes_value(true)
646                .validator(port_range_validator)
647                .help("Range to use for dynamically assigned ports [default: 1024-65535]"),
648        )
649        .arg(
650            Arg::with_name("bind_address")
651                .long("bind-address")
652                .value_name("HOST")
653                .takes_value(true)
654                .validator(solana_net_utils::is_host)
655                .default_value("127.0.0.1")
656                .help(
657                    "IPv4 address to bind the validator ports. Can be repeated. The first \
658                     --bind-address MUST be your public internet address. ALL protocols (gossip, \
659                     repair, IP echo, TVU, TPU, etc.) bind to this address on startup. Additional \
660                     --bind-address values enable multihoming for Gossip/TVU/TPU - these \
661                     protocols bind to ALL interfaces on startup. Gossip reads/sends from one \
662                     interface at a time. TVU/TPU read from ALL interfaces simultaneously but \
663                     send from only one interface at a time. When switching interfaces via \
664                     AdminRPC: Gossip switches to send/receive from the new interface, while \
665                     TVU/TPU continue receiving from ALL interfaces but send from the new \
666                     interface only.",
667                ),
668        )
669        .arg(
670            Arg::with_name("advertised_ip")
671                .long("advertised-ip")
672                .value_name("HOST")
673                .takes_value(true)
674                .validator(solana_net_utils::is_host)
675                .hidden(hidden_unless_forced())
676                .help(
677                    "Use when running a validator behind a NAT. DNS name or IP address for this \
678                     validator to advertise in gossip. This address will be used as the target \
679                     destination address for peers trying to contact this node. [default: the \
680                     first --bind-address, or ask --entrypoint when --bind-address is not \
681                     provided, or 127.0.0.1 when --entrypoint is not provided]. Note: this \
682                     argument cannot be used in a multihoming context (when multiple \
683                     --bind-address values are provided).",
684                ),
685        )
686        .arg(
687            Arg::with_name("clone_account")
688                .long("clone")
689                .short("c")
690                .value_name("ADDRESS")
691                .takes_value(true)
692                .validator(is_pubkey_or_keypair)
693                .multiple(true)
694                .requires("json_rpc_url")
695                .help(
696                    "Copy an account from the cluster referenced by the --url argument the \
697                     genesis configuration. If the ledger already exists then this parameter is \
698                     silently ignored",
699                ),
700        )
701        .arg(
702            Arg::with_name("deep_clone_address_lookup_table")
703                .long("deep-clone-address-lookup-table")
704                .takes_value(true)
705                .validator(is_pubkey_or_keypair)
706                .multiple(true)
707                .requires("json_rpc_url")
708                .help(
709                    "Copy an address lookup table and all accounts it references from the cluster \
710                     referenced by the --url argument in the genesis configuration. If the ledger \
711                     already exists then this parameter is silently ignored",
712                ),
713        )
714        .arg(
715            Arg::with_name("maybe_clone_account")
716                .long("maybe-clone")
717                .value_name("ADDRESS")
718                .takes_value(true)
719                .validator(is_pubkey_or_keypair)
720                .multiple(true)
721                .requires("json_rpc_url")
722                .help(
723                    "Copy an account from the cluster referenced by the --url argument, skipping \
724                     it if it doesn't exist. If the ledger already exists then this parameter is \
725                     silently ignored",
726                ),
727        )
728        .arg(
729            Arg::with_name("clone_upgradeable_program")
730                .long("clone-upgradeable-program")
731                .value_name("ADDRESS")
732                .takes_value(true)
733                .validator(is_pubkey_or_keypair)
734                .multiple(true)
735                .requires("json_rpc_url")
736                .help(
737                    "Copy an upgradeable program and its executable data from the cluster \
738                     referenced by the --url argument the genesis configuration. If the ledger \
739                     already exists then this parameter is silently ignored",
740                ),
741        )
742        .arg(
743            Arg::with_name("warp_slot")
744                .required(false)
745                .long("warp-slot")
746                .short("w")
747                .takes_value(true)
748                .value_name("WARP_SLOT")
749                .validator(is_slot)
750                .min_values(0)
751                .max_values(1)
752                .help(
753                    "Warp the ledger to WARP_SLOT after starting the validator. If no slot is \
754                     provided then the current slot of the cluster referenced by the --url \
755                     argument will be used",
756                ),
757        )
758        .arg(
759            Arg::with_name("limit_ledger_size")
760                .long("limit-ledger-size")
761                .value_name("SHRED_COUNT")
762                .takes_value(true)
763                .default_value(default_args.limit_ledger_size.as_str())
764                .help("Keep this amount of shreds in root slots."),
765        )
766        .arg(
767            Arg::with_name("faucet_sol")
768                .long("faucet-sol")
769                .takes_value(true)
770                .value_name("SOL")
771                .default_value(default_args.faucet_sol.as_str())
772                .help(
773                    "Give the faucet address this much SOL in genesis. If the ledger already \
774                     exists then this parameter is silently ignored",
775                ),
776        )
777        .arg(
778            Arg::with_name("faucet_time_slice_secs")
779                .long("faucet-time-slice-secs")
780                .takes_value(true)
781                .value_name("SECS")
782                .default_value(default_args.faucet_time_slice_secs.as_str())
783                .help("Time slice (in secs) over which to limit faucet requests"),
784        )
785        .arg(
786            Arg::with_name("faucet_per_time_sol_cap")
787                .long("faucet-per-time-sol-cap")
788                .takes_value(true)
789                .value_name("SOL")
790                .min_values(0)
791                .max_values(1)
792                .help("Per-time slice limit for faucet requests, in SOL"),
793        )
794        .arg(
795            Arg::with_name("faucet_per_request_sol_cap")
796                .long("faucet-per-request-sol-cap")
797                .takes_value(true)
798                .value_name("SOL")
799                .min_values(0)
800                .max_values(1)
801                .help("Per-request limit for faucet requests, in SOL"),
802        )
803        .arg(
804            Arg::with_name("geyser_plugin_config")
805                .long("geyser-plugin-config")
806                .alias("accountsdb-plugin-config")
807                .value_name("FILE")
808                .takes_value(true)
809                .multiple(true)
810                .help("Specify the configuration file for the Geyser plugin."),
811        )
812        .arg(
813            Arg::with_name("enable_scheduler_bindings")
814                .long("enable-scheduler-bindings")
815                .takes_value(false)
816                .help("Enables external processes to connect and manage block production"),
817        )
818        .arg(
819            Arg::with_name("deactivate_feature")
820                .long("deactivate-feature")
821                .takes_value(true)
822                .value_name("FEATURE_PUBKEY")
823                .validator(is_pubkey)
824                .multiple(true)
825                .help("deactivate this feature in genesis."),
826        )
827        .arg(
828            Arg::with_name("compute_unit_limit")
829                .long("compute-unit-limit")
830                .alias("max-compute-units")
831                .value_name("COMPUTE_UNITS")
832                .validator(is_parsable::<u64>)
833                .takes_value(true)
834                .help("Override the runtime's compute unit limit per transaction"),
835        )
836        .arg(
837            Arg::with_name("log_messages_bytes_limit")
838                .long("log-messages-bytes-limit")
839                .value_name("BYTES")
840                .validator(is_parsable::<usize>)
841                .takes_value(true)
842                .help("Maximum number of bytes written to the program log before truncation"),
843        )
844        .arg(
845            Arg::with_name("transaction_account_lock_limit")
846                .long("transaction-account-lock-limit")
847                .value_name("NUM_ACCOUNTS")
848                .validator(is_parsable::<u64>)
849                .takes_value(true)
850                .help("Override the runtime's account lock limit per transaction"),
851        )
852        .arg(
853            Arg::with_name("clone_feature_set")
854                .long("clone-feature-set")
855                .takes_value(false)
856                .requires("json_rpc_url")
857                .help(
858                    "Copy a feature set from the cluster referenced by the --url argument in the \
859                     genesis configuration. If the ledger already exists then this parameter is \
860                     silently ignored",
861                ),
862        )
863        .args(&pub_sub_config::args(/*test_validator:*/ true))
864}
865
866pub struct DefaultTestArgs {
867    pub rpc_port: String,
868    pub faucet_port: String,
869    pub limit_ledger_size: String,
870    pub faucet_sol: String,
871    pub faucet_time_slice_secs: String,
872}
873
874impl DefaultTestArgs {
875    pub fn new() -> Self {
876        DefaultTestArgs {
877            rpc_port: 8899.to_string(),
878            faucet_port: FAUCET_PORT.to_string(),
879            /* 10,000 was derived empirically by watching the size
880             * of the rocksdb/ directory self-limit itself to the
881             * 40MB-150MB range when running `solana-test-validator`
882             */
883            limit_ledger_size: 10_000.to_string(),
884            faucet_sol: (1_000_000.).to_string(),
885            faucet_time_slice_secs: (faucet::TIME_SLICE).to_string(),
886        }
887    }
888}
889
890impl Default for DefaultTestArgs {
891    fn default() -> Self {
892        Self::new()
893    }
894}
895
896#[cfg(test)]
897mod test {
898    use super::*;
899
900    #[test]
901    fn make_sure_deprecated_arguments_are_sorted_alphabetically() {
902        let deprecated = deprecated_arguments();
903
904        for i in 0..deprecated.len().saturating_sub(1) {
905            let curr_name = deprecated[i].arg.b.name;
906            let next_name = deprecated[i + 1].arg.b.name;
907
908            assert!(
909                curr_name != next_name,
910                "Arguments in `deprecated_arguments()` should be distinct.\nArguments {} and {} \
911                 use the same name: {}",
912                i,
913                i + 1,
914                curr_name,
915            );
916
917            assert!(
918                curr_name < next_name,
919                "To generate better diffs and for readability purposes, `deprecated_arguments()` \
920                 should list arguments in alphabetical order.\nArguments {} and {} are \
921                 not.\nArgument {} name: {}\nArgument {} name: {}",
922                i,
923                i + 1,
924                i,
925                curr_name,
926                i + 1,
927                next_name,
928            );
929        }
930    }
931}