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