agave_validator/
cli.rs

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
45// The default minimal snapshot download speed (bytes/second)
46const DEFAULT_MIN_SNAPSHOT_DOWNLOAD_SPEED: u64 = 10485760;
47// The maximum times of snapshot download abort and retry
48const MAX_SNAPSHOT_DOWNLOAD_ABORT: u32 = 5;
49// We've observed missed leader slots leading to deadlocks on test validator
50// with less than 2 ticks per slot.
51const 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
84/// Deprecated argument description should be moved into the [`deprecated_arguments()`] function,
85/// expressed as an instance of this type.
86struct DeprecatedArg {
87    /// Deprecated argument description, moved here as is.
88    ///
89    /// `hidden` property will be modified by [`deprecated_arguments()`] to only show this argument
90    /// if [`hidden_unless_forced()`] says they should be displayed.
91    arg: Arg<'static, 'static>,
92
93    /// If simply replaced by a different argument, this is the name of the replacement.
94    ///
95    /// Content should be an argument name, as presented to users.
96    replaced_by: Option<&'static str>,
97
98    /// An explanation to be shown to the user if they still use this argument.
99    ///
100    /// Content should be a complete sentence or several, ending with a period.
101    usage_warning: Option<&'static str>,
102}
103
104fn deprecated_arguments() -> Vec<DeprecatedArg> {
105    let mut res = vec![];
106
107    // This macro reduces indentation and removes some noise from the argument declaration list.
108    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        // deprecated in v3.0.0
130        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        // deprecated in v3.0.0
139        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        // deprecated in v3.0.0
161        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        // deprecated in v3.0.0
177        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        // deprecated in v3.0.0
187        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        // deprecated in v3.0.0
195        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
204// Helper to add arguments that are no longer used but are being kept around to avoid breaking
205// validator startup commands.
206fn get_deprecated_arguments() -> Vec<Arg<'static, 'static>> {
207    deprecated_arguments()
208        .into_iter()
209        .map(|info| {
210            let arg = info.arg;
211            // Hide all deprecated arguments by default.
212            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            // this can not rely on logger since it is not initialized at the time of call
237            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(), // level 1 is optimized for speed
384            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
449/// Test validator
450pub 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            /* 10,000 was derived empirically by watching the size
944             * of the rocksdb/ directory self-limit itself to the
945             * 40MB-150MB range when running `solana-test-validator`
946             */
947            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}