1use {
2 crate::{commands, commands::run::args::pub_sub_config},
3 agave_snapshots::{
4 snapshot_config::{
5 DEFAULT_FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS,
6 DEFAULT_INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS,
7 DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
8 DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
9 },
10 SnapshotVersion, DEFAULT_ARCHIVE_COMPRESSION,
11 },
12 clap::{crate_description, crate_name, App, AppSettings, Arg, ArgMatches, SubCommand},
13 solana_accounts_db::accounts_db::{
14 DEFAULT_ACCOUNTS_SHRINK_OPTIMIZE_TOTAL_SPACE, DEFAULT_ACCOUNTS_SHRINK_RATIO,
15 },
16 solana_clap_utils::{
17 hidden_unless_forced,
18 input_validators::{
19 is_parsable, is_pubkey, is_pubkey_or_keypair, is_slot, is_url_or_moniker,
20 },
21 },
22 solana_clock::Slot,
23 solana_core::{
24 banking_trace::BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT, validator::TransactionStructure,
25 },
26 solana_epoch_schedule::MINIMUM_SLOTS_PER_EPOCH,
27 solana_faucet::faucet::{self, FAUCET_PORT},
28 solana_hash::Hash,
29 solana_net_utils::{MINIMUM_VALIDATOR_PORT_RANGE_WIDTH, VALIDATOR_PORT_RANGE},
30 solana_quic_definitions::QUIC_PORT_OFFSET,
31 solana_send_transaction_service::send_transaction_service::{self},
32 solana_streamer::quic::{
33 DEFAULT_MAX_CONNECTIONS_PER_IPADDR_PER_MINUTE, DEFAULT_MAX_QUIC_CONNECTIONS_PER_PEER,
34 DEFAULT_MAX_STAKED_CONNECTIONS, DEFAULT_MAX_STREAMS_PER_MS,
35 DEFAULT_MAX_UNSTAKED_CONNECTIONS, DEFAULT_QUIC_ENDPOINTS,
36 },
37 solana_tpu_client::tpu_client::{DEFAULT_TPU_CONNECTION_POOL_SIZE, 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::{thread_args, DefaultThreadArgs},
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
81 commands::run::add_args(app, default_args)
82 .args(&thread_args(&default_args.thread_args))
83 .args(&get_deprecated_arguments())
84 .after_help("The default subcommand is run")
85}
86
87struct DeprecatedArg {
90 arg: Arg<'static, 'static>,
95
96 replaced_by: Option<&'static str>,
100
101 usage_warning: Option<&'static str>,
105}
106
107fn deprecated_arguments() -> Vec<DeprecatedArg> {
108 let mut res = vec![];
109
110 macro_rules! add_arg {
112 (
113 $arg:expr
114 $( , replaced_by: $replaced_by:expr )?
115 $( , usage_warning: $usage_warning:expr )?
116 $(,)?
117 ) => {
118 let replaced_by = add_arg!(@into-option $( $replaced_by )?);
119 let usage_warning = add_arg!(@into-option $( $usage_warning )?);
120 res.push(DeprecatedArg {
121 arg: $arg,
122 replaced_by,
123 usage_warning,
124 });
125 };
126
127 (@into-option) => { None };
128 (@into-option $v:expr) => { Some($v) };
129 }
130
131 add_arg!(
132 Arg::with_name("accounts_db_clean_threads")
134 .long("accounts-db-clean-threads")
135 .takes_value(true)
136 .value_name("NUMBER")
137 .conflicts_with("accounts_db_background_threads"),
138 replaced_by: "accounts-db-background-threads",
139 );
140 add_arg!(
141 Arg::with_name("accounts_db_hash_threads")
143 .long("accounts-db-hash-threads")
144 .takes_value(true)
145 .value_name("NUMBER"),
146 usage_warning: "There is no more startup background accounts hash calculation",
147 );
148 add_arg!(
149 Arg::with_name("accounts_db_read_cache_limit_mb")
151 .long("accounts-db-read-cache-limit-mb")
152 .value_name("MAX | LOW,HIGH")
153 .takes_value(true)
154 .min_values(1)
155 .max_values(2)
156 .multiple(false)
157 .require_delimiter(true)
158 .help("How large the read cache for account data can become, in mebibytes")
159 .long_help(
160 "How large the read cache for account data can become, in mebibytes. \
161 If given a single value, it will be the maximum size for the cache. \
162 If given a pair of values, they will be the low and high watermarks \
163 for the cache. When the cache exceeds the high watermark, entries will \
164 be evicted until the size reaches the low watermark."
165 )
166 .hidden(hidden_unless_forced())
167 .conflicts_with("accounts_db_read_cache_limit"),
168 replaced_by: "accounts-db-read-cache-limit",
169 );
170 add_arg!(
171 Arg::with_name("accounts_hash_cache_path")
173 .long("accounts-hash-cache-path")
174 .value_name("PATH")
175 .takes_value(true)
176 .help(
177 "Use PATH as accounts hash cache location \
178 [default: <LEDGER>/accounts_hash_cache]",
179 ),
180 usage_warning: "The accounts hash cache is obsolete",
181 );
182 add_arg!(
183 Arg::with_name("cuda")
185 .long("cuda")
186 .takes_value(false)
187 .help("Use CUDA"),
188 usage_warning: "CUDA support will be dropped"
189 );
190 add_arg!(Arg::with_name("disable_accounts_disk_index")
191 .long("disable-accounts-disk-index")
193 .help("Disable the disk-based accounts index if it is enabled by default.")
194 .conflicts_with("enable_accounts_disk_index"),
195 usage_warning: "The disk-based accounts index is disabled by default",
196 );
197 add_arg!(
198 Arg::with_name("gossip_host")
200 .long("gossip-host")
201 .value_name("HOST")
202 .takes_value(true)
203 .validator(solana_net_utils::is_host),
204 replaced_by : "bind-address",
205 usage_warning:"Use --bind-address instead",
206 );
207 add_arg!(
208 Arg::with_name("tpu_coalesce_ms")
210 .long("tpu-coalesce-ms")
211 .value_name("MILLISECS")
212 .takes_value(true)
213 .validator(is_parsable::<u64>)
214 .help("Milliseconds to wait in the TPU receiver for packet coalescing."),
215 usage_warning:"tpu_coalesce will be dropped (currently ignored)",
216 );
217 add_arg!(
218 Arg::with_name("tpu_disable_quic")
220 .long("tpu-disable-quic")
221 .takes_value(false)
222 .help("Do not use QUIC to send transactions."),
223 usage_warning: "UDP support will be dropped"
224 );
225 add_arg!(
226 Arg::with_name("tpu_enable_udp")
228 .long("tpu-enable-udp")
229 .takes_value(false)
230 .help("Enable UDP for receiving/sending transactions."),
231 usage_warning: "UDP support will be dropped"
232 );
233 add_arg!(
234 Arg::with_name("transaction_struct")
236 .long("transaction-structure")
237 .value_name("STRUCT")
238 .takes_value(true)
239 .possible_values(TransactionStructure::cli_names())
240 .help(TransactionStructure::cli_message()),
241 usage_warning: "Transaction structure is no longer configurable"
242 );
243 res
244}
245
246fn get_deprecated_arguments() -> Vec<Arg<'static, 'static>> {
249 deprecated_arguments()
250 .into_iter()
251 .map(|info| {
252 let arg = info.arg;
253 arg.hidden(hidden_unless_forced())
255 })
256 .collect()
257}
258
259pub fn warn_for_deprecated_arguments(matches: &ArgMatches) {
260 for DeprecatedArg {
261 arg,
262 replaced_by,
263 usage_warning,
264 } in deprecated_arguments().into_iter()
265 {
266 if matches.is_present(arg.b.name) {
267 let mut msg = format!("--{} is deprecated", arg.b.name.replace('_', "-"));
268 if let Some(replaced_by) = replaced_by {
269 msg.push_str(&format!(", please use --{replaced_by}"));
270 }
271 msg.push('.');
272 if let Some(usage_warning) = usage_warning {
273 msg.push_str(&format!(" {usage_warning}"));
274 if !msg.ends_with('.') {
275 msg.push('.');
276 }
277 }
278 eprintln!("{msg}");
280 }
281 }
282}
283
284pub struct DefaultArgs {
285 pub bind_address: String,
286 pub dynamic_port_range: String,
287 pub ledger_path: String,
288
289 pub tower_storage: String,
290 pub send_transaction_service_config: send_transaction_service::Config,
291
292 pub maximum_local_snapshot_age: String,
293 pub maximum_full_snapshot_archives_to_retain: String,
294 pub maximum_incremental_snapshot_archives_to_retain: String,
295 pub snapshot_packager_niceness_adjustment: String,
296 pub full_snapshot_archive_interval_slots: String,
297 pub incremental_snapshot_archive_interval_slots: String,
298 pub min_snapshot_download_speed: String,
299 pub max_snapshot_download_abort: String,
300
301 pub contact_debug_interval: String,
302
303 pub snapshot_version: SnapshotVersion,
304 pub snapshot_archive_format: String,
305 pub snapshot_zstd_compression_level: String,
306
307 pub rocksdb_shred_compaction: String,
308 pub rocksdb_ledger_compression: String,
309 pub rocksdb_perf_sample_interval: String,
310
311 pub accounts_shrink_optimize_total_space: String,
312 pub accounts_shrink_ratio: String,
313 pub tpu_connection_pool_size: String,
314
315 pub tpu_max_connections_per_peer: String,
316 pub tpu_max_connections_per_ipaddr_per_minute: String,
317 pub tpu_max_staked_connections: String,
318 pub tpu_max_unstaked_connections: String,
319 pub tpu_max_fwd_staked_connections: String,
320 pub tpu_max_fwd_unstaked_connections: String,
321 pub tpu_max_streams_per_ms: String,
322
323 pub num_quic_endpoints: String,
324 pub vote_use_quic: String,
325
326 pub banking_trace_dir_byte_limit: String,
327 pub block_production_pacing_fill_time_millis: String,
328
329 pub wen_restart_path: String,
330
331 pub thread_args: DefaultThreadArgs,
332}
333
334impl DefaultArgs {
335 pub fn new() -> Self {
336 DefaultArgs {
337 bind_address: "0.0.0.0".to_string(),
338 ledger_path: "ledger".to_string(),
339 dynamic_port_range: format!("{}-{}", VALIDATOR_PORT_RANGE.0, VALIDATOR_PORT_RANGE.1),
340 maximum_local_snapshot_age: "2500".to_string(),
341 tower_storage: "file".to_string(),
342 send_transaction_service_config: send_transaction_service::Config::default(),
343 maximum_full_snapshot_archives_to_retain: DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN
344 .to_string(),
345 maximum_incremental_snapshot_archives_to_retain:
346 DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN.to_string(),
347 snapshot_packager_niceness_adjustment: "0".to_string(),
348 full_snapshot_archive_interval_slots: DEFAULT_FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS
349 .get()
350 .to_string(),
351 incremental_snapshot_archive_interval_slots:
352 DEFAULT_INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS
353 .get()
354 .to_string(),
355 min_snapshot_download_speed: DEFAULT_MIN_SNAPSHOT_DOWNLOAD_SPEED.to_string(),
356 max_snapshot_download_abort: MAX_SNAPSHOT_DOWNLOAD_ABORT.to_string(),
357 snapshot_archive_format: DEFAULT_ARCHIVE_COMPRESSION.to_string(),
358 snapshot_zstd_compression_level: "1".to_string(), contact_debug_interval: "120000".to_string(),
360 snapshot_version: SnapshotVersion::default(),
361 rocksdb_shred_compaction: "level".to_string(),
362 rocksdb_ledger_compression: "none".to_string(),
363 rocksdb_perf_sample_interval: "0".to_string(),
364 accounts_shrink_optimize_total_space: DEFAULT_ACCOUNTS_SHRINK_OPTIMIZE_TOTAL_SPACE
365 .to_string(),
366 accounts_shrink_ratio: DEFAULT_ACCOUNTS_SHRINK_RATIO.to_string(),
367 tpu_connection_pool_size: DEFAULT_TPU_CONNECTION_POOL_SIZE.to_string(),
368 tpu_max_connections_per_ipaddr_per_minute:
369 DEFAULT_MAX_CONNECTIONS_PER_IPADDR_PER_MINUTE.to_string(),
370 vote_use_quic: DEFAULT_VOTE_USE_QUIC.to_string(),
371 tpu_max_connections_per_peer: DEFAULT_MAX_QUIC_CONNECTIONS_PER_PEER.to_string(),
372 tpu_max_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS.to_string(),
373 tpu_max_unstaked_connections: DEFAULT_MAX_UNSTAKED_CONNECTIONS.to_string(),
374 tpu_max_fwd_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS
375 .saturating_add(DEFAULT_MAX_UNSTAKED_CONNECTIONS)
376 .to_string(),
377 tpu_max_fwd_unstaked_connections: 0.to_string(),
378 tpu_max_streams_per_ms: DEFAULT_MAX_STREAMS_PER_MS.to_string(),
379 num_quic_endpoints: DEFAULT_QUIC_ENDPOINTS.to_string(),
380 banking_trace_dir_byte_limit: BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT.to_string(),
381 block_production_pacing_fill_time_millis: BankingStage::default_fill_time_millis()
382 .to_string(),
383 wen_restart_path: "wen_restart_progress.proto".to_string(),
384 thread_args: DefaultThreadArgs::default(),
385 }
386 }
387}
388
389impl Default for DefaultArgs {
390 fn default() -> Self {
391 Self::new()
392 }
393}
394
395pub fn port_validator(port: String) -> Result<(), String> {
396 port.parse::<u16>()
397 .map(|_| ())
398 .map_err(|e| format!("{e:?}"))
399}
400
401pub fn port_range_validator(port_range: String) -> Result<(), String> {
402 if let Some((start, end)) = solana_net_utils::parse_port_range(&port_range) {
403 if end - start < MINIMUM_VALIDATOR_PORT_RANGE_WIDTH {
404 Err(format!(
405 "Port range is too small. Try --dynamic-port-range {}-{}",
406 start,
407 start + MINIMUM_VALIDATOR_PORT_RANGE_WIDTH
408 ))
409 } else if end.checked_add(QUIC_PORT_OFFSET).is_none() {
410 Err("Invalid dynamic_port_range.".to_string())
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 .validator(port_range_validator)
707 .help("Range to use for dynamically assigned ports [default: 1024-65535]"),
708 )
709 .arg(
710 Arg::with_name("bind_address")
711 .long("bind-address")
712 .value_name("HOST")
713 .takes_value(true)
714 .validator(solana_net_utils::is_host)
715 .default_value("127.0.0.1")
716 .help(
717 "IP address to bind the validator ports. Can be repeated. The first \
718 --bind-address MUST be your public internet address. ALL protocols (gossip, \
719 repair, IP echo, TVU, TPU, etc.) bind to this address on startup. Additional \
720 --bind-address values enable multihoming for Gossip/TVU/TPU - these \
721 protocols bind to ALL interfaces on startup. Gossip reads/sends from one \
722 interface at a time. TVU/TPU read from ALL interfaces simultaneously but \
723 send from only one interface at a time. When switching interfaces via \
724 AdminRPC: Gossip switches to send/receive from the new interface, while \
725 TVU/TPU continue receiving from ALL interfaces but send from the new \
726 interface only.",
727 ),
728 )
729 .arg(
730 Arg::with_name("advertised_ip")
731 .long("advertised-ip")
732 .value_name("HOST")
733 .takes_value(true)
734 .validator(solana_net_utils::is_host)
735 .hidden(hidden_unless_forced())
736 .help(
737 "Use when running a validator behind a NAT. DNS name or IP address for this \
738 validator to advertise in gossip. This address will be used as the target \
739 desination address for peers trying to contact this node. [default: the \
740 first --bind-address, or ask --entrypoint when --bind-address is not \
741 provided, or 127.0.0.1 when --entrypoint is not provided]. Note: this \
742 argument cannot be used in a multihoming context (when multiple \
743 --bind-address values are provided).",
744 ),
745 )
746 .arg(
747 Arg::with_name("clone_account")
748 .long("clone")
749 .short("c")
750 .value_name("ADDRESS")
751 .takes_value(true)
752 .validator(is_pubkey_or_keypair)
753 .multiple(true)
754 .requires("json_rpc_url")
755 .help(
756 "Copy an account from the cluster referenced by the --url argument the \
757 genesis configuration. If the ledger already exists then this parameter is \
758 silently ignored",
759 ),
760 )
761 .arg(
762 Arg::with_name("deep_clone_address_lookup_table")
763 .long("deep-clone-address-lookup-table")
764 .takes_value(true)
765 .validator(is_pubkey_or_keypair)
766 .multiple(true)
767 .requires("json_rpc_url")
768 .help(
769 "Copy an address lookup table and all accounts it references from the cluster \
770 referenced by the --url argument in the genesis configuration. If the ledger \
771 already exists then this parameter is silently ignored",
772 ),
773 )
774 .arg(
775 Arg::with_name("maybe_clone_account")
776 .long("maybe-clone")
777 .value_name("ADDRESS")
778 .takes_value(true)
779 .validator(is_pubkey_or_keypair)
780 .multiple(true)
781 .requires("json_rpc_url")
782 .help(
783 "Copy an account from the cluster referenced by the --url argument, skipping \
784 it if it doesn't exist. If the ledger already exists then this parameter is \
785 silently ignored",
786 ),
787 )
788 .arg(
789 Arg::with_name("clone_upgradeable_program")
790 .long("clone-upgradeable-program")
791 .value_name("ADDRESS")
792 .takes_value(true)
793 .validator(is_pubkey_or_keypair)
794 .multiple(true)
795 .requires("json_rpc_url")
796 .help(
797 "Copy an upgradeable program and its executable data from the cluster \
798 referenced by the --url argument the genesis configuration. If the ledger \
799 already exists then this parameter is silently ignored",
800 ),
801 )
802 .arg(
803 Arg::with_name("warp_slot")
804 .required(false)
805 .long("warp-slot")
806 .short("w")
807 .takes_value(true)
808 .value_name("WARP_SLOT")
809 .validator(is_slot)
810 .min_values(0)
811 .max_values(1)
812 .help(
813 "Warp the ledger to WARP_SLOT after starting the validator. If no slot is \
814 provided then the current slot of the cluster referenced by the --url \
815 argument will be used",
816 ),
817 )
818 .arg(
819 Arg::with_name("limit_ledger_size")
820 .long("limit-ledger-size")
821 .value_name("SHRED_COUNT")
822 .takes_value(true)
823 .default_value(default_args.limit_ledger_size.as_str())
824 .help("Keep this amount of shreds in root slots."),
825 )
826 .arg(
827 Arg::with_name("faucet_sol")
828 .long("faucet-sol")
829 .takes_value(true)
830 .value_name("SOL")
831 .default_value(default_args.faucet_sol.as_str())
832 .help(
833 "Give the faucet address this much SOL in genesis. If the ledger already \
834 exists then this parameter is silently ignored",
835 ),
836 )
837 .arg(
838 Arg::with_name("faucet_time_slice_secs")
839 .long("faucet-time-slice-secs")
840 .takes_value(true)
841 .value_name("SECS")
842 .default_value(default_args.faucet_time_slice_secs.as_str())
843 .help("Time slice (in secs) over which to limit faucet requests"),
844 )
845 .arg(
846 Arg::with_name("faucet_per_time_sol_cap")
847 .long("faucet-per-time-sol-cap")
848 .takes_value(true)
849 .value_name("SOL")
850 .min_values(0)
851 .max_values(1)
852 .help("Per-time slice limit for faucet requests, in SOL"),
853 )
854 .arg(
855 Arg::with_name("faucet_per_request_sol_cap")
856 .long("faucet-per-request-sol-cap")
857 .takes_value(true)
858 .value_name("SOL")
859 .min_values(0)
860 .max_values(1)
861 .help("Per-request limit for faucet requests, in SOL"),
862 )
863 .arg(
864 Arg::with_name("geyser_plugin_config")
865 .long("geyser-plugin-config")
866 .alias("accountsdb-plugin-config")
867 .value_name("FILE")
868 .takes_value(true)
869 .multiple(true)
870 .help("Specify the configuration file for the Geyser plugin."),
871 )
872 .arg(
873 Arg::with_name("deactivate_feature")
874 .long("deactivate-feature")
875 .takes_value(true)
876 .value_name("FEATURE_PUBKEY")
877 .validator(is_pubkey)
878 .multiple(true)
879 .help("deactivate this feature in genesis."),
880 )
881 .arg(
882 Arg::with_name("compute_unit_limit")
883 .long("compute-unit-limit")
884 .alias("max-compute-units")
885 .value_name("COMPUTE_UNITS")
886 .validator(is_parsable::<u64>)
887 .takes_value(true)
888 .help("Override the runtime's compute unit limit per transaction"),
889 )
890 .arg(
891 Arg::with_name("log_messages_bytes_limit")
892 .long("log-messages-bytes-limit")
893 .value_name("BYTES")
894 .validator(is_parsable::<usize>)
895 .takes_value(true)
896 .help("Maximum number of bytes written to the program log before truncation"),
897 )
898 .arg(
899 Arg::with_name("transaction_account_lock_limit")
900 .long("transaction-account-lock-limit")
901 .value_name("NUM_ACCOUNTS")
902 .validator(is_parsable::<u64>)
903 .takes_value(true)
904 .help("Override the runtime's account lock limit per transaction"),
905 )
906 .arg(
907 Arg::with_name("clone_feature_set")
908 .long("clone-feature-set")
909 .takes_value(false)
910 .requires("json_rpc_url")
911 .help(
912 "Copy a feature set from the cluster referenced by the --url argument in the \
913 genesis configuration. If the ledger already exists then this parameter is \
914 silently ignored",
915 ),
916 )
917 .args(&pub_sub_config::args(true))
918}
919
920pub struct DefaultTestArgs {
921 pub rpc_port: String,
922 pub faucet_port: String,
923 pub limit_ledger_size: String,
924 pub faucet_sol: String,
925 pub faucet_time_slice_secs: String,
926}
927
928impl DefaultTestArgs {
929 pub fn new() -> Self {
930 DefaultTestArgs {
931 rpc_port: 8899.to_string(),
932 faucet_port: FAUCET_PORT.to_string(),
933 limit_ledger_size: 10_000.to_string(),
938 faucet_sol: (1_000_000.).to_string(),
939 faucet_time_slice_secs: (faucet::TIME_SLICE).to_string(),
940 }
941 }
942}
943
944impl Default for DefaultTestArgs {
945 fn default() -> Self {
946 Self::new()
947 }
948}
949
950#[cfg(test)]
951mod test {
952 use super::*;
953
954 #[test]
955 fn make_sure_deprecated_arguments_are_sorted_alphabetically() {
956 let deprecated = deprecated_arguments();
957
958 for i in 0..deprecated.len().saturating_sub(1) {
959 let curr_name = deprecated[i].arg.b.name;
960 let next_name = deprecated[i + 1].arg.b.name;
961
962 assert!(
963 curr_name != next_name,
964 "Arguments in `deprecated_arguments()` should be distinct.\nArguments {} and {} \
965 use the same name: {}",
966 i,
967 i + 1,
968 curr_name,
969 );
970
971 assert!(
972 curr_name < next_name,
973 "To generate better diffs and for readability purposes, `deprecated_arguments()` \
974 should list arguments in alphabetical order.\nArguments {} and {} are \
975 not.\nArgument {} name: {}\nArgument {} name: {}",
976 i,
977 i + 1,
978 i,
979 curr_name,
980 i + 1,
981 next_name,
982 );
983 }
984 }
985}