1use {
2 crate::{commands, commands::run::args::pub_sub_config},
3 agave_snapshots::{
4 DEFAULT_ARCHIVE_COMPRESSION, SnapshotVersion,
5 snapshot_config::{
6 DEFAULT_FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS,
7 DEFAULT_INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS,
8 DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
9 DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
10 },
11 },
12 clap::{App, AppSettings, Arg, ArgMatches, SubCommand, crate_description, crate_name},
13 log::warn,
14 solana_accounts_db::accounts_db::{
15 DEFAULT_ACCOUNTS_SHRINK_OPTIMIZE_TOTAL_SPACE, DEFAULT_ACCOUNTS_SHRINK_RATIO,
16 },
17 solana_clap_utils::{
18 hidden_unless_forced,
19 input_validators::{
20 is_parsable, is_pubkey, is_pubkey_or_keypair, is_slot, is_url_or_moniker,
21 validate_cpu_ranges,
22 },
23 },
24 solana_clock::Slot,
25 solana_epoch_schedule::MINIMUM_SLOTS_PER_EPOCH,
26 solana_faucet::faucet::{self, FAUCET_PORT},
27 solana_gossip::cluster_info::DEFAULT_NUM_VOTOR_QUIC_ENDPOINTS,
28 solana_hash::Hash,
29 solana_net_utils::{MINIMUM_VALIDATOR_PORT_RANGE_WIDTH, VALIDATOR_PORT_RANGE},
30 solana_send_transaction_service::send_transaction_service::{self},
31 solana_streamer::quic::{
32 DEFAULT_MAX_CONNECTIONS_PER_IPADDR_PER_MINUTE,
33 DEFAULT_MAX_QUIC_CONNECTIONS_PER_STAKED_PEER,
34 DEFAULT_MAX_QUIC_CONNECTIONS_PER_UNSTAKED_PEER, DEFAULT_MAX_STAKED_CONNECTIONS,
35 DEFAULT_MAX_STREAMS_PER_MS, DEFAULT_MAX_UNSTAKED_CONNECTIONS, DEFAULT_QUIC_ENDPOINTS,
36 },
37 solana_tpu_client::tpu_client::DEFAULT_VOTE_USE_QUIC,
38 std::{cmp::Ordering, path::PathBuf, str::FromStr},
39};
40
41pub mod thread_args;
42use {
43 solana_core::banking_stage::BankingStage,
44 thread_args::{DefaultThreadArgs, thread_args},
45};
46
47const DEFAULT_MIN_SNAPSHOT_DOWNLOAD_SPEED: u64 = 10485760;
49const MAX_SNAPSHOT_DOWNLOAD_ABORT: u32 = 5;
51const MINIMUM_TICKS_PER_SLOT: u64 = 2;
54
55pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> {
56 let app = App::new(crate_name!())
57 .about(crate_description!())
58 .version(version)
59 .global_setting(AppSettings::ColoredHelp)
60 .global_setting(AppSettings::InferSubcommands)
61 .global_setting(AppSettings::UnifiedHelpMessage)
62 .global_setting(AppSettings::VersionlessSubcommands)
63 .subcommand(commands::exit::command())
64 .subcommand(commands::authorized_voter::command())
65 .subcommand(commands::contact_info::command())
66 .subcommand(commands::repair_shred_from_peer::command())
67 .subcommand(commands::repair_whitelist::command())
68 .subcommand(
69 SubCommand::with_name("init").about("Initialize the ledger directory then exit"),
70 )
71 .subcommand(commands::monitor::command())
72 .subcommand(SubCommand::with_name("run").about("Run the validator"))
73 .subcommand(commands::plugin::command())
74 .subcommand(commands::set_identity::command())
75 .subcommand(commands::set_log_filter::command())
76 .subcommand(commands::staked_nodes_overrides::command())
77 .subcommand(commands::wait_for_restart_window::command())
78 .subcommand(commands::set_public_address::command())
79 .subcommand(commands::manage_block_production::command(default_args))
80 .subcommand(commands::blockstore::command());
81
82 commands::run::add_args(app, default_args)
83 .args(&thread_args(&default_args.thread_args))
84 .args(&get_deprecated_arguments())
85 .after_help("The default subcommand is run")
86}
87
88struct DeprecatedArg {
91 arg: Arg<'static, 'static>,
96
97 replaced_by: Option<&'static str>,
101
102 usage_warning: Option<&'static str>,
106}
107
108fn deprecated_arguments() -> Vec<DeprecatedArg> {
109 let mut res = vec![];
110
111 macro_rules! add_arg {
113 (
114 $arg:expr
115 $( , replaced_by: $replaced_by:expr )?
116 $( , usage_warning: $usage_warning:expr )?
117 $(,)?
118 ) => {
119 let replaced_by = add_arg!(@into-option $( $replaced_by )?);
120 let usage_warning = add_arg!(@into-option $( $usage_warning )?);
121 res.push(DeprecatedArg {
122 arg: $arg,
123 replaced_by,
124 usage_warning,
125 });
126 };
127
128 (@into-option) => { None };
129 (@into-option $v:expr) => { Some($v) };
130 }
131
132 add_arg!(
133 Arg::with_name("account_shrink_path")
135 .long("account-shrink-path")
136 .value_name("PATH")
137 .takes_value(true)
138 .multiple(true)
139 .help("Path to accounts shrink path which can hold a compacted account set."),
140 usage_warning: "Shrink paths are no longer used.",
141 );
142 add_arg!(
143 Arg::with_name("accounts_db_access_storages_method")
147 .long("accounts-db-access-storages-method")
148 .value_name("METHOD")
149 .takes_value(true)
150 .possible_values(&["mmap", "file"])
151 .help("No-op; account storages are always accessed via file I/O"),
152 );
153 add_arg!(
154 Arg::with_name("accounts_db_cache_limit_mb")
156 .long("accounts-db-cache-limit-mb")
157 .value_name("MEGABYTES")
158 .validator(is_parsable::<u64>)
159 .takes_value(true)
160 .help(
161 "How large the write cache for account data can become. If this is exceeded, the \
162 cache is flushed more aggressively.",
163 )
164 .conflicts_with("accounts_db_write_cache_limit"),
165 replaced_by: "accounts-db-write-cache-limit",
166 );
167 add_arg!(
168 Arg::with_name("disable_banking_trace")
170 .long("disable-banking-trace")
171 .conflicts_with("banking_trace_dir_byte_limit")
172 .takes_value(false)
173 .help("Disables the banking trace. No-op, banking trace is disabled by default."),
174 );
175 add_arg!(
176 Arg::with_name("enable_accounts_disk_index")
178 .long("enable-accounts-disk-index")
179 .help("Enables the disk-based accounts index")
180 .conflicts_with("accounts_index_limit"),
181 replaced_by: "accounts-index-limit",
182 );
183 add_arg!(
184 Arg::with_name("experimental_poh_pinned_cpu_core")
186 .long("experimental-poh-pinned-cpu-core")
187 .takes_value(true)
188 .value_name("CPU_ID")
189 .conflicts_with("poh_pinned_cpu_core")
190 .validator(is_parsable::<usize>)
191 .help("Specify which CPU core PoH is pinned to. Use --poh-pinned-cpu-core instead"),
192 replaced_by: "poh-pinned-cpu-core",
193 );
194 add_arg!(
195 Arg::with_name("experimental_retransmit_xdp_cpu_cores")
197 .long("experimental-retransmit-xdp-cpu-cores")
198 .takes_value(true)
199 .value_name("CPU_LIST")
200 .conflicts_with("xdp_cpu_cores")
201 .conflicts_with("no_xdp")
202 .validator(|value| {
203 validate_cpu_ranges(value, "--experimental-retransmit-xdp-cpu-cores")
204 })
205 .help("CPU cores to reserve for XDP. Use --xdp-cpu-cores instead"),
206 replaced_by: "xdp-cpu-cores",
207 );
208 add_arg!(
209 Arg::with_name("experimental_retransmit_xdp_interface")
211 .long("experimental-retransmit-xdp-interface")
212 .takes_value(true)
213 .value_name("INTERFACE")
214 .conflicts_with("xdp_interface")
215 .conflicts_with("no_xdp")
216 .help("Network interface to use for XDP. Use --xdp-interface instead"),
217 replaced_by: "xdp-interface",
218 );
219 add_arg!(
220 Arg::with_name("experimental_retransmit_xdp_zero_copy")
222 .long("experimental-retransmit-xdp-zero-copy")
223 .takes_value(false)
224 .conflicts_with("xdp_zero_copy")
225 .conflicts_with("no_xdp")
226 .help("Enable XDP zero copy. Use --xdp-zero-copy instead"),
227 replaced_by: "xdp-zero-copy",
228 );
229 add_arg!(
230 Arg::with_name("limit_ledger_size")
232 .long("limit-ledger-size")
233 .value_name("SHRED_COUNT")
234 .takes_value(true)
235 .min_values(0)
236 .max_values(1)
237 .conflicts_with("limit_blockstore_size")
239 .help("Keep this amount of shreds in root slots."),
240 replaced_by: "limit-blockstore-size",
241 );
242 add_arg!(
243 Arg::with_name("tpu_connection_pool_size")
245 .long("tpu-connection-pool-size")
246 .takes_value(true)
247 .validator(is_parsable::<usize>)
248 .help("Controls the TPU connection pool size per remote address"),
249 usage_warning:"This parameter is misleading, avoid setting it",
250 );
251 res
252}
253
254fn get_deprecated_arguments() -> Vec<Arg<'static, 'static>> {
257 deprecated_arguments()
258 .into_iter()
259 .map(|info| {
260 let arg = info.arg;
261 arg.hidden(hidden_unless_forced())
263 })
264 .collect()
265}
266
267pub fn warn_for_deprecated_arguments(matches: &ArgMatches) {
268 for DeprecatedArg {
269 arg,
270 replaced_by,
271 usage_warning,
272 } in deprecated_arguments().into_iter()
273 {
274 if matches.is_present(arg.b.name) {
275 let mut msg = format!("--{} is deprecated", arg.b.name.replace('_', "-"));
276 if let Some(replaced_by) = replaced_by {
277 msg.push_str(&format!(", please use --{replaced_by}"));
278 }
279 msg.push('.');
280 if let Some(usage_warning) = usage_warning {
281 msg.push_str(&format!(" {usage_warning}"));
282 if !msg.ends_with('.') {
283 msg.push('.');
284 }
285 }
286 warn!("{msg}");
287 }
288 }
289}
290
291pub struct DefaultArgs {
292 pub bind_address: String,
293 pub dynamic_port_range: String,
294 pub ledger_path: String,
295
296 pub tower_storage: String,
297 pub send_transaction_service_config: send_transaction_service::Config,
298
299 pub maximum_local_snapshot_age: String,
300 pub maximum_full_snapshot_archives_to_retain: String,
301 pub maximum_incremental_snapshot_archives_to_retain: String,
302 pub snapshot_packager_niceness_adjustment: String,
303 pub full_snapshot_archive_interval_slots: String,
304 pub incremental_snapshot_archive_interval_slots: String,
305 pub min_snapshot_download_speed: String,
306 pub max_snapshot_download_abort: String,
307
308 pub contact_debug_interval: String,
309
310 pub snapshot_version: SnapshotVersion,
311 pub snapshot_archive_format: String,
312 pub snapshot_zstd_compression_level: String,
313
314 pub accounts_shrink_optimize_total_space: String,
315 pub accounts_shrink_ratio: String,
316
317 pub tpu_max_connections_per_unstaked_peer: String,
318 pub tpu_max_connections_per_staked_peer: String,
319 pub tpu_max_connections_per_ipaddr_per_minute: String,
320 pub tpu_max_staked_connections: String,
321 pub tpu_max_unstaked_connections: String,
322 pub tpu_max_fwd_staked_connections: String,
323 pub tpu_max_fwd_unstaked_connections: String,
324 pub tpu_max_streams_per_ms: String,
325
326 pub num_quic_endpoints: String,
327 pub num_votor_endpoints: String,
328 pub vote_use_quic: String,
329
330 pub banking_trace_dir_byte_limit: String,
331 pub block_production_pacing_fill_time_millis: String,
332
333 pub thread_args: DefaultThreadArgs,
334}
335
336impl DefaultArgs {
337 pub fn new() -> Self {
338 DefaultArgs {
339 bind_address: "0.0.0.0".to_string(),
340 ledger_path: "ledger".to_string(),
341 dynamic_port_range: format!("{}-{}", VALIDATOR_PORT_RANGE.0, VALIDATOR_PORT_RANGE.1),
342 maximum_local_snapshot_age: "2500".to_string(),
343 tower_storage: "file".to_string(),
344 send_transaction_service_config: send_transaction_service::Config::default(),
345 maximum_full_snapshot_archives_to_retain: DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN
346 .to_string(),
347 maximum_incremental_snapshot_archives_to_retain:
348 DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN.to_string(),
349 snapshot_packager_niceness_adjustment: "0".to_string(),
350 full_snapshot_archive_interval_slots: DEFAULT_FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS
351 .get()
352 .to_string(),
353 incremental_snapshot_archive_interval_slots:
354 DEFAULT_INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS
355 .get()
356 .to_string(),
357 min_snapshot_download_speed: DEFAULT_MIN_SNAPSHOT_DOWNLOAD_SPEED.to_string(),
358 max_snapshot_download_abort: MAX_SNAPSHOT_DOWNLOAD_ABORT.to_string(),
359 snapshot_archive_format: DEFAULT_ARCHIVE_COMPRESSION.to_string(),
360 snapshot_zstd_compression_level: "1".to_string(), contact_debug_interval: "120000".to_string(),
362 snapshot_version: SnapshotVersion::default(),
363 accounts_shrink_optimize_total_space: DEFAULT_ACCOUNTS_SHRINK_OPTIMIZE_TOTAL_SPACE
364 .to_string(),
365 accounts_shrink_ratio: DEFAULT_ACCOUNTS_SHRINK_RATIO.to_string(),
366 tpu_max_connections_per_ipaddr_per_minute:
367 DEFAULT_MAX_CONNECTIONS_PER_IPADDR_PER_MINUTE.to_string(),
368 vote_use_quic: DEFAULT_VOTE_USE_QUIC.to_string(),
369 tpu_max_connections_per_unstaked_peer: DEFAULT_MAX_QUIC_CONNECTIONS_PER_UNSTAKED_PEER
370 .to_string(),
371 tpu_max_connections_per_staked_peer: DEFAULT_MAX_QUIC_CONNECTIONS_PER_STAKED_PEER
372 .to_string(),
373 tpu_max_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS.to_string(),
374 tpu_max_unstaked_connections: DEFAULT_MAX_UNSTAKED_CONNECTIONS.to_string(),
375 tpu_max_fwd_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS
376 .saturating_add(DEFAULT_MAX_UNSTAKED_CONNECTIONS)
377 .to_string(),
378 tpu_max_fwd_unstaked_connections: 0.to_string(),
379 tpu_max_streams_per_ms: DEFAULT_MAX_STREAMS_PER_MS.to_string(),
380 num_quic_endpoints: DEFAULT_QUIC_ENDPOINTS.to_string(),
381 num_votor_endpoints: DEFAULT_NUM_VOTOR_QUIC_ENDPOINTS.to_string(),
382 banking_trace_dir_byte_limit: 0.to_string(),
383 block_production_pacing_fill_time_millis: BankingStage::default_fill_time_millis()
384 .to_string(),
385 thread_args: DefaultThreadArgs::default(),
386 }
387 }
388}
389
390impl Default for DefaultArgs {
391 fn default() -> Self {
392 Self::new()
393 }
394}
395
396pub fn port_validator(port: String) -> Result<(), String> {
397 port.parse::<u16>()
398 .map(|_| ())
399 .map_err(|e| format!("{e:?}"))
400}
401
402pub fn port_range_validator(port_range: String) -> Result<(), String> {
403 if let Some((start, end)) = solana_net_utils::parse_port_range(&port_range) {
404 if end - start < MINIMUM_VALIDATOR_PORT_RANGE_WIDTH {
406 Err(format!(
407 "Port range is too small. Try --dynamic-port-range {}-{}",
408 start,
409 start + MINIMUM_VALIDATOR_PORT_RANGE_WIDTH
410 ))
411 } else {
412 Ok(())
413 }
414 } else {
415 Err("Invalid port range".to_string())
416 }
417}
418
419pub(crate) fn hash_validator(hash: String) -> Result<(), String> {
420 Hash::from_str(&hash)
421 .map(|_| ())
422 .map_err(|e| format!("{e:?}"))
423}
424
425pub fn test_app<'a>(version: &'a str, default_args: &'a DefaultTestArgs) -> App<'a, 'a> {
427 App::new("solana-test-validator")
428 .about("Test Validator")
429 .version(version)
430 .arg({
431 let arg = Arg::with_name("config_file")
432 .short("C")
433 .long("config")
434 .value_name("PATH")
435 .takes_value(true)
436 .help("Configuration file to use");
437 if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE {
438 arg.default_value(config_file)
439 } else {
440 arg
441 }
442 })
443 .arg(
444 Arg::with_name("json_rpc_url")
445 .short("u")
446 .long("url")
447 .value_name("URL_OR_MONIKER")
448 .takes_value(true)
449 .validator(is_url_or_moniker)
450 .help(
451 "URL for Solana's JSON RPC or moniker (or their first letter): [mainnet-beta, \
452 testnet, devnet, localhost]",
453 ),
454 )
455 .arg(
456 Arg::with_name("mint_address")
457 .long("mint")
458 .value_name("PUBKEY")
459 .validator(is_pubkey)
460 .takes_value(true)
461 .help(
462 "Address of the mint account that will receive tokens created at genesis. If \
463 the ledger already exists then this parameter is silently ignored [default: \
464 client keypair]",
465 ),
466 )
467 .arg(
468 Arg::with_name("ledger_path")
469 .short("l")
470 .long("ledger")
471 .value_name("DIR")
472 .takes_value(true)
473 .required(true)
474 .default_value("test-ledger")
475 .help("Use DIR as ledger location"),
476 )
477 .arg(
478 Arg::with_name("reset")
479 .short("r")
480 .long("reset")
481 .takes_value(false)
482 .help(
483 "Reset the ledger to genesis if it exists. By default the validator will \
484 resume an existing ledger (if present)",
485 ),
486 )
487 .arg(
488 Arg::with_name("quiet")
489 .short("q")
490 .long("quiet")
491 .takes_value(false)
492 .conflicts_with("log")
493 .help("Quiet mode: suppress normal output"),
494 )
495 .arg(
496 Arg::with_name("log")
497 .long("log")
498 .takes_value(false)
499 .conflicts_with("quiet")
500 .help("Log mode: stream the validator log"),
501 )
502 .arg(
503 Arg::with_name("account_indexes")
504 .long("account-index")
505 .takes_value(true)
506 .multiple(true)
507 .possible_values(&["program-id", "spl-token-owner", "spl-token-mint"])
508 .value_name("INDEX")
509 .help("Enable an accounts index, indexed by the selected account field"),
510 )
511 .arg(
512 Arg::with_name("faucet_port")
513 .long("faucet-port")
514 .value_name("PORT")
515 .takes_value(true)
516 .default_value(&default_args.faucet_port)
517 .validator(port_validator)
518 .help("Enable the faucet on this port"),
519 )
520 .arg(
521 Arg::with_name("rpc_port")
522 .long("rpc-port")
523 .value_name("PORT")
524 .takes_value(true)
525 .default_value(&default_args.rpc_port)
526 .validator(port_validator)
527 .help("Enable JSON RPC on this port, and the next port for the RPC websocket"),
528 )
529 .arg(
530 Arg::with_name("enable_rpc_bigtable_ledger_storage")
531 .long("enable-rpc-bigtable-ledger-storage")
532 .takes_value(false)
533 .hidden(hidden_unless_forced())
534 .help(
535 "Fetch historical transaction info from a BigTable instance as a fallback to \
536 local ledger data",
537 ),
538 )
539 .arg(
540 Arg::with_name("enable_bigtable_ledger_upload")
541 .long("enable-bigtable-ledger-upload")
542 .takes_value(false)
543 .hidden(hidden_unless_forced())
544 .help("Upload new confirmed blocks into a BigTable instance"),
545 )
546 .arg(
547 Arg::with_name("rpc_bigtable_instance")
548 .long("rpc-bigtable-instance")
549 .value_name("INSTANCE_NAME")
550 .takes_value(true)
551 .hidden(hidden_unless_forced())
552 .default_value("solana-ledger")
553 .help("Name of BigTable instance to target"),
554 )
555 .arg(
556 Arg::with_name("rpc_bigtable_app_profile_id")
557 .long("rpc-bigtable-app-profile-id")
558 .value_name("APP_PROFILE_ID")
559 .takes_value(true)
560 .hidden(hidden_unless_forced())
561 .default_value(solana_storage_bigtable::DEFAULT_APP_PROFILE_ID)
562 .help("Application profile id to use in Bigtable requests"),
563 )
564 .arg(
565 Arg::with_name("bpf_program")
566 .long("bpf-program")
567 .value_names(&["ADDRESS_OR_KEYPAIR", "SBF_PROGRAM.SO"])
568 .takes_value(true)
569 .number_of_values(2)
570 .multiple(true)
571 .help(
572 "Add a SBF program to the genesis configuration with upgrades disabled. If \
573 the ledger already exists then this parameter is silently ignored. The first \
574 argument can be a pubkey string or path to a keypair",
575 ),
576 )
577 .arg(
578 Arg::with_name("upgradeable_program")
579 .long("upgradeable-program")
580 .value_names(&["ADDRESS_OR_KEYPAIR", "SBF_PROGRAM.SO", "UPGRADE_AUTHORITY"])
581 .takes_value(true)
582 .number_of_values(3)
583 .multiple(true)
584 .help(
585 "Add an upgradeable SBF program to the genesis configuration. If the ledger \
586 already exists then this parameter is silently ignored. First and third \
587 arguments can be a pubkey string or path to a keypair. Upgrade authority set \
588 to \"none\" disables upgrades",
589 ),
590 )
591 .arg(
592 Arg::with_name("account")
593 .long("account")
594 .value_names(&["ADDRESS", "DUMP.JSON"])
595 .takes_value(true)
596 .number_of_values(2)
597 .allow_hyphen_values(true)
598 .multiple(true)
599 .help(
600 "Load an account from the provided JSON file (see `solana account --help` on \
601 how to dump an account to file). Files are searched for relatively to CWD \
602 and tests/fixtures. If ADDRESS is omitted via the `-` placeholder, the one \
603 in the file will be used. If the ledger already exists then this parameter \
604 is silently ignored",
605 ),
606 )
607 .arg(
608 Arg::with_name("account_dir")
609 .long("account-dir")
610 .value_name("DIRECTORY")
611 .validator(|value| {
612 value
613 .parse::<PathBuf>()
614 .map_err(|err| format!("error parsing '{value}': {err}"))
615 .and_then(|path| {
616 if path.exists() && path.is_dir() {
617 Ok(())
618 } else {
619 Err(format!(
620 "path does not exist or is not a directory: {value}"
621 ))
622 }
623 })
624 })
625 .takes_value(true)
626 .multiple(true)
627 .help(
628 "Load all the accounts from the JSON files found in the specified DIRECTORY \
629 (see also the `--account` flag). If the ledger already exists then this \
630 parameter is silently ignored",
631 ),
632 )
633 .arg(
634 Arg::with_name("ticks_per_slot")
635 .long("ticks-per-slot")
636 .value_name("TICKS")
637 .validator(|value| {
638 value
639 .parse::<u64>()
640 .map_err(|err| format!("error parsing '{value}': {err}"))
641 .and_then(|ticks| {
642 if ticks < MINIMUM_TICKS_PER_SLOT {
643 Err(format!("value must be >= {MINIMUM_TICKS_PER_SLOT}"))
644 } else {
645 Ok(())
646 }
647 })
648 })
649 .takes_value(true)
650 .help("The number of ticks in a slot"),
651 )
652 .arg(
653 Arg::with_name("slots_per_epoch")
654 .long("slots-per-epoch")
655 .value_name("SLOTS")
656 .validator(|value| {
657 value
658 .parse::<Slot>()
659 .map_err(|err| format!("error parsing '{value}': {err}"))
660 .and_then(|slot| {
661 if slot < MINIMUM_SLOTS_PER_EPOCH {
662 Err(format!("value must be >= {MINIMUM_SLOTS_PER_EPOCH}"))
663 } else {
664 Ok(())
665 }
666 })
667 })
668 .takes_value(true)
669 .help(
670 "Override the number of slots in an epoch. If the ledger already exists then \
671 this parameter is silently ignored",
672 ),
673 )
674 .arg(
675 Arg::with_name("inflation_fixed")
676 .long("inflation-fixed")
677 .value_name("RATE")
678 .validator(|value| {
679 value
680 .parse::<f64>()
681 .map_err(|err| format!("error parsing '{value}': {err}"))
682 .and_then(|rate| match rate.partial_cmp(&0.0) {
683 Some(Ordering::Greater) | Some(Ordering::Equal) => Ok(()),
684 Some(Ordering::Less) | None => Err(String::from("value must be >= 0")),
685 })
686 })
687 .takes_value(true)
688 .allow_hyphen_values(true)
689 .help(
690 "Override default inflation with fixed rate. If the ledger already exists \
691 then this parameter is silently ignored",
692 ),
693 )
694 .arg(
695 Arg::with_name("gossip_port")
696 .long("gossip-port")
697 .value_name("PORT")
698 .takes_value(true)
699 .help("Gossip port number for the validator"),
700 )
701 .arg(
702 Arg::with_name("dynamic_port_range")
703 .long("dynamic-port-range")
704 .value_name("MIN_PORT-MAX_PORT")
705 .takes_value(true)
706 .default_value(&default_args.dynamic_port_range)
707 .validator(port_range_validator)
708 .help(
709 "Range to use for dynamically assigned ports. MIN_PORT-MAX_PORT yields the \
710 range [MIN_PORT, MAX_PORT)",
711 ),
712 )
713 .arg(
714 Arg::with_name("bind_address")
715 .long("bind-address")
716 .value_name("HOST")
717 .takes_value(true)
718 .validator(solana_net_utils::is_host)
719 .default_value("127.0.0.1")
720 .help(
721 "IPv4 address to bind the validator ports. Can be repeated. The first \
722 --bind-address MUST be your public internet address. ALL protocols (gossip, \
723 repair, IP echo, TVU, TPU, etc.) bind to this address on startup. Additional \
724 --bind-address values enable multihoming for Gossip/TVU/TPU - these \
725 protocols bind to ALL interfaces on startup. Gossip reads/sends from one \
726 interface at a time. TVU/TPU read from ALL interfaces simultaneously but \
727 send from only one interface at a time. When switching interfaces via \
728 AdminRPC: Gossip switches to send/receive from the new interface, while \
729 TVU/TPU continue receiving from ALL interfaces but send from the new \
730 interface only.",
731 ),
732 )
733 .arg(
734 Arg::with_name("advertised_ip")
735 .long("advertised-ip")
736 .value_name("HOST")
737 .takes_value(true)
738 .validator(solana_net_utils::is_host)
739 .hidden(hidden_unless_forced())
740 .help(
741 "Use when running a validator behind a NAT. DNS name or IP address for this \
742 validator to advertise in gossip. This address will be used as the target \
743 destination address for peers trying to contact this node. [default: the \
744 first --bind-address, or ask --entrypoint when --bind-address is not \
745 provided, or 127.0.0.1 when --entrypoint is not provided]. Note: this \
746 argument cannot be used in a multihoming context (when multiple \
747 --bind-address values are provided).",
748 ),
749 )
750 .arg(
751 Arg::with_name("clone_account")
752 .long("clone")
753 .short("c")
754 .value_name("ADDRESS")
755 .takes_value(true)
756 .validator(is_pubkey_or_keypair)
757 .multiple(true)
758 .requires("json_rpc_url")
759 .help(
760 "Copy an account from the cluster referenced by the --url argument the \
761 genesis configuration. If the ledger already exists then this parameter is \
762 silently ignored",
763 ),
764 )
765 .arg(
766 Arg::with_name("deep_clone_address_lookup_table")
767 .long("deep-clone-address-lookup-table")
768 .takes_value(true)
769 .validator(is_pubkey_or_keypair)
770 .multiple(true)
771 .requires("json_rpc_url")
772 .help(
773 "Copy an address lookup table and all accounts it references from the cluster \
774 referenced by the --url argument in the genesis configuration. If the ledger \
775 already exists then this parameter is silently ignored",
776 ),
777 )
778 .arg(
779 Arg::with_name("maybe_clone_account")
780 .long("maybe-clone")
781 .value_name("ADDRESS")
782 .takes_value(true)
783 .validator(is_pubkey_or_keypair)
784 .multiple(true)
785 .requires("json_rpc_url")
786 .help(
787 "Copy an account from the cluster referenced by the --url argument, skipping \
788 it if it doesn't exist. If the ledger already exists then this parameter is \
789 silently ignored",
790 ),
791 )
792 .arg(
793 Arg::with_name("clone_upgradeable_program")
794 .long("clone-upgradeable-program")
795 .value_name("ADDRESS")
796 .takes_value(true)
797 .validator(is_pubkey_or_keypair)
798 .multiple(true)
799 .requires("json_rpc_url")
800 .help(
801 "Copy an upgradeable program and its executable data from the cluster \
802 referenced by the --url argument the genesis configuration. If the ledger \
803 already exists then this parameter is silently ignored",
804 ),
805 )
806 .arg(
807 Arg::with_name("warp_slot")
808 .required(false)
809 .long("warp-slot")
810 .short("w")
811 .takes_value(true)
812 .value_name("WARP_SLOT")
813 .validator(is_slot)
814 .min_values(0)
815 .max_values(1)
816 .help(
817 "Warp the ledger to WARP_SLOT after starting the validator. If no slot is \
818 provided then the current slot of the cluster referenced by the --url \
819 argument will be used",
820 ),
821 )
822 .arg(
823 Arg::with_name("limit_ledger_size")
825 .long("limit-ledger-size")
826 .value_name("SHRED_COUNT")
827 .takes_value(true)
828 .min_values(0)
829 .max_values(1)
830 .conflicts_with("limit_blockstore_size")
831 .help("Keep this amount of shreds in root slots."),
832 )
833 .arg(
834 Arg::with_name("limit_blockstore_size")
835 .long("limit-blockstore-size")
836 .value_name("SHRED_COUNT")
837 .takes_value(true)
838 .default_value(default_args.limit_blockstore_size.as_str())
839 .help(
840 "Limit the number of total shreds that the Blockstore retains. Once the \
841 Blockstore reaches this capacity, shreds will be purged in a FIFO (oldest \
842 slots first) manner.",
843 ),
844 )
845 .arg(
846 Arg::with_name("faucet_sol")
847 .long("faucet-sol")
848 .takes_value(true)
849 .value_name("SOL")
850 .default_value(default_args.faucet_sol.as_str())
851 .help(
852 "Give the faucet address this much SOL in genesis. If the ledger already \
853 exists then this parameter is silently ignored",
854 ),
855 )
856 .arg(
857 Arg::with_name("faucet_time_slice_secs")
858 .long("faucet-time-slice-secs")
859 .takes_value(true)
860 .value_name("SECS")
861 .default_value(default_args.faucet_time_slice_secs.as_str())
862 .help("Time slice (in secs) over which to limit faucet requests"),
863 )
864 .arg(
865 Arg::with_name("faucet_per_time_sol_cap")
866 .long("faucet-per-time-sol-cap")
867 .takes_value(true)
868 .value_name("SOL")
869 .min_values(0)
870 .max_values(1)
871 .help("Per-time slice limit for faucet requests, in SOL"),
872 )
873 .arg(
874 Arg::with_name("faucet_per_request_sol_cap")
875 .long("faucet-per-request-sol-cap")
876 .takes_value(true)
877 .value_name("SOL")
878 .min_values(0)
879 .max_values(1)
880 .help("Per-request limit for faucet requests, in SOL"),
881 )
882 .arg(
883 Arg::with_name("geyser_plugin_config")
884 .long("geyser-plugin-config")
885 .alias("accountsdb-plugin-config")
886 .value_name("FILE")
887 .takes_value(true)
888 .multiple(true)
889 .help("Specify the configuration file for the Geyser plugin."),
890 )
891 .arg(
892 Arg::with_name("enable_scheduler_bindings")
893 .long("enable-scheduler-bindings")
894 .takes_value(false)
895 .help("Enables external processes to connect and manage block production"),
896 )
897 .arg(
898 Arg::with_name("alpenglow")
899 .long("alpenglow")
900 .takes_value(false)
901 .help("Activate Alpenglow at genesis"),
902 )
903 .arg(
904 Arg::with_name("deactivate_feature")
905 .long("deactivate-feature")
906 .takes_value(true)
907 .value_name("FEATURE_PUBKEY")
908 .validator(is_pubkey)
909 .multiple(true)
910 .help("deactivate this feature in genesis."),
911 )
912 .arg(
913 Arg::with_name("compute_unit_limit")
914 .long("compute-unit-limit")
915 .alias("max-compute-units")
916 .value_name("COMPUTE_UNITS")
917 .validator(is_parsable::<u64>)
918 .takes_value(true)
919 .help("Override the runtime's compute unit limit per transaction"),
920 )
921 .arg(
922 Arg::with_name("log_messages_bytes_limit")
923 .long("log-messages-bytes-limit")
924 .value_name("BYTES")
925 .validator(is_parsable::<usize>)
926 .takes_value(true)
927 .help("Maximum number of bytes written to the program log before truncation"),
928 )
929 .arg(
930 Arg::with_name("transaction_account_lock_limit")
931 .long("transaction-account-lock-limit")
932 .value_name("NUM_ACCOUNTS")
933 .validator(is_parsable::<u64>)
934 .takes_value(true)
935 .help("Override the runtime's account lock limit per transaction"),
936 )
937 .arg(
938 Arg::with_name("clone_feature_set")
939 .long("clone-feature-set")
940 .takes_value(false)
941 .requires("json_rpc_url")
942 .help(
943 "Copy a feature set from the cluster referenced by the --url argument in the \
944 genesis configuration. If the ledger already exists then this parameter is \
945 silently ignored",
946 ),
947 )
948 .args(&pub_sub_config::args(true))
949}
950
951pub struct DefaultTestArgs {
952 pub rpc_port: String,
953 pub faucet_port: String,
954 pub dynamic_port_range: String,
955 pub limit_blockstore_size: String,
956 pub faucet_sol: String,
957 pub faucet_time_slice_secs: String,
958}
959
960impl DefaultTestArgs {
961 pub fn new() -> Self {
962 DefaultTestArgs {
963 rpc_port: 8899.to_string(),
964 faucet_port: FAUCET_PORT.to_string(),
965 dynamic_port_range: format!("{}-{}", VALIDATOR_PORT_RANGE.0, VALIDATOR_PORT_RANGE.1),
966 limit_blockstore_size: 800_000.to_string(),
969 faucet_sol: (1_000_000.).to_string(),
970 faucet_time_slice_secs: (faucet::TIME_SLICE).to_string(),
971 }
972 }
973}
974
975impl Default for DefaultTestArgs {
976 fn default() -> Self {
977 Self::new()
978 }
979}
980
981#[cfg(test)]
982mod test {
983 use super::*;
984
985 #[test]
986 fn make_sure_deprecated_arguments_are_sorted_alphabetically() {
987 let deprecated = deprecated_arguments();
988
989 for i in 0..deprecated.len().saturating_sub(1) {
990 let curr_name = deprecated[i].arg.b.name;
991 let next_name = deprecated[i + 1].arg.b.name;
992
993 assert!(
994 curr_name != next_name,
995 "Arguments in `deprecated_arguments()` should be distinct.\nArguments {} and {} \
996 use the same name: {}",
997 i,
998 i + 1,
999 curr_name,
1000 );
1001
1002 assert!(
1003 curr_name < next_name,
1004 "To generate better diffs and for readability purposes, `deprecated_arguments()` \
1005 should list arguments in alphabetical order.\nArguments {} and {} are \
1006 not.\nArgument {} name: {}\nArgument {} name: {}",
1007 i,
1008 i + 1,
1009 i,
1010 curr_name,
1011 i + 1,
1012 next_name,
1013 );
1014 }
1015 }
1016}