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