agave_validator/commands/run/
args.rs

1use {
2    crate::{
3        bootstrap::RpcBootstrapConfig,
4        cli::{hash_validator, port_range_validator, port_validator, DefaultArgs},
5        commands::{FromClapArgMatches, Result},
6    },
7    agave_snapshots::{SnapshotVersion, SUPPORTED_ARCHIVE_COMPRESSION},
8    clap::{values_t, App, Arg, ArgMatches},
9    solana_accounts_db::utils::create_and_canonicalize_directory,
10    solana_clap_utils::{
11        hidden_unless_forced,
12        input_parsers::keypair_of,
13        input_validators::{
14            is_keypair_or_ask_keyword, is_non_zero, is_parsable, is_pow2, is_pubkey,
15            is_pubkey_or_keypair, is_slot, is_within_range, validate_cpu_ranges,
16            validate_maximum_full_snapshot_archives_to_retain,
17            validate_maximum_incremental_snapshot_archives_to_retain,
18        },
19        keypair::SKIP_SEED_PHRASE_VALIDATION_ARG,
20    },
21    solana_core::{
22        banking_trace::DirByteLimit,
23        validator::{BlockProductionMethod, BlockVerificationMethod},
24    },
25    solana_keypair::Keypair,
26    solana_ledger::{blockstore_options::BlockstoreOptions, use_snapshot_archives_at_startup},
27    solana_pubkey::Pubkey,
28    solana_rpc::{rpc::JsonRpcConfig, rpc_pubsub_service::PubSubConfig},
29    solana_send_transaction_service::send_transaction_service::Config as SendTransactionServiceConfig,
30    solana_signer::Signer,
31    solana_streamer::socket::SocketAddrSpace,
32    solana_unified_scheduler_pool::DefaultSchedulerPool,
33    std::{collections::HashSet, net::SocketAddr, path::PathBuf, str::FromStr},
34};
35
36const EXCLUDE_KEY: &str = "account-index-exclude-key";
37const INCLUDE_KEY: &str = "account-index-include-key";
38
39// Declared out of line to allow use of #[rustfmt::skip]
40#[rustfmt::skip]
41const WEN_RESTART_HELP: &str =
42    "Only used during coordinated cluster restarts.\n\n\
43     Need to also specify the leader's pubkey in --wen-restart-leader.\n\n\
44     When specified, the validator will enter Wen Restart mode which pauses normal activity. \
45     Validators in this mode will gossip their last vote to reach consensus on a safe restart \
46     slot and repair all blocks on the selected fork. The safe slot will be a descendant of the \
47     latest optimistically confirmed slot to ensure we do not roll back any optimistically \
48     confirmed slots.\n\n\
49     The progress in this mode will be saved in the file location provided. If consensus is \
50     reached, the validator will automatically exit with 200 status code. Then the operators are \
51     expected to restart the validator with --wait_for_supermajority and other arguments \
52     (including new shred_version, supermajority slot, and bankhash) given in the error log \
53     before the exit so the cluster will resume execution. The progress file will be kept around \
54     for future debugging.\n\n\
55     If wen_restart fails, refer to the progress file (in proto3 format) for further debugging and \
56     watch the discord channel for instructions.";
57
58pub mod account_secondary_indexes;
59pub mod blockstore_options;
60pub mod json_rpc_config;
61pub mod pub_sub_config;
62pub mod rpc_bigtable_config;
63pub mod rpc_bootstrap_config;
64pub mod send_transaction_config;
65
66#[derive(Debug, PartialEq)]
67pub struct RunArgs {
68    pub identity_keypair: Keypair,
69    pub ledger_path: PathBuf,
70    pub logfile: Option<PathBuf>,
71    pub entrypoints: Vec<SocketAddr>,
72    pub known_validators: Option<HashSet<Pubkey>>,
73    pub socket_addr_space: SocketAddrSpace,
74    pub rpc_bootstrap_config: RpcBootstrapConfig,
75    pub blockstore_options: BlockstoreOptions,
76    pub json_rpc_config: JsonRpcConfig,
77    pub pub_sub_config: PubSubConfig,
78    pub send_transaction_service_config: SendTransactionServiceConfig,
79}
80
81impl FromClapArgMatches for RunArgs {
82    fn from_clap_arg_match(matches: &ArgMatches) -> Result<Self> {
83        let identity_keypair =
84            keypair_of(matches, "identity").ok_or(clap::Error::with_description(
85                "The --identity <KEYPAIR> argument is required",
86                clap::ErrorKind::ArgumentNotFound,
87            ))?;
88
89        let ledger_path = PathBuf::from(matches.value_of("ledger_path").ok_or(
90            clap::Error::with_description(
91                "The --ledger <DIR> argument is required",
92                clap::ErrorKind::ArgumentNotFound,
93            ),
94        )?);
95        // Canonicalize ledger path to avoid issues with symlink creation
96        let ledger_path =
97            create_and_canonicalize_directory(ledger_path.as_path()).map_err(|err| {
98                crate::commands::Error::Dynamic(Box::<dyn std::error::Error>::from(format!(
99                    "failed to create and canonicalize ledger path '{}': {err}",
100                    ledger_path.display(),
101                )))
102            })?;
103
104        let logfile = matches
105            .value_of("logfile")
106            .map(String::from)
107            .unwrap_or_else(|| format!("agave-validator-{}.log", identity_keypair.pubkey()));
108        let logfile = if logfile == "-" {
109            None
110        } else {
111            Some(PathBuf::from(logfile))
112        };
113
114        let mut entrypoints = values_t!(matches, "entrypoint", String).unwrap_or_default();
115        // sort() + dedup() to yield a vector of unique elements
116        entrypoints.sort();
117        entrypoints.dedup();
118        let entrypoints = entrypoints
119            .into_iter()
120            .map(|entrypoint| {
121                solana_net_utils::parse_host_port(&entrypoint).map_err(|err| {
122                    crate::commands::Error::Dynamic(Box::<dyn std::error::Error>::from(format!(
123                        "failed to parse entrypoint address: {err}"
124                    )))
125                })
126            })
127            .collect::<Result<Vec<_>>>()?;
128
129        let known_validators = validators_set(
130            &identity_keypair.pubkey(),
131            matches,
132            "known_validators",
133            "known validator",
134        )?;
135
136        let socket_addr_space = SocketAddrSpace::new(matches.is_present("allow_private_addr"));
137
138        Ok(RunArgs {
139            identity_keypair,
140            ledger_path,
141            logfile,
142            entrypoints,
143            known_validators,
144            socket_addr_space,
145            rpc_bootstrap_config: RpcBootstrapConfig::from_clap_arg_match(matches)?,
146            blockstore_options: BlockstoreOptions::from_clap_arg_match(matches)?,
147            json_rpc_config: JsonRpcConfig::from_clap_arg_match(matches)?,
148            pub_sub_config: PubSubConfig::from_clap_arg_match(matches)?,
149            send_transaction_service_config: SendTransactionServiceConfig::from_clap_arg_match(
150                matches,
151            )?,
152        })
153    }
154}
155
156pub fn add_args<'a>(app: App<'a, 'a>, default_args: &'a DefaultArgs) -> App<'a, 'a> {
157    app.arg(
158        Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
159            .long(SKIP_SEED_PHRASE_VALIDATION_ARG.long)
160            .help(SKIP_SEED_PHRASE_VALIDATION_ARG.help),
161    )
162    .arg(
163        Arg::with_name("identity")
164            .short("i")
165            .long("identity")
166            .value_name("KEYPAIR")
167            .takes_value(true)
168            .validator(is_keypair_or_ask_keyword)
169            .help("Validator identity keypair"),
170    )
171    .arg(
172        Arg::with_name("authorized_voter_keypairs")
173            .long("authorized-voter")
174            .value_name("KEYPAIR")
175            .takes_value(true)
176            .validator(is_keypair_or_ask_keyword)
177            .requires("vote_account")
178            .multiple(true)
179            .help(
180                "Include an additional authorized voter keypair. May be specified multiple times. \
181                 [default: the --identity keypair]",
182            ),
183    )
184    .arg(
185        Arg::with_name("vote_account")
186            .long("vote-account")
187            .value_name("ADDRESS")
188            .takes_value(true)
189            .validator(is_pubkey_or_keypair)
190            .requires("identity")
191            .help(
192                "Validator vote account public key. If unspecified, voting will be disabled. The \
193                 authorized voter for the account must either be the --identity keypair or set by \
194                 the --authorized-voter argument",
195            ),
196    )
197    .arg(
198        Arg::with_name("init_complete_file")
199            .long("init-complete-file")
200            .value_name("FILE")
201            .takes_value(true)
202            .help(
203                "Create this file if it doesn't already exist once validator initialization is \
204                 complete",
205            ),
206    )
207    .arg(
208        Arg::with_name("ledger_path")
209            .short("l")
210            .long("ledger")
211            .value_name("DIR")
212            .takes_value(true)
213            .required(true)
214            .default_value(&default_args.ledger_path)
215            .help("Use DIR as ledger location"),
216    )
217    .arg(
218        Arg::with_name("entrypoint")
219            .short("n")
220            .long("entrypoint")
221            .value_name("HOST:PORT")
222            .takes_value(true)
223            .multiple(true)
224            .validator(solana_net_utils::is_host_port)
225            .help("Rendezvous with the cluster at this gossip entrypoint"),
226    )
227    .arg(
228        Arg::with_name("no_voting")
229            .long("no-voting")
230            .takes_value(false)
231            .help("Launch validator without voting"),
232    )
233    .arg(
234        Arg::with_name("restricted_repair_only_mode")
235            .long("restricted-repair-only-mode")
236            .takes_value(false)
237            .help(
238                "Do not publish the Gossip, TPU, TVU or Repair Service ports. Doing so causes the \
239                 node to operate in a limited capacity that reduces its exposure to the rest of \
240                 the cluster. The --no-voting flag is implicit when this flag is enabled",
241            ),
242    )
243    .arg(
244        Arg::with_name("dev_halt_at_slot")
245            .long("dev-halt-at-slot")
246            .value_name("SLOT")
247            .validator(is_slot)
248            .takes_value(true)
249            .help("Halt the validator when it reaches the given slot"),
250    )
251    .arg(
252        Arg::with_name("rpc_port")
253            .long("rpc-port")
254            .value_name("PORT")
255            .takes_value(true)
256            .validator(port_validator)
257            .help("Enable JSON RPC on this port, and the next port for the RPC websocket"),
258    )
259    .arg(
260        Arg::with_name("private_rpc")
261            .long("private-rpc")
262            .takes_value(false)
263            .help("Do not publish the RPC port for use by others"),
264    )
265    .arg(
266        Arg::with_name("no_port_check")
267            .long("no-port-check")
268            .takes_value(false)
269            .hidden(hidden_unless_forced())
270            .help("Do not perform TCP/UDP reachable port checks at start-up"),
271    )
272    .arg(
273        Arg::with_name("account_paths")
274            .long("accounts")
275            .value_name("PATHS")
276            .takes_value(true)
277            .multiple(true)
278            .help(
279                "Comma separated persistent accounts location. May be specified multiple times. \
280                 [default: <LEDGER>/accounts]",
281            ),
282    )
283    .arg(
284        Arg::with_name("account_shrink_path")
285            .long("account-shrink-path")
286            .value_name("PATH")
287            .takes_value(true)
288            .multiple(true)
289            .help("Path to accounts shrink path which can hold a compacted account set."),
290    )
291    .arg(
292        Arg::with_name("snapshots")
293            .long("snapshots")
294            .value_name("DIR")
295            .takes_value(true)
296            .help("Use DIR as the base location for snapshots.")
297            .long_help(
298                "Use DIR as the base location for snapshots. Snapshot archives will use DIR \
299                 unless --full-snapshot-archive-path or --incremental-snapshot-archive-path is \
300                 specified. Additionally, a subdirectory named \"snapshots\" will be created in \
301                 DIR. This subdirectory holds internal files/data that are used when generating \
302                 snapshot archives. [default: --ledger value]",
303            ),
304    )
305    .arg(
306        Arg::with_name(use_snapshot_archives_at_startup::cli::NAME)
307            .long(use_snapshot_archives_at_startup::cli::LONG_ARG)
308            .takes_value(true)
309            .possible_values(use_snapshot_archives_at_startup::cli::POSSIBLE_VALUES)
310            .default_value(use_snapshot_archives_at_startup::cli::default_value())
311            .help(use_snapshot_archives_at_startup::cli::HELP)
312            .long_help(use_snapshot_archives_at_startup::cli::LONG_HELP),
313    )
314    .arg(
315        Arg::with_name("full_snapshot_archive_path")
316            .long("full-snapshot-archive-path")
317            .value_name("DIR")
318            .takes_value(true)
319            .help("Use DIR as full snapshot archives location [default: --snapshots value]"),
320    )
321    .arg(
322        Arg::with_name("incremental_snapshot_archive_path")
323            .long("incremental-snapshot-archive-path")
324            .conflicts_with("no-incremental-snapshots")
325            .value_name("DIR")
326            .takes_value(true)
327            .help("Use DIR as incremental snapshot archives location [default: --snapshots value]"),
328    )
329    .arg(
330        Arg::with_name("tower")
331            .long("tower")
332            .value_name("DIR")
333            .takes_value(true)
334            .help("Use DIR as file tower storage location [default: --ledger value]"),
335    )
336    .arg(
337        Arg::with_name("gossip_port")
338            .long("gossip-port")
339            .value_name("PORT")
340            .takes_value(true)
341            .help("Gossip port number for the validator"),
342    )
343    .arg(
344        Arg::with_name("public_tpu_addr")
345            .long("public-tpu-address")
346            .alias("tpu-host-addr")
347            .value_name("HOST:PORT")
348            .takes_value(true)
349            .validator(solana_net_utils::is_host_port)
350            .help(
351                "Specify TPU address to advertise in gossip [default: ask --entrypoint or \
352                 localhost when --entrypoint is not provided]",
353            ),
354    )
355    .arg(
356        Arg::with_name("public_tpu_forwards_addr")
357            .long("public-tpu-forwards-address")
358            .value_name("HOST:PORT")
359            .takes_value(true)
360            .validator(solana_net_utils::is_host_port)
361            .help(
362                "Specify TPU Forwards address to advertise in gossip [default: ask --entrypoint \
363                 or localhostwhen --entrypoint is not provided]",
364            ),
365    )
366    .arg(
367        Arg::with_name("tpu_vortexor_receiver_address")
368            .long("tpu-vortexor-receiver-address")
369            .value_name("HOST:PORT")
370            .takes_value(true)
371            .hidden(hidden_unless_forced())
372            .validator(solana_net_utils::is_host_port)
373            .help(
374                "TPU Vortexor Receiver address to which verified transaction packet will be \
375                 forwarded.",
376            ),
377    )
378    .arg(
379        Arg::with_name("public_rpc_addr")
380            .long("public-rpc-address")
381            .value_name("HOST:PORT")
382            .takes_value(true)
383            .conflicts_with("private_rpc")
384            .validator(solana_net_utils::is_host_port)
385            .help(
386                "RPC address for the validator to advertise publicly in gossip. Useful for \
387                 validators running behind a load balancer or proxy [default: use \
388                 --rpc-bind-address / --rpc-port]",
389            ),
390    )
391    .arg(
392        Arg::with_name("dynamic_port_range")
393            .long("dynamic-port-range")
394            .value_name("MIN_PORT-MAX_PORT")
395            .takes_value(true)
396            .default_value(&default_args.dynamic_port_range)
397            .validator(port_range_validator)
398            .help("Range to use for dynamically assigned ports"),
399    )
400    .arg(
401        Arg::with_name("maximum_local_snapshot_age")
402            .long("maximum-local-snapshot-age")
403            .value_name("NUMBER_OF_SLOTS")
404            .takes_value(true)
405            .default_value(&default_args.maximum_local_snapshot_age)
406            .help(
407                "Reuse a local snapshot if it's less than this many slots behind the highest \
408                 snapshot available for download from other validators",
409            ),
410    )
411    .arg(
412        Arg::with_name("no_snapshots")
413            .long("no-snapshots")
414            .takes_value(false)
415            .conflicts_with_all(&[
416                "no_incremental_snapshots",
417                "snapshot_interval_slots",
418                "full_snapshot_interval_slots",
419            ])
420            .help("Disable all snapshot generation"),
421    )
422    .arg(
423        Arg::with_name("snapshot_interval_slots")
424            .long("snapshot-interval-slots")
425            .alias("incremental-snapshot-interval-slots")
426            .value_name("NUMBER")
427            .takes_value(true)
428            .default_value(&default_args.incremental_snapshot_archive_interval_slots)
429            .validator(is_non_zero)
430            .help("Number of slots between generating snapshots")
431            .long_help(
432                "Number of slots between generating snapshots. If incremental snapshots are \
433                 enabled, this sets the incremental snapshot interval. If incremental snapshots \
434                 are disabled, this sets the full snapshot interval. Must be greater than zero.",
435            ),
436    )
437    .arg(
438        Arg::with_name("full_snapshot_interval_slots")
439            .long("full-snapshot-interval-slots")
440            .value_name("NUMBER")
441            .takes_value(true)
442            .default_value(&default_args.full_snapshot_archive_interval_slots)
443            .validator(is_non_zero)
444            .help("Number of slots between generating full snapshots")
445            .long_help(
446                "Number of slots between generating full snapshots. Only used when incremental \
447                 snapshots are enabled. Must be greater than the incremental snapshot interval. \
448                 Must be greater than zero.",
449            ),
450    )
451    .arg(
452        Arg::with_name("maximum_full_snapshots_to_retain")
453            .long("maximum-full-snapshots-to-retain")
454            .alias("maximum-snapshots-to-retain")
455            .value_name("NUMBER")
456            .takes_value(true)
457            .default_value(&default_args.maximum_full_snapshot_archives_to_retain)
458            .validator(validate_maximum_full_snapshot_archives_to_retain)
459            .help(
460                "The maximum number of full snapshot archives to hold on to when purging older \
461                 snapshots.",
462            ),
463    )
464    .arg(
465        Arg::with_name("maximum_incremental_snapshots_to_retain")
466            .long("maximum-incremental-snapshots-to-retain")
467            .value_name("NUMBER")
468            .takes_value(true)
469            .default_value(&default_args.maximum_incremental_snapshot_archives_to_retain)
470            .validator(validate_maximum_incremental_snapshot_archives_to_retain)
471            .help(
472                "The maximum number of incremental snapshot archives to hold on to when purging \
473                 older snapshots.",
474            ),
475    )
476    .arg(
477        Arg::with_name("snapshot_packager_niceness_adj")
478            .long("snapshot-packager-niceness-adjustment")
479            .value_name("ADJUSTMENT")
480            .takes_value(true)
481            .validator(solana_perf::thread::is_niceness_adjustment_valid)
482            .default_value(&default_args.snapshot_packager_niceness_adjustment)
483            .help(
484                "Add this value to niceness of snapshot packager thread. Negative value increases \
485                 priority, positive value decreases priority.",
486            ),
487    )
488    .arg(
489        Arg::with_name("minimal_snapshot_download_speed")
490            .long("minimal-snapshot-download-speed")
491            .value_name("MINIMAL_SNAPSHOT_DOWNLOAD_SPEED")
492            .takes_value(true)
493            .default_value(&default_args.min_snapshot_download_speed)
494            .help(
495                "The minimal speed of snapshot downloads measured in bytes/second. If the initial \
496                 download speed falls below this threshold, the system will retry the download \
497                 against a different rpc node.",
498            ),
499    )
500    .arg(
501        Arg::with_name("maximum_snapshot_download_abort")
502            .long("maximum-snapshot-download-abort")
503            .value_name("MAXIMUM_SNAPSHOT_DOWNLOAD_ABORT")
504            .takes_value(true)
505            .default_value(&default_args.max_snapshot_download_abort)
506            .help(
507                "The maximum number of times to abort and retry when encountering a slow snapshot \
508                 download.",
509            ),
510    )
511    .arg(
512        Arg::with_name("contact_debug_interval")
513            .long("contact-debug-interval")
514            .value_name("CONTACT_DEBUG_INTERVAL")
515            .takes_value(true)
516            .default_value(&default_args.contact_debug_interval)
517            .help("Milliseconds between printing contact debug from gossip."),
518    )
519    .arg(
520        Arg::with_name("no_poh_speed_test")
521            .long("no-poh-speed-test")
522            .hidden(hidden_unless_forced())
523            .help("Skip the check for PoH speed."),
524    )
525    .arg(
526        Arg::with_name("no_os_network_limits_test")
527            .hidden(hidden_unless_forced())
528            .long("no-os-network-limits-test")
529            .help("Skip checks for OS network limits."),
530    )
531    .arg(
532        Arg::with_name("no_os_memory_stats_reporting")
533            .long("no-os-memory-stats-reporting")
534            .hidden(hidden_unless_forced())
535            .help("Disable reporting of OS memory statistics."),
536    )
537    .arg(
538        Arg::with_name("no_os_network_stats_reporting")
539            .long("no-os-network-stats-reporting")
540            .hidden(hidden_unless_forced())
541            .help("Disable reporting of OS network statistics."),
542    )
543    .arg(
544        Arg::with_name("no_os_cpu_stats_reporting")
545            .long("no-os-cpu-stats-reporting")
546            .hidden(hidden_unless_forced())
547            .help("Disable reporting of OS CPU statistics."),
548    )
549    .arg(
550        Arg::with_name("no_os_disk_stats_reporting")
551            .long("no-os-disk-stats-reporting")
552            .hidden(hidden_unless_forced())
553            .help("Disable reporting of OS disk statistics."),
554    )
555    .arg(
556        Arg::with_name("snapshot_version")
557            .long("snapshot-version")
558            .value_name("SNAPSHOT_VERSION")
559            .validator(is_parsable::<SnapshotVersion>)
560            .takes_value(true)
561            .default_value(default_args.snapshot_version.into())
562            .help("Output snapshot version"),
563    )
564    .arg(
565        Arg::with_name("limit_ledger_size")
566            .long("limit-ledger-size")
567            .value_name("SHRED_COUNT")
568            .takes_value(true)
569            .min_values(0)
570            .max_values(1)
571            /* .default_value() intentionally not used here! */
572            .help("Keep this amount of shreds in root slots."),
573    )
574    .arg(
575        Arg::with_name("rocksdb_shred_compaction")
576            .long("rocksdb-shred-compaction")
577            .value_name("ROCKSDB_COMPACTION_STYLE")
578            .takes_value(true)
579            .possible_values(&["level"])
580            .default_value(&default_args.rocksdb_shred_compaction)
581            .help(
582                "Controls how RocksDB compacts shreds. *WARNING*: You will lose your Blockstore \
583                 data when you switch between options.",
584            ),
585    )
586    .arg(
587        Arg::with_name("rocksdb_ledger_compression")
588            .hidden(hidden_unless_forced())
589            .long("rocksdb-ledger-compression")
590            .value_name("COMPRESSION_TYPE")
591            .takes_value(true)
592            .possible_values(&["none", "lz4", "snappy", "zlib"])
593            .default_value(&default_args.rocksdb_ledger_compression)
594            .help(
595                "The compression algorithm that is used to compress transaction status data. \
596                 Turning on compression can save ~10% of the ledger size.",
597            ),
598    )
599    .arg(
600        Arg::with_name("rocksdb_perf_sample_interval")
601            .hidden(hidden_unless_forced())
602            .long("rocksdb-perf-sample-interval")
603            .value_name("ROCKS_PERF_SAMPLE_INTERVAL")
604            .takes_value(true)
605            .validator(is_parsable::<usize>)
606            .default_value(&default_args.rocksdb_perf_sample_interval)
607            .help(
608                "Controls how often RocksDB read/write performance samples are collected. Perf \
609                 samples are collected in 1 / ROCKS_PERF_SAMPLE_INTERVAL sampling rate.",
610            ),
611    )
612    .arg(
613        Arg::with_name("skip_startup_ledger_verification")
614            .long("skip-startup-ledger-verification")
615            .takes_value(false)
616            .help("Skip ledger verification at validator bootup."),
617    )
618    .arg(
619        clap::Arg::with_name("require_tower")
620            .long("require-tower")
621            .takes_value(false)
622            .help("Refuse to start if saved tower state is not found"),
623    )
624    .arg(
625        Arg::with_name("expected_genesis_hash")
626            .long("expected-genesis-hash")
627            .value_name("HASH")
628            .takes_value(true)
629            .validator(hash_validator)
630            .help("Require the genesis have this hash"),
631    )
632    .arg(
633        Arg::with_name("expected_bank_hash")
634            .long("expected-bank-hash")
635            .value_name("HASH")
636            .takes_value(true)
637            .validator(hash_validator)
638            .help("When wait-for-supermajority <x>, require the bank at <x> to have this hash"),
639    )
640    .arg(
641        Arg::with_name("expected_shred_version")
642            .long("expected-shred-version")
643            .value_name("VERSION")
644            .takes_value(true)
645            .validator(is_parsable::<u16>)
646            .help("Require the shred version be this value"),
647    )
648    .arg(
649        Arg::with_name("logfile")
650            .short("o")
651            .long("log")
652            .value_name("FILE")
653            .takes_value(true)
654            .help(
655                "Redirect logging to the specified file, '-' for standard error. Sending the \
656                 SIGUSR1 signal to the validator process will cause it to re-open the log file",
657            ),
658    )
659    .arg(
660        Arg::with_name("wait_for_supermajority")
661            .long("wait-for-supermajority")
662            .requires("expected_bank_hash")
663            .requires("expected_shred_version")
664            .value_name("SLOT")
665            .validator(is_slot)
666            .help(
667                "After processing the ledger and the next slot is SLOT, wait until a \
668                 supermajority of stake is visible on gossip before starting PoH",
669            ),
670    )
671    .arg(
672        Arg::with_name("no_wait_for_vote_to_start_leader")
673            .hidden(hidden_unless_forced())
674            .long("no-wait-for-vote-to-start-leader")
675            .help(
676                "If the validator starts up with no ledger, it will wait to start block \
677                 production until it sees a vote land in a rooted slot. This prevents double \
678                 signing. Turn off to risk double signing a block.",
679            ),
680    )
681    .arg(
682        Arg::with_name("hard_forks")
683            .long("hard-fork")
684            .value_name("SLOT")
685            .validator(is_slot)
686            .multiple(true)
687            .takes_value(true)
688            .help("Add a hard fork at this slot"),
689    )
690    .arg(
691        Arg::with_name("known_validators")
692            .alias("trusted-validator")
693            .long("known-validator")
694            .validator(is_pubkey)
695            .value_name("VALIDATOR IDENTITY")
696            .multiple(true)
697            .takes_value(true)
698            .help(
699                "A snapshot hash must be published in gossip by this validator to be accepted. \
700                 May be specified multiple times. If unspecified any snapshot hash will be \
701                 accepted",
702            ),
703    )
704    .arg(
705        Arg::with_name("debug_key")
706            .long("debug-key")
707            .validator(is_pubkey)
708            .value_name("ADDRESS")
709            .multiple(true)
710            .takes_value(true)
711            .help("Log when transactions are processed which reference a given key."),
712    )
713    .arg(
714        Arg::with_name("repair_validators")
715            .long("repair-validator")
716            .validator(is_pubkey)
717            .value_name("VALIDATOR IDENTITY")
718            .multiple(true)
719            .takes_value(true)
720            .help(
721                "A list of validators to request repairs from. If specified, repair will not \
722                 request from validators outside this set [default: all validators]",
723            ),
724    )
725    .arg(
726        Arg::with_name("repair_whitelist")
727            .hidden(hidden_unless_forced())
728            .long("repair-whitelist")
729            .validator(is_pubkey)
730            .value_name("VALIDATOR IDENTITY")
731            .multiple(true)
732            .takes_value(true)
733            .help(
734                "A list of validators to prioritize repairs from. If specified, repair requests \
735                 from validators in the list will be prioritized over requests from other \
736                 validators. [default: all validators]",
737            ),
738    )
739    .arg(
740        Arg::with_name("gossip_validators")
741            .long("gossip-validator")
742            .validator(is_pubkey)
743            .value_name("VALIDATOR IDENTITY")
744            .multiple(true)
745            .takes_value(true)
746            .help(
747                "A list of validators to gossip with. If specified, gossip will not push/pull \
748                 from from validators outside this set. [default: all validators]",
749            ),
750    )
751    .arg(
752        Arg::with_name("tpu_connection_pool_size")
753            .long("tpu-connection-pool-size")
754            .takes_value(true)
755            .default_value(&default_args.tpu_connection_pool_size)
756            .validator(is_parsable::<usize>)
757            .help("Controls the TPU connection pool size per remote address"),
758    )
759    .arg(
760        Arg::with_name("tpu_max_connections_per_ipaddr_per_minute")
761            .long("tpu-max-connections-per-ipaddr-per-minute")
762            .takes_value(true)
763            .default_value(&default_args.tpu_max_connections_per_ipaddr_per_minute)
764            .validator(is_parsable::<u32>)
765            .hidden(hidden_unless_forced())
766            .help("Controls the rate of the clients connections per IpAddr per minute."),
767    )
768    .arg(
769        Arg::with_name("vote_use_quic")
770            .long("vote-use-quic")
771            .takes_value(true)
772            .default_value(&default_args.vote_use_quic)
773            .hidden(hidden_unless_forced())
774            .help("Controls if to use QUIC to send votes."),
775    )
776    .arg(
777        Arg::with_name("tpu_max_connections_per_peer")
778            .long("tpu-max-connections-per-peer")
779            .takes_value(true)
780            .default_value(&default_args.tpu_max_connections_per_peer)
781            .validator(is_parsable::<u32>)
782            .hidden(hidden_unless_forced())
783            .help("Controls the max concurrent connections per IpAddr."),
784    )
785    .arg(
786        Arg::with_name("tpu_max_staked_connections")
787            .long("tpu-max-staked-connections")
788            .takes_value(true)
789            .default_value(&default_args.tpu_max_staked_connections)
790            .validator(is_parsable::<u32>)
791            .hidden(hidden_unless_forced())
792            .help("Controls the max concurrent connections for TPU from staked nodes."),
793    )
794    .arg(
795        Arg::with_name("tpu_max_unstaked_connections")
796            .long("tpu-max-unstaked-connections")
797            .takes_value(true)
798            .default_value(&default_args.tpu_max_unstaked_connections)
799            .validator(is_parsable::<u32>)
800            .hidden(hidden_unless_forced())
801            .help("Controls the max concurrent connections fort TPU from unstaked nodes."),
802    )
803    .arg(
804        Arg::with_name("tpu_max_fwd_staked_connections")
805            .long("tpu-max-fwd-staked-connections")
806            .takes_value(true)
807            .default_value(&default_args.tpu_max_fwd_staked_connections)
808            .validator(is_parsable::<u32>)
809            .hidden(hidden_unless_forced())
810            .help("Controls the max concurrent connections for TPU-forward from staked nodes."),
811    )
812    .arg(
813        Arg::with_name("tpu_max_fwd_unstaked_connections")
814            .long("tpu-max-fwd-unstaked-connections")
815            .takes_value(true)
816            .default_value(&default_args.tpu_max_fwd_unstaked_connections)
817            .validator(is_parsable::<u32>)
818            .hidden(hidden_unless_forced())
819            .help("Controls the max concurrent connections for TPU-forward from unstaked nodes."),
820    )
821    .arg(
822        Arg::with_name("tpu_max_streams_per_ms")
823            .long("tpu-max-streams-per-ms")
824            .takes_value(true)
825            .default_value(&default_args.tpu_max_streams_per_ms)
826            .validator(is_parsable::<usize>)
827            .hidden(hidden_unless_forced())
828            .help("Controls the max number of streams for a TPU service."),
829    )
830    .arg(
831        Arg::with_name("num_quic_endpoints")
832            .long("num-quic-endpoints")
833            .takes_value(true)
834            .default_value(&default_args.num_quic_endpoints)
835            .validator(is_parsable::<usize>)
836            .hidden(hidden_unless_forced())
837            .help(
838                "The number of QUIC endpoints used for TPU and TPU-Forward. It can be increased \
839                 to increase network ingest throughput, at the expense of higher CPU and general \
840                 validator load.",
841            ),
842    )
843    .arg(
844        Arg::with_name("staked_nodes_overrides")
845            .long("staked-nodes-overrides")
846            .value_name("PATH")
847            .takes_value(true)
848            .help(
849                "Provide path to a yaml file with custom overrides for stakes of specific \
850                 identities. Overriding the amount of stake this validator considers as valid for \
851                 other peers in network. The stake amount is used for calculating the number of \
852                 QUIC streams permitted from the peer and vote packet sender stage. Format of the \
853                 file: `staked_map_id: {<pubkey>: <SOL stake amount>}",
854            ),
855    )
856    .arg(
857        Arg::with_name("bind_address")
858            .long("bind-address")
859            .value_name("HOST")
860            .takes_value(true)
861            .validator(solana_net_utils::is_host)
862            .default_value(&default_args.bind_address)
863            .multiple(true)
864            .help(
865                "Repeatable. IP addresses to bind the validator ports on. First is primary (used \
866                 on startup), the rest may be switched to during operation.",
867            ),
868    )
869    .arg(
870        Arg::with_name("rpc_bind_address")
871            .long("rpc-bind-address")
872            .value_name("HOST")
873            .takes_value(true)
874            .validator(solana_net_utils::is_host)
875            .help(
876                "IP address to bind the RPC port [default: 127.0.0.1 if --private-rpc is present, \
877                 otherwise use --bind-address]",
878            ),
879    )
880    .arg(
881        Arg::with_name("geyser_plugin_config")
882            .long("geyser-plugin-config")
883            .alias("accountsdb-plugin-config")
884            .value_name("FILE")
885            .takes_value(true)
886            .multiple(true)
887            .help("Specify the configuration file for the Geyser plugin."),
888    )
889    .arg(
890        Arg::with_name("geyser_plugin_always_enabled")
891            .long("geyser-plugin-always-enabled")
892            .value_name("BOOLEAN")
893            .takes_value(false)
894            .help("Еnable Geyser interface even if no Geyser configs are specified."),
895    )
896    .arg(
897        Arg::with_name("snapshot_archive_format")
898            .long("snapshot-archive-format")
899            .alias("snapshot-compression") // Legacy name used by Solana v1.5.x and older
900            .possible_values(SUPPORTED_ARCHIVE_COMPRESSION)
901            .default_value(&default_args.snapshot_archive_format)
902            .value_name("ARCHIVE_TYPE")
903            .takes_value(true)
904            .help("Snapshot archive format to use."),
905    )
906    .arg(
907        Arg::with_name("snapshot_zstd_compression_level")
908            .long("snapshot-zstd-compression-level")
909            .default_value(&default_args.snapshot_zstd_compression_level)
910            .value_name("LEVEL")
911            .takes_value(true)
912            .help("The compression level to use when archiving with zstd")
913            .long_help(
914                "The compression level to use when archiving with zstd. Higher compression levels \
915                 generally produce higher compression ratio at the expense of speed and memory. \
916                 See the zstd manpage for more information.",
917            ),
918    )
919    .arg(
920        Arg::with_name("wal_recovery_mode")
921            .long("wal-recovery-mode")
922            .value_name("MODE")
923            .takes_value(true)
924            .possible_values(&[
925                "tolerate_corrupted_tail_records",
926                "absolute_consistency",
927                "point_in_time",
928                "skip_any_corrupted_record",
929            ])
930            .help("Mode to recovery the ledger db write ahead log."),
931    )
932    .arg(
933        Arg::with_name("poh_pinned_cpu_core")
934            .hidden(hidden_unless_forced())
935            .long("experimental-poh-pinned-cpu-core")
936            .takes_value(true)
937            .value_name("CPU_CORE_INDEX")
938            .validator(|s| {
939                let core_index = usize::from_str(&s).map_err(|e| e.to_string())?;
940                let max_index = core_affinity::get_core_ids()
941                    .map(|cids| cids.len() - 1)
942                    .unwrap_or(0);
943                if core_index > max_index {
944                    return Err(format!("core index must be in the range [0, {max_index}]"));
945                }
946                Ok(())
947            })
948            .help("EXPERIMENTAL: Specify which CPU core PoH is pinned to"),
949    )
950    .arg(
951        Arg::with_name("poh_hashes_per_batch")
952            .hidden(hidden_unless_forced())
953            .long("poh-hashes-per-batch")
954            .takes_value(true)
955            .value_name("NUM")
956            .help("Specify hashes per batch in PoH service"),
957    )
958    .arg(
959        Arg::with_name("process_ledger_before_services")
960            .long("process-ledger-before-services")
961            .hidden(hidden_unless_forced())
962            .help("Process the local ledger fully before starting networking services"),
963    )
964    .arg(
965        Arg::with_name("account_indexes")
966            .long("account-index")
967            .takes_value(true)
968            .multiple(true)
969            .possible_values(&["program-id", "spl-token-owner", "spl-token-mint"])
970            .value_name("INDEX")
971            .help("Enable an accounts index, indexed by the selected account field"),
972    )
973    .arg(
974        Arg::with_name("account_index_exclude_key")
975            .long(EXCLUDE_KEY)
976            .takes_value(true)
977            .validator(is_pubkey)
978            .multiple(true)
979            .value_name("KEY")
980            .help("When account indexes are enabled, exclude this key from the index."),
981    )
982    .arg(
983        Arg::with_name("account_index_include_key")
984            .long(INCLUDE_KEY)
985            .takes_value(true)
986            .validator(is_pubkey)
987            .conflicts_with("account_index_exclude_key")
988            .multiple(true)
989            .value_name("KEY")
990            .help(
991                "When account indexes are enabled, only include specific keys in the index. This \
992                 overrides --account-index-exclude-key.",
993            ),
994    )
995    .arg(
996        Arg::with_name("accounts_db_verify_refcounts")
997            .long("accounts-db-verify-refcounts")
998            .help(
999                "Debug option to scan all append vecs and verify account index refcounts prior to \
1000                 clean",
1001            )
1002            .hidden(hidden_unless_forced()),
1003    )
1004    .arg(
1005        Arg::with_name("accounts_db_scan_filter_for_shrinking")
1006            .long("accounts-db-scan-filter-for-shrinking")
1007            .takes_value(true)
1008            .possible_values(&["all", "only-abnormal", "only-abnormal-with-verify"])
1009            .help(
1010                "Debug option to use different type of filtering for accounts index scan in \
1011                 shrinking. \"all\" will scan both in-memory and on-disk accounts index, which is \
1012                 the default. \"only-abnormal\" will scan in-memory accounts index only for \
1013                 abnormal entries and skip scanning on-disk accounts index by assuming that \
1014                 on-disk accounts index contains only normal accounts index entry. \
1015                 \"only-abnormal-with-verify\" is similar to \"only-abnormal\", which will scan \
1016                 in-memory index for abnormal entries, but will also verify that on-disk account \
1017                 entries are indeed normal.",
1018            )
1019            .hidden(hidden_unless_forced()),
1020    )
1021    .arg(
1022        Arg::with_name("no_skip_initial_accounts_db_clean")
1023            .long("no-skip-initial-accounts-db-clean")
1024            .help("Do not skip the initial cleaning of accounts when verifying snapshot bank")
1025            .hidden(hidden_unless_forced()),
1026    )
1027    .arg(
1028        Arg::with_name("accounts_db_access_storages_method")
1029            .long("accounts-db-access-storages-method")
1030            .value_name("METHOD")
1031            .takes_value(true)
1032            .possible_values(&["mmap", "file"])
1033            .help("Access account storages using this method"),
1034    )
1035    .arg(
1036        Arg::with_name("accounts_db_ancient_append_vecs")
1037            .long("accounts-db-ancient-append-vecs")
1038            .value_name("SLOT-OFFSET")
1039            .validator(is_parsable::<i64>)
1040            .takes_value(true)
1041            .help(
1042                "AppendVecs that are older than (slots_per_epoch - SLOT-OFFSET) are squashed \
1043                 together.",
1044            )
1045            .hidden(hidden_unless_forced()),
1046    )
1047    .arg(
1048        Arg::with_name("accounts_db_ancient_storage_ideal_size")
1049            .long("accounts-db-ancient-storage-ideal-size")
1050            .value_name("BYTES")
1051            .validator(is_parsable::<u64>)
1052            .takes_value(true)
1053            .help("The smallest size of ideal ancient storage.")
1054            .hidden(hidden_unless_forced()),
1055    )
1056    .arg(
1057        Arg::with_name("accounts_db_max_ancient_storages")
1058            .long("accounts-db-max-ancient-storages")
1059            .value_name("USIZE")
1060            .validator(is_parsable::<usize>)
1061            .takes_value(true)
1062            .help("The number of ancient storages the ancient slot combining should converge to.")
1063            .hidden(hidden_unless_forced()),
1064    )
1065    .arg(
1066        Arg::with_name("accounts_db_cache_limit_mb")
1067            .long("accounts-db-cache-limit-mb")
1068            .value_name("MEGABYTES")
1069            .validator(is_parsable::<u64>)
1070            .takes_value(true)
1071            .help(
1072                "How large the write cache for account data can become. If this is exceeded, the \
1073                 cache is flushed more aggressively.",
1074            ),
1075    )
1076    .arg(
1077        Arg::with_name("accounts_db_read_cache_limit")
1078            .long("accounts-db-read-cache-limit")
1079            .value_name("LOW,HIGH")
1080            .takes_value(true)
1081            .min_values(2)
1082            .max_values(2)
1083            .multiple(false)
1084            .require_delimiter(true)
1085            .help("How large the read cache for account data can become, in bytes")
1086            .long_help(
1087                "How large the read cache for account data can become, in bytes. The values will \
1088                 be the low and high watermarks for the cache. When the cache exceeds the high \
1089                 watermark, entries will be evicted until the size reaches the low watermark.",
1090            )
1091            .hidden(hidden_unless_forced()),
1092    )
1093    .arg(
1094        Arg::with_name("accounts_db_mark_obsolete_accounts")
1095            .long("accounts-db-mark-obsolete-accounts")
1096            .help("Enables experimental obsolete account tracking")
1097            .long_help(
1098                "Enables experimental obsolete account tracking. This feature tracks obsolete \
1099                 accounts in the account storage entry allowing for earlier cleaning of obsolete \
1100                 accounts in the storages and index. At this time this feature is not compatible \
1101                 with booting from local snapshot state and must unpack from archives.",
1102            )
1103            .hidden(hidden_unless_forced()),
1104    )
1105    .arg(
1106        Arg::with_name("accounts_index_scan_results_limit_mb")
1107            .long("accounts-index-scan-results-limit-mb")
1108            .value_name("MEGABYTES")
1109            .validator(is_parsable::<usize>)
1110            .takes_value(true)
1111            .help(
1112                "How large accumulated results from an accounts index scan can become. If this is \
1113                 exceeded, the scan aborts.",
1114            ),
1115    )
1116    .arg(
1117        Arg::with_name("accounts_index_bins")
1118            .long("accounts-index-bins")
1119            .value_name("BINS")
1120            .validator(is_pow2)
1121            .takes_value(true)
1122            .help("Number of bins to divide the accounts index into"),
1123    )
1124    .arg(
1125        Arg::with_name("accounts_index_initial_accounts_count")
1126            .long("accounts-index-initial-accounts-count")
1127            .value_name("NUMBER")
1128            .validator(is_parsable::<usize>)
1129            .takes_value(true)
1130            .help("Pre-allocate the accounts index, assuming this many accounts")
1131            .hidden(hidden_unless_forced()),
1132    )
1133    .arg(
1134        Arg::with_name("accounts_index_path")
1135            .long("accounts-index-path")
1136            .value_name("PATH")
1137            .takes_value(true)
1138            .multiple(true)
1139            .requires("enable_accounts_disk_index")
1140            .help(
1141                "Persistent accounts-index location. May be specified multiple times. [default: \
1142                 <LEDGER>/accounts_index]",
1143            ),
1144    )
1145    .arg(
1146        Arg::with_name("enable_accounts_disk_index")
1147            .long("enable-accounts-disk-index")
1148            .help("Enables the disk-based accounts index")
1149            .long_help(
1150                "Enables the disk-based accounts index. Reduce the memory footprint of the \
1151                 accounts index at the cost of index performance.",
1152            ),
1153    )
1154    .arg(
1155        Arg::with_name("accounts_shrink_optimize_total_space")
1156            .long("accounts-shrink-optimize-total-space")
1157            .takes_value(true)
1158            .value_name("BOOLEAN")
1159            .default_value(&default_args.accounts_shrink_optimize_total_space)
1160            .help(
1161                "When this is set to true, the system will shrink the most sparse accounts and \
1162                 when the overall shrink ratio is above the specified accounts-shrink-ratio, the \
1163                 shrink will stop and it will skip all other less sparse accounts.",
1164            ),
1165    )
1166    .arg(
1167        Arg::with_name("accounts_shrink_ratio")
1168            .long("accounts-shrink-ratio")
1169            .takes_value(true)
1170            .value_name("RATIO")
1171            .default_value(&default_args.accounts_shrink_ratio)
1172            .help(
1173                "Specifies the shrink ratio for the accounts to be shrunk. The shrink ratio is \
1174                 defined as the ratio of the bytes alive over the  total bytes used. If the \
1175                 account's shrink ratio is less than this ratio it becomes a candidate for \
1176                 shrinking. The value must between 0. and 1.0 inclusive.",
1177            ),
1178    )
1179    .arg(
1180        Arg::with_name("allow_private_addr")
1181            .long("allow-private-addr")
1182            .takes_value(false)
1183            .help("Allow contacting private ip addresses")
1184            .hidden(hidden_unless_forced()),
1185    )
1186    .arg(
1187        Arg::with_name("log_messages_bytes_limit")
1188            .long("log-messages-bytes-limit")
1189            .takes_value(true)
1190            .validator(is_parsable::<usize>)
1191            .value_name("BYTES")
1192            .help("Maximum number of bytes written to the program log before truncation"),
1193    )
1194    .arg(
1195        Arg::with_name("banking_trace_dir_byte_limit")
1196            // expose friendly alternative name to cli than internal
1197            // implementation-oriented one
1198            .long("enable-banking-trace")
1199            .value_name("BYTES")
1200            .validator(is_parsable::<DirByteLimit>)
1201            .takes_value(true)
1202            // Firstly, zero limit value causes tracer to be disabled
1203            // altogether, intuitively. On the other hand, this non-zero
1204            // default doesn't enable banking tracer unless this flag is
1205            // explicitly given, similar to --limit-ledger-size.
1206            // see configure_banking_trace_dir_byte_limit() for this.
1207            .default_value(&default_args.banking_trace_dir_byte_limit)
1208            .help(
1209                "Enables the banking trace explicitly, which is enabled by default and writes \
1210                 trace files for simulate-leader-blocks, retaining up to the default or specified \
1211                 total bytes in the ledger. This flag can be used to override its byte limit.",
1212            ),
1213    )
1214    .arg(
1215        Arg::with_name("disable_banking_trace")
1216            .long("disable-banking-trace")
1217            .conflicts_with("banking_trace_dir_byte_limit")
1218            .takes_value(false)
1219            .help("Disables the banking trace"),
1220    )
1221    .arg(
1222        Arg::with_name("delay_leader_block_for_pending_fork")
1223            .hidden(hidden_unless_forced())
1224            .long("delay-leader-block-for-pending-fork")
1225            .takes_value(false)
1226            .help(
1227                "Delay leader block creation while replaying a block which descends from the \
1228                 current fork and has a lower slot than our next leader slot. If we don't delay \
1229                 here, our new leader block will be on a different fork from the block we are \
1230                 replaying and there is a high chance that the cluster will confirm that block's \
1231                 fork rather than our leader block's fork because it was created before we \
1232                 started creating ours.",
1233            ),
1234    )
1235    .arg(
1236        Arg::with_name("block_verification_method")
1237            .long("block-verification-method")
1238            .value_name("METHOD")
1239            .takes_value(true)
1240            .possible_values(BlockVerificationMethod::cli_names())
1241            .default_value(BlockVerificationMethod::default().into())
1242            .help(BlockVerificationMethod::cli_message()),
1243    )
1244    .arg(
1245        Arg::with_name("block_production_method")
1246            .long("block-production-method")
1247            .value_name("METHOD")
1248            .takes_value(true)
1249            .possible_values(BlockProductionMethod::cli_names())
1250            .default_value(BlockProductionMethod::default().into())
1251            .help(BlockProductionMethod::cli_message()),
1252    )
1253    .arg(
1254        Arg::with_name("block_production_pacing_fill_time_millis")
1255            .long("block-production-pacing-fill-time-millis")
1256            .value_name("MILLIS")
1257            .takes_value(true)
1258            .default_value(&default_args.block_production_pacing_fill_time_millis)
1259            .help(
1260                "Pacing fill time in milliseconds for the central-scheduler block production \
1261                 method",
1262            ),
1263    )
1264    .arg(
1265        Arg::with_name("unified_scheduler_handler_threads")
1266            .long("unified-scheduler-handler-threads")
1267            .value_name("COUNT")
1268            .takes_value(true)
1269            .validator(|s| is_within_range(s, 1..))
1270            .help(DefaultSchedulerPool::cli_message()),
1271    )
1272    .arg(
1273        Arg::with_name("wen_restart")
1274            .long("wen-restart")
1275            .hidden(hidden_unless_forced())
1276            .value_name("FILE")
1277            .takes_value(true)
1278            .required(false)
1279            .conflicts_with("wait_for_supermajority")
1280            .requires("wen_restart_coordinator")
1281            .help(WEN_RESTART_HELP),
1282    )
1283    .arg(
1284        Arg::with_name("wen_restart_coordinator")
1285            .long("wen-restart-coordinator")
1286            .hidden(hidden_unless_forced())
1287            .value_name("PUBKEY")
1288            .takes_value(true)
1289            .required(false)
1290            .requires("wen_restart")
1291            .help(
1292                "Specifies the pubkey of the leader used in wen restart. May get stuck if the \
1293                 leader used is different from others.",
1294            ),
1295    )
1296    .arg(
1297        Arg::with_name("retransmit_xdp_interface")
1298            .hidden(hidden_unless_forced())
1299            .long("experimental-retransmit-xdp-interface")
1300            .takes_value(true)
1301            .value_name("INTERFACE")
1302            .requires("retransmit_xdp_cpu_cores")
1303            .help("EXPERIMENTAL: The network interface to use for XDP retransmit"),
1304    )
1305    .arg(
1306        Arg::with_name("retransmit_xdp_cpu_cores")
1307            .hidden(hidden_unless_forced())
1308            .long("experimental-retransmit-xdp-cpu-cores")
1309            .takes_value(true)
1310            .value_name("CPU_LIST")
1311            .validator(|value| {
1312                validate_cpu_ranges(value, "--experimental-retransmit-xdp-cpu-cores")
1313            })
1314            .help("EXPERIMENTAL: Enable XDP retransmit on the specified CPU cores"),
1315    )
1316    .arg(
1317        Arg::with_name("retransmit_xdp_zero_copy")
1318            .hidden(hidden_unless_forced())
1319            .long("experimental-retransmit-xdp-zero-copy")
1320            .takes_value(false)
1321            .requires("retransmit_xdp_cpu_cores")
1322            .help("EXPERIMENTAL: Enable XDP zero copy. Requires hardware support"),
1323    )
1324    .arg(
1325        Arg::with_name("use_connection_cache")
1326            .long("use-connection-cache")
1327            .takes_value(false)
1328            .help(
1329                "Use connection-cache crate to send transactions over TPU ports. If not \
1330                 set,tpu-client-next is used by default.",
1331            ),
1332    )
1333    .args(&pub_sub_config::args(/*test_validator:*/ false))
1334    .args(&json_rpc_config::args())
1335    .args(&rpc_bigtable_config::args())
1336    .args(&send_transaction_config::args())
1337    .args(&rpc_bootstrap_config::args())
1338}
1339
1340fn validators_set(
1341    identity_pubkey: &Pubkey,
1342    matches: &ArgMatches<'_>,
1343    matches_name: &str,
1344    arg_name: &str,
1345) -> Result<Option<HashSet<Pubkey>>> {
1346    if matches.is_present(matches_name) {
1347        let validators_set: Option<HashSet<Pubkey>> = values_t!(matches, matches_name, Pubkey)
1348            .ok()
1349            .map(|validators| validators.into_iter().collect());
1350        if let Some(validators_set) = &validators_set {
1351            if validators_set.contains(identity_pubkey) {
1352                return Err(crate::commands::Error::Dynamic(
1353                    Box::<dyn std::error::Error>::from(format!(
1354                        "the validator's identity pubkey cannot be a {arg_name}: {identity_pubkey}"
1355                    )),
1356                ));
1357            }
1358        }
1359        Ok(validators_set)
1360    } else {
1361        Ok(None)
1362    }
1363}
1364
1365#[cfg(test)]
1366mod tests {
1367    use {
1368        super::*,
1369        crate::cli::thread_args::thread_args,
1370        scopeguard::defer,
1371        std::{
1372            fs,
1373            net::{IpAddr, Ipv4Addr},
1374            path::{absolute, PathBuf},
1375        },
1376    };
1377
1378    impl Default for RunArgs {
1379        fn default() -> Self {
1380            let identity_keypair = Keypair::new();
1381            let ledger_path = absolute(PathBuf::from("ledger")).unwrap();
1382            let logfile =
1383                PathBuf::from(format!("agave-validator-{}.log", identity_keypair.pubkey()));
1384            let entrypoints = vec![];
1385            let known_validators = None;
1386
1387            let json_rpc_config =
1388                crate::commands::run::args::json_rpc_config::tests::default_json_rpc_config();
1389
1390            RunArgs {
1391                identity_keypair,
1392                ledger_path,
1393                logfile: Some(logfile),
1394                entrypoints,
1395                known_validators,
1396                socket_addr_space: SocketAddrSpace::Global,
1397                rpc_bootstrap_config: RpcBootstrapConfig::default(),
1398                blockstore_options: BlockstoreOptions::default(),
1399                json_rpc_config,
1400                pub_sub_config: PubSubConfig {
1401                    worker_threads: 4,
1402                    notification_threads: None,
1403                    queue_capacity_items:
1404                        solana_rpc::rpc_pubsub_service::DEFAULT_QUEUE_CAPACITY_ITEMS,
1405                    ..PubSubConfig::default_for_tests()
1406                },
1407                send_transaction_service_config: SendTransactionServiceConfig::default(),
1408            }
1409        }
1410    }
1411
1412    impl Clone for RunArgs {
1413        fn clone(&self) -> Self {
1414            RunArgs {
1415                identity_keypair: self.identity_keypair.insecure_clone(),
1416                logfile: self.logfile.clone(),
1417                entrypoints: self.entrypoints.clone(),
1418                known_validators: self.known_validators.clone(),
1419                socket_addr_space: self.socket_addr_space,
1420                ledger_path: self.ledger_path.clone(),
1421                rpc_bootstrap_config: self.rpc_bootstrap_config.clone(),
1422                blockstore_options: self.blockstore_options.clone(),
1423                json_rpc_config: self.json_rpc_config.clone(),
1424                pub_sub_config: self.pub_sub_config.clone(),
1425                send_transaction_service_config: self.send_transaction_service_config.clone(),
1426            }
1427        }
1428    }
1429
1430    fn verify_args_struct_by_command(
1431        default_args: &DefaultArgs,
1432        args: Vec<&str>,
1433        expected_args: RunArgs,
1434    ) {
1435        let app = add_args(App::new("run_command"), default_args)
1436            .args(&thread_args(&default_args.thread_args));
1437
1438        crate::commands::tests::verify_args_struct_by_command::<RunArgs>(
1439            app,
1440            [&["run_command"], &args[..]].concat(),
1441            expected_args,
1442        );
1443    }
1444
1445    #[test]
1446    fn verify_args_struct_by_command_run_with_identity() {
1447        let default_args = DefaultArgs::default();
1448        let default_run_args = RunArgs::default();
1449
1450        // generate a keypair
1451        let tmp_dir = tempfile::tempdir().unwrap();
1452        let file = tmp_dir.path().join("id.json");
1453        let keypair = default_run_args.identity_keypair.insecure_clone();
1454        solana_keypair::write_keypair_file(&keypair, &file).unwrap();
1455
1456        let expected_args = RunArgs {
1457            identity_keypair: keypair.insecure_clone(),
1458            ..default_run_args
1459        };
1460
1461        // short arg
1462        {
1463            verify_args_struct_by_command(
1464                &default_args,
1465                vec!["-i", file.to_str().unwrap()],
1466                expected_args.clone(),
1467            );
1468        }
1469
1470        // long arg
1471        {
1472            verify_args_struct_by_command(
1473                &default_args,
1474                vec!["--identity", file.to_str().unwrap()],
1475                expected_args.clone(),
1476            );
1477        }
1478    }
1479
1480    pub fn verify_args_struct_by_command_run_with_identity_setup(
1481        default_run_args: RunArgs,
1482        args: Vec<&str>,
1483        expected_args: RunArgs,
1484    ) {
1485        let default_args = DefaultArgs::default();
1486
1487        // generate a keypair
1488        let tmp_dir = tempfile::tempdir().unwrap();
1489        let file = tmp_dir.path().join("id.json");
1490        let keypair = default_run_args.identity_keypair.insecure_clone();
1491        solana_keypair::write_keypair_file(&keypair, &file).unwrap();
1492
1493        let args = [&["--identity", file.to_str().unwrap()], &args[..]].concat();
1494        verify_args_struct_by_command(&default_args, args, expected_args);
1495    }
1496
1497    pub fn verify_args_struct_by_command_run_is_error_with_identity_setup(
1498        default_run_args: RunArgs,
1499        args: Vec<&str>,
1500    ) {
1501        let default_args = DefaultArgs::default();
1502
1503        // generate a keypair
1504        let tmp_dir = tempfile::tempdir().unwrap();
1505        let file = tmp_dir.path().join("id.json");
1506        let keypair = default_run_args.identity_keypair.insecure_clone();
1507        solana_keypair::write_keypair_file(&keypair, &file).unwrap();
1508
1509        let app = add_args(App::new("run_command"), &default_args)
1510            .args(&thread_args(&default_args.thread_args));
1511
1512        crate::commands::tests::verify_args_struct_by_command_is_error::<RunArgs>(
1513            app,
1514            [
1515                &["run_command"],
1516                &["--identity", file.to_str().unwrap()][..],
1517                &args[..],
1518            ]
1519            .concat(),
1520        );
1521    }
1522
1523    #[test]
1524    fn verify_args_struct_by_command_run_with_ledger_path() {
1525        // nonexistent absolute ledger path
1526        {
1527            let default_run_args = RunArgs::default();
1528            let tmp_dir = fs::canonicalize(tempfile::tempdir().unwrap()).unwrap();
1529            let ledger_path = tmp_dir.join("nonexistent_ledger_path");
1530            assert!(!fs::exists(&ledger_path).unwrap());
1531
1532            let expected_args = RunArgs {
1533                ledger_path: ledger_path.clone(),
1534                ..default_run_args.clone()
1535            };
1536            verify_args_struct_by_command_run_with_identity_setup(
1537                default_run_args,
1538                vec!["--ledger", ledger_path.to_str().unwrap()],
1539                expected_args,
1540            );
1541            assert!(fs::exists(&ledger_path).unwrap());
1542        }
1543
1544        // existing absolute ledger path
1545        {
1546            let default_run_args = RunArgs::default();
1547            let tmp_dir = tempfile::tempdir().unwrap();
1548            let ledger_path = tmp_dir.path().join("existing_ledger_path");
1549            fs::create_dir_all(&ledger_path).unwrap();
1550            let ledger_path = fs::canonicalize(ledger_path).unwrap();
1551            assert!(fs::exists(ledger_path.as_path()).unwrap());
1552
1553            let expected_args = RunArgs {
1554                ledger_path: ledger_path.clone(),
1555                ..default_run_args.clone()
1556            };
1557            verify_args_struct_by_command_run_with_identity_setup(
1558                default_run_args,
1559                vec!["--ledger", ledger_path.to_str().unwrap()],
1560                expected_args,
1561            );
1562            assert!(fs::exists(&ledger_path).unwrap());
1563        }
1564
1565        // nonexistent relative ledger path
1566        {
1567            let default_run_args = RunArgs::default();
1568            let ledger_path = PathBuf::from("nonexistent_ledger_path");
1569            assert!(!fs::exists(&ledger_path).unwrap());
1570            defer! {
1571                fs::remove_dir_all(&ledger_path).unwrap()
1572            };
1573
1574            let expected_args = RunArgs {
1575                ledger_path: absolute(&ledger_path).unwrap(),
1576                ..default_run_args.clone()
1577            };
1578            verify_args_struct_by_command_run_with_identity_setup(
1579                default_run_args,
1580                vec!["--ledger", ledger_path.to_str().unwrap()],
1581                expected_args,
1582            );
1583            assert!(fs::exists(&ledger_path).unwrap());
1584        }
1585
1586        // existing relative ledger path
1587        {
1588            let default_run_args = RunArgs::default();
1589            let ledger_path = PathBuf::from("existing_ledger_path");
1590            fs::create_dir_all(&ledger_path).unwrap();
1591            assert!(fs::exists(&ledger_path).unwrap());
1592            defer! {
1593                fs::remove_dir_all(&ledger_path).unwrap()
1594            };
1595
1596            let expected_args = RunArgs {
1597                ledger_path: absolute(&ledger_path).unwrap(),
1598                ..default_run_args.clone()
1599            };
1600            verify_args_struct_by_command_run_with_identity_setup(
1601                default_run_args,
1602                vec!["--ledger", ledger_path.to_str().unwrap()],
1603                expected_args,
1604            );
1605            assert!(fs::exists(&ledger_path).unwrap());
1606        }
1607    }
1608
1609    #[test]
1610    fn verify_args_struct_by_command_run_with_log() {
1611        let default_run_args = RunArgs::default();
1612
1613        // default
1614        {
1615            let expected_args = RunArgs {
1616                logfile: Some(PathBuf::from(format!(
1617                    "agave-validator-{}.log",
1618                    default_run_args.identity_keypair.pubkey()
1619                ))),
1620                ..default_run_args.clone()
1621            };
1622            verify_args_struct_by_command_run_with_identity_setup(
1623                default_run_args.clone(),
1624                vec![],
1625                expected_args,
1626            );
1627        }
1628
1629        // short arg
1630        {
1631            let expected_args = RunArgs {
1632                logfile: None,
1633                ..default_run_args.clone()
1634            };
1635            verify_args_struct_by_command_run_with_identity_setup(
1636                default_run_args.clone(),
1637                vec!["-o", "-"],
1638                expected_args,
1639            );
1640        }
1641
1642        // long arg
1643        {
1644            let expected_args = RunArgs {
1645                logfile: Some(PathBuf::from("custom_log.log")),
1646                ..default_run_args.clone()
1647            };
1648            verify_args_struct_by_command_run_with_identity_setup(
1649                default_run_args.clone(),
1650                vec!["--log", "custom_log.log"],
1651                expected_args,
1652            );
1653        }
1654    }
1655
1656    #[test]
1657    fn verify_args_struct_by_command_run_with_entrypoints() {
1658        // short arg + single entrypoint
1659        {
1660            let default_run_args = RunArgs::default();
1661            let expected_args = RunArgs {
1662                entrypoints: vec![SocketAddr::new(
1663                    IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
1664                    8000,
1665                )],
1666                ..default_run_args.clone()
1667            };
1668            verify_args_struct_by_command_run_with_identity_setup(
1669                default_run_args.clone(),
1670                vec!["-n", "127.0.0.1:8000"],
1671                expected_args,
1672            );
1673        }
1674
1675        // long arg + single entrypoint
1676        {
1677            let default_run_args = RunArgs::default();
1678            let expected_args = RunArgs {
1679                entrypoints: vec![SocketAddr::new(
1680                    IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
1681                    8000,
1682                )],
1683                ..default_run_args.clone()
1684            };
1685            verify_args_struct_by_command_run_with_identity_setup(
1686                default_run_args.clone(),
1687                vec!["--entrypoint", "127.0.0.1:8000"],
1688                expected_args,
1689            );
1690        }
1691
1692        // long arg + multiple entrypoints
1693        {
1694            let default_run_args = RunArgs::default();
1695            let expected_args = RunArgs {
1696                entrypoints: vec![
1697                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8000),
1698                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8001),
1699                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8002),
1700                ],
1701                ..default_run_args.clone()
1702            };
1703            verify_args_struct_by_command_run_with_identity_setup(
1704                default_run_args.clone(),
1705                vec![
1706                    "--entrypoint",
1707                    "127.0.0.1:8000",
1708                    "--entrypoint",
1709                    "127.0.0.1:8001",
1710                    "--entrypoint",
1711                    "127.0.0.1:8002",
1712                ],
1713                expected_args,
1714            );
1715        }
1716
1717        // long arg + duplicate entrypoints
1718        {
1719            let default_run_args = RunArgs::default();
1720            let expected_args = RunArgs {
1721                entrypoints: vec![
1722                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8000),
1723                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8001),
1724                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8002),
1725                ],
1726                ..default_run_args.clone()
1727            };
1728            verify_args_struct_by_command_run_with_identity_setup(
1729                default_run_args.clone(),
1730                vec![
1731                    "--entrypoint",
1732                    "127.0.0.1:8000",
1733                    "--entrypoint",
1734                    "127.0.0.1:8001",
1735                    "--entrypoint",
1736                    "127.0.0.1:8002",
1737                    "--entrypoint",
1738                    "127.0.0.1:8000",
1739                ],
1740                expected_args,
1741            );
1742        }
1743    }
1744
1745    #[test]
1746    fn verify_args_struct_by_command_run_with_known_validators() {
1747        // long arg + single known validator
1748        {
1749            let default_run_args = RunArgs::default();
1750            let known_validators_pubkey = Pubkey::new_unique();
1751            let known_validators = Some(HashSet::from([known_validators_pubkey]));
1752            let expected_args = RunArgs {
1753                known_validators,
1754                ..default_run_args.clone()
1755            };
1756            verify_args_struct_by_command_run_with_identity_setup(
1757                default_run_args,
1758                vec!["--known-validator", &known_validators_pubkey.to_string()],
1759                expected_args,
1760            );
1761        }
1762
1763        // alias + single known validator
1764        {
1765            let default_run_args = RunArgs::default();
1766            let known_validators_pubkey = Pubkey::new_unique();
1767            let known_validators = Some(HashSet::from([known_validators_pubkey]));
1768            let expected_args = RunArgs {
1769                known_validators,
1770                ..default_run_args.clone()
1771            };
1772            verify_args_struct_by_command_run_with_identity_setup(
1773                default_run_args,
1774                vec!["--trusted-validator", &known_validators_pubkey.to_string()],
1775                expected_args,
1776            );
1777        }
1778
1779        // long arg + multiple known validators
1780        {
1781            let default_run_args = RunArgs::default();
1782            let known_validators_pubkey_1 = Pubkey::new_unique();
1783            let known_validators_pubkey_2 = Pubkey::new_unique();
1784            let known_validators_pubkey_3 = Pubkey::new_unique();
1785            let known_validators = Some(HashSet::from([
1786                known_validators_pubkey_1,
1787                known_validators_pubkey_2,
1788                known_validators_pubkey_3,
1789            ]));
1790            let expected_args = RunArgs {
1791                known_validators,
1792                ..default_run_args.clone()
1793            };
1794            verify_args_struct_by_command_run_with_identity_setup(
1795                default_run_args,
1796                vec![
1797                    "--known-validator",
1798                    &known_validators_pubkey_1.to_string(),
1799                    "--known-validator",
1800                    &known_validators_pubkey_2.to_string(),
1801                    "--known-validator",
1802                    &known_validators_pubkey_3.to_string(),
1803                ],
1804                expected_args,
1805            );
1806        }
1807
1808        // long arg + duplicate known validators
1809        {
1810            let default_run_args = RunArgs::default();
1811            let known_validators_pubkey_1 = Pubkey::new_unique();
1812            let known_validators_pubkey_2 = Pubkey::new_unique();
1813            let known_validators = Some(HashSet::from([
1814                known_validators_pubkey_1,
1815                known_validators_pubkey_2,
1816            ]));
1817            let expected_args = RunArgs {
1818                known_validators,
1819                ..default_run_args.clone()
1820            };
1821            verify_args_struct_by_command_run_with_identity_setup(
1822                default_run_args,
1823                vec![
1824                    "--known-validator",
1825                    &known_validators_pubkey_1.to_string(),
1826                    "--known-validator",
1827                    &known_validators_pubkey_2.to_string(),
1828                    "--known-validator",
1829                    &known_validators_pubkey_1.to_string(),
1830                ],
1831                expected_args,
1832            );
1833        }
1834
1835        // use identity pubkey as known validator
1836        {
1837            let default_args = DefaultArgs::default();
1838            let default_run_args = RunArgs::default();
1839
1840            // generate a keypair
1841            let tmp_dir = tempfile::tempdir().unwrap();
1842            let file = tmp_dir.path().join("id.json");
1843            solana_keypair::write_keypair_file(&default_run_args.identity_keypair, &file).unwrap();
1844
1845            let matches = add_args(App::new("run_command"), &default_args).get_matches_from(vec![
1846                "run_command",
1847                "--identity",
1848                file.to_str().unwrap(),
1849                "--known-validator",
1850                &default_run_args.identity_keypair.pubkey().to_string(),
1851            ]);
1852            let result = RunArgs::from_clap_arg_match(&matches);
1853            assert!(result.is_err());
1854            let error = result.unwrap_err();
1855            assert_eq!(
1856                error.to_string(),
1857                format!(
1858                    "the validator's identity pubkey cannot be a known validator: {}",
1859                    default_run_args.identity_keypair.pubkey()
1860                )
1861            );
1862        }
1863    }
1864
1865    #[test]
1866    fn verify_args_struct_by_command_run_with_max_genesis_archive_unpacked_size() {
1867        // long arg
1868        {
1869            let default_run_args = RunArgs::default();
1870            let max_genesis_archive_unpacked_size = 1000000000;
1871            let expected_args = RunArgs {
1872                rpc_bootstrap_config: RpcBootstrapConfig {
1873                    max_genesis_archive_unpacked_size,
1874                    ..RpcBootstrapConfig::default()
1875                },
1876                ..default_run_args.clone()
1877            };
1878            verify_args_struct_by_command_run_with_identity_setup(
1879                default_run_args,
1880                vec![
1881                    "--max-genesis-archive-unpacked-size",
1882                    &max_genesis_archive_unpacked_size.to_string(),
1883                ],
1884                expected_args,
1885            );
1886        }
1887    }
1888
1889    #[test]
1890    fn verify_args_struct_by_command_run_with_allow_private_addr() {
1891        let default_run_args = RunArgs::default();
1892        let expected_args = RunArgs {
1893            socket_addr_space: SocketAddrSpace::Unspecified,
1894            ..default_run_args.clone()
1895        };
1896        verify_args_struct_by_command_run_with_identity_setup(
1897            default_run_args,
1898            vec!["--allow-private-addr"],
1899            expected_args,
1900        );
1901    }
1902}