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