1use {
2 crate::commands,
3 clap::{crate_description, crate_name, App, AppSettings, Arg, ArgMatches, SubCommand},
4 log::warn,
5 solana_accounts_db::{
6 accounts_db::{
7 DEFAULT_ACCOUNTS_SHRINK_OPTIMIZE_TOTAL_SPACE, DEFAULT_ACCOUNTS_SHRINK_RATIO,
8 },
9 hardened_unpack::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE,
10 },
11 solana_clap_utils::{
12 hidden_unless_forced,
13 input_validators::{
14 is_parsable, is_pubkey, is_pubkey_or_keypair, is_slot, is_url_or_moniker,
15 },
16 },
17 solana_clock::Slot,
18 solana_core::banking_trace::BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT,
19 solana_epoch_schedule::MINIMUM_SLOTS_PER_EPOCH,
20 solana_faucet::faucet::{self, FAUCET_PORT},
21 solana_hash::Hash,
22 solana_net_utils::{MINIMUM_VALIDATOR_PORT_RANGE_WIDTH, VALIDATOR_PORT_RANGE},
23 solana_quic_definitions::QUIC_PORT_OFFSET,
24 solana_rayon_threadlimit::get_thread_count,
25 solana_rpc::{rpc::MAX_REQUEST_BODY_SIZE, rpc_pubsub_service::PubSubConfig},
26 solana_rpc_client_api::request::{DELINQUENT_VALIDATOR_SLOT_DISTANCE, MAX_MULTIPLE_ACCOUNTS},
27 solana_runtime::{
28 snapshot_bank_utils::{
29 DEFAULT_FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS,
30 DEFAULT_INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS,
31 },
32 snapshot_utils::{
33 SnapshotVersion, DEFAULT_ARCHIVE_COMPRESSION,
34 DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
35 DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
36 },
37 },
38 solana_send_transaction_service::send_transaction_service::{self},
39 solana_streamer::quic::{
40 DEFAULT_MAX_CONNECTIONS_PER_IPADDR_PER_MINUTE, DEFAULT_MAX_QUIC_CONNECTIONS_PER_PEER,
41 DEFAULT_MAX_STAKED_CONNECTIONS, DEFAULT_MAX_STREAMS_PER_MS,
42 DEFAULT_MAX_UNSTAKED_CONNECTIONS, DEFAULT_QUIC_ENDPOINTS,
43 },
44 solana_tpu_client::tpu_client::{DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_VOTE_USE_QUIC},
45 std::{path::PathBuf, str::FromStr},
46};
47
48pub mod thread_args;
49use thread_args::{thread_args, DefaultThreadArgs};
50
51const DEFAULT_MIN_SNAPSHOT_DOWNLOAD_SPEED: u64 = 10485760;
53const MAX_SNAPSHOT_DOWNLOAD_ABORT: u32 = 5;
55const MINIMUM_TICKS_PER_SLOT: u64 = 2;
58
59pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> {
60 let app = App::new(crate_name!())
61 .about(crate_description!())
62 .version(version)
63 .global_setting(AppSettings::ColoredHelp)
64 .global_setting(AppSettings::InferSubcommands)
65 .global_setting(AppSettings::UnifiedHelpMessage)
66 .global_setting(AppSettings::VersionlessSubcommands)
67 .subcommand(commands::exit::command())
68 .subcommand(commands::authorized_voter::command())
69 .subcommand(commands::contact_info::command())
70 .subcommand(commands::repair_shred_from_peer::command())
71 .subcommand(commands::repair_whitelist::command())
72 .subcommand(
73 SubCommand::with_name("init").about("Initialize the ledger directory then exit"),
74 )
75 .subcommand(commands::monitor::command())
76 .subcommand(SubCommand::with_name("run").about("Run the validator"))
77 .subcommand(commands::plugin::command())
78 .subcommand(commands::set_identity::command())
79 .subcommand(commands::set_log_filter::command())
80 .subcommand(commands::staked_nodes_overrides::command())
81 .subcommand(commands::wait_for_restart_window::command())
82 .subcommand(commands::set_public_address::command());
83
84 commands::run::add_args(app, default_args)
85 .args(&thread_args(&default_args.thread_args))
86 .args(&get_deprecated_arguments())
87 .after_help("The default subcommand is run")
88}
89
90struct DeprecatedArg {
93 arg: Arg<'static, 'static>,
98
99 replaced_by: Option<&'static str>,
103
104 usage_warning: Option<&'static str>,
108}
109
110fn deprecated_arguments() -> Vec<DeprecatedArg> {
111 let mut res = vec![];
112
113 macro_rules! add_arg {
115 (
116 $arg:expr
117 $( , replaced_by: $replaced_by:expr )?
118 $( , usage_warning: $usage_warning:expr )?
119 $(,)?
120 ) => {
121 let replaced_by = add_arg!(@into-option $( $replaced_by )?);
122 let usage_warning = add_arg!(@into-option $( $usage_warning )?);
123 res.push(DeprecatedArg {
124 arg: $arg,
125 replaced_by,
126 usage_warning,
127 });
128 };
129
130 (@into-option) => { None };
131 (@into-option $v:expr) => { Some($v) };
132 }
133
134 add_arg!(Arg::with_name("accounts_index_memory_limit_mb")
136 .long("accounts-index-memory-limit-mb")
137 .value_name("MEGABYTES")
138 .validator(is_parsable::<usize>)
139 .takes_value(true)
140 .help(
141 "How much memory the accounts index can consume. If this is exceeded, some \
142 account index entries will be stored on disk.",
143 ),
144 usage_warning: "index memory limit has been deprecated. The limit arg has no effect now.",
145 );
146 add_arg!(Arg::with_name("accountsdb_repl_bind_address")
147 .long("accountsdb-repl-bind-address")
148 .value_name("HOST")
149 .takes_value(true)
150 .validator(solana_net_utils::is_host)
151 .help(
152 "IP address to bind the AccountsDb Replication port [default: use \
153 --bind-address]",
154 ));
155 add_arg!(Arg::with_name("accountsdb_repl_port")
156 .long("accountsdb-repl-port")
157 .value_name("PORT")
158 .takes_value(true)
159 .validator(port_validator)
160 .help("Enable AccountsDb Replication Service on this port"));
161 add_arg!(Arg::with_name("accountsdb_repl_threads")
162 .long("accountsdb-repl-threads")
163 .value_name("NUMBER")
164 .validator(is_parsable::<usize>)
165 .takes_value(true)
166 .help("Number of threads to use for servicing AccountsDb Replication requests"));
167 add_arg!(Arg::with_name("disable_accounts_disk_index")
168 .long("disable-accounts-disk-index")
169 .help("Disable the disk-based accounts index if it is enabled by default.")
170 .conflicts_with("accounts_index_memory_limit_mb"));
171 add_arg!(
172 Arg::with_name("disable_quic_servers")
173 .long("disable-quic-servers")
174 .takes_value(false),
175 usage_warning: "The quic server cannot be disabled.",
176 );
177 add_arg!(Arg::with_name("enable_accountsdb_repl")
178 .long("enable-accountsdb-repl")
179 .takes_value(false)
180 .help("Enable AccountsDb Replication"));
181 add_arg!(
182 Arg::with_name("enable_cpi_and_log_storage")
183 .long("enable-cpi-and-log-storage")
184 .requires("enable_rpc_transaction_history")
185 .takes_value(false)
186 .help(
187 "Include CPI inner instructions, logs and return data in the historical \
188 transaction info stored",
189 ),
190 replaced_by: "enable-extended-tx-metadata-storage",
191 );
192 add_arg!(
193 Arg::with_name("enable_quic_servers")
194 .long("enable-quic-servers"),
195 usage_warning: "The quic server is now enabled by default.",
196 );
197 add_arg!(Arg::with_name("etcd_cacert_file")
199 .long("etcd-cacert-file")
200 .required_if("tower_storage", "etcd")
201 .value_name("FILE")
202 .takes_value(true)
203 .help("verify the TLS certificate of the etcd endpoint using this CA bundle"),);
204 add_arg!(Arg::with_name("etcd_cert_file")
205 .long("etcd-cert-file")
206 .required_if("tower_storage", "etcd")
207 .value_name("FILE")
208 .takes_value(true)
209 .help("TLS certificate to use when establishing a connection to the etcd endpoint"),);
210 add_arg!(Arg::with_name("etcd_domain_name")
211 .long("etcd-domain-name")
212 .required_if("tower_storage", "etcd")
213 .value_name("DOMAIN")
214 .default_value("localhost")
215 .takes_value(true)
216 .help("domain name against which to verify the etcd server’s TLS certificate"),);
217 add_arg!(Arg::with_name("etcd_endpoint")
218 .long("etcd-endpoint")
219 .required_if("tower_storage", "etcd")
220 .value_name("HOST:PORT")
221 .takes_value(true)
222 .multiple(true)
223 .validator(solana_net_utils::is_host_port)
224 .help("etcd gRPC endpoint to connect with"),);
225 add_arg!(Arg::with_name("etcd_key_file")
226 .long("etcd-key-file")
227 .required_if("tower_storage", "etcd")
228 .value_name("FILE")
229 .takes_value(true)
230 .help("TLS key file to use when establishing a connection to the etcd endpoint"),);
231
232 add_arg!(Arg::with_name("minimal_rpc_api")
233 .long("minimal-rpc-api")
234 .takes_value(false)
235 .help("Only expose the RPC methods required to serve snapshots to other nodes"));
236 add_arg!(
237 Arg::with_name("no_check_vote_account")
238 .long("no-check-vote-account")
239 .takes_value(false)
240 .conflicts_with("no_voting")
241 .requires("entrypoint")
242 .help("Skip the RPC vote account sanity check"),
243 usage_warning: "Vote account sanity checks are no longer performed by default.",
244 );
245 add_arg!(Arg::with_name("no_rocksdb_compaction")
246 .long("no-rocksdb-compaction")
247 .takes_value(false)
248 .help("Disable manual compaction of the ledger database"));
249 add_arg!(
250 Arg::with_name("replay_slots_concurrently")
251 .long("replay-slots-concurrently")
252 .help("Allow concurrent replay of slots on different forks")
253 .conflicts_with("replay_forks_threads"),
254 replaced_by: "replay_forks_threads",
255 usage_warning: "Equivalent behavior to this flag would be --replay-forks-threads 4");
256 add_arg!(Arg::with_name("rocksdb_compaction_interval")
257 .long("rocksdb-compaction-interval-slots")
258 .value_name("ROCKSDB_COMPACTION_INTERVAL_SLOTS")
259 .takes_value(true)
260 .help("Number of slots between compacting ledger"));
261 add_arg!(Arg::with_name("rocksdb_fifo_shred_storage_size")
263 .long("rocksdb-fifo-shred-storage-size")
264 .value_name("SHRED_STORAGE_SIZE_BYTES")
265 .takes_value(true)
266 .validator(is_parsable::<u64>)
267 .help(
268 "The shred storage size in bytes. The suggested value is at least 50% of your ledger \
269 storage size. If this argument is unspecified, we will assign a proper value based \
270 on --limit-ledger-size. If --limit-ledger-size is not presented, it means there is \
271 no limitation on the ledger size and thus rocksdb_fifo_shred_storage_size will also \
272 be unbounded.",
273 ));
274 add_arg!(Arg::with_name("rocksdb_max_compaction_jitter")
275 .long("rocksdb-max-compaction-jitter-slots")
276 .value_name("ROCKSDB_MAX_COMPACTION_JITTER_SLOTS")
277 .takes_value(true)
278 .help("Introduce jitter into the compaction to offset compaction operation"));
279 add_arg!(Arg::with_name("rpc_pubsub_max_connections")
280 .long("rpc-pubsub-max-connections")
281 .value_name("NUMBER")
282 .takes_value(true)
283 .validator(is_parsable::<usize>)
284 .help(
285 "The maximum number of connections that RPC PubSub will support. This is a \
286 hard limit and no new connections beyond this limit can be made until an old \
287 connection is dropped."
288 ));
289 add_arg!(Arg::with_name("rpc_pubsub_max_fragment_size")
290 .long("rpc-pubsub-max-fragment-size")
291 .value_name("BYTES")
292 .takes_value(true)
293 .validator(is_parsable::<usize>)
294 .help(
295 "The maximum length in bytes of acceptable incoming frames. Messages longer \
296 than this will be rejected"
297 ));
298 add_arg!(Arg::with_name("rpc_pubsub_max_in_buffer_capacity")
299 .long("rpc-pubsub-max-in-buffer-capacity")
300 .value_name("BYTES")
301 .takes_value(true)
302 .validator(is_parsable::<usize>)
303 .help("The maximum size in bytes to which the incoming websocket buffer can grow."));
304 add_arg!(Arg::with_name("rpc_pubsub_max_out_buffer_capacity")
305 .long("rpc-pubsub-max-out-buffer-capacity")
306 .value_name("BYTES")
307 .takes_value(true)
308 .validator(is_parsable::<usize>)
309 .help("The maximum size in bytes to which the outgoing websocket buffer can grow."));
310 add_arg!(
311 Arg::with_name("skip_poh_verify")
312 .long("skip-poh-verify")
313 .takes_value(false)
314 .help("Skip ledger verification at validator bootup."),
315 replaced_by: "skip-startup-ledger-verification",
316 );
317 add_arg!(
319 Arg::with_name("tower_storage")
320 .long("tower-storage")
321 .possible_values(&["file", "etcd"])
322 .default_value("file")
323 .takes_value(true)
324 .help("Where to store the tower"),
325 usage_warning: "\"etcd\" is no longer supported, and the functionality from setting \
326 \"file\" will be become the sole behavior",
327 );
328
329 res
330}
331
332fn get_deprecated_arguments() -> Vec<Arg<'static, 'static>> {
335 deprecated_arguments()
336 .into_iter()
337 .map(|info| {
338 let arg = info.arg;
339 arg.hidden(hidden_unless_forced())
341 })
342 .collect()
343}
344
345pub fn warn_for_deprecated_arguments(matches: &ArgMatches) {
346 for DeprecatedArg {
347 arg,
348 replaced_by,
349 usage_warning,
350 } in deprecated_arguments().into_iter()
351 {
352 if matches.is_present(arg.b.name) {
353 let mut msg = format!("--{} is deprecated", arg.b.name.replace('_', "-"));
354 if let Some(replaced_by) = replaced_by {
355 msg.push_str(&format!(", please use --{replaced_by}"));
356 }
357 msg.push('.');
358 if let Some(usage_warning) = usage_warning {
359 msg.push_str(&format!(" {usage_warning}"));
360 if !msg.ends_with('.') {
361 msg.push('.');
362 }
363 }
364 warn!("{}", msg);
365 }
366 }
367}
368
369pub struct DefaultArgs {
370 pub bind_address: String,
371 pub dynamic_port_range: String,
372 pub ledger_path: String,
373
374 pub genesis_archive_unpacked_size: String,
375 pub health_check_slot_distance: String,
376 pub tower_storage: String,
377 pub etcd_domain_name: String,
378 pub send_transaction_service_config: send_transaction_service::Config,
379
380 pub rpc_max_multiple_accounts: String,
381 pub rpc_pubsub_max_active_subscriptions: String,
382 pub rpc_pubsub_queue_capacity_items: String,
383 pub rpc_pubsub_queue_capacity_bytes: String,
384 pub rpc_send_transaction_retry_ms: String,
385 pub rpc_send_transaction_batch_ms: String,
386 pub rpc_send_transaction_leader_forward_count: String,
387 pub rpc_send_transaction_service_max_retries: String,
388 pub rpc_send_transaction_batch_size: String,
389 pub rpc_send_transaction_retry_pool_max_size: String,
390 pub rpc_threads: String,
391 pub rpc_blocking_threads: String,
392 pub rpc_niceness_adjustment: String,
393 pub rpc_bigtable_timeout: String,
394 pub rpc_bigtable_instance_name: String,
395 pub rpc_bigtable_app_profile_id: String,
396 pub rpc_bigtable_max_message_size: String,
397 pub rpc_max_request_body_size: String,
398 pub rpc_pubsub_worker_threads: String,
399 pub rpc_pubsub_notification_threads: String,
400
401 pub maximum_local_snapshot_age: String,
402 pub maximum_full_snapshot_archives_to_retain: String,
403 pub maximum_incremental_snapshot_archives_to_retain: String,
404 pub snapshot_packager_niceness_adjustment: String,
405 pub full_snapshot_archive_interval_slots: String,
406 pub incremental_snapshot_archive_interval_slots: String,
407 pub min_snapshot_download_speed: String,
408 pub max_snapshot_download_abort: String,
409
410 pub contact_debug_interval: String,
411
412 pub snapshot_version: SnapshotVersion,
413 pub snapshot_archive_format: String,
414 pub snapshot_zstd_compression_level: String,
415
416 pub rocksdb_shred_compaction: String,
417 pub rocksdb_ledger_compression: String,
418 pub rocksdb_perf_sample_interval: String,
419
420 pub accounts_shrink_optimize_total_space: String,
421 pub accounts_shrink_ratio: String,
422 pub tpu_connection_pool_size: String,
423
424 pub tpu_max_connections_per_peer: String,
425 pub tpu_max_connections_per_ipaddr_per_minute: String,
426 pub tpu_max_staked_connections: String,
427 pub tpu_max_unstaked_connections: String,
428 pub tpu_max_fwd_staked_connections: String,
429 pub tpu_max_fwd_unstaked_connections: String,
430 pub tpu_max_streams_per_ms: String,
431
432 pub num_quic_endpoints: String,
433 pub vote_use_quic: String,
434
435 pub banking_trace_dir_byte_limit: String,
436
437 pub wen_restart_path: String,
438
439 pub thread_args: DefaultThreadArgs,
440}
441
442impl DefaultArgs {
443 pub fn new() -> Self {
444 let default_send_transaction_service_config = send_transaction_service::Config::default();
445
446 DefaultArgs {
447 bind_address: "0.0.0.0".to_string(),
448 ledger_path: "ledger".to_string(),
449 dynamic_port_range: format!("{}-{}", VALIDATOR_PORT_RANGE.0, VALIDATOR_PORT_RANGE.1),
450 maximum_local_snapshot_age: "2500".to_string(),
451 genesis_archive_unpacked_size: MAX_GENESIS_ARCHIVE_UNPACKED_SIZE.to_string(),
452 rpc_max_multiple_accounts: MAX_MULTIPLE_ACCOUNTS.to_string(),
453 health_check_slot_distance: DELINQUENT_VALIDATOR_SLOT_DISTANCE.to_string(),
454 tower_storage: "file".to_string(),
455 etcd_domain_name: "localhost".to_string(),
456 rpc_pubsub_max_active_subscriptions: PubSubConfig::default()
457 .max_active_subscriptions
458 .to_string(),
459 rpc_pubsub_queue_capacity_items: PubSubConfig::default()
460 .queue_capacity_items
461 .to_string(),
462 rpc_pubsub_queue_capacity_bytes: PubSubConfig::default()
463 .queue_capacity_bytes
464 .to_string(),
465 send_transaction_service_config: send_transaction_service::Config::default(),
466 rpc_send_transaction_retry_ms: default_send_transaction_service_config
467 .retry_rate_ms
468 .to_string(),
469 rpc_send_transaction_batch_ms: default_send_transaction_service_config
470 .batch_send_rate_ms
471 .to_string(),
472 rpc_send_transaction_leader_forward_count: default_send_transaction_service_config
473 .leader_forward_count
474 .to_string(),
475 rpc_send_transaction_service_max_retries: default_send_transaction_service_config
476 .service_max_retries
477 .to_string(),
478 rpc_send_transaction_batch_size: default_send_transaction_service_config
479 .batch_size
480 .to_string(),
481 rpc_send_transaction_retry_pool_max_size: default_send_transaction_service_config
482 .retry_pool_max_size
483 .to_string(),
484 rpc_threads: num_cpus::get().to_string(),
485 rpc_blocking_threads: 1.max(num_cpus::get() / 4).to_string(),
486 rpc_niceness_adjustment: "0".to_string(),
487 rpc_bigtable_timeout: "30".to_string(),
488 rpc_bigtable_instance_name: solana_storage_bigtable::DEFAULT_INSTANCE_NAME.to_string(),
489 rpc_bigtable_app_profile_id: solana_storage_bigtable::DEFAULT_APP_PROFILE_ID
490 .to_string(),
491 rpc_bigtable_max_message_size: solana_storage_bigtable::DEFAULT_MAX_MESSAGE_SIZE
492 .to_string(),
493 rpc_pubsub_worker_threads: "4".to_string(),
494 rpc_pubsub_notification_threads: get_thread_count().to_string(),
495 maximum_full_snapshot_archives_to_retain: DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN
496 .to_string(),
497 maximum_incremental_snapshot_archives_to_retain:
498 DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN.to_string(),
499 snapshot_packager_niceness_adjustment: "0".to_string(),
500 full_snapshot_archive_interval_slots: DEFAULT_FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS
501 .to_string(),
502 incremental_snapshot_archive_interval_slots:
503 DEFAULT_INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS.to_string(),
504 min_snapshot_download_speed: DEFAULT_MIN_SNAPSHOT_DOWNLOAD_SPEED.to_string(),
505 max_snapshot_download_abort: MAX_SNAPSHOT_DOWNLOAD_ABORT.to_string(),
506 snapshot_archive_format: DEFAULT_ARCHIVE_COMPRESSION.to_string(),
507 snapshot_zstd_compression_level: "1".to_string(), contact_debug_interval: "120000".to_string(),
509 snapshot_version: SnapshotVersion::default(),
510 rocksdb_shred_compaction: "level".to_string(),
511 rocksdb_ledger_compression: "none".to_string(),
512 rocksdb_perf_sample_interval: "0".to_string(),
513 accounts_shrink_optimize_total_space: DEFAULT_ACCOUNTS_SHRINK_OPTIMIZE_TOTAL_SPACE
514 .to_string(),
515 accounts_shrink_ratio: DEFAULT_ACCOUNTS_SHRINK_RATIO.to_string(),
516 tpu_connection_pool_size: DEFAULT_TPU_CONNECTION_POOL_SIZE.to_string(),
517 tpu_max_connections_per_ipaddr_per_minute:
518 DEFAULT_MAX_CONNECTIONS_PER_IPADDR_PER_MINUTE.to_string(),
519 vote_use_quic: DEFAULT_VOTE_USE_QUIC.to_string(),
520 tpu_max_connections_per_peer: DEFAULT_MAX_QUIC_CONNECTIONS_PER_PEER.to_string(),
521 tpu_max_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS.to_string(),
522 tpu_max_unstaked_connections: DEFAULT_MAX_UNSTAKED_CONNECTIONS.to_string(),
523 tpu_max_fwd_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS
524 .saturating_add(DEFAULT_MAX_UNSTAKED_CONNECTIONS)
525 .to_string(),
526 tpu_max_fwd_unstaked_connections: 0.to_string(),
527 tpu_max_streams_per_ms: DEFAULT_MAX_STREAMS_PER_MS.to_string(),
528 num_quic_endpoints: DEFAULT_QUIC_ENDPOINTS.to_string(),
529 rpc_max_request_body_size: MAX_REQUEST_BODY_SIZE.to_string(),
530 banking_trace_dir_byte_limit: BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT.to_string(),
531 wen_restart_path: "wen_restart_progress.proto".to_string(),
532 thread_args: DefaultThreadArgs::default(),
533 }
534 }
535}
536
537impl Default for DefaultArgs {
538 fn default() -> Self {
539 Self::new()
540 }
541}
542
543pub fn port_validator(port: String) -> Result<(), String> {
544 port.parse::<u16>()
545 .map(|_| ())
546 .map_err(|e| format!("{e:?}"))
547}
548
549pub fn port_range_validator(port_range: String) -> Result<(), String> {
550 if let Some((start, end)) = solana_net_utils::parse_port_range(&port_range) {
551 if end - start < MINIMUM_VALIDATOR_PORT_RANGE_WIDTH {
552 Err(format!(
553 "Port range is too small. Try --dynamic-port-range {}-{}",
554 start,
555 start + MINIMUM_VALIDATOR_PORT_RANGE_WIDTH
556 ))
557 } else if end.checked_add(QUIC_PORT_OFFSET).is_none() {
558 Err("Invalid dynamic_port_range.".to_string())
559 } else {
560 Ok(())
561 }
562 } else {
563 Err("Invalid port range".to_string())
564 }
565}
566
567pub(crate) fn hash_validator(hash: String) -> Result<(), String> {
568 Hash::from_str(&hash)
569 .map(|_| ())
570 .map_err(|e| format!("{e:?}"))
571}
572
573pub fn test_app<'a>(version: &'a str, default_args: &'a DefaultTestArgs) -> App<'a, 'a> {
575 App::new("solana-test-validator")
576 .about("Test Validator")
577 .version(version)
578 .arg({
579 let arg = Arg::with_name("config_file")
580 .short("C")
581 .long("config")
582 .value_name("PATH")
583 .takes_value(true)
584 .help("Configuration file to use");
585 if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE {
586 arg.default_value(config_file)
587 } else {
588 arg
589 }
590 })
591 .arg(
592 Arg::with_name("json_rpc_url")
593 .short("u")
594 .long("url")
595 .value_name("URL_OR_MONIKER")
596 .takes_value(true)
597 .validator(is_url_or_moniker)
598 .help(
599 "URL for Solana's JSON RPC or moniker (or their first letter): \
600 [mainnet-beta, testnet, devnet, localhost]",
601 ),
602 )
603 .arg(
604 Arg::with_name("mint_address")
605 .long("mint")
606 .value_name("PUBKEY")
607 .validator(is_pubkey)
608 .takes_value(true)
609 .help(
610 "Address of the mint account that will receive tokens created at genesis. If \
611 the ledger already exists then this parameter is silently ignored \
612 [default: client keypair]",
613 ),
614 )
615 .arg(
616 Arg::with_name("ledger_path")
617 .short("l")
618 .long("ledger")
619 .value_name("DIR")
620 .takes_value(true)
621 .required(true)
622 .default_value("test-ledger")
623 .help("Use DIR as ledger location"),
624 )
625 .arg(
626 Arg::with_name("reset")
627 .short("r")
628 .long("reset")
629 .takes_value(false)
630 .help(
631 "Reset the ledger to genesis if it exists. By default the validator will \
632 resume an existing ledger (if present)",
633 ),
634 )
635 .arg(
636 Arg::with_name("quiet")
637 .short("q")
638 .long("quiet")
639 .takes_value(false)
640 .conflicts_with("log")
641 .help("Quiet mode: suppress normal output"),
642 )
643 .arg(
644 Arg::with_name("log")
645 .long("log")
646 .takes_value(false)
647 .conflicts_with("quiet")
648 .help("Log mode: stream the validator log"),
649 )
650 .arg(
651 Arg::with_name("account_indexes")
652 .long("account-index")
653 .takes_value(true)
654 .multiple(true)
655 .possible_values(&["program-id", "spl-token-owner", "spl-token-mint"])
656 .value_name("INDEX")
657 .help("Enable an accounts index, indexed by the selected account field"),
658 )
659 .arg(
660 Arg::with_name("faucet_port")
661 .long("faucet-port")
662 .value_name("PORT")
663 .takes_value(true)
664 .default_value(&default_args.faucet_port)
665 .validator(port_validator)
666 .help("Enable the faucet on this port"),
667 )
668 .arg(
669 Arg::with_name("rpc_port")
670 .long("rpc-port")
671 .value_name("PORT")
672 .takes_value(true)
673 .default_value(&default_args.rpc_port)
674 .validator(port_validator)
675 .help("Enable JSON RPC on this port, and the next port for the RPC websocket"),
676 )
677 .arg(
678 Arg::with_name("enable_rpc_bigtable_ledger_storage")
679 .long("enable-rpc-bigtable-ledger-storage")
680 .takes_value(false)
681 .hidden(hidden_unless_forced())
682 .help(
683 "Fetch historical transaction info from a BigTable instance as a fallback to \
684 local ledger data",
685 ),
686 )
687 .arg(
688 Arg::with_name("enable_bigtable_ledger_upload")
689 .long("enable-bigtable-ledger-upload")
690 .takes_value(false)
691 .hidden(hidden_unless_forced())
692 .help("Upload new confirmed blocks into a BigTable instance"),
693 )
694 .arg(
695 Arg::with_name("rpc_bigtable_instance")
696 .long("rpc-bigtable-instance")
697 .value_name("INSTANCE_NAME")
698 .takes_value(true)
699 .hidden(hidden_unless_forced())
700 .default_value("solana-ledger")
701 .help("Name of BigTable instance to target"),
702 )
703 .arg(
704 Arg::with_name("rpc_bigtable_app_profile_id")
705 .long("rpc-bigtable-app-profile-id")
706 .value_name("APP_PROFILE_ID")
707 .takes_value(true)
708 .hidden(hidden_unless_forced())
709 .default_value(solana_storage_bigtable::DEFAULT_APP_PROFILE_ID)
710 .help("Application profile id to use in Bigtable requests"),
711 )
712 .arg(
713 Arg::with_name("rpc_pubsub_enable_vote_subscription")
714 .long("rpc-pubsub-enable-vote-subscription")
715 .takes_value(false)
716 .help("Enable the unstable RPC PubSub `voteSubscribe` subscription"),
717 )
718 .arg(
719 Arg::with_name("rpc_pubsub_enable_block_subscription")
720 .long("rpc-pubsub-enable-block-subscription")
721 .takes_value(false)
722 .help("Enable the unstable RPC PubSub `blockSubscribe` subscription"),
723 )
724 .arg(
725 Arg::with_name("bpf_program")
726 .long("bpf-program")
727 .value_names(&["ADDRESS_OR_KEYPAIR", "SBF_PROGRAM.SO"])
728 .takes_value(true)
729 .number_of_values(2)
730 .multiple(true)
731 .help(
732 "Add a SBF program to the genesis configuration with upgrades disabled. If \
733 the ledger already exists then this parameter is silently ignored. The first \
734 argument can be a pubkey string or path to a keypair",
735 ),
736 )
737 .arg(
738 Arg::with_name("upgradeable_program")
739 .long("upgradeable-program")
740 .value_names(&["ADDRESS_OR_KEYPAIR", "SBF_PROGRAM.SO", "UPGRADE_AUTHORITY"])
741 .takes_value(true)
742 .number_of_values(3)
743 .multiple(true)
744 .help(
745 "Add an upgradeable SBF program to the genesis configuration. If the ledger \
746 already exists then this parameter is silently ignored. First and third \
747 arguments can be a pubkey string or path to a keypair. Upgrade authority set \
748 to \"none\" disables upgrades",
749 ),
750 )
751 .arg(
752 Arg::with_name("account")
753 .long("account")
754 .value_names(&["ADDRESS", "DUMP.JSON"])
755 .takes_value(true)
756 .number_of_values(2)
757 .allow_hyphen_values(true)
758 .multiple(true)
759 .help(
760 "Load an account from the provided JSON file (see `solana account --help` on \
761 how to dump an account to file). Files are searched for relatively to CWD \
762 and tests/fixtures. If ADDRESS is omitted via the `-` placeholder, the one \
763 in the file will be used. If the ledger already exists then this parameter \
764 is silently ignored",
765 ),
766 )
767 .arg(
768 Arg::with_name("account_dir")
769 .long("account-dir")
770 .value_name("DIRECTORY")
771 .validator(|value| {
772 value
773 .parse::<PathBuf>()
774 .map_err(|err| format!("error parsing '{value}': {err}"))
775 .and_then(|path| {
776 if path.exists() && path.is_dir() {
777 Ok(())
778 } else {
779 Err(format!(
780 "path does not exist or is not a directory: {value}"
781 ))
782 }
783 })
784 })
785 .takes_value(true)
786 .multiple(true)
787 .help(
788 "Load all the accounts from the JSON files found in the specified DIRECTORY \
789 (see also the `--account` flag). If the ledger already exists then this \
790 parameter is silently ignored",
791 ),
792 )
793 .arg(
794 Arg::with_name("ticks_per_slot")
795 .long("ticks-per-slot")
796 .value_name("TICKS")
797 .validator(|value| {
798 value
799 .parse::<u64>()
800 .map_err(|err| format!("error parsing '{value}': {err}"))
801 .and_then(|ticks| {
802 if ticks < MINIMUM_TICKS_PER_SLOT {
803 Err(format!("value must be >= {MINIMUM_TICKS_PER_SLOT}"))
804 } else {
805 Ok(())
806 }
807 })
808 })
809 .takes_value(true)
810 .help("The number of ticks in a slot"),
811 )
812 .arg(
813 Arg::with_name("slots_per_epoch")
814 .long("slots-per-epoch")
815 .value_name("SLOTS")
816 .validator(|value| {
817 value
818 .parse::<Slot>()
819 .map_err(|err| format!("error parsing '{value}': {err}"))
820 .and_then(|slot| {
821 if slot < MINIMUM_SLOTS_PER_EPOCH {
822 Err(format!("value must be >= {MINIMUM_SLOTS_PER_EPOCH}"))
823 } else {
824 Ok(())
825 }
826 })
827 })
828 .takes_value(true)
829 .help(
830 "Override the number of slots in an epoch. If the ledger already exists then \
831 this parameter is silently ignored",
832 ),
833 )
834 .arg(
835 Arg::with_name("gossip_port")
836 .long("gossip-port")
837 .value_name("PORT")
838 .takes_value(true)
839 .help("Gossip port number for the validator"),
840 )
841 .arg(
842 Arg::with_name("gossip_host")
843 .long("gossip-host")
844 .value_name("HOST")
845 .takes_value(true)
846 .validator(solana_net_utils::is_host)
847 .help(
848 "Gossip DNS name or IP address for the validator to advertise in gossip \
849 [default: 127.0.0.1]",
850 ),
851 )
852 .arg(
853 Arg::with_name("dynamic_port_range")
854 .long("dynamic-port-range")
855 .value_name("MIN_PORT-MAX_PORT")
856 .takes_value(true)
857 .validator(port_range_validator)
858 .help("Range to use for dynamically assigned ports [default: 1024-65535]"),
859 )
860 .arg(
861 Arg::with_name("bind_address")
862 .long("bind-address")
863 .value_name("HOST")
864 .takes_value(true)
865 .validator(solana_net_utils::is_host)
866 .default_value("127.0.0.1")
867 .help("IP address to bind the validator ports [default: 127.0.0.1]"),
868 )
869 .arg(
870 Arg::with_name("clone_account")
871 .long("clone")
872 .short("c")
873 .value_name("ADDRESS")
874 .takes_value(true)
875 .validator(is_pubkey_or_keypair)
876 .multiple(true)
877 .requires("json_rpc_url")
878 .help(
879 "Copy an account from the cluster referenced by the --url argument the \
880 genesis configuration. If the ledger already exists then this parameter is \
881 silently ignored",
882 ),
883 )
884 .arg(
885 Arg::with_name("deep_clone_address_lookup_table")
886 .long("deep-clone-address-lookup-table")
887 .takes_value(true)
888 .validator(is_pubkey_or_keypair)
889 .multiple(true)
890 .requires("json_rpc_url")
891 .help(
892 "Copy an address lookup table and all accounts it references from the cluster referenced by the --url \
893 argument in the genesis configuration. If the ledger already exists then this \
894 parameter is silently ignored",
895 ),
896 )
897 .arg(
898 Arg::with_name("maybe_clone_account")
899 .long("maybe-clone")
900 .value_name("ADDRESS")
901 .takes_value(true)
902 .validator(is_pubkey_or_keypair)
903 .multiple(true)
904 .requires("json_rpc_url")
905 .help(
906 "Copy an account from the cluster referenced by the --url argument, skipping \
907 it if it doesn't exist. If the ledger already exists then this parameter is \
908 silently ignored",
909 ),
910 )
911 .arg(
912 Arg::with_name("clone_upgradeable_program")
913 .long("clone-upgradeable-program")
914 .value_name("ADDRESS")
915 .takes_value(true)
916 .validator(is_pubkey_or_keypair)
917 .multiple(true)
918 .requires("json_rpc_url")
919 .help(
920 "Copy an upgradeable program and its executable data from the cluster \
921 referenced by the --url argument the genesis configuration. If the ledger \
922 already exists then this parameter is silently ignored",
923 ),
924 )
925 .arg(
926 Arg::with_name("warp_slot")
927 .required(false)
928 .long("warp-slot")
929 .short("w")
930 .takes_value(true)
931 .value_name("WARP_SLOT")
932 .validator(is_slot)
933 .min_values(0)
934 .max_values(1)
935 .help(
936 "Warp the ledger to WARP_SLOT after starting the validator. If no slot is \
937 provided then the current slot of the cluster referenced by the --url \
938 argument will be used",
939 ),
940 )
941 .arg(
942 Arg::with_name("limit_ledger_size")
943 .long("limit-ledger-size")
944 .value_name("SHRED_COUNT")
945 .takes_value(true)
946 .default_value(default_args.limit_ledger_size.as_str())
947 .help("Keep this amount of shreds in root slots."),
948 )
949 .arg(
950 Arg::with_name("faucet_sol")
951 .long("faucet-sol")
952 .takes_value(true)
953 .value_name("SOL")
954 .default_value(default_args.faucet_sol.as_str())
955 .help(
956 "Give the faucet address this much SOL in genesis. If the ledger already \
957 exists then this parameter is silently ignored",
958 ),
959 )
960 .arg(
961 Arg::with_name("faucet_time_slice_secs")
962 .long("faucet-time-slice-secs")
963 .takes_value(true)
964 .value_name("SECS")
965 .default_value(default_args.faucet_time_slice_secs.as_str())
966 .help("Time slice (in secs) over which to limit faucet requests"),
967 )
968 .arg(
969 Arg::with_name("faucet_per_time_sol_cap")
970 .long("faucet-per-time-sol-cap")
971 .takes_value(true)
972 .value_name("SOL")
973 .min_values(0)
974 .max_values(1)
975 .help("Per-time slice limit for faucet requests, in SOL"),
976 )
977 .arg(
978 Arg::with_name("faucet_per_request_sol_cap")
979 .long("faucet-per-request-sol-cap")
980 .takes_value(true)
981 .value_name("SOL")
982 .min_values(0)
983 .max_values(1)
984 .help("Per-request limit for faucet requests, in SOL"),
985 )
986 .arg(
987 Arg::with_name("geyser_plugin_config")
988 .long("geyser-plugin-config")
989 .alias("accountsdb-plugin-config")
990 .value_name("FILE")
991 .takes_value(true)
992 .multiple(true)
993 .help("Specify the configuration file for the Geyser plugin."),
994 )
995 .arg(
996 Arg::with_name("deactivate_feature")
997 .long("deactivate-feature")
998 .takes_value(true)
999 .value_name("FEATURE_PUBKEY")
1000 .validator(is_pubkey)
1001 .multiple(true)
1002 .help("deactivate this feature in genesis."),
1003 )
1004 .arg(
1005 Arg::with_name("compute_unit_limit")
1006 .long("compute-unit-limit")
1007 .alias("max-compute-units")
1008 .value_name("COMPUTE_UNITS")
1009 .validator(is_parsable::<u64>)
1010 .takes_value(true)
1011 .help("Override the runtime's compute unit limit per transaction"),
1012 )
1013 .arg(
1014 Arg::with_name("log_messages_bytes_limit")
1015 .long("log-messages-bytes-limit")
1016 .value_name("BYTES")
1017 .validator(is_parsable::<usize>)
1018 .takes_value(true)
1019 .help("Maximum number of bytes written to the program log before truncation"),
1020 )
1021 .arg(
1022 Arg::with_name("transaction_account_lock_limit")
1023 .long("transaction-account-lock-limit")
1024 .value_name("NUM_ACCOUNTS")
1025 .validator(is_parsable::<u64>)
1026 .takes_value(true)
1027 .help("Override the runtime's account lock limit per transaction"),
1028 )
1029 .arg(
1030 Arg::with_name("clone_feature_set")
1031 .long("clone-feature-set")
1032 .takes_value(false)
1033 .requires("json_rpc_url")
1034 .help(
1035 "Copy a feature set from the cluster referenced by the --url \
1036 argument in the genesis configuration. If the ledger \
1037 already exists then this parameter is silently ignored",
1038 ),
1039 )
1040}
1041
1042pub struct DefaultTestArgs {
1043 pub rpc_port: String,
1044 pub faucet_port: String,
1045 pub limit_ledger_size: String,
1046 pub faucet_sol: String,
1047 pub faucet_time_slice_secs: String,
1048}
1049
1050impl DefaultTestArgs {
1051 pub fn new() -> Self {
1052 DefaultTestArgs {
1053 rpc_port: 8899.to_string(),
1054 faucet_port: FAUCET_PORT.to_string(),
1055 limit_ledger_size: 10_000.to_string(),
1060 faucet_sol: (1_000_000.).to_string(),
1061 faucet_time_slice_secs: (faucet::TIME_SLICE).to_string(),
1062 }
1063 }
1064}
1065
1066impl Default for DefaultTestArgs {
1067 fn default() -> Self {
1068 Self::new()
1069 }
1070}
1071
1072#[cfg(test)]
1073mod test {
1074 use super::*;
1075
1076 #[test]
1077 fn make_sure_deprecated_arguments_are_sorted_alphabetically() {
1078 let deprecated = deprecated_arguments();
1079
1080 for i in 0..deprecated.len().saturating_sub(1) {
1081 let curr_name = deprecated[i].arg.b.name;
1082 let next_name = deprecated[i + 1].arg.b.name;
1083
1084 assert!(
1085 curr_name != next_name,
1086 "Arguments in `deprecated_arguments()` should be distinct.\nArguments {} and {} \
1087 use the same name: {}",
1088 i,
1089 i + 1,
1090 curr_name,
1091 );
1092
1093 assert!(
1094 curr_name < next_name,
1095 "To generate better diffs and for readability purposes, `deprecated_arguments()` \
1096 should list arguments in alphabetical order.\nArguments {} and {} are \
1097 not.\nArgument {} name: {}\nArgument {} name: {}",
1098 i,
1099 i + 1,
1100 i,
1101 curr_name,
1102 i + 1,
1103 next_name,
1104 );
1105 }
1106 }
1107}