agave_validator/
cli.rs

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
51// The default minimal snapshot download speed (bytes/second)
52const DEFAULT_MIN_SNAPSHOT_DOWNLOAD_SPEED: u64 = 10485760;
53// The maximum times of snapshot download abort and retry
54const MAX_SNAPSHOT_DOWNLOAD_ABORT: u32 = 5;
55// We've observed missed leader slots leading to deadlocks on test validator
56// with less than 2 ticks per slot.
57const 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
90/// Deprecated argument description should be moved into the [`deprecated_arguments()`] function,
91/// expressed as an instance of this type.
92struct DeprecatedArg {
93    /// Deprecated argument description, moved here as is.
94    ///
95    /// `hidden` property will be modified by [`deprecated_arguments()`] to only show this argument
96    /// if [`hidden_unless_forced()`] says they should be displayed.
97    arg: Arg<'static, 'static>,
98
99    /// If simply replaced by a different argument, this is the name of the replacement.
100    ///
101    /// Content should be an argument name, as presented to users.
102    replaced_by: Option<&'static str>,
103
104    /// An explanation to be shown to the user if they still use this argument.
105    ///
106    /// Content should be a complete sentence or several, ending with a period.
107    usage_warning: Option<&'static str>,
108}
109
110fn deprecated_arguments() -> Vec<DeprecatedArg> {
111    let mut res = vec![];
112
113    // This macro reduces indentation and removes some noise from the argument declaration list.
114    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    // deprecated in v2.1 by PR #2721
135    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    // All etcd config is deprecated as of v2.2
198    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    // Deprecated in v2.2
262    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    // Deprecated as of v2.2
318    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
332// Helper to add arguments that are no longer used but are being kept around to avoid breaking
333// validator startup commands.
334fn get_deprecated_arguments() -> Vec<Arg<'static, 'static>> {
335    deprecated_arguments()
336        .into_iter()
337        .map(|info| {
338            let arg = info.arg;
339            // Hide all deprecated arguments by default.
340            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(), // level 1 is optimized for speed
508            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
573/// Test validator
574pub 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            /* 10,000 was derived empirically by watching the size
1056             * of the rocksdb/ directory self-limit itself to the
1057             * 40MB-150MB range when running `solana-test-validator`
1058             */
1059            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}