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