1use {
2 crate::commands,
3 clap::{crate_description, crate_name, App, AppSettings, Arg, ArgMatches, SubCommand},
4 solana_accounts_db::{
5 accounts_db::{
6 DEFAULT_ACCOUNTS_SHRINK_OPTIMIZE_TOTAL_SPACE, DEFAULT_ACCOUNTS_SHRINK_RATIO,
7 },
8 hardened_unpack::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE,
9 },
10 solana_clap_utils::{
11 hidden_unless_forced,
12 input_validators::{
13 is_parsable, is_pubkey, is_pubkey_or_keypair, is_slot, is_url_or_moniker,
14 },
15 },
16 solana_clock::Slot,
17 solana_core::banking_trace::BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT,
18 solana_epoch_schedule::MINIMUM_SLOTS_PER_EPOCH,
19 solana_faucet::faucet::{self, FAUCET_PORT},
20 solana_hash::Hash,
21 solana_net_utils::{MINIMUM_VALIDATOR_PORT_RANGE_WIDTH, VALIDATOR_PORT_RANGE},
22 solana_quic_definitions::QUIC_PORT_OFFSET,
23 solana_rayon_threadlimit::get_thread_count,
24 solana_rpc::{rpc::MAX_REQUEST_BODY_SIZE, rpc_pubsub_service::PubSubConfig},
25 solana_rpc_client_api::request::{DELINQUENT_VALIDATOR_SLOT_DISTANCE, MAX_MULTIPLE_ACCOUNTS},
26 solana_runtime::snapshot_utils::{
27 SnapshotVersion, DEFAULT_ARCHIVE_COMPRESSION, DEFAULT_FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS,
28 DEFAULT_INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS,
29 DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
30 DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
31 },
32 solana_send_transaction_service::send_transaction_service::{self},
33 solana_streamer::quic::{
34 DEFAULT_MAX_CONNECTIONS_PER_IPADDR_PER_MINUTE, DEFAULT_MAX_QUIC_CONNECTIONS_PER_PEER,
35 DEFAULT_MAX_STAKED_CONNECTIONS, DEFAULT_MAX_STREAMS_PER_MS,
36 DEFAULT_MAX_UNSTAKED_CONNECTIONS, DEFAULT_QUIC_ENDPOINTS,
37 },
38 solana_tpu_client::tpu_client::{DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_VOTE_USE_QUIC},
39 std::{cmp::Ordering, path::PathBuf, str::FromStr},
40};
41
42pub mod thread_args;
43use thread_args::{thread_args, DefaultThreadArgs};
44
45const DEFAULT_MIN_SNAPSHOT_DOWNLOAD_SPEED: u64 = 10485760;
47const MAX_SNAPSHOT_DOWNLOAD_ABORT: u32 = 5;
49const MINIMUM_TICKS_PER_SLOT: u64 = 2;
52
53pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> {
54 let app = App::new(crate_name!())
55 .about(crate_description!())
56 .version(version)
57 .global_setting(AppSettings::ColoredHelp)
58 .global_setting(AppSettings::InferSubcommands)
59 .global_setting(AppSettings::UnifiedHelpMessage)
60 .global_setting(AppSettings::VersionlessSubcommands)
61 .subcommand(commands::exit::command())
62 .subcommand(commands::authorized_voter::command())
63 .subcommand(commands::contact_info::command())
64 .subcommand(commands::repair_shred_from_peer::command())
65 .subcommand(commands::repair_whitelist::command())
66 .subcommand(
67 SubCommand::with_name("init").about("Initialize the ledger directory then exit"),
68 )
69 .subcommand(commands::monitor::command())
70 .subcommand(SubCommand::with_name("run").about("Run the validator"))
71 .subcommand(commands::plugin::command())
72 .subcommand(commands::set_identity::command())
73 .subcommand(commands::set_log_filter::command())
74 .subcommand(commands::staked_nodes_overrides::command())
75 .subcommand(commands::wait_for_restart_window::command())
76 .subcommand(commands::set_public_address::command());
77
78 commands::run::add_args(app, default_args)
79 .args(&thread_args(&default_args.thread_args))
80 .args(&get_deprecated_arguments())
81 .after_help("The default subcommand is run")
82}
83
84struct DeprecatedArg {
87 arg: Arg<'static, 'static>,
92
93 replaced_by: Option<&'static str>,
97
98 usage_warning: Option<&'static str>,
102}
103
104fn deprecated_arguments() -> Vec<DeprecatedArg> {
105 let mut res = vec![];
106
107 macro_rules! add_arg {
109 (
110 $arg:expr
111 $( , replaced_by: $replaced_by:expr )?
112 $( , usage_warning: $usage_warning:expr )?
113 $(,)?
114 ) => {
115 let replaced_by = add_arg!(@into-option $( $replaced_by )?);
116 let usage_warning = add_arg!(@into-option $( $usage_warning )?);
117 res.push(DeprecatedArg {
118 arg: $arg,
119 replaced_by,
120 usage_warning,
121 });
122 };
123
124 (@into-option) => { None };
125 (@into-option $v:expr) => { Some($v) };
126 }
127
128 add_arg!(
129 Arg::with_name("accounts_db_clean_threads")
131 .long("accounts-db-clean-threads")
132 .takes_value(true)
133 .value_name("NUMBER")
134 .conflicts_with("accounts_db_background_threads"),
135 replaced_by: "accounts-db-background-threads",
136 );
137 add_arg!(
138 Arg::with_name("accounts_db_read_cache_limit_mb")
140 .long("accounts-db-read-cache-limit-mb")
141 .value_name("MAX | LOW,HIGH")
142 .takes_value(true)
143 .min_values(1)
144 .max_values(2)
145 .multiple(false)
146 .require_delimiter(true)
147 .help("How large the read cache for account data can become, in mebibytes")
148 .long_help(
149 "How large the read cache for account data can become, in mebibytes. \
150 If given a single value, it will be the maximum size for the cache. \
151 If given a pair of values, they will be the low and high watermarks \
152 for the cache. When the cache exceeds the high watermark, entries will \
153 be evicted until the size reaches the low watermark."
154 )
155 .hidden(hidden_unless_forced())
156 .conflicts_with("accounts_db_read_cache_limit"),
157 replaced_by: "accounts-db-read-cache-limit",
158 );
159 add_arg!(
160 Arg::with_name("accounts_hash_cache_path")
162 .long("accounts-hash-cache-path")
163 .value_name("PATH")
164 .takes_value(true)
165 .help(
166 "Use PATH as accounts hash cache location \
167 [default: <LEDGER>/accounts_hash_cache]",
168 ),
169 usage_warning: "The accounts hash cache is obsolete",
170 );
171 add_arg!(Arg::with_name("disable_accounts_disk_index")
172 .long("disable-accounts-disk-index")
173 .help("Disable the disk-based accounts index if it is enabled by default."));
174
175 add_arg!(
176 Arg::with_name("gossip_host")
178 .long("gossip-host")
179 .value_name("HOST")
180 .takes_value(true)
181 .validator(solana_net_utils::is_host),
182 replaced_by : "bind-address",
183 usage_warning:"Use --bind-address instead",
184 );
185 add_arg!(
186 Arg::with_name("tpu_disable_quic")
188 .long("tpu-disable-quic")
189 .takes_value(false)
190 .help("Do not use QUIC to send transactions."),
191 usage_warning: "UDP support will be dropped"
192 );
193 add_arg!(
194 Arg::with_name("tpu_enable_udp")
196 .long("tpu-enable-udp")
197 .takes_value(false)
198 .help("Enable UDP for receiving/sending transactions."),
199 usage_warning: "UDP support will be dropped"
200 );
201 res
202}
203
204fn get_deprecated_arguments() -> Vec<Arg<'static, 'static>> {
207 deprecated_arguments()
208 .into_iter()
209 .map(|info| {
210 let arg = info.arg;
211 arg.hidden(hidden_unless_forced())
213 })
214 .collect()
215}
216
217pub fn warn_for_deprecated_arguments(matches: &ArgMatches) {
218 for DeprecatedArg {
219 arg,
220 replaced_by,
221 usage_warning,
222 } in deprecated_arguments().into_iter()
223 {
224 if matches.is_present(arg.b.name) {
225 let mut msg = format!("--{} is deprecated", arg.b.name.replace('_', "-"));
226 if let Some(replaced_by) = replaced_by {
227 msg.push_str(&format!(", please use --{replaced_by}"));
228 }
229 msg.push('.');
230 if let Some(usage_warning) = usage_warning {
231 msg.push_str(&format!(" {usage_warning}"));
232 if !msg.ends_with('.') {
233 msg.push('.');
234 }
235 }
236 eprintln!("{msg}");
238 }
239 }
240}
241
242pub struct DefaultArgs {
243 pub bind_address: String,
244 pub dynamic_port_range: String,
245 pub ledger_path: String,
246
247 pub genesis_archive_unpacked_size: String,
248 pub health_check_slot_distance: String,
249 pub tower_storage: String,
250 pub etcd_domain_name: String,
251 pub send_transaction_service_config: send_transaction_service::Config,
252
253 pub rpc_max_multiple_accounts: String,
254 pub rpc_pubsub_max_active_subscriptions: String,
255 pub rpc_pubsub_queue_capacity_items: String,
256 pub rpc_pubsub_queue_capacity_bytes: String,
257 pub rpc_send_transaction_retry_ms: String,
258 pub rpc_send_transaction_batch_ms: String,
259 pub rpc_send_transaction_leader_forward_count: String,
260 pub rpc_send_transaction_service_max_retries: String,
261 pub rpc_send_transaction_batch_size: String,
262 pub rpc_send_transaction_retry_pool_max_size: String,
263 pub rpc_threads: String,
264 pub rpc_blocking_threads: String,
265 pub rpc_niceness_adjustment: String,
266 pub rpc_bigtable_timeout: String,
267 pub rpc_bigtable_instance_name: String,
268 pub rpc_bigtable_app_profile_id: String,
269 pub rpc_bigtable_max_message_size: String,
270 pub rpc_max_request_body_size: String,
271 pub rpc_pubsub_worker_threads: String,
272 pub rpc_pubsub_notification_threads: String,
273
274 pub maximum_local_snapshot_age: String,
275 pub maximum_full_snapshot_archives_to_retain: String,
276 pub maximum_incremental_snapshot_archives_to_retain: String,
277 pub snapshot_packager_niceness_adjustment: String,
278 pub full_snapshot_archive_interval_slots: String,
279 pub incremental_snapshot_archive_interval_slots: String,
280 pub min_snapshot_download_speed: String,
281 pub max_snapshot_download_abort: String,
282
283 pub contact_debug_interval: String,
284
285 pub snapshot_version: SnapshotVersion,
286 pub snapshot_archive_format: String,
287 pub snapshot_zstd_compression_level: String,
288
289 pub rocksdb_shred_compaction: String,
290 pub rocksdb_ledger_compression: String,
291 pub rocksdb_perf_sample_interval: String,
292
293 pub accounts_shrink_optimize_total_space: String,
294 pub accounts_shrink_ratio: String,
295 pub tpu_connection_pool_size: String,
296
297 pub tpu_max_connections_per_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
310 pub wen_restart_path: String,
311
312 pub thread_args: DefaultThreadArgs,
313}
314
315impl DefaultArgs {
316 pub fn new() -> Self {
317 let default_send_transaction_service_config = send_transaction_service::Config::default();
318
319 DefaultArgs {
320 bind_address: "0.0.0.0".to_string(),
321 ledger_path: "ledger".to_string(),
322 dynamic_port_range: format!("{}-{}", VALIDATOR_PORT_RANGE.0, VALIDATOR_PORT_RANGE.1),
323 maximum_local_snapshot_age: "2500".to_string(),
324 genesis_archive_unpacked_size: MAX_GENESIS_ARCHIVE_UNPACKED_SIZE.to_string(),
325 rpc_max_multiple_accounts: MAX_MULTIPLE_ACCOUNTS.to_string(),
326 health_check_slot_distance: DELINQUENT_VALIDATOR_SLOT_DISTANCE.to_string(),
327 tower_storage: "file".to_string(),
328 etcd_domain_name: "localhost".to_string(),
329 rpc_pubsub_max_active_subscriptions: PubSubConfig::default()
330 .max_active_subscriptions
331 .to_string(),
332 rpc_pubsub_queue_capacity_items: PubSubConfig::default()
333 .queue_capacity_items
334 .to_string(),
335 rpc_pubsub_queue_capacity_bytes: PubSubConfig::default()
336 .queue_capacity_bytes
337 .to_string(),
338 send_transaction_service_config: send_transaction_service::Config::default(),
339 rpc_send_transaction_retry_ms: default_send_transaction_service_config
340 .retry_rate_ms
341 .to_string(),
342 rpc_send_transaction_batch_ms: default_send_transaction_service_config
343 .batch_send_rate_ms
344 .to_string(),
345 rpc_send_transaction_leader_forward_count: default_send_transaction_service_config
346 .leader_forward_count
347 .to_string(),
348 rpc_send_transaction_service_max_retries: default_send_transaction_service_config
349 .service_max_retries
350 .to_string(),
351 rpc_send_transaction_batch_size: default_send_transaction_service_config
352 .batch_size
353 .to_string(),
354 rpc_send_transaction_retry_pool_max_size: default_send_transaction_service_config
355 .retry_pool_max_size
356 .to_string(),
357 rpc_threads: num_cpus::get().to_string(),
358 rpc_blocking_threads: 1.max(num_cpus::get() / 4).to_string(),
359 rpc_niceness_adjustment: "0".to_string(),
360 rpc_bigtable_timeout: "30".to_string(),
361 rpc_bigtable_instance_name: solana_storage_bigtable::DEFAULT_INSTANCE_NAME.to_string(),
362 rpc_bigtable_app_profile_id: solana_storage_bigtable::DEFAULT_APP_PROFILE_ID
363 .to_string(),
364 rpc_bigtable_max_message_size: solana_storage_bigtable::DEFAULT_MAX_MESSAGE_SIZE
365 .to_string(),
366 rpc_pubsub_worker_threads: "4".to_string(),
367 rpc_pubsub_notification_threads: get_thread_count().to_string(),
368 maximum_full_snapshot_archives_to_retain: DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN
369 .to_string(),
370 maximum_incremental_snapshot_archives_to_retain:
371 DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN.to_string(),
372 snapshot_packager_niceness_adjustment: "0".to_string(),
373 full_snapshot_archive_interval_slots: DEFAULT_FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS
374 .get()
375 .to_string(),
376 incremental_snapshot_archive_interval_slots:
377 DEFAULT_INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS
378 .get()
379 .to_string(),
380 min_snapshot_download_speed: DEFAULT_MIN_SNAPSHOT_DOWNLOAD_SPEED.to_string(),
381 max_snapshot_download_abort: MAX_SNAPSHOT_DOWNLOAD_ABORT.to_string(),
382 snapshot_archive_format: DEFAULT_ARCHIVE_COMPRESSION.to_string(),
383 snapshot_zstd_compression_level: "1".to_string(), contact_debug_interval: "120000".to_string(),
385 snapshot_version: SnapshotVersion::default(),
386 rocksdb_shred_compaction: "level".to_string(),
387 rocksdb_ledger_compression: "none".to_string(),
388 rocksdb_perf_sample_interval: "0".to_string(),
389 accounts_shrink_optimize_total_space: DEFAULT_ACCOUNTS_SHRINK_OPTIMIZE_TOTAL_SPACE
390 .to_string(),
391 accounts_shrink_ratio: DEFAULT_ACCOUNTS_SHRINK_RATIO.to_string(),
392 tpu_connection_pool_size: DEFAULT_TPU_CONNECTION_POOL_SIZE.to_string(),
393 tpu_max_connections_per_ipaddr_per_minute:
394 DEFAULT_MAX_CONNECTIONS_PER_IPADDR_PER_MINUTE.to_string(),
395 vote_use_quic: DEFAULT_VOTE_USE_QUIC.to_string(),
396 tpu_max_connections_per_peer: DEFAULT_MAX_QUIC_CONNECTIONS_PER_PEER.to_string(),
397 tpu_max_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS.to_string(),
398 tpu_max_unstaked_connections: DEFAULT_MAX_UNSTAKED_CONNECTIONS.to_string(),
399 tpu_max_fwd_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS
400 .saturating_add(DEFAULT_MAX_UNSTAKED_CONNECTIONS)
401 .to_string(),
402 tpu_max_fwd_unstaked_connections: 0.to_string(),
403 tpu_max_streams_per_ms: DEFAULT_MAX_STREAMS_PER_MS.to_string(),
404 num_quic_endpoints: DEFAULT_QUIC_ENDPOINTS.to_string(),
405 rpc_max_request_body_size: MAX_REQUEST_BODY_SIZE.to_string(),
406 banking_trace_dir_byte_limit: BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT.to_string(),
407 wen_restart_path: "wen_restart_progress.proto".to_string(),
408 thread_args: DefaultThreadArgs::default(),
409 }
410 }
411}
412
413impl Default for DefaultArgs {
414 fn default() -> Self {
415 Self::new()
416 }
417}
418
419pub fn port_validator(port: String) -> Result<(), String> {
420 port.parse::<u16>()
421 .map(|_| ())
422 .map_err(|e| format!("{e:?}"))
423}
424
425pub fn port_range_validator(port_range: String) -> Result<(), String> {
426 if let Some((start, end)) = solana_net_utils::parse_port_range(&port_range) {
427 if end - start < MINIMUM_VALIDATOR_PORT_RANGE_WIDTH {
428 Err(format!(
429 "Port range is too small. Try --dynamic-port-range {}-{}",
430 start,
431 start + MINIMUM_VALIDATOR_PORT_RANGE_WIDTH
432 ))
433 } else if end.checked_add(QUIC_PORT_OFFSET).is_none() {
434 Err("Invalid dynamic_port_range.".to_string())
435 } else {
436 Ok(())
437 }
438 } else {
439 Err("Invalid port range".to_string())
440 }
441}
442
443pub(crate) fn hash_validator(hash: String) -> Result<(), String> {
444 Hash::from_str(&hash)
445 .map(|_| ())
446 .map_err(|e| format!("{e:?}"))
447}
448
449pub fn test_app<'a>(version: &'a str, default_args: &'a DefaultTestArgs) -> App<'a, 'a> {
451 App::new("solana-test-validator")
452 .about("Test Validator")
453 .version(version)
454 .arg({
455 let arg = Arg::with_name("config_file")
456 .short("C")
457 .long("config")
458 .value_name("PATH")
459 .takes_value(true)
460 .help("Configuration file to use");
461 if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE {
462 arg.default_value(config_file)
463 } else {
464 arg
465 }
466 })
467 .arg(
468 Arg::with_name("json_rpc_url")
469 .short("u")
470 .long("url")
471 .value_name("URL_OR_MONIKER")
472 .takes_value(true)
473 .validator(is_url_or_moniker)
474 .help(
475 "URL for Solana's JSON RPC or moniker (or their first letter): [mainnet-beta, \
476 testnet, devnet, localhost]",
477 ),
478 )
479 .arg(
480 Arg::with_name("mint_address")
481 .long("mint")
482 .value_name("PUBKEY")
483 .validator(is_pubkey)
484 .takes_value(true)
485 .help(
486 "Address of the mint account that will receive tokens created at genesis. If \
487 the ledger already exists then this parameter is silently ignored [default: \
488 client keypair]",
489 ),
490 )
491 .arg(
492 Arg::with_name("ledger_path")
493 .short("l")
494 .long("ledger")
495 .value_name("DIR")
496 .takes_value(true)
497 .required(true)
498 .default_value("test-ledger")
499 .help("Use DIR as ledger location"),
500 )
501 .arg(
502 Arg::with_name("reset")
503 .short("r")
504 .long("reset")
505 .takes_value(false)
506 .help(
507 "Reset the ledger to genesis if it exists. By default the validator will \
508 resume an existing ledger (if present)",
509 ),
510 )
511 .arg(
512 Arg::with_name("quiet")
513 .short("q")
514 .long("quiet")
515 .takes_value(false)
516 .conflicts_with("log")
517 .help("Quiet mode: suppress normal output"),
518 )
519 .arg(
520 Arg::with_name("log")
521 .long("log")
522 .takes_value(false)
523 .conflicts_with("quiet")
524 .help("Log mode: stream the validator log"),
525 )
526 .arg(
527 Arg::with_name("account_indexes")
528 .long("account-index")
529 .takes_value(true)
530 .multiple(true)
531 .possible_values(&["program-id", "spl-token-owner", "spl-token-mint"])
532 .value_name("INDEX")
533 .help("Enable an accounts index, indexed by the selected account field"),
534 )
535 .arg(
536 Arg::with_name("faucet_port")
537 .long("faucet-port")
538 .value_name("PORT")
539 .takes_value(true)
540 .default_value(&default_args.faucet_port)
541 .validator(port_validator)
542 .help("Enable the faucet on this port"),
543 )
544 .arg(
545 Arg::with_name("rpc_port")
546 .long("rpc-port")
547 .value_name("PORT")
548 .takes_value(true)
549 .default_value(&default_args.rpc_port)
550 .validator(port_validator)
551 .help("Enable JSON RPC on this port, and the next port for the RPC websocket"),
552 )
553 .arg(
554 Arg::with_name("enable_rpc_bigtable_ledger_storage")
555 .long("enable-rpc-bigtable-ledger-storage")
556 .takes_value(false)
557 .hidden(hidden_unless_forced())
558 .help(
559 "Fetch historical transaction info from a BigTable instance as a fallback to \
560 local ledger data",
561 ),
562 )
563 .arg(
564 Arg::with_name("enable_bigtable_ledger_upload")
565 .long("enable-bigtable-ledger-upload")
566 .takes_value(false)
567 .hidden(hidden_unless_forced())
568 .help("Upload new confirmed blocks into a BigTable instance"),
569 )
570 .arg(
571 Arg::with_name("rpc_bigtable_instance")
572 .long("rpc-bigtable-instance")
573 .value_name("INSTANCE_NAME")
574 .takes_value(true)
575 .hidden(hidden_unless_forced())
576 .default_value("solana-ledger")
577 .help("Name of BigTable instance to target"),
578 )
579 .arg(
580 Arg::with_name("rpc_bigtable_app_profile_id")
581 .long("rpc-bigtable-app-profile-id")
582 .value_name("APP_PROFILE_ID")
583 .takes_value(true)
584 .hidden(hidden_unless_forced())
585 .default_value(solana_storage_bigtable::DEFAULT_APP_PROFILE_ID)
586 .help("Application profile id to use in Bigtable requests"),
587 )
588 .arg(
589 Arg::with_name("rpc_pubsub_enable_vote_subscription")
590 .long("rpc-pubsub-enable-vote-subscription")
591 .takes_value(false)
592 .help("Enable the unstable RPC PubSub `voteSubscribe` subscription"),
593 )
594 .arg(
595 Arg::with_name("rpc_pubsub_enable_block_subscription")
596 .long("rpc-pubsub-enable-block-subscription")
597 .takes_value(false)
598 .help("Enable the unstable RPC PubSub `blockSubscribe` subscription"),
599 )
600 .arg(
601 Arg::with_name("bpf_program")
602 .long("bpf-program")
603 .value_names(&["ADDRESS_OR_KEYPAIR", "SBF_PROGRAM.SO"])
604 .takes_value(true)
605 .number_of_values(2)
606 .multiple(true)
607 .help(
608 "Add a SBF program to the genesis configuration with upgrades disabled. If \
609 the ledger already exists then this parameter is silently ignored. The first \
610 argument can be a pubkey string or path to a keypair",
611 ),
612 )
613 .arg(
614 Arg::with_name("upgradeable_program")
615 .long("upgradeable-program")
616 .value_names(&["ADDRESS_OR_KEYPAIR", "SBF_PROGRAM.SO", "UPGRADE_AUTHORITY"])
617 .takes_value(true)
618 .number_of_values(3)
619 .multiple(true)
620 .help(
621 "Add an upgradeable SBF program to the genesis configuration. If the ledger \
622 already exists then this parameter is silently ignored. First and third \
623 arguments can be a pubkey string or path to a keypair. Upgrade authority set \
624 to \"none\" disables upgrades",
625 ),
626 )
627 .arg(
628 Arg::with_name("account")
629 .long("account")
630 .value_names(&["ADDRESS", "DUMP.JSON"])
631 .takes_value(true)
632 .number_of_values(2)
633 .allow_hyphen_values(true)
634 .multiple(true)
635 .help(
636 "Load an account from the provided JSON file (see `solana account --help` on \
637 how to dump an account to file). Files are searched for relatively to CWD \
638 and tests/fixtures. If ADDRESS is omitted via the `-` placeholder, the one \
639 in the file will be used. If the ledger already exists then this parameter \
640 is silently ignored",
641 ),
642 )
643 .arg(
644 Arg::with_name("account_dir")
645 .long("account-dir")
646 .value_name("DIRECTORY")
647 .validator(|value| {
648 value
649 .parse::<PathBuf>()
650 .map_err(|err| format!("error parsing '{value}': {err}"))
651 .and_then(|path| {
652 if path.exists() && path.is_dir() {
653 Ok(())
654 } else {
655 Err(format!(
656 "path does not exist or is not a directory: {value}"
657 ))
658 }
659 })
660 })
661 .takes_value(true)
662 .multiple(true)
663 .help(
664 "Load all the accounts from the JSON files found in the specified DIRECTORY \
665 (see also the `--account` flag). If the ledger already exists then this \
666 parameter is silently ignored",
667 ),
668 )
669 .arg(
670 Arg::with_name("ticks_per_slot")
671 .long("ticks-per-slot")
672 .value_name("TICKS")
673 .validator(|value| {
674 value
675 .parse::<u64>()
676 .map_err(|err| format!("error parsing '{value}': {err}"))
677 .and_then(|ticks| {
678 if ticks < MINIMUM_TICKS_PER_SLOT {
679 Err(format!("value must be >= {MINIMUM_TICKS_PER_SLOT}"))
680 } else {
681 Ok(())
682 }
683 })
684 })
685 .takes_value(true)
686 .help("The number of ticks in a slot"),
687 )
688 .arg(
689 Arg::with_name("slots_per_epoch")
690 .long("slots-per-epoch")
691 .value_name("SLOTS")
692 .validator(|value| {
693 value
694 .parse::<Slot>()
695 .map_err(|err| format!("error parsing '{value}': {err}"))
696 .and_then(|slot| {
697 if slot < MINIMUM_SLOTS_PER_EPOCH {
698 Err(format!("value must be >= {MINIMUM_SLOTS_PER_EPOCH}"))
699 } else {
700 Ok(())
701 }
702 })
703 })
704 .takes_value(true)
705 .help(
706 "Override the number of slots in an epoch. If the ledger already exists then \
707 this parameter is silently ignored",
708 ),
709 )
710 .arg(
711 Arg::with_name("inflation_fixed")
712 .long("inflation-fixed")
713 .value_name("RATE")
714 .validator(|value| {
715 value
716 .parse::<f64>()
717 .map_err(|err| format!("error parsing '{value}': {err}"))
718 .and_then(|rate| match rate.partial_cmp(&0.0) {
719 Some(Ordering::Greater) | Some(Ordering::Equal) => Ok(()),
720 Some(Ordering::Less) | None => Err(String::from("value must be >= 0")),
721 })
722 })
723 .takes_value(true)
724 .allow_hyphen_values(true)
725 .help(
726 "Override default inflation with fixed rate. If the ledger already exists \
727 then this parameter is silently ignored",
728 ),
729 )
730 .arg(
731 Arg::with_name("gossip_port")
732 .long("gossip-port")
733 .value_name("PORT")
734 .takes_value(true)
735 .help("Gossip port number for the validator"),
736 )
737 .arg(
738 Arg::with_name("dynamic_port_range")
739 .long("dynamic-port-range")
740 .value_name("MIN_PORT-MAX_PORT")
741 .takes_value(true)
742 .validator(port_range_validator)
743 .help("Range to use for dynamically assigned ports [default: 1024-65535]"),
744 )
745 .arg(
746 Arg::with_name("bind_address")
747 .long("bind-address")
748 .value_name("HOST")
749 .takes_value(true)
750 .validator(solana_net_utils::is_host)
751 .default_value("127.0.0.1")
752 .help(
753 "IP address to bind the validator ports [default: 127.0.0.1]. Can be repeated \
754 to specify multihoming options.",
755 ),
756 )
757 .arg(
758 Arg::with_name("clone_account")
759 .long("clone")
760 .short("c")
761 .value_name("ADDRESS")
762 .takes_value(true)
763 .validator(is_pubkey_or_keypair)
764 .multiple(true)
765 .requires("json_rpc_url")
766 .help(
767 "Copy an account from the cluster referenced by the --url argument the \
768 genesis configuration. If the ledger already exists then this parameter is \
769 silently ignored",
770 ),
771 )
772 .arg(
773 Arg::with_name("deep_clone_address_lookup_table")
774 .long("deep-clone-address-lookup-table")
775 .takes_value(true)
776 .validator(is_pubkey_or_keypair)
777 .multiple(true)
778 .requires("json_rpc_url")
779 .help(
780 "Copy an address lookup table and all accounts it references from the cluster \
781 referenced by the --url argument in the genesis configuration. If the ledger \
782 already exists then this parameter is silently ignored",
783 ),
784 )
785 .arg(
786 Arg::with_name("maybe_clone_account")
787 .long("maybe-clone")
788 .value_name("ADDRESS")
789 .takes_value(true)
790 .validator(is_pubkey_or_keypair)
791 .multiple(true)
792 .requires("json_rpc_url")
793 .help(
794 "Copy an account from the cluster referenced by the --url argument, skipping \
795 it if it doesn't exist. If the ledger already exists then this parameter is \
796 silently ignored",
797 ),
798 )
799 .arg(
800 Arg::with_name("clone_upgradeable_program")
801 .long("clone-upgradeable-program")
802 .value_name("ADDRESS")
803 .takes_value(true)
804 .validator(is_pubkey_or_keypair)
805 .multiple(true)
806 .requires("json_rpc_url")
807 .help(
808 "Copy an upgradeable program and its executable data from the cluster \
809 referenced by the --url argument the genesis configuration. If the ledger \
810 already exists then this parameter is silently ignored",
811 ),
812 )
813 .arg(
814 Arg::with_name("warp_slot")
815 .required(false)
816 .long("warp-slot")
817 .short("w")
818 .takes_value(true)
819 .value_name("WARP_SLOT")
820 .validator(is_slot)
821 .min_values(0)
822 .max_values(1)
823 .help(
824 "Warp the ledger to WARP_SLOT after starting the validator. If no slot is \
825 provided then the current slot of the cluster referenced by the --url \
826 argument will be used",
827 ),
828 )
829 .arg(
830 Arg::with_name("limit_ledger_size")
831 .long("limit-ledger-size")
832 .value_name("SHRED_COUNT")
833 .takes_value(true)
834 .default_value(default_args.limit_ledger_size.as_str())
835 .help("Keep this amount of shreds in root slots."),
836 )
837 .arg(
838 Arg::with_name("faucet_sol")
839 .long("faucet-sol")
840 .takes_value(true)
841 .value_name("SOL")
842 .default_value(default_args.faucet_sol.as_str())
843 .help(
844 "Give the faucet address this much SOL in genesis. If the ledger already \
845 exists then this parameter is silently ignored",
846 ),
847 )
848 .arg(
849 Arg::with_name("faucet_time_slice_secs")
850 .long("faucet-time-slice-secs")
851 .takes_value(true)
852 .value_name("SECS")
853 .default_value(default_args.faucet_time_slice_secs.as_str())
854 .help("Time slice (in secs) over which to limit faucet requests"),
855 )
856 .arg(
857 Arg::with_name("faucet_per_time_sol_cap")
858 .long("faucet-per-time-sol-cap")
859 .takes_value(true)
860 .value_name("SOL")
861 .min_values(0)
862 .max_values(1)
863 .help("Per-time slice limit for faucet requests, in SOL"),
864 )
865 .arg(
866 Arg::with_name("faucet_per_request_sol_cap")
867 .long("faucet-per-request-sol-cap")
868 .takes_value(true)
869 .value_name("SOL")
870 .min_values(0)
871 .max_values(1)
872 .help("Per-request limit for faucet requests, in SOL"),
873 )
874 .arg(
875 Arg::with_name("geyser_plugin_config")
876 .long("geyser-plugin-config")
877 .alias("accountsdb-plugin-config")
878 .value_name("FILE")
879 .takes_value(true)
880 .multiple(true)
881 .help("Specify the configuration file for the Geyser plugin."),
882 )
883 .arg(
884 Arg::with_name("deactivate_feature")
885 .long("deactivate-feature")
886 .takes_value(true)
887 .value_name("FEATURE_PUBKEY")
888 .validator(is_pubkey)
889 .multiple(true)
890 .help("deactivate this feature in genesis."),
891 )
892 .arg(
893 Arg::with_name("compute_unit_limit")
894 .long("compute-unit-limit")
895 .alias("max-compute-units")
896 .value_name("COMPUTE_UNITS")
897 .validator(is_parsable::<u64>)
898 .takes_value(true)
899 .help("Override the runtime's compute unit limit per transaction"),
900 )
901 .arg(
902 Arg::with_name("log_messages_bytes_limit")
903 .long("log-messages-bytes-limit")
904 .value_name("BYTES")
905 .validator(is_parsable::<usize>)
906 .takes_value(true)
907 .help("Maximum number of bytes written to the program log before truncation"),
908 )
909 .arg(
910 Arg::with_name("transaction_account_lock_limit")
911 .long("transaction-account-lock-limit")
912 .value_name("NUM_ACCOUNTS")
913 .validator(is_parsable::<u64>)
914 .takes_value(true)
915 .help("Override the runtime's account lock limit per transaction"),
916 )
917 .arg(
918 Arg::with_name("clone_feature_set")
919 .long("clone-feature-set")
920 .takes_value(false)
921 .requires("json_rpc_url")
922 .help(
923 "Copy a feature set from the cluster referenced by the --url argument in the \
924 genesis configuration. If the ledger already exists then this parameter is \
925 silently ignored",
926 ),
927 )
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}