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