Skip to main content

agave_validator/commands/run/
args.rs

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