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!(Arg::with_name("disable_accounts_disk_index")
191        // (actually) deprecated in v3.1.0
192        .long("disable-accounts-disk-index")
193        .help("Disable the disk-based accounts index if it is enabled by default.")
194        .conflicts_with("enable_accounts_disk_index"),
195        usage_warning: "The disk-based accounts index is disabled by default",
196    );
197    add_arg!(
198        // deprecated in v3.0.0
199        Arg::with_name("gossip_host")
200            .long("gossip-host")
201            .value_name("HOST")
202            .takes_value(true)
203            .validator(solana_net_utils::is_host),
204            replaced_by : "bind-address",
205            usage_warning:"Use --bind-address instead",
206    );
207    add_arg!(
208        // deprecated in v3.1.0
209        Arg::with_name("tpu_coalesce_ms")
210            .long("tpu-coalesce-ms")
211            .value_name("MILLISECS")
212            .takes_value(true)
213            .validator(is_parsable::<u64>)
214            .help("Milliseconds to wait in the TPU receiver for packet coalescing."),
215            usage_warning:"tpu_coalesce will be dropped (currently ignored)",
216    );
217    add_arg!(
218        // deprecated in v3.0.0
219        Arg::with_name("tpu_disable_quic")
220            .long("tpu-disable-quic")
221            .takes_value(false)
222            .help("Do not use QUIC to send transactions."),
223        usage_warning: "UDP support will be dropped"
224    );
225    add_arg!(
226        // deprecated in v3.0.0
227        Arg::with_name("tpu_enable_udp")
228            .long("tpu-enable-udp")
229            .takes_value(false)
230            .help("Enable UDP for receiving/sending transactions."),
231        usage_warning: "UDP support will be dropped"
232    );
233    add_arg!(
234        // deprecated in v3.1.0
235        Arg::with_name("transaction_struct")
236            .long("transaction-structure")
237            .value_name("STRUCT")
238            .takes_value(true)
239            .possible_values(TransactionStructure::cli_names())
240            .help(TransactionStructure::cli_message()),
241        usage_warning: "Transaction structure is no longer configurable"
242    );
243    res
244}
245
246// Helper to add arguments that are no longer used but are being kept around to avoid breaking
247// validator startup commands.
248fn get_deprecated_arguments() -> Vec<Arg<'static, 'static>> {
249    deprecated_arguments()
250        .into_iter()
251        .map(|info| {
252            let arg = info.arg;
253            // Hide all deprecated arguments by default.
254            arg.hidden(hidden_unless_forced())
255        })
256        .collect()
257}
258
259pub fn warn_for_deprecated_arguments(matches: &ArgMatches) {
260    for DeprecatedArg {
261        arg,
262        replaced_by,
263        usage_warning,
264    } in deprecated_arguments().into_iter()
265    {
266        if matches.is_present(arg.b.name) {
267            let mut msg = format!("--{} is deprecated", arg.b.name.replace('_', "-"));
268            if let Some(replaced_by) = replaced_by {
269                msg.push_str(&format!(", please use --{replaced_by}"));
270            }
271            msg.push('.');
272            if let Some(usage_warning) = usage_warning {
273                msg.push_str(&format!("  {usage_warning}"));
274                if !msg.ends_with('.') {
275                    msg.push('.');
276                }
277            }
278            // this can not rely on logger since it is not initialized at the time of call
279            eprintln!("{msg}");
280        }
281    }
282}
283
284pub struct DefaultArgs {
285    pub bind_address: String,
286    pub dynamic_port_range: String,
287    pub ledger_path: String,
288
289    pub tower_storage: String,
290    pub send_transaction_service_config: send_transaction_service::Config,
291
292    pub maximum_local_snapshot_age: String,
293    pub maximum_full_snapshot_archives_to_retain: String,
294    pub maximum_incremental_snapshot_archives_to_retain: String,
295    pub snapshot_packager_niceness_adjustment: String,
296    pub full_snapshot_archive_interval_slots: String,
297    pub incremental_snapshot_archive_interval_slots: String,
298    pub min_snapshot_download_speed: String,
299    pub max_snapshot_download_abort: String,
300
301    pub contact_debug_interval: String,
302
303    pub snapshot_version: SnapshotVersion,
304    pub snapshot_archive_format: String,
305    pub snapshot_zstd_compression_level: String,
306
307    pub rocksdb_shred_compaction: String,
308    pub rocksdb_ledger_compression: String,
309    pub rocksdb_perf_sample_interval: String,
310
311    pub accounts_shrink_optimize_total_space: String,
312    pub accounts_shrink_ratio: String,
313    pub tpu_connection_pool_size: String,
314
315    pub tpu_max_connections_per_peer: String,
316    pub tpu_max_connections_per_ipaddr_per_minute: String,
317    pub tpu_max_staked_connections: String,
318    pub tpu_max_unstaked_connections: String,
319    pub tpu_max_fwd_staked_connections: String,
320    pub tpu_max_fwd_unstaked_connections: String,
321    pub tpu_max_streams_per_ms: String,
322
323    pub num_quic_endpoints: String,
324    pub vote_use_quic: String,
325
326    pub banking_trace_dir_byte_limit: String,
327    pub block_production_pacing_fill_time_millis: String,
328
329    pub wen_restart_path: String,
330
331    pub thread_args: DefaultThreadArgs,
332}
333
334impl DefaultArgs {
335    pub fn new() -> Self {
336        DefaultArgs {
337            bind_address: "0.0.0.0".to_string(),
338            ledger_path: "ledger".to_string(),
339            dynamic_port_range: format!("{}-{}", VALIDATOR_PORT_RANGE.0, VALIDATOR_PORT_RANGE.1),
340            maximum_local_snapshot_age: "2500".to_string(),
341            tower_storage: "file".to_string(),
342            send_transaction_service_config: send_transaction_service::Config::default(),
343            maximum_full_snapshot_archives_to_retain: DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN
344                .to_string(),
345            maximum_incremental_snapshot_archives_to_retain:
346                DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN.to_string(),
347            snapshot_packager_niceness_adjustment: "0".to_string(),
348            full_snapshot_archive_interval_slots: DEFAULT_FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS
349                .get()
350                .to_string(),
351            incremental_snapshot_archive_interval_slots:
352                DEFAULT_INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS
353                    .get()
354                    .to_string(),
355            min_snapshot_download_speed: DEFAULT_MIN_SNAPSHOT_DOWNLOAD_SPEED.to_string(),
356            max_snapshot_download_abort: MAX_SNAPSHOT_DOWNLOAD_ABORT.to_string(),
357            snapshot_archive_format: DEFAULT_ARCHIVE_COMPRESSION.to_string(),
358            snapshot_zstd_compression_level: "1".to_string(), // level 1 is optimized for speed
359            contact_debug_interval: "120000".to_string(),
360            snapshot_version: SnapshotVersion::default(),
361            rocksdb_shred_compaction: "level".to_string(),
362            rocksdb_ledger_compression: "none".to_string(),
363            rocksdb_perf_sample_interval: "0".to_string(),
364            accounts_shrink_optimize_total_space: DEFAULT_ACCOUNTS_SHRINK_OPTIMIZE_TOTAL_SPACE
365                .to_string(),
366            accounts_shrink_ratio: DEFAULT_ACCOUNTS_SHRINK_RATIO.to_string(),
367            tpu_connection_pool_size: DEFAULT_TPU_CONNECTION_POOL_SIZE.to_string(),
368            tpu_max_connections_per_ipaddr_per_minute:
369                DEFAULT_MAX_CONNECTIONS_PER_IPADDR_PER_MINUTE.to_string(),
370            vote_use_quic: DEFAULT_VOTE_USE_QUIC.to_string(),
371            tpu_max_connections_per_peer: DEFAULT_MAX_QUIC_CONNECTIONS_PER_PEER.to_string(),
372            tpu_max_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS.to_string(),
373            tpu_max_unstaked_connections: DEFAULT_MAX_UNSTAKED_CONNECTIONS.to_string(),
374            tpu_max_fwd_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS
375                .saturating_add(DEFAULT_MAX_UNSTAKED_CONNECTIONS)
376                .to_string(),
377            tpu_max_fwd_unstaked_connections: 0.to_string(),
378            tpu_max_streams_per_ms: DEFAULT_MAX_STREAMS_PER_MS.to_string(),
379            num_quic_endpoints: DEFAULT_QUIC_ENDPOINTS.to_string(),
380            banking_trace_dir_byte_limit: BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT.to_string(),
381            block_production_pacing_fill_time_millis: BankingStage::default_fill_time_millis()
382                .to_string(),
383            wen_restart_path: "wen_restart_progress.proto".to_string(),
384            thread_args: DefaultThreadArgs::default(),
385        }
386    }
387}
388
389impl Default for DefaultArgs {
390    fn default() -> Self {
391        Self::new()
392    }
393}
394
395pub fn port_validator(port: String) -> Result<(), String> {
396    port.parse::<u16>()
397        .map(|_| ())
398        .map_err(|e| format!("{e:?}"))
399}
400
401pub fn port_range_validator(port_range: String) -> Result<(), String> {
402    if let Some((start, end)) = solana_net_utils::parse_port_range(&port_range) {
403        if end - start < MINIMUM_VALIDATOR_PORT_RANGE_WIDTH {
404            Err(format!(
405                "Port range is too small.  Try --dynamic-port-range {}-{}",
406                start,
407                start + MINIMUM_VALIDATOR_PORT_RANGE_WIDTH
408            ))
409        } else if end.checked_add(QUIC_PORT_OFFSET).is_none() {
410            Err("Invalid dynamic_port_range.".to_string())
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                .validator(port_range_validator)
707                .help("Range to use for dynamically assigned ports [default: 1024-65535]"),
708        )
709        .arg(
710            Arg::with_name("bind_address")
711                .long("bind-address")
712                .value_name("HOST")
713                .takes_value(true)
714                .validator(solana_net_utils::is_host)
715                .default_value("127.0.0.1")
716                .help(
717                    "IP address to bind the validator ports. Can be repeated. The first \
718                     --bind-address MUST be your public internet address. ALL protocols (gossip, \
719                     repair, IP echo, TVU, TPU, etc.) bind to this address on startup. Additional \
720                     --bind-address values enable multihoming for Gossip/TVU/TPU - these \
721                     protocols bind to ALL interfaces on startup. Gossip reads/sends from one \
722                     interface at a time. TVU/TPU read from ALL interfaces simultaneously but \
723                     send from only one interface at a time. When switching interfaces via \
724                     AdminRPC: Gossip switches to send/receive from the new interface, while \
725                     TVU/TPU continue receiving from ALL interfaces but send from the new \
726                     interface only.",
727                ),
728        )
729        .arg(
730            Arg::with_name("advertised_ip")
731                .long("advertised-ip")
732                .value_name("HOST")
733                .takes_value(true)
734                .validator(solana_net_utils::is_host)
735                .hidden(hidden_unless_forced())
736                .help(
737                    "Use when running a validator behind a NAT. DNS name or IP address for this \
738                     validator to advertise in gossip. This address will be used as the target \
739                     desination address for peers trying to contact this node. [default: the \
740                     first --bind-address, or ask --entrypoint when --bind-address is not \
741                     provided, or 127.0.0.1 when --entrypoint is not provided]. Note: this \
742                     argument cannot be used in a multihoming context (when multiple \
743                     --bind-address values are provided).",
744                ),
745        )
746        .arg(
747            Arg::with_name("clone_account")
748                .long("clone")
749                .short("c")
750                .value_name("ADDRESS")
751                .takes_value(true)
752                .validator(is_pubkey_or_keypair)
753                .multiple(true)
754                .requires("json_rpc_url")
755                .help(
756                    "Copy an account from the cluster referenced by the --url argument the \
757                     genesis configuration. If the ledger already exists then this parameter is \
758                     silently ignored",
759                ),
760        )
761        .arg(
762            Arg::with_name("deep_clone_address_lookup_table")
763                .long("deep-clone-address-lookup-table")
764                .takes_value(true)
765                .validator(is_pubkey_or_keypair)
766                .multiple(true)
767                .requires("json_rpc_url")
768                .help(
769                    "Copy an address lookup table and all accounts it references from the cluster \
770                     referenced by the --url argument in the genesis configuration. If the ledger \
771                     already exists then this parameter is silently ignored",
772                ),
773        )
774        .arg(
775            Arg::with_name("maybe_clone_account")
776                .long("maybe-clone")
777                .value_name("ADDRESS")
778                .takes_value(true)
779                .validator(is_pubkey_or_keypair)
780                .multiple(true)
781                .requires("json_rpc_url")
782                .help(
783                    "Copy an account from the cluster referenced by the --url argument, skipping \
784                     it if it doesn't exist. If the ledger already exists then this parameter is \
785                     silently ignored",
786                ),
787        )
788        .arg(
789            Arg::with_name("clone_upgradeable_program")
790                .long("clone-upgradeable-program")
791                .value_name("ADDRESS")
792                .takes_value(true)
793                .validator(is_pubkey_or_keypair)
794                .multiple(true)
795                .requires("json_rpc_url")
796                .help(
797                    "Copy an upgradeable program and its executable data from the cluster \
798                     referenced by the --url argument the genesis configuration. If the ledger \
799                     already exists then this parameter is silently ignored",
800                ),
801        )
802        .arg(
803            Arg::with_name("warp_slot")
804                .required(false)
805                .long("warp-slot")
806                .short("w")
807                .takes_value(true)
808                .value_name("WARP_SLOT")
809                .validator(is_slot)
810                .min_values(0)
811                .max_values(1)
812                .help(
813                    "Warp the ledger to WARP_SLOT after starting the validator. If no slot is \
814                     provided then the current slot of the cluster referenced by the --url \
815                     argument will be used",
816                ),
817        )
818        .arg(
819            Arg::with_name("limit_ledger_size")
820                .long("limit-ledger-size")
821                .value_name("SHRED_COUNT")
822                .takes_value(true)
823                .default_value(default_args.limit_ledger_size.as_str())
824                .help("Keep this amount of shreds in root slots."),
825        )
826        .arg(
827            Arg::with_name("faucet_sol")
828                .long("faucet-sol")
829                .takes_value(true)
830                .value_name("SOL")
831                .default_value(default_args.faucet_sol.as_str())
832                .help(
833                    "Give the faucet address this much SOL in genesis. If the ledger already \
834                     exists then this parameter is silently ignored",
835                ),
836        )
837        .arg(
838            Arg::with_name("faucet_time_slice_secs")
839                .long("faucet-time-slice-secs")
840                .takes_value(true)
841                .value_name("SECS")
842                .default_value(default_args.faucet_time_slice_secs.as_str())
843                .help("Time slice (in secs) over which to limit faucet requests"),
844        )
845        .arg(
846            Arg::with_name("faucet_per_time_sol_cap")
847                .long("faucet-per-time-sol-cap")
848                .takes_value(true)
849                .value_name("SOL")
850                .min_values(0)
851                .max_values(1)
852                .help("Per-time slice limit for faucet requests, in SOL"),
853        )
854        .arg(
855            Arg::with_name("faucet_per_request_sol_cap")
856                .long("faucet-per-request-sol-cap")
857                .takes_value(true)
858                .value_name("SOL")
859                .min_values(0)
860                .max_values(1)
861                .help("Per-request limit for faucet requests, in SOL"),
862        )
863        .arg(
864            Arg::with_name("geyser_plugin_config")
865                .long("geyser-plugin-config")
866                .alias("accountsdb-plugin-config")
867                .value_name("FILE")
868                .takes_value(true)
869                .multiple(true)
870                .help("Specify the configuration file for the Geyser plugin."),
871        )
872        .arg(
873            Arg::with_name("deactivate_feature")
874                .long("deactivate-feature")
875                .takes_value(true)
876                .value_name("FEATURE_PUBKEY")
877                .validator(is_pubkey)
878                .multiple(true)
879                .help("deactivate this feature in genesis."),
880        )
881        .arg(
882            Arg::with_name("compute_unit_limit")
883                .long("compute-unit-limit")
884                .alias("max-compute-units")
885                .value_name("COMPUTE_UNITS")
886                .validator(is_parsable::<u64>)
887                .takes_value(true)
888                .help("Override the runtime's compute unit limit per transaction"),
889        )
890        .arg(
891            Arg::with_name("log_messages_bytes_limit")
892                .long("log-messages-bytes-limit")
893                .value_name("BYTES")
894                .validator(is_parsable::<usize>)
895                .takes_value(true)
896                .help("Maximum number of bytes written to the program log before truncation"),
897        )
898        .arg(
899            Arg::with_name("transaction_account_lock_limit")
900                .long("transaction-account-lock-limit")
901                .value_name("NUM_ACCOUNTS")
902                .validator(is_parsable::<u64>)
903                .takes_value(true)
904                .help("Override the runtime's account lock limit per transaction"),
905        )
906        .arg(
907            Arg::with_name("clone_feature_set")
908                .long("clone-feature-set")
909                .takes_value(false)
910                .requires("json_rpc_url")
911                .help(
912                    "Copy a feature set from the cluster referenced by the --url argument in the \
913                     genesis configuration. If the ledger already exists then this parameter is \
914                     silently ignored",
915                ),
916        )
917        .args(&pub_sub_config::args(/*test_validator:*/ true))
918}
919
920pub struct DefaultTestArgs {
921    pub rpc_port: String,
922    pub faucet_port: String,
923    pub limit_ledger_size: String,
924    pub faucet_sol: String,
925    pub faucet_time_slice_secs: String,
926}
927
928impl DefaultTestArgs {
929    pub fn new() -> Self {
930        DefaultTestArgs {
931            rpc_port: 8899.to_string(),
932            faucet_port: FAUCET_PORT.to_string(),
933            /* 10,000 was derived empirically by watching the size
934             * of the rocksdb/ directory self-limit itself to the
935             * 40MB-150MB range when running `solana-test-validator`
936             */
937            limit_ledger_size: 10_000.to_string(),
938            faucet_sol: (1_000_000.).to_string(),
939            faucet_time_slice_secs: (faucet::TIME_SLICE).to_string(),
940        }
941    }
942}
943
944impl Default for DefaultTestArgs {
945    fn default() -> Self {
946        Self::new()
947    }
948}
949
950#[cfg(test)]
951mod test {
952    use super::*;
953
954    #[test]
955    fn make_sure_deprecated_arguments_are_sorted_alphabetically() {
956        let deprecated = deprecated_arguments();
957
958        for i in 0..deprecated.len().saturating_sub(1) {
959            let curr_name = deprecated[i].arg.b.name;
960            let next_name = deprecated[i + 1].arg.b.name;
961
962            assert!(
963                curr_name != next_name,
964                "Arguments in `deprecated_arguments()` should be distinct.\nArguments {} and {} \
965                 use the same name: {}",
966                i,
967                i + 1,
968                curr_name,
969            );
970
971            assert!(
972                curr_name < next_name,
973                "To generate better diffs and for readability purposes, `deprecated_arguments()` \
974                 should list arguments in alphabetical order.\nArguments {} and {} are \
975                 not.\nArgument {} name: {}\nArgument {} name: {}",
976                i,
977                i + 1,
978                i,
979                curr_name,
980                i + 1,
981                next_name,
982            );
983        }
984    }
985}