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