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!(
191 Arg::with_name("dev_halt_at_slot")
193 .long("dev-halt-at-slot")
194 .value_name("SLOT")
195 .validator(is_slot)
196 .takes_value(true)
197 .help("Halt the validator when it reaches the given slot"),
198 usage_warning: "--dev-halt-at-slot will be removed in the future"
199 );
200 add_arg!(Arg::with_name("disable_accounts_disk_index")
201 .long("disable-accounts-disk-index")
203 .help("Disable the disk-based accounts index if it is enabled by default.")
204 .conflicts_with("enable_accounts_disk_index"),
205 usage_warning: "The disk-based accounts index is disabled by default",
206 );
207 add_arg!(
208 Arg::with_name("gossip_host")
210 .long("gossip-host")
211 .value_name("HOST")
212 .takes_value(true)
213 .validator(solana_net_utils::is_host),
214 replaced_by : "bind-address",
215 usage_warning:"Use --bind-address instead",
216 );
217 add_arg!(
218 Arg::with_name("tpu_coalesce_ms")
220 .long("tpu-coalesce-ms")
221 .value_name("MILLISECS")
222 .takes_value(true)
223 .validator(is_parsable::<u64>)
224 .help("Milliseconds to wait in the TPU receiver for packet coalescing."),
225 usage_warning:"tpu_coalesce will be dropped (currently ignored)",
226 );
227 add_arg!(
228 Arg::with_name("tpu_disable_quic")
230 .long("tpu-disable-quic")
231 .takes_value(false)
232 .help("Do not use QUIC to send transactions."),
233 usage_warning: "UDP support will be dropped"
234 );
235 add_arg!(
236 Arg::with_name("tpu_enable_udp")
238 .long("tpu-enable-udp")
239 .takes_value(false)
240 .help("Enable UDP for receiving/sending transactions."),
241 usage_warning: "UDP support will be dropped"
242 );
243 add_arg!(
244 Arg::with_name("transaction_struct")
246 .long("transaction-structure")
247 .value_name("STRUCT")
248 .takes_value(true)
249 .possible_values(TransactionStructure::cli_names())
250 .help(TransactionStructure::cli_message()),
251 usage_warning: "Transaction structure is no longer configurable"
252 );
253 res
254}
255
256fn get_deprecated_arguments() -> Vec<Arg<'static, 'static>> {
259 deprecated_arguments()
260 .into_iter()
261 .map(|info| {
262 let arg = info.arg;
263 arg.hidden(hidden_unless_forced())
265 })
266 .collect()
267}
268
269pub fn warn_for_deprecated_arguments(matches: &ArgMatches) {
270 for DeprecatedArg {
271 arg,
272 replaced_by,
273 usage_warning,
274 } in deprecated_arguments().into_iter()
275 {
276 if matches.is_present(arg.b.name) {
277 let mut msg = format!("--{} is deprecated", arg.b.name.replace('_', "-"));
278 if let Some(replaced_by) = replaced_by {
279 msg.push_str(&format!(", please use --{replaced_by}"));
280 }
281 msg.push('.');
282 if let Some(usage_warning) = usage_warning {
283 msg.push_str(&format!(" {usage_warning}"));
284 if !msg.ends_with('.') {
285 msg.push('.');
286 }
287 }
288 eprintln!("{msg}");
290 }
291 }
292}
293
294pub struct DefaultArgs {
295 pub bind_address: String,
296 pub dynamic_port_range: String,
297 pub ledger_path: String,
298
299 pub tower_storage: String,
300 pub send_transaction_service_config: send_transaction_service::Config,
301
302 pub maximum_local_snapshot_age: String,
303 pub maximum_full_snapshot_archives_to_retain: String,
304 pub maximum_incremental_snapshot_archives_to_retain: String,
305 pub snapshot_packager_niceness_adjustment: String,
306 pub full_snapshot_archive_interval_slots: String,
307 pub incremental_snapshot_archive_interval_slots: String,
308 pub min_snapshot_download_speed: String,
309 pub max_snapshot_download_abort: String,
310
311 pub contact_debug_interval: String,
312
313 pub snapshot_version: SnapshotVersion,
314 pub snapshot_archive_format: String,
315 pub snapshot_zstd_compression_level: String,
316
317 pub rocksdb_shred_compaction: String,
318 pub rocksdb_ledger_compression: String,
319 pub rocksdb_perf_sample_interval: String,
320
321 pub accounts_shrink_optimize_total_space: String,
322 pub accounts_shrink_ratio: String,
323 pub tpu_connection_pool_size: String,
324
325 pub tpu_max_connections_per_peer: String,
326 pub tpu_max_connections_per_ipaddr_per_minute: String,
327 pub tpu_max_staked_connections: String,
328 pub tpu_max_unstaked_connections: String,
329 pub tpu_max_fwd_staked_connections: String,
330 pub tpu_max_fwd_unstaked_connections: String,
331 pub tpu_max_streams_per_ms: String,
332
333 pub num_quic_endpoints: String,
334 pub vote_use_quic: String,
335
336 pub banking_trace_dir_byte_limit: String,
337 pub block_production_pacing_fill_time_millis: String,
338
339 pub wen_restart_path: String,
340
341 pub thread_args: DefaultThreadArgs,
342}
343
344impl DefaultArgs {
345 pub fn new() -> Self {
346 DefaultArgs {
347 bind_address: "0.0.0.0".to_string(),
348 ledger_path: "ledger".to_string(),
349 dynamic_port_range: format!("{}-{}", VALIDATOR_PORT_RANGE.0, VALIDATOR_PORT_RANGE.1),
350 maximum_local_snapshot_age: "2500".to_string(),
351 tower_storage: "file".to_string(),
352 send_transaction_service_config: send_transaction_service::Config::default(),
353 maximum_full_snapshot_archives_to_retain: DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN
354 .to_string(),
355 maximum_incremental_snapshot_archives_to_retain:
356 DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN.to_string(),
357 snapshot_packager_niceness_adjustment: "0".to_string(),
358 full_snapshot_archive_interval_slots: DEFAULT_FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS
359 .get()
360 .to_string(),
361 incremental_snapshot_archive_interval_slots:
362 DEFAULT_INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS
363 .get()
364 .to_string(),
365 min_snapshot_download_speed: DEFAULT_MIN_SNAPSHOT_DOWNLOAD_SPEED.to_string(),
366 max_snapshot_download_abort: MAX_SNAPSHOT_DOWNLOAD_ABORT.to_string(),
367 snapshot_archive_format: DEFAULT_ARCHIVE_COMPRESSION.to_string(),
368 snapshot_zstd_compression_level: "1".to_string(), contact_debug_interval: "120000".to_string(),
370 snapshot_version: SnapshotVersion::default(),
371 rocksdb_shred_compaction: "level".to_string(),
372 rocksdb_ledger_compression: "none".to_string(),
373 rocksdb_perf_sample_interval: "0".to_string(),
374 accounts_shrink_optimize_total_space: DEFAULT_ACCOUNTS_SHRINK_OPTIMIZE_TOTAL_SPACE
375 .to_string(),
376 accounts_shrink_ratio: DEFAULT_ACCOUNTS_SHRINK_RATIO.to_string(),
377 tpu_connection_pool_size: DEFAULT_TPU_CONNECTION_POOL_SIZE.to_string(),
378 tpu_max_connections_per_ipaddr_per_minute:
379 DEFAULT_MAX_CONNECTIONS_PER_IPADDR_PER_MINUTE.to_string(),
380 vote_use_quic: DEFAULT_VOTE_USE_QUIC.to_string(),
381 tpu_max_connections_per_peer: DEFAULT_MAX_QUIC_CONNECTIONS_PER_PEER.to_string(),
382 tpu_max_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS.to_string(),
383 tpu_max_unstaked_connections: DEFAULT_MAX_UNSTAKED_CONNECTIONS.to_string(),
384 tpu_max_fwd_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS
385 .saturating_add(DEFAULT_MAX_UNSTAKED_CONNECTIONS)
386 .to_string(),
387 tpu_max_fwd_unstaked_connections: 0.to_string(),
388 tpu_max_streams_per_ms: DEFAULT_MAX_STREAMS_PER_MS.to_string(),
389 num_quic_endpoints: DEFAULT_QUIC_ENDPOINTS.to_string(),
390 banking_trace_dir_byte_limit: BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT.to_string(),
391 block_production_pacing_fill_time_millis: BankingStage::default_fill_time_millis()
392 .to_string(),
393 wen_restart_path: "wen_restart_progress.proto".to_string(),
394 thread_args: DefaultThreadArgs::default(),
395 }
396 }
397}
398
399impl Default for DefaultArgs {
400 fn default() -> Self {
401 Self::new()
402 }
403}
404
405pub fn port_validator(port: String) -> Result<(), String> {
406 port.parse::<u16>()
407 .map(|_| ())
408 .map_err(|e| format!("{e:?}"))
409}
410
411pub fn port_range_validator(port_range: String) -> Result<(), String> {
412 if let Some((start, end)) = solana_net_utils::parse_port_range(&port_range) {
413 if end - start < MINIMUM_VALIDATOR_PORT_RANGE_WIDTH {
414 Err(format!(
415 "Port range is too small. Try --dynamic-port-range {}-{}",
416 start,
417 start + MINIMUM_VALIDATOR_PORT_RANGE_WIDTH
418 ))
419 } else if end.checked_add(QUIC_PORT_OFFSET).is_none() {
420 Err("Invalid dynamic_port_range.".to_string())
421 } else {
422 Ok(())
423 }
424 } else {
425 Err("Invalid port range".to_string())
426 }
427}
428
429pub(crate) fn hash_validator(hash: String) -> Result<(), String> {
430 Hash::from_str(&hash)
431 .map(|_| ())
432 .map_err(|e| format!("{e:?}"))
433}
434
435pub fn test_app<'a>(version: &'a str, default_args: &'a DefaultTestArgs) -> App<'a, 'a> {
437 App::new("solana-test-validator")
438 .about("Test Validator")
439 .version(version)
440 .arg({
441 let arg = Arg::with_name("config_file")
442 .short("C")
443 .long("config")
444 .value_name("PATH")
445 .takes_value(true)
446 .help("Configuration file to use");
447 if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE {
448 arg.default_value(config_file)
449 } else {
450 arg
451 }
452 })
453 .arg(
454 Arg::with_name("json_rpc_url")
455 .short("u")
456 .long("url")
457 .value_name("URL_OR_MONIKER")
458 .takes_value(true)
459 .validator(is_url_or_moniker)
460 .help(
461 "URL for Solana's JSON RPC or moniker (or their first letter): [mainnet-beta, \
462 testnet, devnet, localhost]",
463 ),
464 )
465 .arg(
466 Arg::with_name("mint_address")
467 .long("mint")
468 .value_name("PUBKEY")
469 .validator(is_pubkey)
470 .takes_value(true)
471 .help(
472 "Address of the mint account that will receive tokens created at genesis. If \
473 the ledger already exists then this parameter is silently ignored [default: \
474 client keypair]",
475 ),
476 )
477 .arg(
478 Arg::with_name("ledger_path")
479 .short("l")
480 .long("ledger")
481 .value_name("DIR")
482 .takes_value(true)
483 .required(true)
484 .default_value("test-ledger")
485 .help("Use DIR as ledger location"),
486 )
487 .arg(
488 Arg::with_name("reset")
489 .short("r")
490 .long("reset")
491 .takes_value(false)
492 .help(
493 "Reset the ledger to genesis if it exists. By default the validator will \
494 resume an existing ledger (if present)",
495 ),
496 )
497 .arg(
498 Arg::with_name("quiet")
499 .short("q")
500 .long("quiet")
501 .takes_value(false)
502 .conflicts_with("log")
503 .help("Quiet mode: suppress normal output"),
504 )
505 .arg(
506 Arg::with_name("log")
507 .long("log")
508 .takes_value(false)
509 .conflicts_with("quiet")
510 .help("Log mode: stream the validator log"),
511 )
512 .arg(
513 Arg::with_name("account_indexes")
514 .long("account-index")
515 .takes_value(true)
516 .multiple(true)
517 .possible_values(&["program-id", "spl-token-owner", "spl-token-mint"])
518 .value_name("INDEX")
519 .help("Enable an accounts index, indexed by the selected account field"),
520 )
521 .arg(
522 Arg::with_name("faucet_port")
523 .long("faucet-port")
524 .value_name("PORT")
525 .takes_value(true)
526 .default_value(&default_args.faucet_port)
527 .validator(port_validator)
528 .help("Enable the faucet on this port"),
529 )
530 .arg(
531 Arg::with_name("rpc_port")
532 .long("rpc-port")
533 .value_name("PORT")
534 .takes_value(true)
535 .default_value(&default_args.rpc_port)
536 .validator(port_validator)
537 .help("Enable JSON RPC on this port, and the next port for the RPC websocket"),
538 )
539 .arg(
540 Arg::with_name("enable_rpc_bigtable_ledger_storage")
541 .long("enable-rpc-bigtable-ledger-storage")
542 .takes_value(false)
543 .hidden(hidden_unless_forced())
544 .help(
545 "Fetch historical transaction info from a BigTable instance as a fallback to \
546 local ledger data",
547 ),
548 )
549 .arg(
550 Arg::with_name("enable_bigtable_ledger_upload")
551 .long("enable-bigtable-ledger-upload")
552 .takes_value(false)
553 .hidden(hidden_unless_forced())
554 .help("Upload new confirmed blocks into a BigTable instance"),
555 )
556 .arg(
557 Arg::with_name("rpc_bigtable_instance")
558 .long("rpc-bigtable-instance")
559 .value_name("INSTANCE_NAME")
560 .takes_value(true)
561 .hidden(hidden_unless_forced())
562 .default_value("solana-ledger")
563 .help("Name of BigTable instance to target"),
564 )
565 .arg(
566 Arg::with_name("rpc_bigtable_app_profile_id")
567 .long("rpc-bigtable-app-profile-id")
568 .value_name("APP_PROFILE_ID")
569 .takes_value(true)
570 .hidden(hidden_unless_forced())
571 .default_value(solana_storage_bigtable::DEFAULT_APP_PROFILE_ID)
572 .help("Application profile id to use in Bigtable requests"),
573 )
574 .arg(
575 Arg::with_name("bpf_program")
576 .long("bpf-program")
577 .value_names(&["ADDRESS_OR_KEYPAIR", "SBF_PROGRAM.SO"])
578 .takes_value(true)
579 .number_of_values(2)
580 .multiple(true)
581 .help(
582 "Add a SBF program to the genesis configuration with upgrades disabled. If \
583 the ledger already exists then this parameter is silently ignored. The first \
584 argument can be a pubkey string or path to a keypair",
585 ),
586 )
587 .arg(
588 Arg::with_name("upgradeable_program")
589 .long("upgradeable-program")
590 .value_names(&["ADDRESS_OR_KEYPAIR", "SBF_PROGRAM.SO", "UPGRADE_AUTHORITY"])
591 .takes_value(true)
592 .number_of_values(3)
593 .multiple(true)
594 .help(
595 "Add an upgradeable SBF program to the genesis configuration. If the ledger \
596 already exists then this parameter is silently ignored. First and third \
597 arguments can be a pubkey string or path to a keypair. Upgrade authority set \
598 to \"none\" disables upgrades",
599 ),
600 )
601 .arg(
602 Arg::with_name("account")
603 .long("account")
604 .value_names(&["ADDRESS", "DUMP.JSON"])
605 .takes_value(true)
606 .number_of_values(2)
607 .allow_hyphen_values(true)
608 .multiple(true)
609 .help(
610 "Load an account from the provided JSON file (see `solana account --help` on \
611 how to dump an account to file). Files are searched for relatively to CWD \
612 and tests/fixtures. If ADDRESS is omitted via the `-` placeholder, the one \
613 in the file will be used. If the ledger already exists then this parameter \
614 is silently ignored",
615 ),
616 )
617 .arg(
618 Arg::with_name("account_dir")
619 .long("account-dir")
620 .value_name("DIRECTORY")
621 .validator(|value| {
622 value
623 .parse::<PathBuf>()
624 .map_err(|err| format!("error parsing '{value}': {err}"))
625 .and_then(|path| {
626 if path.exists() && path.is_dir() {
627 Ok(())
628 } else {
629 Err(format!(
630 "path does not exist or is not a directory: {value}"
631 ))
632 }
633 })
634 })
635 .takes_value(true)
636 .multiple(true)
637 .help(
638 "Load all the accounts from the JSON files found in the specified DIRECTORY \
639 (see also the `--account` flag). If the ledger already exists then this \
640 parameter is silently ignored",
641 ),
642 )
643 .arg(
644 Arg::with_name("ticks_per_slot")
645 .long("ticks-per-slot")
646 .value_name("TICKS")
647 .validator(|value| {
648 value
649 .parse::<u64>()
650 .map_err(|err| format!("error parsing '{value}': {err}"))
651 .and_then(|ticks| {
652 if ticks < MINIMUM_TICKS_PER_SLOT {
653 Err(format!("value must be >= {MINIMUM_TICKS_PER_SLOT}"))
654 } else {
655 Ok(())
656 }
657 })
658 })
659 .takes_value(true)
660 .help("The number of ticks in a slot"),
661 )
662 .arg(
663 Arg::with_name("slots_per_epoch")
664 .long("slots-per-epoch")
665 .value_name("SLOTS")
666 .validator(|value| {
667 value
668 .parse::<Slot>()
669 .map_err(|err| format!("error parsing '{value}': {err}"))
670 .and_then(|slot| {
671 if slot < MINIMUM_SLOTS_PER_EPOCH {
672 Err(format!("value must be >= {MINIMUM_SLOTS_PER_EPOCH}"))
673 } else {
674 Ok(())
675 }
676 })
677 })
678 .takes_value(true)
679 .help(
680 "Override the number of slots in an epoch. If the ledger already exists then \
681 this parameter is silently ignored",
682 ),
683 )
684 .arg(
685 Arg::with_name("inflation_fixed")
686 .long("inflation-fixed")
687 .value_name("RATE")
688 .validator(|value| {
689 value
690 .parse::<f64>()
691 .map_err(|err| format!("error parsing '{value}': {err}"))
692 .and_then(|rate| match rate.partial_cmp(&0.0) {
693 Some(Ordering::Greater) | Some(Ordering::Equal) => Ok(()),
694 Some(Ordering::Less) | None => Err(String::from("value must be >= 0")),
695 })
696 })
697 .takes_value(true)
698 .allow_hyphen_values(true)
699 .help(
700 "Override default inflation with fixed rate. If the ledger already exists \
701 then this parameter is silently ignored",
702 ),
703 )
704 .arg(
705 Arg::with_name("gossip_port")
706 .long("gossip-port")
707 .value_name("PORT")
708 .takes_value(true)
709 .help("Gossip port number for the validator"),
710 )
711 .arg(
712 Arg::with_name("dynamic_port_range")
713 .long("dynamic-port-range")
714 .value_name("MIN_PORT-MAX_PORT")
715 .takes_value(true)
716 .validator(port_range_validator)
717 .help("Range to use for dynamically assigned ports [default: 1024-65535]"),
718 )
719 .arg(
720 Arg::with_name("bind_address")
721 .long("bind-address")
722 .value_name("HOST")
723 .takes_value(true)
724 .validator(solana_net_utils::is_host)
725 .default_value("127.0.0.1")
726 .help(
727 "IP address to bind the validator ports. Can be repeated. The first \
728 --bind-address MUST be your public internet address. ALL protocols (gossip, \
729 repair, IP echo, TVU, TPU, etc.) bind to this address on startup. Additional \
730 --bind-address values enable multihoming for Gossip/TVU/TPU - these \
731 protocols bind to ALL interfaces on startup. Gossip reads/sends from one \
732 interface at a time. TVU/TPU read from ALL interfaces simultaneously but \
733 send from only one interface at a time. When switching interfaces via \
734 AdminRPC: Gossip switches to send/receive from the new interface, while \
735 TVU/TPU continue receiving from ALL interfaces but send from the new \
736 interface only.",
737 ),
738 )
739 .arg(
740 Arg::with_name("advertised_ip")
741 .long("advertised-ip")
742 .value_name("HOST")
743 .takes_value(true)
744 .validator(solana_net_utils::is_host)
745 .hidden(hidden_unless_forced())
746 .help(
747 "Use when running a validator behind a NAT. DNS name or IP address for this \
748 validator to advertise in gossip. This address will be used as the target \
749 desination address for peers trying to contact this node. [default: the \
750 first --bind-address, or ask --entrypoint when --bind-address is not \
751 provided, or 127.0.0.1 when --entrypoint is not provided]. Note: this \
752 argument cannot be used in a multihoming context (when multiple \
753 --bind-address values are provided).",
754 ),
755 )
756 .arg(
757 Arg::with_name("clone_account")
758 .long("clone")
759 .short("c")
760 .value_name("ADDRESS")
761 .takes_value(true)
762 .validator(is_pubkey_or_keypair)
763 .multiple(true)
764 .requires("json_rpc_url")
765 .help(
766 "Copy an account from the cluster referenced by the --url argument the \
767 genesis configuration. If the ledger already exists then this parameter is \
768 silently ignored",
769 ),
770 )
771 .arg(
772 Arg::with_name("deep_clone_address_lookup_table")
773 .long("deep-clone-address-lookup-table")
774 .takes_value(true)
775 .validator(is_pubkey_or_keypair)
776 .multiple(true)
777 .requires("json_rpc_url")
778 .help(
779 "Copy an address lookup table and all accounts it references from the cluster \
780 referenced by the --url argument in the genesis configuration. If the ledger \
781 already exists then this parameter is silently ignored",
782 ),
783 )
784 .arg(
785 Arg::with_name("maybe_clone_account")
786 .long("maybe-clone")
787 .value_name("ADDRESS")
788 .takes_value(true)
789 .validator(is_pubkey_or_keypair)
790 .multiple(true)
791 .requires("json_rpc_url")
792 .help(
793 "Copy an account from the cluster referenced by the --url argument, skipping \
794 it if it doesn't exist. If the ledger already exists then this parameter is \
795 silently ignored",
796 ),
797 )
798 .arg(
799 Arg::with_name("clone_upgradeable_program")
800 .long("clone-upgradeable-program")
801 .value_name("ADDRESS")
802 .takes_value(true)
803 .validator(is_pubkey_or_keypair)
804 .multiple(true)
805 .requires("json_rpc_url")
806 .help(
807 "Copy an upgradeable program and its executable data from the cluster \
808 referenced by the --url argument the genesis configuration. If the ledger \
809 already exists then this parameter is silently ignored",
810 ),
811 )
812 .arg(
813 Arg::with_name("warp_slot")
814 .required(false)
815 .long("warp-slot")
816 .short("w")
817 .takes_value(true)
818 .value_name("WARP_SLOT")
819 .validator(is_slot)
820 .min_values(0)
821 .max_values(1)
822 .help(
823 "Warp the ledger to WARP_SLOT after starting the validator. If no slot is \
824 provided then the current slot of the cluster referenced by the --url \
825 argument will be used",
826 ),
827 )
828 .arg(
829 Arg::with_name("limit_ledger_size")
830 .long("limit-ledger-size")
831 .value_name("SHRED_COUNT")
832 .takes_value(true)
833 .default_value(default_args.limit_ledger_size.as_str())
834 .help("Keep this amount of shreds in root slots."),
835 )
836 .arg(
837 Arg::with_name("faucet_sol")
838 .long("faucet-sol")
839 .takes_value(true)
840 .value_name("SOL")
841 .default_value(default_args.faucet_sol.as_str())
842 .help(
843 "Give the faucet address this much SOL in genesis. If the ledger already \
844 exists then this parameter is silently ignored",
845 ),
846 )
847 .arg(
848 Arg::with_name("faucet_time_slice_secs")
849 .long("faucet-time-slice-secs")
850 .takes_value(true)
851 .value_name("SECS")
852 .default_value(default_args.faucet_time_slice_secs.as_str())
853 .help("Time slice (in secs) over which to limit faucet requests"),
854 )
855 .arg(
856 Arg::with_name("faucet_per_time_sol_cap")
857 .long("faucet-per-time-sol-cap")
858 .takes_value(true)
859 .value_name("SOL")
860 .min_values(0)
861 .max_values(1)
862 .help("Per-time slice limit for faucet requests, in SOL"),
863 )
864 .arg(
865 Arg::with_name("faucet_per_request_sol_cap")
866 .long("faucet-per-request-sol-cap")
867 .takes_value(true)
868 .value_name("SOL")
869 .min_values(0)
870 .max_values(1)
871 .help("Per-request limit for faucet requests, in SOL"),
872 )
873 .arg(
874 Arg::with_name("geyser_plugin_config")
875 .long("geyser-plugin-config")
876 .alias("accountsdb-plugin-config")
877 .value_name("FILE")
878 .takes_value(true)
879 .multiple(true)
880 .help("Specify the configuration file for the Geyser plugin."),
881 )
882 .arg(
883 Arg::with_name("deactivate_feature")
884 .long("deactivate-feature")
885 .takes_value(true)
886 .value_name("FEATURE_PUBKEY")
887 .validator(is_pubkey)
888 .multiple(true)
889 .help("deactivate this feature in genesis."),
890 )
891 .arg(
892 Arg::with_name("compute_unit_limit")
893 .long("compute-unit-limit")
894 .alias("max-compute-units")
895 .value_name("COMPUTE_UNITS")
896 .validator(is_parsable::<u64>)
897 .takes_value(true)
898 .help("Override the runtime's compute unit limit per transaction"),
899 )
900 .arg(
901 Arg::with_name("log_messages_bytes_limit")
902 .long("log-messages-bytes-limit")
903 .value_name("BYTES")
904 .validator(is_parsable::<usize>)
905 .takes_value(true)
906 .help("Maximum number of bytes written to the program log before truncation"),
907 )
908 .arg(
909 Arg::with_name("transaction_account_lock_limit")
910 .long("transaction-account-lock-limit")
911 .value_name("NUM_ACCOUNTS")
912 .validator(is_parsable::<u64>)
913 .takes_value(true)
914 .help("Override the runtime's account lock limit per transaction"),
915 )
916 .arg(
917 Arg::with_name("clone_feature_set")
918 .long("clone-feature-set")
919 .takes_value(false)
920 .requires("json_rpc_url")
921 .help(
922 "Copy a feature set from the cluster referenced by the --url argument in the \
923 genesis configuration. If the ledger already exists then this parameter is \
924 silently ignored",
925 ),
926 )
927 .args(&pub_sub_config::args(true))
928}
929
930pub struct DefaultTestArgs {
931 pub rpc_port: String,
932 pub faucet_port: String,
933 pub limit_ledger_size: String,
934 pub faucet_sol: String,
935 pub faucet_time_slice_secs: String,
936}
937
938impl DefaultTestArgs {
939 pub fn new() -> Self {
940 DefaultTestArgs {
941 rpc_port: 8899.to_string(),
942 faucet_port: FAUCET_PORT.to_string(),
943 limit_ledger_size: 10_000.to_string(),
948 faucet_sol: (1_000_000.).to_string(),
949 faucet_time_slice_secs: (faucet::TIME_SLICE).to_string(),
950 }
951 }
952}
953
954impl Default for DefaultTestArgs {
955 fn default() -> Self {
956 Self::new()
957 }
958}
959
960#[cfg(test)]
961mod test {
962 use super::*;
963
964 #[test]
965 fn make_sure_deprecated_arguments_are_sorted_alphabetically() {
966 let deprecated = deprecated_arguments();
967
968 for i in 0..deprecated.len().saturating_sub(1) {
969 let curr_name = deprecated[i].arg.b.name;
970 let next_name = deprecated[i + 1].arg.b.name;
971
972 assert!(
973 curr_name != next_name,
974 "Arguments in `deprecated_arguments()` should be distinct.\nArguments {} and {} \
975 use the same name: {}",
976 i,
977 i + 1,
978 curr_name,
979 );
980
981 assert!(
982 curr_name < next_name,
983 "To generate better diffs and for readability purposes, `deprecated_arguments()` \
984 should list arguments in alphabetical order.\nArguments {} and {} are \
985 not.\nArgument {} name: {}\nArgument {} name: {}",
986 i,
987 i + 1,
988 i,
989 curr_name,
990 i + 1,
991 next_name,
992 );
993 }
994 }
995}