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