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