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        usage_warning: "Shrink paths are no longer used.",
141    );
142    add_arg!(
143        // deprecated in v4.2.0; the `mmap` value was deprecated in v4.0.0, and now mmap mode has
144        // been removed entirely. The only remaining mode (`file`) is the default and only
145        // behavior, so this flag is now a no-op.
146        Arg::with_name("accounts_db_access_storages_method")
147            .long("accounts-db-access-storages-method")
148            .value_name("METHOD")
149            .takes_value(true)
150            .possible_values(&["mmap", "file"])
151            .help("No-op; account storages are always accessed via file I/O"),
152    );
153    add_arg!(
154        // deprecated in v4.2.0
155        Arg::with_name("accounts_db_cache_limit_mb")
156            .long("accounts-db-cache-limit-mb")
157            .value_name("MEGABYTES")
158            .validator(is_parsable::<u64>)
159            .takes_value(true)
160            .help(
161                "How large the write cache for account data can become. If this is exceeded, the \
162                 cache is flushed more aggressively.",
163            )
164            .conflicts_with("accounts_db_write_cache_limit"),
165        replaced_by: "accounts-db-write-cache-limit",
166    );
167    add_arg!(
168        // deprecated in v4.0.0
169        Arg::with_name("enable_accounts_disk_index")
170            .long("enable-accounts-disk-index")
171            .help("Enables the disk-based accounts index")
172            .conflicts_with("accounts_index_limit"),
173        replaced_by: "accounts-index-limit",
174    );
175    add_arg!(
176        // deprecated in v4.2.0
177        Arg::with_name("experimental_poh_pinned_cpu_core")
178            .long("experimental-poh-pinned-cpu-core")
179            .takes_value(true)
180            .value_name("CPU_ID")
181            .conflicts_with("poh_pinned_cpu_core")
182            .validator(is_parsable::<usize>)
183            .help("Specify which CPU core PoH is pinned to. Use --poh-pinned-cpu-core instead"),
184        replaced_by: "poh-pinned-cpu-core",
185    );
186    add_arg!(
187        // deprecated in v4.1.0
188        Arg::with_name("experimental_retransmit_xdp_cpu_cores")
189            .long("experimental-retransmit-xdp-cpu-cores")
190            .takes_value(true)
191            .value_name("CPU_LIST")
192            .conflicts_with("xdp_cpu_cores")
193            .conflicts_with("no_xdp")
194            .validator(|value| {
195                validate_cpu_ranges(value, "--experimental-retransmit-xdp-cpu-cores")
196            })
197            .help("CPU cores to reserve for XDP. Use --xdp-cpu-cores instead"),
198        replaced_by: "xdp-cpu-cores",
199    );
200    add_arg!(
201        // deprecated in v4.1.0
202        Arg::with_name("experimental_retransmit_xdp_interface")
203            .long("experimental-retransmit-xdp-interface")
204            .takes_value(true)
205            .value_name("INTERFACE")
206            .conflicts_with("xdp_interface")
207            .conflicts_with("no_xdp")
208            .help("Network interface to use for XDP. Use --xdp-interface instead"),
209        replaced_by: "xdp-interface",
210    );
211    add_arg!(
212        // deprecated in v4.1.0
213        Arg::with_name("experimental_retransmit_xdp_zero_copy")
214            .long("experimental-retransmit-xdp-zero-copy")
215            .takes_value(false)
216            .conflicts_with("xdp_zero_copy")
217            .conflicts_with("no_xdp")
218            .help("Enable XDP zero copy. Use --xdp-zero-copy instead"),
219        replaced_by: "xdp-zero-copy",
220    );
221    add_arg!(
222        // deprecated in v4.0.0
223        Arg::with_name("tpu_connection_pool_size")
224            .long("tpu-connection-pool-size")
225            .takes_value(true)
226            .validator(is_parsable::<usize>)
227            .help("Controls the TPU connection pool size per remote address"),
228         usage_warning:"This parameter is misleading, avoid setting it",
229    );
230    res
231}
232
233// Helper to add arguments that are no longer used but are being kept around to avoid breaking
234// validator startup commands.
235fn get_deprecated_arguments() -> Vec<Arg<'static, 'static>> {
236    deprecated_arguments()
237        .into_iter()
238        .map(|info| {
239            let arg = info.arg;
240            // Hide all deprecated arguments by default.
241            arg.hidden(hidden_unless_forced())
242        })
243        .collect()
244}
245
246pub fn warn_for_deprecated_arguments(matches: &ArgMatches) {
247    for DeprecatedArg {
248        arg,
249        replaced_by,
250        usage_warning,
251    } in deprecated_arguments().into_iter()
252    {
253        if matches.is_present(arg.b.name) {
254            let mut msg = format!("--{} is deprecated", arg.b.name.replace('_', "-"));
255            if let Some(replaced_by) = replaced_by {
256                msg.push_str(&format!(", please use --{replaced_by}"));
257            }
258            msg.push('.');
259            if let Some(usage_warning) = usage_warning {
260                msg.push_str(&format!("  {usage_warning}"));
261                if !msg.ends_with('.') {
262                    msg.push('.');
263                }
264            }
265            warn!("{msg}");
266        }
267    }
268}
269
270pub struct DefaultArgs {
271    pub bind_address: String,
272    pub dynamic_port_range: String,
273    pub ledger_path: String,
274
275    pub tower_storage: String,
276    pub send_transaction_service_config: send_transaction_service::Config,
277
278    pub maximum_local_snapshot_age: String,
279    pub maximum_full_snapshot_archives_to_retain: String,
280    pub maximum_incremental_snapshot_archives_to_retain: String,
281    pub snapshot_packager_niceness_adjustment: String,
282    pub full_snapshot_archive_interval_slots: String,
283    pub incremental_snapshot_archive_interval_slots: String,
284    pub min_snapshot_download_speed: String,
285    pub max_snapshot_download_abort: String,
286
287    pub contact_debug_interval: String,
288
289    pub snapshot_version: SnapshotVersion,
290    pub snapshot_archive_format: String,
291    pub snapshot_zstd_compression_level: String,
292
293    pub accounts_shrink_optimize_total_space: String,
294    pub accounts_shrink_ratio: String,
295
296    pub tpu_max_connections_per_unstaked_peer: String,
297    pub tpu_max_connections_per_staked_peer: String,
298    pub tpu_max_connections_per_ipaddr_per_minute: String,
299    pub tpu_max_staked_connections: String,
300    pub tpu_max_unstaked_connections: String,
301    pub tpu_max_fwd_staked_connections: String,
302    pub tpu_max_fwd_unstaked_connections: String,
303    pub tpu_max_streams_per_ms: String,
304
305    pub num_quic_endpoints: String,
306    pub vote_use_quic: String,
307
308    pub banking_trace_dir_byte_limit: String,
309    pub block_production_pacing_fill_time_millis: String,
310
311    pub thread_args: DefaultThreadArgs,
312}
313
314impl DefaultArgs {
315    pub fn new() -> Self {
316        DefaultArgs {
317            bind_address: "0.0.0.0".to_string(),
318            ledger_path: "ledger".to_string(),
319            dynamic_port_range: format!("{}-{}", VALIDATOR_PORT_RANGE.0, VALIDATOR_PORT_RANGE.1),
320            maximum_local_snapshot_age: "2500".to_string(),
321            tower_storage: "file".to_string(),
322            send_transaction_service_config: send_transaction_service::Config::default(),
323            maximum_full_snapshot_archives_to_retain: DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN
324                .to_string(),
325            maximum_incremental_snapshot_archives_to_retain:
326                DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN.to_string(),
327            snapshot_packager_niceness_adjustment: "0".to_string(),
328            full_snapshot_archive_interval_slots: DEFAULT_FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS
329                .get()
330                .to_string(),
331            incremental_snapshot_archive_interval_slots:
332                DEFAULT_INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS
333                    .get()
334                    .to_string(),
335            min_snapshot_download_speed: DEFAULT_MIN_SNAPSHOT_DOWNLOAD_SPEED.to_string(),
336            max_snapshot_download_abort: MAX_SNAPSHOT_DOWNLOAD_ABORT.to_string(),
337            snapshot_archive_format: DEFAULT_ARCHIVE_COMPRESSION.to_string(),
338            snapshot_zstd_compression_level: "1".to_string(), // level 1 is optimized for speed
339            contact_debug_interval: "120000".to_string(),
340            snapshot_version: SnapshotVersion::default(),
341            accounts_shrink_optimize_total_space: DEFAULT_ACCOUNTS_SHRINK_OPTIMIZE_TOTAL_SPACE
342                .to_string(),
343            accounts_shrink_ratio: DEFAULT_ACCOUNTS_SHRINK_RATIO.to_string(),
344            tpu_max_connections_per_ipaddr_per_minute:
345                DEFAULT_MAX_CONNECTIONS_PER_IPADDR_PER_MINUTE.to_string(),
346            vote_use_quic: DEFAULT_VOTE_USE_QUIC.to_string(),
347            tpu_max_connections_per_unstaked_peer: DEFAULT_MAX_QUIC_CONNECTIONS_PER_UNSTAKED_PEER
348                .to_string(),
349            tpu_max_connections_per_staked_peer: DEFAULT_MAX_QUIC_CONNECTIONS_PER_STAKED_PEER
350                .to_string(),
351            tpu_max_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS.to_string(),
352            tpu_max_unstaked_connections: DEFAULT_MAX_UNSTAKED_CONNECTIONS.to_string(),
353            tpu_max_fwd_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS
354                .saturating_add(DEFAULT_MAX_UNSTAKED_CONNECTIONS)
355                .to_string(),
356            tpu_max_fwd_unstaked_connections: 0.to_string(),
357            tpu_max_streams_per_ms: DEFAULT_MAX_STREAMS_PER_MS.to_string(),
358            num_quic_endpoints: DEFAULT_QUIC_ENDPOINTS.to_string(),
359            banking_trace_dir_byte_limit: BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT.to_string(),
360            block_production_pacing_fill_time_millis: BankingStage::default_fill_time_millis()
361                .to_string(),
362            thread_args: DefaultThreadArgs::default(),
363        }
364    }
365}
366
367impl Default for DefaultArgs {
368    fn default() -> Self {
369        Self::new()
370    }
371}
372
373pub fn port_validator(port: String) -> Result<(), String> {
374    port.parse::<u16>()
375        .map(|_| ())
376        .map_err(|e| format!("{e:?}"))
377}
378
379pub fn port_range_validator(port_range: String) -> Result<(), String> {
380    if let Some((start, end)) = solana_net_utils::parse_port_range(&port_range) {
381        // The port range is half-open: [start, end)
382        if end - start < MINIMUM_VALIDATOR_PORT_RANGE_WIDTH {
383            Err(format!(
384                "Port range is too small.  Try --dynamic-port-range {}-{}",
385                start,
386                start + MINIMUM_VALIDATOR_PORT_RANGE_WIDTH
387            ))
388        } else {
389            Ok(())
390        }
391    } else {
392        Err("Invalid port range".to_string())
393    }
394}
395
396pub(crate) fn hash_validator(hash: String) -> Result<(), String> {
397    Hash::from_str(&hash)
398        .map(|_| ())
399        .map_err(|e| format!("{e:?}"))
400}
401
402/// Test validator
403pub fn test_app<'a>(version: &'a str, default_args: &'a DefaultTestArgs) -> App<'a, 'a> {
404    App::new("solana-test-validator")
405        .about("Test Validator")
406        .version(version)
407        .arg({
408            let arg = Arg::with_name("config_file")
409                .short("C")
410                .long("config")
411                .value_name("PATH")
412                .takes_value(true)
413                .help("Configuration file to use");
414            if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE {
415                arg.default_value(config_file)
416            } else {
417                arg
418            }
419        })
420        .arg(
421            Arg::with_name("json_rpc_url")
422                .short("u")
423                .long("url")
424                .value_name("URL_OR_MONIKER")
425                .takes_value(true)
426                .validator(is_url_or_moniker)
427                .help(
428                    "URL for Solana's JSON RPC or moniker (or their first letter): [mainnet-beta, \
429                     testnet, devnet, localhost]",
430                ),
431        )
432        .arg(
433            Arg::with_name("mint_address")
434                .long("mint")
435                .value_name("PUBKEY")
436                .validator(is_pubkey)
437                .takes_value(true)
438                .help(
439                    "Address of the mint account that will receive tokens created at genesis. If \
440                     the ledger already exists then this parameter is silently ignored [default: \
441                     client keypair]",
442                ),
443        )
444        .arg(
445            Arg::with_name("ledger_path")
446                .short("l")
447                .long("ledger")
448                .value_name("DIR")
449                .takes_value(true)
450                .required(true)
451                .default_value("test-ledger")
452                .help("Use DIR as ledger location"),
453        )
454        .arg(
455            Arg::with_name("reset")
456                .short("r")
457                .long("reset")
458                .takes_value(false)
459                .help(
460                    "Reset the ledger to genesis if it exists. By default the validator will \
461                     resume an existing ledger (if present)",
462                ),
463        )
464        .arg(
465            Arg::with_name("quiet")
466                .short("q")
467                .long("quiet")
468                .takes_value(false)
469                .conflicts_with("log")
470                .help("Quiet mode: suppress normal output"),
471        )
472        .arg(
473            Arg::with_name("log")
474                .long("log")
475                .takes_value(false)
476                .conflicts_with("quiet")
477                .help("Log mode: stream the validator log"),
478        )
479        .arg(
480            Arg::with_name("account_indexes")
481                .long("account-index")
482                .takes_value(true)
483                .multiple(true)
484                .possible_values(&["program-id", "spl-token-owner", "spl-token-mint"])
485                .value_name("INDEX")
486                .help("Enable an accounts index, indexed by the selected account field"),
487        )
488        .arg(
489            Arg::with_name("faucet_port")
490                .long("faucet-port")
491                .value_name("PORT")
492                .takes_value(true)
493                .default_value(&default_args.faucet_port)
494                .validator(port_validator)
495                .help("Enable the faucet on this port"),
496        )
497        .arg(
498            Arg::with_name("rpc_port")
499                .long("rpc-port")
500                .value_name("PORT")
501                .takes_value(true)
502                .default_value(&default_args.rpc_port)
503                .validator(port_validator)
504                .help("Enable JSON RPC on this port, and the next port for the RPC websocket"),
505        )
506        .arg(
507            Arg::with_name("enable_rpc_bigtable_ledger_storage")
508                .long("enable-rpc-bigtable-ledger-storage")
509                .takes_value(false)
510                .hidden(hidden_unless_forced())
511                .help(
512                    "Fetch historical transaction info from a BigTable instance as a fallback to \
513                     local ledger data",
514                ),
515        )
516        .arg(
517            Arg::with_name("enable_bigtable_ledger_upload")
518                .long("enable-bigtable-ledger-upload")
519                .takes_value(false)
520                .hidden(hidden_unless_forced())
521                .help("Upload new confirmed blocks into a BigTable instance"),
522        )
523        .arg(
524            Arg::with_name("rpc_bigtable_instance")
525                .long("rpc-bigtable-instance")
526                .value_name("INSTANCE_NAME")
527                .takes_value(true)
528                .hidden(hidden_unless_forced())
529                .default_value("solana-ledger")
530                .help("Name of BigTable instance to target"),
531        )
532        .arg(
533            Arg::with_name("rpc_bigtable_app_profile_id")
534                .long("rpc-bigtable-app-profile-id")
535                .value_name("APP_PROFILE_ID")
536                .takes_value(true)
537                .hidden(hidden_unless_forced())
538                .default_value(solana_storage_bigtable::DEFAULT_APP_PROFILE_ID)
539                .help("Application profile id to use in Bigtable requests"),
540        )
541        .arg(
542            Arg::with_name("bpf_program")
543                .long("bpf-program")
544                .value_names(&["ADDRESS_OR_KEYPAIR", "SBF_PROGRAM.SO"])
545                .takes_value(true)
546                .number_of_values(2)
547                .multiple(true)
548                .help(
549                    "Add a SBF program to the genesis configuration with upgrades disabled. If \
550                     the ledger already exists then this parameter is silently ignored. The first \
551                     argument can be a pubkey string or path to a keypair",
552                ),
553        )
554        .arg(
555            Arg::with_name("upgradeable_program")
556                .long("upgradeable-program")
557                .value_names(&["ADDRESS_OR_KEYPAIR", "SBF_PROGRAM.SO", "UPGRADE_AUTHORITY"])
558                .takes_value(true)
559                .number_of_values(3)
560                .multiple(true)
561                .help(
562                    "Add an upgradeable SBF program to the genesis configuration. If the ledger \
563                     already exists then this parameter is silently ignored. First and third \
564                     arguments can be a pubkey string or path to a keypair. Upgrade authority set \
565                     to \"none\" disables upgrades",
566                ),
567        )
568        .arg(
569            Arg::with_name("account")
570                .long("account")
571                .value_names(&["ADDRESS", "DUMP.JSON"])
572                .takes_value(true)
573                .number_of_values(2)
574                .allow_hyphen_values(true)
575                .multiple(true)
576                .help(
577                    "Load an account from the provided JSON file (see `solana account --help` on \
578                     how to dump an account to file). Files are searched for relatively to CWD \
579                     and tests/fixtures. If ADDRESS is omitted via the `-` placeholder, the one \
580                     in the file will be used. If the ledger already exists then this parameter \
581                     is silently ignored",
582                ),
583        )
584        .arg(
585            Arg::with_name("account_dir")
586                .long("account-dir")
587                .value_name("DIRECTORY")
588                .validator(|value| {
589                    value
590                        .parse::<PathBuf>()
591                        .map_err(|err| format!("error parsing '{value}': {err}"))
592                        .and_then(|path| {
593                            if path.exists() && path.is_dir() {
594                                Ok(())
595                            } else {
596                                Err(format!(
597                                    "path does not exist or is not a directory: {value}"
598                                ))
599                            }
600                        })
601                })
602                .takes_value(true)
603                .multiple(true)
604                .help(
605                    "Load all the accounts from the JSON files found in the specified DIRECTORY \
606                     (see also the `--account` flag). If the ledger already exists then this \
607                     parameter is silently ignored",
608                ),
609        )
610        .arg(
611            Arg::with_name("ticks_per_slot")
612                .long("ticks-per-slot")
613                .value_name("TICKS")
614                .validator(|value| {
615                    value
616                        .parse::<u64>()
617                        .map_err(|err| format!("error parsing '{value}': {err}"))
618                        .and_then(|ticks| {
619                            if ticks < MINIMUM_TICKS_PER_SLOT {
620                                Err(format!("value must be >= {MINIMUM_TICKS_PER_SLOT}"))
621                            } else {
622                                Ok(())
623                            }
624                        })
625                })
626                .takes_value(true)
627                .help("The number of ticks in a slot"),
628        )
629        .arg(
630            Arg::with_name("slots_per_epoch")
631                .long("slots-per-epoch")
632                .value_name("SLOTS")
633                .validator(|value| {
634                    value
635                        .parse::<Slot>()
636                        .map_err(|err| format!("error parsing '{value}': {err}"))
637                        .and_then(|slot| {
638                            if slot < MINIMUM_SLOTS_PER_EPOCH {
639                                Err(format!("value must be >= {MINIMUM_SLOTS_PER_EPOCH}"))
640                            } else {
641                                Ok(())
642                            }
643                        })
644                })
645                .takes_value(true)
646                .help(
647                    "Override the number of slots in an epoch. If the ledger already exists then \
648                     this parameter is silently ignored",
649                ),
650        )
651        .arg(
652            Arg::with_name("inflation_fixed")
653                .long("inflation-fixed")
654                .value_name("RATE")
655                .validator(|value| {
656                    value
657                        .parse::<f64>()
658                        .map_err(|err| format!("error parsing '{value}': {err}"))
659                        .and_then(|rate| match rate.partial_cmp(&0.0) {
660                            Some(Ordering::Greater) | Some(Ordering::Equal) => Ok(()),
661                            Some(Ordering::Less) | None => Err(String::from("value must be >= 0")),
662                        })
663                })
664                .takes_value(true)
665                .allow_hyphen_values(true)
666                .help(
667                    "Override default inflation with fixed rate. If the ledger already exists \
668                     then this parameter is silently ignored",
669                ),
670        )
671        .arg(
672            Arg::with_name("gossip_port")
673                .long("gossip-port")
674                .value_name("PORT")
675                .takes_value(true)
676                .help("Gossip port number for the validator"),
677        )
678        .arg(
679            Arg::with_name("dynamic_port_range")
680                .long("dynamic-port-range")
681                .value_name("MIN_PORT-MAX_PORT")
682                .takes_value(true)
683                .default_value(&default_args.dynamic_port_range)
684                .validator(port_range_validator)
685                .help(
686                    "Range to use for dynamically assigned ports. MIN_PORT-MAX_PORT yields the \
687                     range [MIN_PORT, MAX_PORT)",
688                ),
689        )
690        .arg(
691            Arg::with_name("bind_address")
692                .long("bind-address")
693                .value_name("HOST")
694                .takes_value(true)
695                .validator(solana_net_utils::is_host)
696                .default_value("127.0.0.1")
697                .help(
698                    "IPv4 address to bind the validator ports. Can be repeated. The first \
699                     --bind-address MUST be your public internet address. ALL protocols (gossip, \
700                     repair, IP echo, TVU, TPU, etc.) bind to this address on startup. Additional \
701                     --bind-address values enable multihoming for Gossip/TVU/TPU - these \
702                     protocols bind to ALL interfaces on startup. Gossip reads/sends from one \
703                     interface at a time. TVU/TPU read from ALL interfaces simultaneously but \
704                     send from only one interface at a time. When switching interfaces via \
705                     AdminRPC: Gossip switches to send/receive from the new interface, while \
706                     TVU/TPU continue receiving from ALL interfaces but send from the new \
707                     interface only.",
708                ),
709        )
710        .arg(
711            Arg::with_name("advertised_ip")
712                .long("advertised-ip")
713                .value_name("HOST")
714                .takes_value(true)
715                .validator(solana_net_utils::is_host)
716                .hidden(hidden_unless_forced())
717                .help(
718                    "Use when running a validator behind a NAT. DNS name or IP address for this \
719                     validator to advertise in gossip. This address will be used as the target \
720                     destination address for peers trying to contact this node. [default: the \
721                     first --bind-address, or ask --entrypoint when --bind-address is not \
722                     provided, or 127.0.0.1 when --entrypoint is not provided]. Note: this \
723                     argument cannot be used in a multihoming context (when multiple \
724                     --bind-address values are provided).",
725                ),
726        )
727        .arg(
728            Arg::with_name("clone_account")
729                .long("clone")
730                .short("c")
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 account from the cluster referenced by the --url argument the \
738                     genesis configuration. If the ledger already exists then this parameter is \
739                     silently ignored",
740                ),
741        )
742        .arg(
743            Arg::with_name("deep_clone_address_lookup_table")
744                .long("deep-clone-address-lookup-table")
745                .takes_value(true)
746                .validator(is_pubkey_or_keypair)
747                .multiple(true)
748                .requires("json_rpc_url")
749                .help(
750                    "Copy an address lookup table and all accounts it references from the cluster \
751                     referenced by the --url argument in the genesis configuration. If the ledger \
752                     already exists then this parameter is silently ignored",
753                ),
754        )
755        .arg(
756            Arg::with_name("maybe_clone_account")
757                .long("maybe-clone")
758                .value_name("ADDRESS")
759                .takes_value(true)
760                .validator(is_pubkey_or_keypair)
761                .multiple(true)
762                .requires("json_rpc_url")
763                .help(
764                    "Copy an account from the cluster referenced by the --url argument, skipping \
765                     it if it doesn't exist. If the ledger already exists then this parameter is \
766                     silently ignored",
767                ),
768        )
769        .arg(
770            Arg::with_name("clone_upgradeable_program")
771                .long("clone-upgradeable-program")
772                .value_name("ADDRESS")
773                .takes_value(true)
774                .validator(is_pubkey_or_keypair)
775                .multiple(true)
776                .requires("json_rpc_url")
777                .help(
778                    "Copy an upgradeable program and its executable data from the cluster \
779                     referenced by the --url argument the genesis configuration. If the ledger \
780                     already exists then this parameter is silently ignored",
781                ),
782        )
783        .arg(
784            Arg::with_name("warp_slot")
785                .required(false)
786                .long("warp-slot")
787                .short("w")
788                .takes_value(true)
789                .value_name("WARP_SLOT")
790                .validator(is_slot)
791                .min_values(0)
792                .max_values(1)
793                .help(
794                    "Warp the ledger to WARP_SLOT after starting the validator. If no slot is \
795                     provided then the current slot of the cluster referenced by the --url \
796                     argument will be used",
797                ),
798        )
799        .arg(
800            Arg::with_name("limit_ledger_size")
801                .long("limit-ledger-size")
802                .value_name("SHRED_COUNT")
803                .takes_value(true)
804                .default_value(default_args.limit_ledger_size.as_str())
805                .help("Keep this amount of shreds in root slots."),
806        )
807        .arg(
808            Arg::with_name("faucet_sol")
809                .long("faucet-sol")
810                .takes_value(true)
811                .value_name("SOL")
812                .default_value(default_args.faucet_sol.as_str())
813                .help(
814                    "Give the faucet address this much SOL in genesis. If the ledger already \
815                     exists then this parameter is silently ignored",
816                ),
817        )
818        .arg(
819            Arg::with_name("faucet_time_slice_secs")
820                .long("faucet-time-slice-secs")
821                .takes_value(true)
822                .value_name("SECS")
823                .default_value(default_args.faucet_time_slice_secs.as_str())
824                .help("Time slice (in secs) over which to limit faucet requests"),
825        )
826        .arg(
827            Arg::with_name("faucet_per_time_sol_cap")
828                .long("faucet-per-time-sol-cap")
829                .takes_value(true)
830                .value_name("SOL")
831                .min_values(0)
832                .max_values(1)
833                .help("Per-time slice limit for faucet requests, in SOL"),
834        )
835        .arg(
836            Arg::with_name("faucet_per_request_sol_cap")
837                .long("faucet-per-request-sol-cap")
838                .takes_value(true)
839                .value_name("SOL")
840                .min_values(0)
841                .max_values(1)
842                .help("Per-request limit for faucet requests, in SOL"),
843        )
844        .arg(
845            Arg::with_name("geyser_plugin_config")
846                .long("geyser-plugin-config")
847                .alias("accountsdb-plugin-config")
848                .value_name("FILE")
849                .takes_value(true)
850                .multiple(true)
851                .help("Specify the configuration file for the Geyser plugin."),
852        )
853        .arg(
854            Arg::with_name("enable_scheduler_bindings")
855                .long("enable-scheduler-bindings")
856                .takes_value(false)
857                .help("Enables external processes to connect and manage block production"),
858        )
859        .arg(
860            Arg::with_name("alpenglow")
861                .long("alpenglow")
862                .takes_value(false)
863                .help(
864                    "Activate Alpenglow at genesis. The validator_admission_ticket feature must \
865                     remain active",
866                ),
867        )
868        .arg(
869            Arg::with_name("deactivate_feature")
870                .long("deactivate-feature")
871                .takes_value(true)
872                .value_name("FEATURE_PUBKEY")
873                .validator(is_pubkey)
874                .multiple(true)
875                .help("deactivate this feature in genesis."),
876        )
877        .arg(
878            Arg::with_name("compute_unit_limit")
879                .long("compute-unit-limit")
880                .alias("max-compute-units")
881                .value_name("COMPUTE_UNITS")
882                .validator(is_parsable::<u64>)
883                .takes_value(true)
884                .help("Override the runtime's compute unit limit per transaction"),
885        )
886        .arg(
887            Arg::with_name("log_messages_bytes_limit")
888                .long("log-messages-bytes-limit")
889                .value_name("BYTES")
890                .validator(is_parsable::<usize>)
891                .takes_value(true)
892                .help("Maximum number of bytes written to the program log before truncation"),
893        )
894        .arg(
895            Arg::with_name("transaction_account_lock_limit")
896                .long("transaction-account-lock-limit")
897                .value_name("NUM_ACCOUNTS")
898                .validator(is_parsable::<u64>)
899                .takes_value(true)
900                .help("Override the runtime's account lock limit per transaction"),
901        )
902        .arg(
903            Arg::with_name("clone_feature_set")
904                .long("clone-feature-set")
905                .takes_value(false)
906                .requires("json_rpc_url")
907                .help(
908                    "Copy a feature set from the cluster referenced by the --url argument in the \
909                     genesis configuration. If the ledger already exists then this parameter is \
910                     silently ignored",
911                ),
912        )
913        .args(&pub_sub_config::args(/*test_validator:*/ true))
914}
915
916pub struct DefaultTestArgs {
917    pub rpc_port: String,
918    pub faucet_port: String,
919    pub dynamic_port_range: String,
920    pub limit_ledger_size: String,
921    pub faucet_sol: String,
922    pub faucet_time_slice_secs: String,
923}
924
925impl DefaultTestArgs {
926    pub fn new() -> Self {
927        DefaultTestArgs {
928            rpc_port: 8899.to_string(),
929            faucet_port: FAUCET_PORT.to_string(),
930            dynamic_port_range: format!("{}-{}", VALIDATOR_PORT_RANGE.0, VALIDATOR_PORT_RANGE.1),
931            /* 10,000 was derived empirically by watching the size
932             * of the rocksdb/ directory self-limit itself to the
933             * 40MB-150MB range when running `solana-test-validator`
934             */
935            limit_ledger_size: 10_000.to_string(),
936            faucet_sol: (1_000_000.).to_string(),
937            faucet_time_slice_secs: (faucet::TIME_SLICE).to_string(),
938        }
939    }
940}
941
942impl Default for DefaultTestArgs {
943    fn default() -> Self {
944        Self::new()
945    }
946}
947
948#[cfg(test)]
949mod test {
950    use super::*;
951
952    #[test]
953    fn make_sure_deprecated_arguments_are_sorted_alphabetically() {
954        let deprecated = deprecated_arguments();
955
956        for i in 0..deprecated.len().saturating_sub(1) {
957            let curr_name = deprecated[i].arg.b.name;
958            let next_name = deprecated[i + 1].arg.b.name;
959
960            assert!(
961                curr_name != next_name,
962                "Arguments in `deprecated_arguments()` should be distinct.\nArguments {} and {} \
963                 use the same name: {}",
964                i,
965                i + 1,
966                curr_name,
967            );
968
969            assert!(
970                curr_name < next_name,
971                "To generate better diffs and for readability purposes, `deprecated_arguments()` \
972                 should list arguments in alphabetical order.\nArguments {} and {} are \
973                 not.\nArgument {} name: {}\nArgument {} name: {}",
974                i,
975                i + 1,
976                i,
977                curr_name,
978                i + 1,
979                next_name,
980            );
981        }
982    }
983}