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