agave_validator/
cli.rs

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