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