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