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