use {
crate::commands,
clap::{crate_description, crate_name, App, AppSettings, Arg, ArgMatches, SubCommand},
log::warn,
solana_accounts_db::{
accounts_db::{
DEFAULT_ACCOUNTS_SHRINK_OPTIMIZE_TOTAL_SPACE, DEFAULT_ACCOUNTS_SHRINK_RATIO,
},
hardened_unpack::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE,
},
solana_clap_utils::{
hidden_unless_forced,
input_validators::{
is_keypair_or_ask_keyword, is_parsable, is_pow2, is_pubkey, is_pubkey_or_keypair,
is_slot, is_url_or_moniker, is_within_range,
validate_maximum_full_snapshot_archives_to_retain,
validate_maximum_incremental_snapshot_archives_to_retain,
},
keypair::SKIP_SEED_PHRASE_VALIDATION_ARG,
},
solana_core::{
banking_trace::{DirByteLimit, BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT},
validator::{BlockProductionMethod, BlockVerificationMethod, TransactionStructure},
},
solana_faucet::faucet::{self, FAUCET_PORT},
solana_ledger::use_snapshot_archives_at_startup,
solana_net_utils::{MINIMUM_VALIDATOR_PORT_RANGE_WIDTH, VALIDATOR_PORT_RANGE},
solana_rayon_threadlimit::get_thread_count,
solana_rpc::{rpc::MAX_REQUEST_BODY_SIZE, rpc_pubsub_service::PubSubConfig},
solana_rpc_client_api::request::{DELINQUENT_VALIDATOR_SLOT_DISTANCE, MAX_MULTIPLE_ACCOUNTS},
solana_runtime::{
snapshot_bank_utils::{
DEFAULT_FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS,
DEFAULT_INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS,
},
snapshot_utils::{
SnapshotVersion, DEFAULT_ARCHIVE_COMPRESSION,
DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN, SUPPORTED_ARCHIVE_COMPRESSION,
},
},
solana_sdk::{
clock::Slot, epoch_schedule::MINIMUM_SLOTS_PER_EPOCH, hash::Hash, quic::QUIC_PORT_OFFSET,
rpc_port,
},
solana_send_transaction_service::send_transaction_service::{
self, MAX_BATCH_SEND_RATE_MS, MAX_TRANSACTION_BATCH_SIZE,
},
solana_streamer::quic::{
DEFAULT_MAX_CONNECTIONS_PER_IPADDR_PER_MINUTE, DEFAULT_MAX_QUIC_CONNECTIONS_PER_PEER,
DEFAULT_MAX_STAKED_CONNECTIONS, DEFAULT_MAX_STREAMS_PER_MS,
DEFAULT_MAX_UNSTAKED_CONNECTIONS, DEFAULT_QUIC_ENDPOINTS,
},
solana_tpu_client::tpu_client::{DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_VOTE_USE_QUIC},
solana_unified_scheduler_pool::DefaultSchedulerPool,
std::{path::PathBuf, str::FromStr},
};
pub mod thread_args;
use thread_args::{thread_args, DefaultThreadArgs};
const EXCLUDE_KEY: &str = "account-index-exclude-key";
const INCLUDE_KEY: &str = "account-index-include-key";
const DEFAULT_MIN_SNAPSHOT_DOWNLOAD_SPEED: u64 = 10485760;
const MAX_SNAPSHOT_DOWNLOAD_ABORT: u32 = 5;
const MINIMUM_TICKS_PER_SLOT: u64 = 2;
pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> {
return App::new(crate_name!())
.about(crate_description!())
.version(version)
.global_setting(AppSettings::ColoredHelp)
.global_setting(AppSettings::InferSubcommands)
.global_setting(AppSettings::UnifiedHelpMessage)
.global_setting(AppSettings::VersionlessSubcommands)
.arg(
Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
.long(SKIP_SEED_PHRASE_VALIDATION_ARG.long)
.help(SKIP_SEED_PHRASE_VALIDATION_ARG.help),
)
.arg(
Arg::with_name("identity")
.short("i")
.long("identity")
.value_name("KEYPAIR")
.takes_value(true)
.validator(is_keypair_or_ask_keyword)
.help("Validator identity keypair"),
)
.arg(
Arg::with_name("authorized_voter_keypairs")
.long("authorized-voter")
.value_name("KEYPAIR")
.takes_value(true)
.validator(is_keypair_or_ask_keyword)
.requires("vote_account")
.multiple(true)
.help(
"Include an additional authorized voter keypair. May be specified multiple \
times. [default: the --identity keypair]",
),
)
.arg(
Arg::with_name("vote_account")
.long("vote-account")
.value_name("ADDRESS")
.takes_value(true)
.validator(is_pubkey_or_keypair)
.requires("identity")
.help(
"Validator vote account public key. If unspecified, voting will be disabled. \
The authorized voter for the account must either be the --identity keypair \
or set by the --authorized-voter argument",
),
)
.arg(
Arg::with_name("init_complete_file")
.long("init-complete-file")
.value_name("FILE")
.takes_value(true)
.help(
"Create this file if it doesn't already exist once validator initialization \
is complete",
),
)
.arg(
Arg::with_name("ledger_path")
.short("l")
.long("ledger")
.value_name("DIR")
.takes_value(true)
.required(true)
.default_value(&default_args.ledger_path)
.help("Use DIR as ledger location"),
)
.arg(
Arg::with_name("entrypoint")
.short("n")
.long("entrypoint")
.value_name("HOST:PORT")
.takes_value(true)
.multiple(true)
.validator(solana_net_utils::is_host_port)
.help("Rendezvous with the cluster at this gossip entrypoint"),
)
.arg(
Arg::with_name("no_snapshot_fetch")
.long("no-snapshot-fetch")
.takes_value(false)
.help(
"Do not attempt to fetch a snapshot from the cluster, start from a local \
snapshot if present",
),
)
.arg(
Arg::with_name("no_genesis_fetch")
.long("no-genesis-fetch")
.takes_value(false)
.help("Do not fetch genesis from the cluster"),
)
.arg(
Arg::with_name("no_voting")
.long("no-voting")
.takes_value(false)
.help("Launch validator without voting"),
)
.arg(
Arg::with_name("check_vote_account")
.long("check-vote-account")
.takes_value(true)
.value_name("RPC_URL")
.requires("entrypoint")
.conflicts_with_all(&["no_check_vote_account", "no_voting"])
.help(
"Sanity check vote account state at startup. The JSON RPC endpoint at RPC_URL \
must expose `--full-rpc-api`",
),
)
.arg(
Arg::with_name("restricted_repair_only_mode")
.long("restricted-repair-only-mode")
.takes_value(false)
.help(
"Do not publish the Gossip, TPU, TVU or Repair Service ports. Doing so causes \
the node to operate in a limited capacity that reduces its exposure to the \
rest of the cluster. The --no-voting flag is implicit when this flag is \
enabled",
),
)
.arg(
Arg::with_name("dev_halt_at_slot")
.long("dev-halt-at-slot")
.value_name("SLOT")
.validator(is_slot)
.takes_value(true)
.help("Halt the validator when it reaches the given slot"),
)
.arg(
Arg::with_name("rpc_port")
.long("rpc-port")
.value_name("PORT")
.takes_value(true)
.validator(port_validator)
.help("Enable JSON RPC on this port, and the next port for the RPC websocket"),
)
.arg(
Arg::with_name("full_rpc_api")
.long("full-rpc-api")
.conflicts_with("minimal_rpc_api")
.takes_value(false)
.help("Expose RPC methods for querying chain state and transaction history"),
)
.arg(
Arg::with_name("private_rpc")
.long("private-rpc")
.takes_value(false)
.help("Do not publish the RPC port for use by others"),
)
.arg(
Arg::with_name("no_port_check")
.long("no-port-check")
.takes_value(false)
.hidden(hidden_unless_forced())
.help("Do not perform TCP/UDP reachable port checks at start-up"),
)
.arg(
Arg::with_name("enable_rpc_transaction_history")
.long("enable-rpc-transaction-history")
.takes_value(false)
.help(
"Enable historical transaction info over JSON RPC, including the \
'getConfirmedBlock' API. This will cause an increase in disk usage and IOPS",
),
)
.arg(
Arg::with_name("enable_rpc_bigtable_ledger_storage")
.long("enable-rpc-bigtable-ledger-storage")
.requires("enable_rpc_transaction_history")
.takes_value(false)
.help(
"Fetch historical transaction info from a BigTable instance as a fallback to \
local ledger data",
),
)
.arg(
Arg::with_name("enable_bigtable_ledger_upload")
.long("enable-bigtable-ledger-upload")
.requires("enable_rpc_transaction_history")
.takes_value(false)
.help("Upload new confirmed blocks into a BigTable instance"),
)
.arg(
Arg::with_name("enable_extended_tx_metadata_storage")
.long("enable-extended-tx-metadata-storage")
.requires("enable_rpc_transaction_history")
.takes_value(false)
.help(
"Include CPI inner instructions, logs, and return data in the historical \
transaction info stored",
),
)
.arg(
Arg::with_name("rpc_max_multiple_accounts")
.long("rpc-max-multiple-accounts")
.value_name("MAX ACCOUNTS")
.takes_value(true)
.default_value(&default_args.rpc_max_multiple_accounts)
.help(
"Override the default maximum accounts accepted by the getMultipleAccounts \
JSON RPC method",
),
)
.arg(
Arg::with_name("health_check_slot_distance")
.long("health-check-slot-distance")
.value_name("SLOT_DISTANCE")
.takes_value(true)
.default_value(&default_args.health_check_slot_distance)
.help(
"Report this validator as healthy if its latest replayed optimistically \
confirmed slot is within the specified number of slots from the cluster's \
latest optimistically confirmed slot",
),
)
.arg(
Arg::with_name("skip_preflight_health_check")
.long("skip-preflight-health-check")
.takes_value(false)
.help(
"Skip health check when running a preflight check",
),
)
.arg(
Arg::with_name("rpc_faucet_addr")
.long("rpc-faucet-address")
.value_name("HOST:PORT")
.takes_value(true)
.validator(solana_net_utils::is_host_port)
.help("Enable the JSON RPC 'requestAirdrop' API with this faucet address."),
)
.arg(
Arg::with_name("account_paths")
.long("accounts")
.value_name("PATHS")
.takes_value(true)
.multiple(true)
.help(
"Comma separated persistent accounts location. \
May be specified multiple times. \
[default: <LEDGER>/accounts]",
),
)
.arg(
Arg::with_name("account_shrink_path")
.long("account-shrink-path")
.value_name("PATH")
.takes_value(true)
.multiple(true)
.help("Path to accounts shrink path which can hold a compacted account set."),
)
.arg(
Arg::with_name("accounts_hash_cache_path")
.long("accounts-hash-cache-path")
.value_name("PATH")
.takes_value(true)
.help(
"Use PATH as accounts hash cache location \
[default: <LEDGER>/accounts_hash_cache]",
),
)
.arg(
Arg::with_name("snapshots")
.long("snapshots")
.value_name("DIR")
.takes_value(true)
.help("Use DIR as the base location for snapshots.")
.long_help(
"Use DIR as the base location for snapshots. \
Snapshot archives will use DIR unless --full-snapshot-archive-path or \
--incremental-snapshot-archive-path is specified. \
Additionally, a subdirectory named \"snapshots\" will be created in DIR. \
This subdirectory holds internal files/data that are used when generating \
snapshot archives. \
[default: --ledger value]",
),
)
.arg(
Arg::with_name(use_snapshot_archives_at_startup::cli::NAME)
.long(use_snapshot_archives_at_startup::cli::LONG_ARG)
.takes_value(true)
.possible_values(use_snapshot_archives_at_startup::cli::POSSIBLE_VALUES)
.default_value(use_snapshot_archives_at_startup::cli::default_value())
.help(use_snapshot_archives_at_startup::cli::HELP)
.long_help(use_snapshot_archives_at_startup::cli::LONG_HELP),
)
.arg(
Arg::with_name("full_snapshot_archive_path")
.long("full-snapshot-archive-path")
.value_name("DIR")
.takes_value(true)
.help(
"Use DIR as full snapshot archives location \
[default: --snapshots value]",
),
)
.arg(
Arg::with_name("incremental_snapshot_archive_path")
.long("incremental-snapshot-archive-path")
.conflicts_with("no-incremental-snapshots")
.value_name("DIR")
.takes_value(true)
.help(
"Use DIR as incremental snapshot archives location \
[default: --snapshots value]",
),
)
.arg(
Arg::with_name("tower")
.long("tower")
.value_name("DIR")
.takes_value(true)
.help("Use DIR as file tower storage location [default: --ledger value]"),
)
.arg(
Arg::with_name("gossip_port")
.long("gossip-port")
.value_name("PORT")
.takes_value(true)
.help("Gossip port number for the validator"),
)
.arg(
Arg::with_name("gossip_host")
.long("gossip-host")
.value_name("HOST")
.takes_value(true)
.validator(solana_net_utils::is_host)
.help(
"Gossip DNS name or IP address for the validator to advertise in gossip \
[default: ask --entrypoint, or 127.0.0.1 when --entrypoint is not provided]",
),
)
.arg(
Arg::with_name("public_tpu_addr")
.long("public-tpu-address")
.alias("tpu-host-addr")
.value_name("HOST:PORT")
.takes_value(true)
.validator(solana_net_utils::is_host_port)
.help(
"Specify TPU address to advertise in gossip \
[default: ask --entrypoint or localhost when --entrypoint is not provided]",
),
)
.arg(
Arg::with_name("public_tpu_forwards_addr")
.long("public-tpu-forwards-address")
.value_name("HOST:PORT")
.takes_value(true)
.validator(solana_net_utils::is_host_port)
.help(
"Specify TPU Forwards address to advertise in gossip [default: ask \
--entrypoint or localhostwhen --entrypoint is not provided]",
),
)
.arg(
Arg::with_name("public_rpc_addr")
.long("public-rpc-address")
.value_name("HOST:PORT")
.takes_value(true)
.conflicts_with("private_rpc")
.validator(solana_net_utils::is_host_port)
.help(
"RPC address for the validator to advertise publicly in gossip. Useful for \
validators running behind a load balancer or proxy [default: use \
--rpc-bind-address / --rpc-port]",
),
)
.arg(
Arg::with_name("dynamic_port_range")
.long("dynamic-port-range")
.value_name("MIN_PORT-MAX_PORT")
.takes_value(true)
.default_value(&default_args.dynamic_port_range)
.validator(port_range_validator)
.help("Range to use for dynamically assigned ports"),
)
.arg(
Arg::with_name("maximum_local_snapshot_age")
.long("maximum-local-snapshot-age")
.value_name("NUMBER_OF_SLOTS")
.takes_value(true)
.default_value(&default_args.maximum_local_snapshot_age)
.help(
"Reuse a local snapshot if it's less than this many slots behind the highest \
snapshot available for download from other validators",
),
)
.arg(
Arg::with_name("no_incremental_snapshots")
.long("no-incremental-snapshots")
.takes_value(false)
.help("Disable incremental snapshots")
)
.arg(
Arg::with_name("snapshot_interval_slots")
.long("snapshot-interval-slots")
.alias("incremental-snapshot-interval-slots")
.value_name("NUMBER")
.takes_value(true)
.default_value(&default_args.incremental_snapshot_archive_interval_slots)
.help("Number of slots between generating snapshots")
.long_help(
"Number of slots between generating snapshots. \
If incremental snapshots are enabled, this sets the incremental snapshot interval. \
If incremental snapshots are disabled, this sets the full snapshot interval. \
Setting this to 0 disables all snapshots.",
),
)
.arg(
Arg::with_name("full_snapshot_interval_slots")
.long("full-snapshot-interval-slots")
.value_name("NUMBER")
.takes_value(true)
.default_value(&default_args.full_snapshot_archive_interval_slots)
.help("Number of slots between generating full snapshots")
.long_help(
"Number of slots between generating full snapshots. Must be a multiple of the \
incremental snapshot interval. Only used when incremental snapshots are enabled.",
),
)
.arg(
Arg::with_name("maximum_full_snapshots_to_retain")
.long("maximum-full-snapshots-to-retain")
.alias("maximum-snapshots-to-retain")
.value_name("NUMBER")
.takes_value(true)
.default_value(&default_args.maximum_full_snapshot_archives_to_retain)
.validator(validate_maximum_full_snapshot_archives_to_retain)
.help(
"The maximum number of full snapshot archives to hold on to when purging \
older snapshots.",
),
)
.arg(
Arg::with_name("maximum_incremental_snapshots_to_retain")
.long("maximum-incremental-snapshots-to-retain")
.value_name("NUMBER")
.takes_value(true)
.default_value(&default_args.maximum_incremental_snapshot_archives_to_retain)
.validator(validate_maximum_incremental_snapshot_archives_to_retain)
.help(
"The maximum number of incremental snapshot archives to hold on to when \
purging older snapshots.",
),
)
.arg(
Arg::with_name("snapshot_packager_niceness_adj")
.long("snapshot-packager-niceness-adjustment")
.value_name("ADJUSTMENT")
.takes_value(true)
.validator(solana_perf::thread::is_niceness_adjustment_valid)
.default_value(&default_args.snapshot_packager_niceness_adjustment)
.help(
"Add this value to niceness of snapshot packager thread. Negative value \
increases priority, positive value decreases priority.",
),
)
.arg(
Arg::with_name("minimal_snapshot_download_speed")
.long("minimal-snapshot-download-speed")
.value_name("MINIMAL_SNAPSHOT_DOWNLOAD_SPEED")
.takes_value(true)
.default_value(&default_args.min_snapshot_download_speed)
.help(
"The minimal speed of snapshot downloads measured in bytes/second. If the \
initial download speed falls below this threshold, the system will retry the \
download against a different rpc node.",
),
)
.arg(
Arg::with_name("maximum_snapshot_download_abort")
.long("maximum-snapshot-download-abort")
.value_name("MAXIMUM_SNAPSHOT_DOWNLOAD_ABORT")
.takes_value(true)
.default_value(&default_args.max_snapshot_download_abort)
.help(
"The maximum number of times to abort and retry when encountering a slow \
snapshot download.",
),
)
.arg(
Arg::with_name("contact_debug_interval")
.long("contact-debug-interval")
.value_name("CONTACT_DEBUG_INTERVAL")
.takes_value(true)
.default_value(&default_args.contact_debug_interval)
.help("Milliseconds between printing contact debug from gossip."),
)
.arg(
Arg::with_name("no_poh_speed_test")
.long("no-poh-speed-test")
.hidden(hidden_unless_forced())
.help("Skip the check for PoH speed."),
)
.arg(
Arg::with_name("no_os_network_limits_test")
.hidden(hidden_unless_forced())
.long("no-os-network-limits-test")
.help("Skip checks for OS network limits."),
)
.arg(
Arg::with_name("no_os_memory_stats_reporting")
.long("no-os-memory-stats-reporting")
.hidden(hidden_unless_forced())
.help("Disable reporting of OS memory statistics."),
)
.arg(
Arg::with_name("no_os_network_stats_reporting")
.long("no-os-network-stats-reporting")
.hidden(hidden_unless_forced())
.help("Disable reporting of OS network statistics."),
)
.arg(
Arg::with_name("no_os_cpu_stats_reporting")
.long("no-os-cpu-stats-reporting")
.hidden(hidden_unless_forced())
.help("Disable reporting of OS CPU statistics."),
)
.arg(
Arg::with_name("no_os_disk_stats_reporting")
.long("no-os-disk-stats-reporting")
.hidden(hidden_unless_forced())
.help("Disable reporting of OS disk statistics."),
)
.arg(
Arg::with_name("snapshot_version")
.long("snapshot-version")
.value_name("SNAPSHOT_VERSION")
.validator(is_parsable::<SnapshotVersion>)
.takes_value(true)
.default_value(default_args.snapshot_version.into())
.help("Output snapshot version"),
)
.arg(
Arg::with_name("limit_ledger_size")
.long("limit-ledger-size")
.value_name("SHRED_COUNT")
.takes_value(true)
.min_values(0)
.max_values(1)
.help("Keep this amount of shreds in root slots."),
)
.arg(
Arg::with_name("rocksdb_shred_compaction")
.long("rocksdb-shred-compaction")
.value_name("ROCKSDB_COMPACTION_STYLE")
.takes_value(true)
.possible_values(&["level"])
.default_value(&default_args.rocksdb_shred_compaction)
.help(
"Controls how RocksDB compacts shreds. *WARNING*: You will lose your \
Blockstore data when you switch between options. Possible values are: \
'level': stores shreds using RocksDB's default (level) compaction.",
),
)
.arg(
Arg::with_name("rocksdb_ledger_compression")
.hidden(hidden_unless_forced())
.long("rocksdb-ledger-compression")
.value_name("COMPRESSION_TYPE")
.takes_value(true)
.possible_values(&["none", "lz4", "snappy", "zlib"])
.default_value(&default_args.rocksdb_ledger_compression)
.help(
"The compression algorithm that is used to compress transaction status data. \
Turning on compression can save ~10% of the ledger size.",
),
)
.arg(
Arg::with_name("rocksdb_perf_sample_interval")
.hidden(hidden_unless_forced())
.long("rocksdb-perf-sample-interval")
.value_name("ROCKS_PERF_SAMPLE_INTERVAL")
.takes_value(true)
.validator(is_parsable::<usize>)
.default_value(&default_args.rocksdb_perf_sample_interval)
.help(
"Controls how often RocksDB read/write performance samples are collected. \
Perf samples are collected in 1 / ROCKS_PERF_SAMPLE_INTERVAL sampling rate.",
),
)
.arg(
Arg::with_name("skip_startup_ledger_verification")
.long("skip-startup-ledger-verification")
.takes_value(false)
.help("Skip ledger verification at validator bootup."),
)
.arg(
Arg::with_name("cuda")
.long("cuda")
.takes_value(false)
.help("Use CUDA"),
)
.arg(
clap::Arg::with_name("require_tower")
.long("require-tower")
.takes_value(false)
.help("Refuse to start if saved tower state is not found"),
)
.arg(
Arg::with_name("expected_genesis_hash")
.long("expected-genesis-hash")
.value_name("HASH")
.takes_value(true)
.validator(hash_validator)
.help("Require the genesis have this hash"),
)
.arg(
Arg::with_name("expected_bank_hash")
.long("expected-bank-hash")
.value_name("HASH")
.takes_value(true)
.validator(hash_validator)
.help("When wait-for-supermajority <x>, require the bank at <x> to have this hash"),
)
.arg(
Arg::with_name("expected_shred_version")
.long("expected-shred-version")
.value_name("VERSION")
.takes_value(true)
.validator(is_parsable::<u16>)
.help("Require the shred version be this value"),
)
.arg(
Arg::with_name("logfile")
.short("o")
.long("log")
.value_name("FILE")
.takes_value(true)
.help(
"Redirect logging to the specified file, '-' for standard error. Sending the \
SIGUSR1 signal to the validator process will cause it to re-open the log file",
),
)
.arg(
Arg::with_name("wait_for_supermajority")
.long("wait-for-supermajority")
.requires("expected_bank_hash")
.requires("expected_shred_version")
.value_name("SLOT")
.validator(is_slot)
.help(
"After processing the ledger and the next slot is SLOT, wait until a \
supermajority of stake is visible on gossip before starting PoH",
),
)
.arg(
Arg::with_name("no_wait_for_vote_to_start_leader")
.hidden(hidden_unless_forced())
.long("no-wait-for-vote-to-start-leader")
.help(
"If the validator starts up with no ledger, it will wait to start block \
production until it sees a vote land in a rooted slot. This prevents \
double signing. Turn off to risk double signing a block.",
),
)
.arg(
Arg::with_name("hard_forks")
.long("hard-fork")
.value_name("SLOT")
.validator(is_slot)
.multiple(true)
.takes_value(true)
.help("Add a hard fork at this slot"),
)
.arg(
Arg::with_name("known_validators")
.alias("trusted-validator")
.long("known-validator")
.validator(is_pubkey)
.value_name("VALIDATOR IDENTITY")
.multiple(true)
.takes_value(true)
.help(
"A snapshot hash must be published in gossip by this validator to be \
accepted. May be specified multiple times. If unspecified any snapshot hash \
will be accepted",
),
)
.arg(
Arg::with_name("debug_key")
.long("debug-key")
.validator(is_pubkey)
.value_name("ADDRESS")
.multiple(true)
.takes_value(true)
.help("Log when transactions are processed which reference a given key."),
)
.arg(
Arg::with_name("only_known_rpc")
.alias("no-untrusted-rpc")
.long("only-known-rpc")
.takes_value(false)
.requires("known_validators")
.help("Use the RPC service of known validators only"),
)
.arg(
Arg::with_name("repair_validators")
.long("repair-validator")
.validator(is_pubkey)
.value_name("VALIDATOR IDENTITY")
.multiple(true)
.takes_value(true)
.help(
"A list of validators to request repairs from. If specified, repair will not \
request from validators outside this set [default: all validators]",
),
)
.arg(
Arg::with_name("repair_whitelist")
.hidden(hidden_unless_forced())
.long("repair-whitelist")
.validator(is_pubkey)
.value_name("VALIDATOR IDENTITY")
.multiple(true)
.takes_value(true)
.help(
"A list of validators to prioritize repairs from. If specified, repair \
requests from validators in the list will be prioritized over requests from \
other validators. [default: all validators]",
),
)
.arg(
Arg::with_name("gossip_validators")
.long("gossip-validator")
.validator(is_pubkey)
.value_name("VALIDATOR IDENTITY")
.multiple(true)
.takes_value(true)
.help(
"A list of validators to gossip with. If specified, gossip will not \
push/pull from from validators outside this set. [default: all validators]",
),
)
.arg(
Arg::with_name("tpu_coalesce_ms")
.long("tpu-coalesce-ms")
.value_name("MILLISECS")
.takes_value(true)
.validator(is_parsable::<u64>)
.help("Milliseconds to wait in the TPU receiver for packet coalescing."),
)
.arg(
Arg::with_name("tpu_use_quic")
.long("tpu-use-quic")
.takes_value(false)
.hidden(hidden_unless_forced())
.conflicts_with("tpu_disable_quic")
.help("Use QUIC to send transactions."),
)
.arg(
Arg::with_name("tpu_disable_quic")
.long("tpu-disable-quic")
.takes_value(false)
.help("Do not use QUIC to send transactions."),
)
.arg(
Arg::with_name("tpu_enable_udp")
.long("tpu-enable-udp")
.takes_value(false)
.help("Enable UDP for receiving/sending transactions."),
)
.arg(
Arg::with_name("tpu_connection_pool_size")
.long("tpu-connection-pool-size")
.takes_value(true)
.default_value(&default_args.tpu_connection_pool_size)
.validator(is_parsable::<usize>)
.help("Controls the TPU connection pool size per remote address"),
)
.arg(
Arg::with_name("tpu_max_connections_per_ipaddr_per_minute")
.long("tpu-max-connections-per-ipaddr-per-minute")
.takes_value(true)
.default_value(&default_args.tpu_max_connections_per_ipaddr_per_minute)
.validator(is_parsable::<u32>)
.hidden(hidden_unless_forced())
.help("Controls the rate of the clients connections per IpAddr per minute."),
)
.arg(
Arg::with_name("vote_use_quic")
.long("vote-use-quic")
.takes_value(true)
.default_value(&default_args.vote_use_quic)
.hidden(hidden_unless_forced())
.help("Controls if to use QUIC to send votes."),
)
.arg(
Arg::with_name("tpu_max_connections_per_peer")
.long("tpu-max-connections-per-peer")
.takes_value(true)
.default_value(&default_args.tpu_max_connections_per_peer)
.validator(is_parsable::<u32>)
.hidden(hidden_unless_forced())
.help("Controls the max concurrent connections per IpAddr."),
)
.arg(
Arg::with_name("tpu_max_staked_connections")
.long("tpu-max-staked-connections")
.takes_value(true)
.default_value(&default_args.tpu_max_staked_connections)
.validator(is_parsable::<u32>)
.hidden(hidden_unless_forced())
.help("Controls the max concurrent connections for TPU from staked nodes."),
)
.arg(
Arg::with_name("tpu_max_unstaked_connections")
.long("tpu-max-unstaked-connections")
.takes_value(true)
.default_value(&default_args.tpu_max_unstaked_connections)
.validator(is_parsable::<u32>)
.hidden(hidden_unless_forced())
.help("Controls the max concurrent connections fort TPU from unstaked nodes."),
)
.arg(
Arg::with_name("tpu_max_fwd_staked_connections")
.long("tpu-max-fwd-staked-connections")
.takes_value(true)
.default_value(&default_args.tpu_max_fwd_staked_connections)
.validator(is_parsable::<u32>)
.hidden(hidden_unless_forced())
.help("Controls the max concurrent connections for TPU-forward from staked nodes."),
)
.arg(
Arg::with_name("tpu_max_fwd_unstaked_connections")
.long("tpu-max-fwd-unstaked-connections")
.takes_value(true)
.default_value(&default_args.tpu_max_fwd_unstaked_connections)
.validator(is_parsable::<u32>)
.hidden(hidden_unless_forced())
.help("Controls the max concurrent connections for TPU-forward from unstaked nodes."),
)
.arg(
Arg::with_name("tpu_max_streams_per_ms")
.long("tpu-max-streams-per-ms")
.takes_value(true)
.default_value(&default_args.tpu_max_streams_per_ms)
.validator(is_parsable::<usize>)
.hidden(hidden_unless_forced())
.help("Controls the max number of streams for a TPU service."),
)
.arg(
Arg::with_name("num_quic_endpoints")
.long("num-quic-endpoints")
.takes_value(true)
.default_value(&default_args.num_quic_endpoints)
.validator(is_parsable::<usize>)
.hidden(hidden_unless_forced())
.help("The number of QUIC endpoints used for TPU and TPU-Forward. It can be increased to \
increase network ingest throughput, at the expense of higher CPU and general \
validator load."),
)
.arg(
Arg::with_name("staked_nodes_overrides")
.long("staked-nodes-overrides")
.value_name("PATH")
.takes_value(true)
.help(
"Provide path to a yaml file with custom overrides for stakes of specific \
identities. Overriding the amount of stake this validator considers as valid \
for other peers in network. The stake amount is used for calculating the \
number of QUIC streams permitted from the peer and vote packet sender stage. \
Format of the file: `staked_map_id: {<pubkey>: <SOL stake amount>}",
),
)
.arg(
Arg::with_name("bind_address")
.long("bind-address")
.value_name("HOST")
.takes_value(true)
.validator(solana_net_utils::is_host)
.default_value(&default_args.bind_address)
.help("IP address to bind the validator ports"),
)
.arg(
Arg::with_name("rpc_bind_address")
.long("rpc-bind-address")
.value_name("HOST")
.takes_value(true)
.validator(solana_net_utils::is_host)
.help(
"IP address to bind the RPC port [default: 127.0.0.1 if --private-rpc is \
present, otherwise use --bind-address]",
),
)
.arg(
Arg::with_name("rpc_threads")
.long("rpc-threads")
.value_name("NUMBER")
.validator(is_parsable::<usize>)
.takes_value(true)
.default_value(&default_args.rpc_threads)
.help("Number of threads to use for servicing RPC requests"),
)
.arg(
Arg::with_name("rpc_blocking_threads")
.long("rpc-blocking-threads")
.value_name("NUMBER")
.validator(is_parsable::<usize>)
.validator(|value| {
value
.parse::<u64>()
.map_err(|err| format!("error parsing '{value}': {err}"))
.and_then(|threads| {
if threads > 0 {
Ok(())
} else {
Err("value must be >= 1".to_string())
}
})
})
.takes_value(true)
.default_value(&default_args.rpc_blocking_threads)
.help("Number of blocking threads to use for servicing CPU bound RPC requests (eg getMultipleAccounts)"),
)
.arg(
Arg::with_name("rpc_niceness_adj")
.long("rpc-niceness-adjustment")
.value_name("ADJUSTMENT")
.takes_value(true)
.validator(solana_perf::thread::is_niceness_adjustment_valid)
.default_value(&default_args.rpc_niceness_adjustment)
.help(
"Add this value to niceness of RPC threads. Negative value increases \
priority, positive value decreases priority.",
),
)
.arg(
Arg::with_name("rpc_bigtable_timeout")
.long("rpc-bigtable-timeout")
.value_name("SECONDS")
.validator(is_parsable::<u64>)
.takes_value(true)
.default_value(&default_args.rpc_bigtable_timeout)
.help("Number of seconds before timing out RPC requests backed by BigTable"),
)
.arg(
Arg::with_name("rpc_bigtable_instance_name")
.long("rpc-bigtable-instance-name")
.takes_value(true)
.value_name("INSTANCE_NAME")
.default_value(&default_args.rpc_bigtable_instance_name)
.help("Name of the Bigtable instance to upload to"),
)
.arg(
Arg::with_name("rpc_bigtable_app_profile_id")
.long("rpc-bigtable-app-profile-id")
.takes_value(true)
.value_name("APP_PROFILE_ID")
.default_value(&default_args.rpc_bigtable_app_profile_id)
.help("Bigtable application profile id to use in requests"),
)
.arg(
Arg::with_name("rpc_bigtable_max_message_size")
.long("rpc-bigtable-max-message-size")
.value_name("BYTES")
.validator(is_parsable::<usize>)
.takes_value(true)
.default_value(&default_args.rpc_bigtable_max_message_size)
.help("Max encoding and decoding message size used in Bigtable Grpc client"),
)
.arg(
Arg::with_name("rpc_pubsub_worker_threads")
.long("rpc-pubsub-worker-threads")
.takes_value(true)
.value_name("NUMBER")
.validator(is_parsable::<usize>)
.default_value(&default_args.rpc_pubsub_worker_threads)
.help("PubSub worker threads"),
)
.arg(
Arg::with_name("rpc_pubsub_enable_block_subscription")
.long("rpc-pubsub-enable-block-subscription")
.requires("enable_rpc_transaction_history")
.takes_value(false)
.help("Enable the unstable RPC PubSub `blockSubscribe` subscription"),
)
.arg(
Arg::with_name("rpc_pubsub_enable_vote_subscription")
.long("rpc-pubsub-enable-vote-subscription")
.takes_value(false)
.help("Enable the unstable RPC PubSub `voteSubscribe` subscription"),
)
.arg(
Arg::with_name("rpc_pubsub_max_active_subscriptions")
.long("rpc-pubsub-max-active-subscriptions")
.takes_value(true)
.value_name("NUMBER")
.validator(is_parsable::<usize>)
.default_value(&default_args.rpc_pubsub_max_active_subscriptions)
.help(
"The maximum number of active subscriptions that RPC PubSub will accept \
across all connections.",
),
)
.arg(
Arg::with_name("rpc_pubsub_queue_capacity_items")
.long("rpc-pubsub-queue-capacity-items")
.takes_value(true)
.value_name("NUMBER")
.validator(is_parsable::<usize>)
.default_value(&default_args.rpc_pubsub_queue_capacity_items)
.help(
"The maximum number of notifications that RPC PubSub will store across all \
connections.",
),
)
.arg(
Arg::with_name("rpc_pubsub_queue_capacity_bytes")
.long("rpc-pubsub-queue-capacity-bytes")
.takes_value(true)
.value_name("BYTES")
.validator(is_parsable::<usize>)
.default_value(&default_args.rpc_pubsub_queue_capacity_bytes)
.help(
"The maximum total size of notifications that RPC PubSub will store across \
all connections.",
),
)
.arg(
Arg::with_name("rpc_pubsub_notification_threads")
.long("rpc-pubsub-notification-threads")
.requires("full_rpc_api")
.takes_value(true)
.value_name("NUM_THREADS")
.validator(is_parsable::<usize>)
.default_value_if(
"full_rpc_api",
None,
&default_args.rpc_pubsub_notification_threads,
)
.help(
"The maximum number of threads that RPC PubSub will use for generating \
notifications. 0 will disable RPC PubSub notifications",
),
)
.arg(
Arg::with_name("rpc_send_transaction_retry_ms")
.long("rpc-send-retry-ms")
.value_name("MILLISECS")
.takes_value(true)
.validator(is_parsable::<u64>)
.default_value(&default_args.rpc_send_transaction_retry_ms)
.help("The rate at which transactions sent via rpc service are retried."),
)
.arg(
Arg::with_name("rpc_send_transaction_batch_ms")
.long("rpc-send-batch-ms")
.value_name("MILLISECS")
.hidden(hidden_unless_forced())
.takes_value(true)
.validator(|s| is_within_range(s, 1..=MAX_BATCH_SEND_RATE_MS))
.default_value(&default_args.rpc_send_transaction_batch_ms)
.help("The rate at which transactions sent via rpc service are sent in batch."),
)
.arg(
Arg::with_name("rpc_send_transaction_leader_forward_count")
.long("rpc-send-leader-count")
.value_name("NUMBER")
.takes_value(true)
.validator(is_parsable::<u64>)
.default_value(&default_args.rpc_send_transaction_leader_forward_count)
.help(
"The number of upcoming leaders to which to forward transactions sent via rpc \
service.",
),
)
.arg(
Arg::with_name("rpc_send_transaction_default_max_retries")
.long("rpc-send-default-max-retries")
.value_name("NUMBER")
.takes_value(true)
.validator(is_parsable::<usize>)
.help(
"The maximum number of transaction broadcast retries when unspecified by the \
request, otherwise retried until expiration.",
),
)
.arg(
Arg::with_name("rpc_send_transaction_service_max_retries")
.long("rpc-send-service-max-retries")
.value_name("NUMBER")
.takes_value(true)
.validator(is_parsable::<usize>)
.default_value(&default_args.rpc_send_transaction_service_max_retries)
.help(
"The maximum number of transaction broadcast retries, regardless of requested \
value.",
),
)
.arg(
Arg::with_name("rpc_send_transaction_batch_size")
.long("rpc-send-batch-size")
.value_name("NUMBER")
.hidden(hidden_unless_forced())
.takes_value(true)
.validator(|s| is_within_range(s, 1..=MAX_TRANSACTION_BATCH_SIZE))
.default_value(&default_args.rpc_send_transaction_batch_size)
.help("The size of transactions to be sent in batch."),
)
.arg(
Arg::with_name("rpc_send_transaction_retry_pool_max_size")
.long("rpc-send-transaction-retry-pool-max-size")
.value_name("NUMBER")
.takes_value(true)
.validator(is_parsable::<usize>)
.default_value(&default_args.rpc_send_transaction_retry_pool_max_size)
.help("The maximum size of transactions retry pool."),
)
.arg(
Arg::with_name("rpc_send_transaction_tpu_peer")
.long("rpc-send-transaction-tpu-peer")
.takes_value(true)
.number_of_values(1)
.multiple(true)
.value_name("HOST:PORT")
.validator(solana_net_utils::is_host_port)
.help("Peer(s) to broadcast transactions to instead of the current leader")
)
.arg(
Arg::with_name("rpc_send_transaction_also_leader")
.long("rpc-send-transaction-also-leader")
.requires("rpc_send_transaction_tpu_peer")
.help("With `--rpc-send-transaction-tpu-peer HOST:PORT`, also send to the current leader")
)
.arg(
Arg::with_name("rpc_scan_and_fix_roots")
.long("rpc-scan-and-fix-roots")
.takes_value(false)
.requires("enable_rpc_transaction_history")
.help("Verifies blockstore roots on boot and fixes any gaps"),
)
.arg(
Arg::with_name("rpc_max_request_body_size")
.long("rpc-max-request-body-size")
.value_name("BYTES")
.takes_value(true)
.validator(is_parsable::<usize>)
.default_value(&default_args.rpc_max_request_body_size)
.help("The maximum request body size accepted by rpc service"),
)
.arg(
Arg::with_name("geyser_plugin_config")
.long("geyser-plugin-config")
.alias("accountsdb-plugin-config")
.value_name("FILE")
.takes_value(true)
.multiple(true)
.help("Specify the configuration file for the Geyser plugin."),
)
.arg(
Arg::with_name("geyser_plugin_always_enabled")
.long("geyser-plugin-always-enabled")
.value_name("BOOLEAN")
.takes_value(false)
.help("Еnable Geyser interface even if no Geyser configs are specified."),
)
.arg(
Arg::with_name("snapshot_archive_format")
.long("snapshot-archive-format")
.alias("snapshot-compression") .possible_values(SUPPORTED_ARCHIVE_COMPRESSION)
.default_value(&default_args.snapshot_archive_format)
.value_name("ARCHIVE_TYPE")
.takes_value(true)
.help("Snapshot archive format to use."),
)
.arg(
Arg::with_name("snapshot_zstd_compression_level")
.long("snapshot-zstd-compression-level")
.default_value(&default_args.snapshot_zstd_compression_level)
.value_name("LEVEL")
.takes_value(true)
.help("The compression level to use when archiving with zstd")
.long_help(
"The compression level to use when archiving with zstd. \
Higher compression levels generally produce higher \
compression ratio at the expense of speed and memory. \
See the zstd manpage for more information."
),
)
.arg(
Arg::with_name("max_genesis_archive_unpacked_size")
.long("max-genesis-archive-unpacked-size")
.value_name("NUMBER")
.takes_value(true)
.default_value(&default_args.genesis_archive_unpacked_size)
.help("maximum total uncompressed file size of downloaded genesis archive"),
)
.arg(
Arg::with_name("wal_recovery_mode")
.long("wal-recovery-mode")
.value_name("MODE")
.takes_value(true)
.possible_values(&[
"tolerate_corrupted_tail_records",
"absolute_consistency",
"point_in_time",
"skip_any_corrupted_record",
])
.help("Mode to recovery the ledger db write ahead log."),
)
.arg(
Arg::with_name("poh_pinned_cpu_core")
.hidden(hidden_unless_forced())
.long("experimental-poh-pinned-cpu-core")
.takes_value(true)
.value_name("CPU_CORE_INDEX")
.validator(|s| {
let core_index = usize::from_str(&s).map_err(|e| e.to_string())?;
let max_index = core_affinity::get_core_ids()
.map(|cids| cids.len() - 1)
.unwrap_or(0);
if core_index > max_index {
return Err(format!("core index must be in the range [0, {max_index}]"));
}
Ok(())
})
.help("EXPERIMENTAL: Specify which CPU core PoH is pinned to"),
)
.arg(
Arg::with_name("poh_hashes_per_batch")
.hidden(hidden_unless_forced())
.long("poh-hashes-per-batch")
.takes_value(true)
.value_name("NUM")
.help("Specify hashes per batch in PoH service"),
)
.arg(
Arg::with_name("process_ledger_before_services")
.long("process-ledger-before-services")
.hidden(hidden_unless_forced())
.help("Process the local ledger fully before starting networking services"),
)
.arg(
Arg::with_name("account_indexes")
.long("account-index")
.takes_value(true)
.multiple(true)
.possible_values(&["program-id", "spl-token-owner", "spl-token-mint"])
.value_name("INDEX")
.help("Enable an accounts index, indexed by the selected account field"),
)
.arg(
Arg::with_name("account_index_exclude_key")
.long(EXCLUDE_KEY)
.takes_value(true)
.validator(is_pubkey)
.multiple(true)
.value_name("KEY")
.help("When account indexes are enabled, exclude this key from the index."),
)
.arg(
Arg::with_name("account_index_include_key")
.long(INCLUDE_KEY)
.takes_value(true)
.validator(is_pubkey)
.conflicts_with("account_index_exclude_key")
.multiple(true)
.value_name("KEY")
.help(
"When account indexes are enabled, only include specific keys in the index. \
This overrides --account-index-exclude-key.",
),
)
.arg(
Arg::with_name("accounts_db_verify_refcounts")
.long("accounts-db-verify-refcounts")
.help(
"Debug option to scan all append vecs and verify account index refcounts \
prior to clean",
)
.hidden(hidden_unless_forced()),
)
.arg(
Arg::with_name("accounts_db_scan_filter_for_shrinking")
.long("accounts-db-scan-filter-for-shrinking")
.takes_value(true)
.possible_values(&["all", "only-abnormal", "only-abnormal-with-verify"])
.help(
"Debug option to use different type of filtering for accounts index scan in \
shrinking. \"all\" will scan both in-memory and on-disk accounts index, which is the default. \
\"only-abnormal\" will scan in-memory accounts index only for abnormal entries and \
skip scanning on-disk accounts index by assuming that on-disk accounts index contains \
only normal accounts index entry. \"only-abnormal-with-verify\" is similar to \
\"only-abnormal\", which will scan in-memory index for abnormal entries, but will also \
verify that on-disk account entries are indeed normal.",
)
.hidden(hidden_unless_forced()),
)
.arg(
Arg::with_name("accounts_db_test_skip_rewrites")
.long("accounts-db-test-skip-rewrites")
.help(
"Debug option to skip rewrites for rent-exempt accounts but still add them in \
bank delta hash calculation",
)
.hidden(hidden_unless_forced()),
)
.arg(
Arg::with_name("no_skip_initial_accounts_db_clean")
.long("no-skip-initial-accounts-db-clean")
.help("Do not skip the initial cleaning of accounts when verifying snapshot bank")
.hidden(hidden_unless_forced())
.conflicts_with("accounts_db_skip_shrink"),
)
.arg(
Arg::with_name("accounts_db_squash_storages_method")
.long("accounts-db-squash-storages-method")
.value_name("METHOD")
.takes_value(true)
.possible_values(&["pack", "append"])
.help("Squash multiple account storage files together using this method")
.hidden(hidden_unless_forced()),
)
.arg(
Arg::with_name("accounts_db_access_storages_method")
.long("accounts-db-access-storages-method")
.value_name("METHOD")
.takes_value(true)
.possible_values(&["mmap", "file"])
.help("Access account storages using this method")
)
.arg(
Arg::with_name("accounts_db_ancient_append_vecs")
.long("accounts-db-ancient-append-vecs")
.value_name("SLOT-OFFSET")
.validator(is_parsable::<i64>)
.takes_value(true)
.help(
"AppendVecs that are older than (slots_per_epoch - SLOT-OFFSET) are squashed \
together.",
)
.hidden(hidden_unless_forced()),
)
.arg(
Arg::with_name("accounts_db_ancient_storage_ideal_size")
.long("accounts-db-ancient-storage-ideal-size")
.value_name("BYTES")
.validator(is_parsable::<u64>)
.takes_value(true)
.help("The smallest size of ideal ancient storage.")
.hidden(hidden_unless_forced()),
)
.arg(
Arg::with_name("accounts_db_max_ancient_storages")
.long("accounts-db-max-ancient-storages")
.value_name("USIZE")
.validator(is_parsable::<usize>)
.takes_value(true)
.help("The number of ancient storages the ancient slot combining should converge to.")
.hidden(hidden_unless_forced()),
)
.arg(
Arg::with_name("accounts_db_hash_calculation_pubkey_bins")
.long("accounts-db-hash-calculation-pubkey-bins")
.value_name("USIZE")
.validator(is_parsable::<usize>)
.takes_value(true)
.help("The number of pubkey bins used for accounts hash calculation.")
.hidden(hidden_unless_forced()),
)
.arg(
Arg::with_name("accounts_db_cache_limit_mb")
.long("accounts-db-cache-limit-mb")
.value_name("MEGABYTES")
.validator(is_parsable::<u64>)
.takes_value(true)
.help(
"How large the write cache for account data can become. If this is exceeded, \
the cache is flushed more aggressively.",
),
)
.arg(
Arg::with_name("accounts_db_read_cache_limit_mb")
.long("accounts-db-read-cache-limit-mb")
.value_name("MAX | LOW,HIGH")
.takes_value(true)
.min_values(1)
.max_values(2)
.multiple(false)
.require_delimiter(true)
.help("How large the read cache for account data can become, in mebibytes")
.long_help(
"How large the read cache for account data can become, in mebibytes. \
If given a single value, it will be the maximum size for the cache. \
If given a pair of values, they will be the low and high watermarks \
for the cache. When the cache exceeds the high watermark, entries will \
be evicted until the size reaches the low watermark."
)
.hidden(hidden_unless_forced()),
)
.arg(
Arg::with_name("accounts_db_experimental_accumulator_hash")
.long("accounts-db-experimental-accumulator-hash")
.help("Enables the experimental accumulator hash")
.hidden(hidden_unless_forced()),
)
.arg(
Arg::with_name("accounts_db_verify_experimental_accumulator_hash")
.long("accounts-db-verify-experimental-accumulator-hash")
.help("Verifies the experimental accumulator hash")
.hidden(hidden_unless_forced()),
)
.arg(
Arg::with_name("accounts_db_snapshots_use_experimental_accumulator_hash")
.long("accounts-db-snapshots-use-experimental-accumulator-hash")
.help("Snapshots use the experimental accumulator hash")
.hidden(hidden_unless_forced()),
)
.arg(
Arg::with_name("accounts_index_scan_results_limit_mb")
.long("accounts-index-scan-results-limit-mb")
.value_name("MEGABYTES")
.validator(is_parsable::<usize>)
.takes_value(true)
.help(
"How large accumulated results from an accounts index scan can become. If \
this is exceeded, the scan aborts.",
),
)
.arg(
Arg::with_name("accounts_index_bins")
.long("accounts-index-bins")
.value_name("BINS")
.validator(is_pow2)
.takes_value(true)
.help("Number of bins to divide the accounts index into"),
)
.arg(
Arg::with_name("accounts_index_path")
.long("accounts-index-path")
.value_name("PATH")
.takes_value(true)
.multiple(true)
.help(
"Persistent accounts-index location. \
May be specified multiple times. \
[default: <LEDGER>/accounts_index]",
),
)
.arg(
Arg::with_name("accounts_db_test_hash_calculation")
.long("accounts-db-test-hash-calculation")
.help(
"Enables testing of hash calculation using stores in AccountsHashVerifier. \
This has a computational cost.",
),
)
.arg(
Arg::with_name("accounts_shrink_optimize_total_space")
.long("accounts-shrink-optimize-total-space")
.takes_value(true)
.value_name("BOOLEAN")
.default_value(&default_args.accounts_shrink_optimize_total_space)
.help(
"When this is set to true, the system will shrink the most sparse accounts \
and when the overall shrink ratio is above the specified \
accounts-shrink-ratio, the shrink will stop and it will skip all other less \
sparse accounts.",
),
)
.arg(
Arg::with_name("accounts_shrink_ratio")
.long("accounts-shrink-ratio")
.takes_value(true)
.value_name("RATIO")
.default_value(&default_args.accounts_shrink_ratio)
.help(
"Specifies the shrink ratio for the accounts to be shrunk. The shrink ratio \
is defined as the ratio of the bytes alive over the total bytes used. If \
the account's shrink ratio is less than this ratio it becomes a candidate \
for shrinking. The value must between 0. and 1.0 inclusive.",
),
)
.arg(
Arg::with_name("allow_private_addr")
.long("allow-private-addr")
.takes_value(false)
.help("Allow contacting private ip addresses")
.hidden(hidden_unless_forced()),
)
.arg(
Arg::with_name("log_messages_bytes_limit")
.long("log-messages-bytes-limit")
.takes_value(true)
.validator(is_parsable::<usize>)
.value_name("BYTES")
.help("Maximum number of bytes written to the program log before truncation"),
)
.arg(
Arg::with_name("banking_trace_dir_byte_limit")
.long("enable-banking-trace")
.value_name("BYTES")
.validator(is_parsable::<DirByteLimit>)
.takes_value(true)
.default_value(&default_args.banking_trace_dir_byte_limit)
.help(
"Enables the banking trace explicitly, which is enabled by default and writes \
trace files for simulate-leader-blocks, retaining up to the default or \
specified total bytes in the ledger. This flag can be used to override its \
byte limit.",
),
)
.arg(
Arg::with_name("disable_banking_trace")
.long("disable-banking-trace")
.conflicts_with("banking_trace_dir_byte_limit")
.takes_value(false)
.help("Disables the banking trace"),
)
.arg(
Arg::with_name("delay_leader_block_for_pending_fork")
.hidden(hidden_unless_forced())
.long("delay-leader-block-for-pending-fork")
.takes_value(false)
.help(
"Delay leader block creation while replaying a block which descends from the \
current fork and has a lower slot than our next leader slot. If we don't \
delay here, our new leader block will be on a different fork from the \
block we are replaying and there is a high chance that the cluster will \
confirm that block's fork rather than our leader block's fork because it \
was created before we started creating ours.",
),
)
.arg(
Arg::with_name("block_verification_method")
.long("block-verification-method")
.value_name("METHOD")
.takes_value(true)
.possible_values(BlockVerificationMethod::cli_names())
.help(BlockVerificationMethod::cli_message()),
)
.arg(
Arg::with_name("block_production_method")
.long("block-production-method")
.value_name("METHOD")
.takes_value(true)
.possible_values(BlockProductionMethod::cli_names())
.help(BlockProductionMethod::cli_message()),
)
.arg(
Arg::with_name("transaction_struct")
.long("transaction-structure")
.value_name("STRUCT")
.takes_value(true)
.possible_values(TransactionStructure::cli_names())
.help(TransactionStructure::cli_message()),
)
.arg(
Arg::with_name("unified_scheduler_handler_threads")
.long("unified-scheduler-handler-threads")
.value_name("COUNT")
.takes_value(true)
.validator(|s| is_within_range(s, 1..))
.help(DefaultSchedulerPool::cli_message()),
)
.arg(
Arg::with_name("wen_restart")
.long("wen-restart")
.hidden(hidden_unless_forced())
.value_name("FILE")
.takes_value(true)
.required(false)
.conflicts_with("wait_for_supermajority")
.requires("wen_restart_coordinator")
.help(
"Only used during coordinated cluster restarts.\
\n\n\
Need to also specify the leader's pubkey in --wen-restart-leader.\
\n\n\
When specified, the validator will enter Wen Restart mode which \
pauses normal activity. Validators in this mode will gossip their last \
vote to reach consensus on a safe restart slot and repair all blocks \
on the selected fork. The safe slot will be a descendant of the latest \
optimistically confirmed slot to ensure we do not roll back any \
optimistically confirmed slots. \
\n\n\
The progress in this mode will be saved in the file location provided. \
If consensus is reached, the validator will automatically exit with 200 \
status code. Then the operators are expected to restart the validator \
with --wait_for_supermajority and other arguments (including new shred_version, \
supermajority slot, and bankhash) given in the error log before the exit so \
the cluster will resume execution. The progress file will be kept around \
for future debugging. \
\n\n\
If wen_restart fails, refer to the progress file (in proto3 format) for \
further debugging and watch the discord channel for instructions.",
),
)
.arg(
Arg::with_name("wen_restart_coordinator")
.long("wen-restart-coordinator")
.hidden(hidden_unless_forced())
.value_name("PUBKEY")
.takes_value(true)
.required(false)
.requires("wen_restart")
.help(
"Specifies the pubkey of the leader used in wen restart. \
May get stuck if the leader used is different from others.",
),
)
.args(&thread_args(&default_args.thread_args))
.args(&get_deprecated_arguments())
.after_help("The default subcommand is run")
.subcommand(commands::exit::command(default_args))
.subcommand(commands::authorized_voter::command(default_args))
.subcommand(commands::contact_info::command(default_args))
.subcommand(commands::repair_shred_from_peer::command(default_args))
.subcommand(commands::repair_whitelist::command(default_args))
.subcommand(
SubCommand::with_name("init").about("Initialize the ledger directory then exit"),
)
.subcommand(commands::monitor::command(default_args))
.subcommand(SubCommand::with_name("run").about("Run the validator"))
.subcommand(commands::plugin::command(default_args))
.subcommand(commands::set_identity::command(default_args))
.subcommand(commands::set_log_filter::command(default_args))
.subcommand(commands::staked_nodes_overrides::command(default_args))
.subcommand(commands::wait_for_restart_window::command(default_args))
.subcommand(commands::set_public_address::command(default_args));
}
struct DeprecatedArg {
arg: Arg<'static, 'static>,
replaced_by: Option<&'static str>,
usage_warning: Option<&'static str>,
}
fn deprecated_arguments() -> Vec<DeprecatedArg> {
let mut res = vec![];
macro_rules! add_arg {
(
$arg:expr
$( , replaced_by: $replaced_by:expr )?
$( , usage_warning: $usage_warning:expr )?
$(,)?
) => {
let replaced_by = add_arg!(@into-option $( $replaced_by )?);
let usage_warning = add_arg!(@into-option $( $usage_warning )?);
res.push(DeprecatedArg {
arg: $arg,
replaced_by,
usage_warning,
});
};
(@into-option) => { None };
(@into-option $v:expr) => { Some($v) };
}
add_arg!(
Arg::with_name("accounts_db_skip_shrink")
.long("accounts-db-skip-shrink")
.help("Enables faster starting of validators by skipping startup clean and shrink."),
usage_warning: "Enabled by default",
);
add_arg!(Arg::with_name("accounts_hash_interval_slots")
.long("accounts-hash-interval-slots")
.value_name("NUMBER")
.takes_value(true)
.help("Number of slots between verifying accounts hash.")
.validator(|val| {
if val.eq("0") {
Err(String::from("Accounts hash interval cannot be zero"))
} else {
Ok(())
}
}));
add_arg!(Arg::with_name("accounts_index_memory_limit_mb")
.long("accounts-index-memory-limit-mb")
.value_name("MEGABYTES")
.validator(is_parsable::<usize>)
.takes_value(true)
.help(
"How much memory the accounts index can consume. If this is exceeded, some \
account index entries will be stored on disk.",
),
usage_warning: "index memory limit has been deprecated. The limit arg has no effect now.",
);
add_arg!(Arg::with_name("accountsdb_repl_bind_address")
.long("accountsdb-repl-bind-address")
.value_name("HOST")
.takes_value(true)
.validator(solana_net_utils::is_host)
.help(
"IP address to bind the AccountsDb Replication port [default: use \
--bind-address]",
));
add_arg!(Arg::with_name("accountsdb_repl_port")
.long("accountsdb-repl-port")
.value_name("PORT")
.takes_value(true)
.validator(port_validator)
.help("Enable AccountsDb Replication Service on this port"));
add_arg!(Arg::with_name("accountsdb_repl_threads")
.long("accountsdb-repl-threads")
.value_name("NUMBER")
.validator(is_parsable::<usize>)
.takes_value(true)
.help("Number of threads to use for servicing AccountsDb Replication requests"));
add_arg!(Arg::with_name("disable_accounts_disk_index")
.long("disable-accounts-disk-index")
.help("Disable the disk-based accounts index if it is enabled by default.")
.conflicts_with("accounts_index_memory_limit_mb"));
add_arg!(
Arg::with_name("disable_quic_servers")
.long("disable-quic-servers")
.takes_value(false),
usage_warning: "The quic server cannot be disabled.",
);
add_arg!(Arg::with_name("enable_accountsdb_repl")
.long("enable-accountsdb-repl")
.takes_value(false)
.help("Enable AccountsDb Replication"));
add_arg!(
Arg::with_name("enable_cpi_and_log_storage")
.long("enable-cpi-and-log-storage")
.requires("enable_rpc_transaction_history")
.takes_value(false)
.help(
"Include CPI inner instructions, logs and return data in the historical \
transaction info stored",
),
replaced_by: "enable-extended-tx-metadata-storage",
);
add_arg!(
Arg::with_name("enable_quic_servers")
.long("enable-quic-servers"),
usage_warning: "The quic server is now enabled by default.",
);
add_arg!(Arg::with_name("etcd_cacert_file")
.long("etcd-cacert-file")
.required_if("tower_storage", "etcd")
.value_name("FILE")
.takes_value(true)
.help("verify the TLS certificate of the etcd endpoint using this CA bundle"),);
add_arg!(Arg::with_name("etcd_cert_file")
.long("etcd-cert-file")
.required_if("tower_storage", "etcd")
.value_name("FILE")
.takes_value(true)
.help("TLS certificate to use when establishing a connection to the etcd endpoint"),);
add_arg!(Arg::with_name("etcd_domain_name")
.long("etcd-domain-name")
.required_if("tower_storage", "etcd")
.value_name("DOMAIN")
.default_value("localhost")
.takes_value(true)
.help("domain name against which to verify the etcd server’s TLS certificate"),);
add_arg!(Arg::with_name("etcd_endpoint")
.long("etcd-endpoint")
.required_if("tower_storage", "etcd")
.value_name("HOST:PORT")
.takes_value(true)
.multiple(true)
.validator(solana_net_utils::is_host_port)
.help("etcd gRPC endpoint to connect with"),);
add_arg!(Arg::with_name("etcd_key_file")
.long("etcd-key-file")
.required_if("tower_storage", "etcd")
.value_name("FILE")
.takes_value(true)
.help("TLS key file to use when establishing a connection to the etcd endpoint"),);
add_arg!(Arg::with_name("minimal_rpc_api")
.long("minimal-rpc-api")
.takes_value(false)
.help("Only expose the RPC methods required to serve snapshots to other nodes"));
add_arg!(
Arg::with_name("no_check_vote_account")
.long("no-check-vote-account")
.takes_value(false)
.conflicts_with("no_voting")
.requires("entrypoint")
.help("Skip the RPC vote account sanity check"),
usage_warning: "Vote account sanity checks are no longer performed by default.",
);
add_arg!(Arg::with_name("no_rocksdb_compaction")
.long("no-rocksdb-compaction")
.takes_value(false)
.help("Disable manual compaction of the ledger database"));
add_arg!(
Arg::with_name("replay_slots_concurrently")
.long("replay-slots-concurrently")
.help("Allow concurrent replay of slots on different forks")
.conflicts_with("replay_forks_threads"),
replaced_by: "replay_forks_threads",
usage_warning: "Equivalent behavior to this flag would be --replay-forks-threads 4");
add_arg!(Arg::with_name("rocksdb_compaction_interval")
.long("rocksdb-compaction-interval-slots")
.value_name("ROCKSDB_COMPACTION_INTERVAL_SLOTS")
.takes_value(true)
.help("Number of slots between compacting ledger"));
add_arg!(Arg::with_name("rocksdb_fifo_shred_storage_size")
.long("rocksdb-fifo-shred-storage-size")
.value_name("SHRED_STORAGE_SIZE_BYTES")
.takes_value(true)
.validator(is_parsable::<u64>)
.help(
"The shred storage size in bytes. The suggested value is at least 50% of your ledger \
storage size. If this argument is unspecified, we will assign a proper value based \
on --limit-ledger-size. If --limit-ledger-size is not presented, it means there is \
no limitation on the ledger size and thus rocksdb_fifo_shred_storage_size will also \
be unbounded.",
));
add_arg!(Arg::with_name("rocksdb_max_compaction_jitter")
.long("rocksdb-max-compaction-jitter-slots")
.value_name("ROCKSDB_MAX_COMPACTION_JITTER_SLOTS")
.takes_value(true)
.help("Introduce jitter into the compaction to offset compaction operation"));
add_arg!(Arg::with_name("rpc_pubsub_max_connections")
.long("rpc-pubsub-max-connections")
.value_name("NUMBER")
.takes_value(true)
.validator(is_parsable::<usize>)
.help(
"The maximum number of connections that RPC PubSub will support. This is a \
hard limit and no new connections beyond this limit can be made until an old \
connection is dropped."
));
add_arg!(Arg::with_name("rpc_pubsub_max_fragment_size")
.long("rpc-pubsub-max-fragment-size")
.value_name("BYTES")
.takes_value(true)
.validator(is_parsable::<usize>)
.help(
"The maximum length in bytes of acceptable incoming frames. Messages longer \
than this will be rejected"
));
add_arg!(Arg::with_name("rpc_pubsub_max_in_buffer_capacity")
.long("rpc-pubsub-max-in-buffer-capacity")
.value_name("BYTES")
.takes_value(true)
.validator(is_parsable::<usize>)
.help("The maximum size in bytes to which the incoming websocket buffer can grow."));
add_arg!(Arg::with_name("rpc_pubsub_max_out_buffer_capacity")
.long("rpc-pubsub-max-out-buffer-capacity")
.value_name("BYTES")
.takes_value(true)
.validator(is_parsable::<usize>)
.help("The maximum size in bytes to which the outgoing websocket buffer can grow."));
add_arg!(
Arg::with_name("skip_poh_verify")
.long("skip-poh-verify")
.takes_value(false)
.help("Skip ledger verification at validator bootup."),
replaced_by: "skip-startup-ledger-verification",
);
add_arg!(
Arg::with_name("tower_storage")
.long("tower-storage")
.possible_values(&["file", "etcd"])
.default_value("file")
.takes_value(true)
.help("Where to store the tower"),
usage_warning: "\"etcd\" is no longer supported, and the functionality from setting \
\"file\" will be become the sole behavior",
);
res
}
fn get_deprecated_arguments() -> Vec<Arg<'static, 'static>> {
deprecated_arguments()
.into_iter()
.map(|info| {
let arg = info.arg;
arg.hidden(hidden_unless_forced())
})
.collect()
}
pub fn warn_for_deprecated_arguments(matches: &ArgMatches) {
for DeprecatedArg {
arg,
replaced_by,
usage_warning,
} in deprecated_arguments().into_iter()
{
if matches.is_present(arg.b.name) {
let mut msg = format!("--{} is deprecated", arg.b.name.replace('_', "-"));
if let Some(replaced_by) = replaced_by {
msg.push_str(&format!(", please use --{replaced_by}"));
}
msg.push('.');
if let Some(usage_warning) = usage_warning {
msg.push_str(&format!(" {usage_warning}"));
if !msg.ends_with('.') {
msg.push('.');
}
}
warn!("{}", msg);
}
}
}
pub struct DefaultArgs {
pub bind_address: String,
pub dynamic_port_range: String,
pub ledger_path: String,
pub genesis_archive_unpacked_size: String,
pub health_check_slot_distance: String,
pub tower_storage: String,
pub etcd_domain_name: String,
pub send_transaction_service_config: send_transaction_service::Config,
pub rpc_max_multiple_accounts: String,
pub rpc_pubsub_max_active_subscriptions: String,
pub rpc_pubsub_queue_capacity_items: String,
pub rpc_pubsub_queue_capacity_bytes: String,
pub rpc_send_transaction_retry_ms: String,
pub rpc_send_transaction_batch_ms: String,
pub rpc_send_transaction_leader_forward_count: String,
pub rpc_send_transaction_service_max_retries: String,
pub rpc_send_transaction_batch_size: String,
pub rpc_send_transaction_retry_pool_max_size: String,
pub rpc_threads: String,
pub rpc_blocking_threads: String,
pub rpc_niceness_adjustment: String,
pub rpc_bigtable_timeout: String,
pub rpc_bigtable_instance_name: String,
pub rpc_bigtable_app_profile_id: String,
pub rpc_bigtable_max_message_size: String,
pub rpc_max_request_body_size: String,
pub rpc_pubsub_worker_threads: String,
pub rpc_pubsub_notification_threads: String,
pub maximum_local_snapshot_age: String,
pub maximum_full_snapshot_archives_to_retain: String,
pub maximum_incremental_snapshot_archives_to_retain: String,
pub snapshot_packager_niceness_adjustment: String,
pub full_snapshot_archive_interval_slots: String,
pub incremental_snapshot_archive_interval_slots: String,
pub min_snapshot_download_speed: String,
pub max_snapshot_download_abort: String,
pub contact_debug_interval: String,
pub snapshot_version: SnapshotVersion,
pub snapshot_archive_format: String,
pub snapshot_zstd_compression_level: String,
pub rocksdb_shred_compaction: String,
pub rocksdb_ledger_compression: String,
pub rocksdb_perf_sample_interval: String,
pub accounts_shrink_optimize_total_space: String,
pub accounts_shrink_ratio: String,
pub tpu_connection_pool_size: String,
pub tpu_max_connections_per_peer: String,
pub tpu_max_connections_per_ipaddr_per_minute: String,
pub tpu_max_staked_connections: String,
pub tpu_max_unstaked_connections: String,
pub tpu_max_fwd_staked_connections: String,
pub tpu_max_fwd_unstaked_connections: String,
pub tpu_max_streams_per_ms: String,
pub num_quic_endpoints: String,
pub vote_use_quic: String,
pub exit_min_idle_time: String,
pub exit_max_delinquent_stake: String,
pub wait_for_restart_window_min_idle_time: String,
pub wait_for_restart_window_max_delinquent_stake: String,
pub banking_trace_dir_byte_limit: String,
pub wen_restart_path: String,
pub thread_args: DefaultThreadArgs,
}
impl DefaultArgs {
pub fn new() -> Self {
let default_send_transaction_service_config = send_transaction_service::Config::default();
DefaultArgs {
bind_address: "0.0.0.0".to_string(),
ledger_path: "ledger".to_string(),
dynamic_port_range: format!("{}-{}", VALIDATOR_PORT_RANGE.0, VALIDATOR_PORT_RANGE.1),
maximum_local_snapshot_age: "2500".to_string(),
genesis_archive_unpacked_size: MAX_GENESIS_ARCHIVE_UNPACKED_SIZE.to_string(),
rpc_max_multiple_accounts: MAX_MULTIPLE_ACCOUNTS.to_string(),
health_check_slot_distance: DELINQUENT_VALIDATOR_SLOT_DISTANCE.to_string(),
tower_storage: "file".to_string(),
etcd_domain_name: "localhost".to_string(),
rpc_pubsub_max_active_subscriptions: PubSubConfig::default()
.max_active_subscriptions
.to_string(),
rpc_pubsub_queue_capacity_items: PubSubConfig::default()
.queue_capacity_items
.to_string(),
rpc_pubsub_queue_capacity_bytes: PubSubConfig::default()
.queue_capacity_bytes
.to_string(),
send_transaction_service_config: send_transaction_service::Config::default(),
rpc_send_transaction_retry_ms: default_send_transaction_service_config
.retry_rate_ms
.to_string(),
rpc_send_transaction_batch_ms: default_send_transaction_service_config
.batch_send_rate_ms
.to_string(),
rpc_send_transaction_leader_forward_count: default_send_transaction_service_config
.leader_forward_count
.to_string(),
rpc_send_transaction_service_max_retries: default_send_transaction_service_config
.service_max_retries
.to_string(),
rpc_send_transaction_batch_size: default_send_transaction_service_config
.batch_size
.to_string(),
rpc_send_transaction_retry_pool_max_size: default_send_transaction_service_config
.retry_pool_max_size
.to_string(),
rpc_threads: num_cpus::get().to_string(),
rpc_blocking_threads: 1.max(num_cpus::get() / 4).to_string(),
rpc_niceness_adjustment: "0".to_string(),
rpc_bigtable_timeout: "30".to_string(),
rpc_bigtable_instance_name: solana_storage_bigtable::DEFAULT_INSTANCE_NAME.to_string(),
rpc_bigtable_app_profile_id: solana_storage_bigtable::DEFAULT_APP_PROFILE_ID
.to_string(),
rpc_bigtable_max_message_size: solana_storage_bigtable::DEFAULT_MAX_MESSAGE_SIZE
.to_string(),
rpc_pubsub_worker_threads: "4".to_string(),
rpc_pubsub_notification_threads: get_thread_count().to_string(),
maximum_full_snapshot_archives_to_retain: DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN
.to_string(),
maximum_incremental_snapshot_archives_to_retain:
DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN.to_string(),
snapshot_packager_niceness_adjustment: "0".to_string(),
full_snapshot_archive_interval_slots: DEFAULT_FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS
.to_string(),
incremental_snapshot_archive_interval_slots:
DEFAULT_INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS.to_string(),
min_snapshot_download_speed: DEFAULT_MIN_SNAPSHOT_DOWNLOAD_SPEED.to_string(),
max_snapshot_download_abort: MAX_SNAPSHOT_DOWNLOAD_ABORT.to_string(),
snapshot_archive_format: DEFAULT_ARCHIVE_COMPRESSION.to_string(),
snapshot_zstd_compression_level: "1".to_string(), contact_debug_interval: "120000".to_string(),
snapshot_version: SnapshotVersion::default(),
rocksdb_shred_compaction: "level".to_string(),
rocksdb_ledger_compression: "none".to_string(),
rocksdb_perf_sample_interval: "0".to_string(),
accounts_shrink_optimize_total_space: DEFAULT_ACCOUNTS_SHRINK_OPTIMIZE_TOTAL_SPACE
.to_string(),
accounts_shrink_ratio: DEFAULT_ACCOUNTS_SHRINK_RATIO.to_string(),
tpu_connection_pool_size: DEFAULT_TPU_CONNECTION_POOL_SIZE.to_string(),
tpu_max_connections_per_ipaddr_per_minute:
DEFAULT_MAX_CONNECTIONS_PER_IPADDR_PER_MINUTE.to_string(),
vote_use_quic: DEFAULT_VOTE_USE_QUIC.to_string(),
tpu_max_connections_per_peer: DEFAULT_MAX_QUIC_CONNECTIONS_PER_PEER.to_string(),
tpu_max_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS.to_string(),
tpu_max_unstaked_connections: DEFAULT_MAX_UNSTAKED_CONNECTIONS.to_string(),
tpu_max_fwd_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS
.saturating_add(DEFAULT_MAX_UNSTAKED_CONNECTIONS)
.to_string(),
tpu_max_fwd_unstaked_connections: 0.to_string(),
tpu_max_streams_per_ms: DEFAULT_MAX_STREAMS_PER_MS.to_string(),
num_quic_endpoints: DEFAULT_QUIC_ENDPOINTS.to_string(),
rpc_max_request_body_size: MAX_REQUEST_BODY_SIZE.to_string(),
exit_min_idle_time: "10".to_string(),
exit_max_delinquent_stake: "5".to_string(),
wait_for_restart_window_min_idle_time: "10".to_string(),
wait_for_restart_window_max_delinquent_stake: "5".to_string(),
banking_trace_dir_byte_limit: BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT.to_string(),
wen_restart_path: "wen_restart_progress.proto".to_string(),
thread_args: DefaultThreadArgs::default(),
}
}
}
impl Default for DefaultArgs {
fn default() -> Self {
Self::new()
}
}
pub fn port_validator(port: String) -> Result<(), String> {
port.parse::<u16>()
.map(|_| ())
.map_err(|e| format!("{e:?}"))
}
pub fn port_range_validator(port_range: String) -> Result<(), String> {
if let Some((start, end)) = solana_net_utils::parse_port_range(&port_range) {
if end - start < MINIMUM_VALIDATOR_PORT_RANGE_WIDTH {
Err(format!(
"Port range is too small. Try --dynamic-port-range {}-{}",
start,
start + MINIMUM_VALIDATOR_PORT_RANGE_WIDTH
))
} else if end.checked_add(QUIC_PORT_OFFSET).is_none() {
Err("Invalid dynamic_port_range.".to_string())
} else {
Ok(())
}
} else {
Err("Invalid port range".to_string())
}
}
fn hash_validator(hash: String) -> Result<(), String> {
Hash::from_str(&hash)
.map(|_| ())
.map_err(|e| format!("{e:?}"))
}
pub fn test_app<'a>(version: &'a str, default_args: &'a DefaultTestArgs) -> App<'a, 'a> {
App::new("solana-test-validator")
.about("Test Validator")
.version(version)
.arg({
let arg = Arg::with_name("config_file")
.short("C")
.long("config")
.value_name("PATH")
.takes_value(true)
.help("Configuration file to use");
if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE {
arg.default_value(config_file)
} else {
arg
}
})
.arg(
Arg::with_name("json_rpc_url")
.short("u")
.long("url")
.value_name("URL_OR_MONIKER")
.takes_value(true)
.validator(is_url_or_moniker)
.help(
"URL for Solana's JSON RPC or moniker (or their first letter): \
[mainnet-beta, testnet, devnet, localhost]",
),
)
.arg(
Arg::with_name("mint_address")
.long("mint")
.value_name("PUBKEY")
.validator(is_pubkey)
.takes_value(true)
.help(
"Address of the mint account that will receive tokens created at genesis. If \
the ledger already exists then this parameter is silently ignored \
[default: client keypair]",
),
)
.arg(
Arg::with_name("ledger_path")
.short("l")
.long("ledger")
.value_name("DIR")
.takes_value(true)
.required(true)
.default_value("test-ledger")
.help("Use DIR as ledger location"),
)
.arg(
Arg::with_name("reset")
.short("r")
.long("reset")
.takes_value(false)
.help(
"Reset the ledger to genesis if it exists. By default the validator will \
resume an existing ledger (if present)",
),
)
.arg(
Arg::with_name("quiet")
.short("q")
.long("quiet")
.takes_value(false)
.conflicts_with("log")
.help("Quiet mode: suppress normal output"),
)
.arg(
Arg::with_name("log")
.long("log")
.takes_value(false)
.conflicts_with("quiet")
.help("Log mode: stream the validator log"),
)
.arg(
Arg::with_name("account_indexes")
.long("account-index")
.takes_value(true)
.multiple(true)
.possible_values(&["program-id", "spl-token-owner", "spl-token-mint"])
.value_name("INDEX")
.help("Enable an accounts index, indexed by the selected account field"),
)
.arg(
Arg::with_name("faucet_port")
.long("faucet-port")
.value_name("PORT")
.takes_value(true)
.default_value(&default_args.faucet_port)
.validator(port_validator)
.help("Enable the faucet on this port"),
)
.arg(
Arg::with_name("rpc_port")
.long("rpc-port")
.value_name("PORT")
.takes_value(true)
.default_value(&default_args.rpc_port)
.validator(port_validator)
.help("Enable JSON RPC on this port, and the next port for the RPC websocket"),
)
.arg(
Arg::with_name("enable_rpc_bigtable_ledger_storage")
.long("enable-rpc-bigtable-ledger-storage")
.takes_value(false)
.hidden(hidden_unless_forced())
.help(
"Fetch historical transaction info from a BigTable instance as a fallback to \
local ledger data",
),
)
.arg(
Arg::with_name("enable_bigtable_ledger_upload")
.long("enable-bigtable-ledger-upload")
.takes_value(false)
.hidden(hidden_unless_forced())
.help("Upload new confirmed blocks into a BigTable instance"),
)
.arg(
Arg::with_name("rpc_bigtable_instance")
.long("rpc-bigtable-instance")
.value_name("INSTANCE_NAME")
.takes_value(true)
.hidden(hidden_unless_forced())
.default_value("solana-ledger")
.help("Name of BigTable instance to target"),
)
.arg(
Arg::with_name("rpc_bigtable_app_profile_id")
.long("rpc-bigtable-app-profile-id")
.value_name("APP_PROFILE_ID")
.takes_value(true)
.hidden(hidden_unless_forced())
.default_value(solana_storage_bigtable::DEFAULT_APP_PROFILE_ID)
.help("Application profile id to use in Bigtable requests"),
)
.arg(
Arg::with_name("rpc_pubsub_enable_vote_subscription")
.long("rpc-pubsub-enable-vote-subscription")
.takes_value(false)
.help("Enable the unstable RPC PubSub `voteSubscribe` subscription"),
)
.arg(
Arg::with_name("rpc_pubsub_enable_block_subscription")
.long("rpc-pubsub-enable-block-subscription")
.takes_value(false)
.help("Enable the unstable RPC PubSub `blockSubscribe` subscription"),
)
.arg(
Arg::with_name("bpf_program")
.long("bpf-program")
.value_names(&["ADDRESS_OR_KEYPAIR", "SBF_PROGRAM.SO"])
.takes_value(true)
.number_of_values(2)
.multiple(true)
.help(
"Add a SBF program to the genesis configuration with upgrades disabled. If \
the ledger already exists then this parameter is silently ignored. The first \
argument can be a pubkey string or path to a keypair",
),
)
.arg(
Arg::with_name("upgradeable_program")
.long("upgradeable-program")
.value_names(&["ADDRESS_OR_KEYPAIR", "SBF_PROGRAM.SO", "UPGRADE_AUTHORITY"])
.takes_value(true)
.number_of_values(3)
.multiple(true)
.help(
"Add an upgradeable SBF program to the genesis configuration. If the ledger \
already exists then this parameter is silently ignored. First and third \
arguments can be a pubkey string or path to a keypair. Upgrade authority set \
to \"none\" disables upgrades",
),
)
.arg(
Arg::with_name("account")
.long("account")
.value_names(&["ADDRESS", "DUMP.JSON"])
.takes_value(true)
.number_of_values(2)
.allow_hyphen_values(true)
.multiple(true)
.help(
"Load an account from the provided JSON file (see `solana account --help` on \
how to dump an account to file). Files are searched for relatively to CWD \
and tests/fixtures. If ADDRESS is omitted via the `-` placeholder, the one \
in the file will be used. If the ledger already exists then this parameter \
is silently ignored",
),
)
.arg(
Arg::with_name("account_dir")
.long("account-dir")
.value_name("DIRECTORY")
.validator(|value| {
value
.parse::<PathBuf>()
.map_err(|err| format!("error parsing '{value}': {err}"))
.and_then(|path| {
if path.exists() && path.is_dir() {
Ok(())
} else {
Err(format!(
"path does not exist or is not a directory: {value}"
))
}
})
})
.takes_value(true)
.multiple(true)
.help(
"Load all the accounts from the JSON files found in the specified DIRECTORY \
(see also the `--account` flag). If the ledger already exists then this \
parameter is silently ignored",
),
)
.arg(
Arg::with_name("ticks_per_slot")
.long("ticks-per-slot")
.value_name("TICKS")
.validator(|value| {
value
.parse::<u64>()
.map_err(|err| format!("error parsing '{value}': {err}"))
.and_then(|ticks| {
if ticks < MINIMUM_TICKS_PER_SLOT {
Err(format!("value must be >= {MINIMUM_TICKS_PER_SLOT}"))
} else {
Ok(())
}
})
})
.takes_value(true)
.help("The number of ticks in a slot"),
)
.arg(
Arg::with_name("slots_per_epoch")
.long("slots-per-epoch")
.value_name("SLOTS")
.validator(|value| {
value
.parse::<Slot>()
.map_err(|err| format!("error parsing '{value}': {err}"))
.and_then(|slot| {
if slot < MINIMUM_SLOTS_PER_EPOCH {
Err(format!("value must be >= {MINIMUM_SLOTS_PER_EPOCH}"))
} else {
Ok(())
}
})
})
.takes_value(true)
.help(
"Override the number of slots in an epoch. If the ledger already exists then \
this parameter is silently ignored",
),
)
.arg(
Arg::with_name("gossip_port")
.long("gossip-port")
.value_name("PORT")
.takes_value(true)
.help("Gossip port number for the validator"),
)
.arg(
Arg::with_name("gossip_host")
.long("gossip-host")
.value_name("HOST")
.takes_value(true)
.validator(solana_net_utils::is_host)
.help(
"Gossip DNS name or IP address for the validator to advertise in gossip \
[default: 127.0.0.1]",
),
)
.arg(
Arg::with_name("dynamic_port_range")
.long("dynamic-port-range")
.value_name("MIN_PORT-MAX_PORT")
.takes_value(true)
.validator(port_range_validator)
.help("Range to use for dynamically assigned ports [default: 1024-65535]"),
)
.arg(
Arg::with_name("bind_address")
.long("bind-address")
.value_name("HOST")
.takes_value(true)
.validator(solana_net_utils::is_host)
.default_value("0.0.0.0")
.help("IP address to bind the validator ports [default: 0.0.0.0]"),
)
.arg(
Arg::with_name("clone_account")
.long("clone")
.short("c")
.value_name("ADDRESS")
.takes_value(true)
.validator(is_pubkey_or_keypair)
.multiple(true)
.requires("json_rpc_url")
.help(
"Copy an account from the cluster referenced by the --url argument the \
genesis configuration. If the ledger already exists then this parameter is \
silently ignored",
),
)
.arg(
Arg::with_name("maybe_clone_account")
.long("maybe-clone")
.value_name("ADDRESS")
.takes_value(true)
.validator(is_pubkey_or_keypair)
.multiple(true)
.requires("json_rpc_url")
.help(
"Copy an account from the cluster referenced by the --url argument, skipping \
it if it doesn't exist. If the ledger already exists then this parameter is \
silently ignored",
),
)
.arg(
Arg::with_name("clone_upgradeable_program")
.long("clone-upgradeable-program")
.value_name("ADDRESS")
.takes_value(true)
.validator(is_pubkey_or_keypair)
.multiple(true)
.requires("json_rpc_url")
.help(
"Copy an upgradeable program and its executable data from the cluster \
referenced by the --url argument the genesis configuration. If the ledger \
already exists then this parameter is silently ignored",
),
)
.arg(
Arg::with_name("warp_slot")
.required(false)
.long("warp-slot")
.short("w")
.takes_value(true)
.value_name("WARP_SLOT")
.validator(is_slot)
.min_values(0)
.max_values(1)
.help(
"Warp the ledger to WARP_SLOT after starting the validator. If no slot is \
provided then the current slot of the cluster referenced by the --url \
argument will be used",
),
)
.arg(
Arg::with_name("limit_ledger_size")
.long("limit-ledger-size")
.value_name("SHRED_COUNT")
.takes_value(true)
.default_value(default_args.limit_ledger_size.as_str())
.help("Keep this amount of shreds in root slots."),
)
.arg(
Arg::with_name("faucet_sol")
.long("faucet-sol")
.takes_value(true)
.value_name("SOL")
.default_value(default_args.faucet_sol.as_str())
.help(
"Give the faucet address this much SOL in genesis. If the ledger already \
exists then this parameter is silently ignored",
),
)
.arg(
Arg::with_name("faucet_time_slice_secs")
.long("faucet-time-slice-secs")
.takes_value(true)
.value_name("SECS")
.default_value(default_args.faucet_time_slice_secs.as_str())
.help("Time slice (in secs) over which to limit faucet requests"),
)
.arg(
Arg::with_name("faucet_per_time_sol_cap")
.long("faucet-per-time-sol-cap")
.takes_value(true)
.value_name("SOL")
.min_values(0)
.max_values(1)
.help("Per-time slice limit for faucet requests, in SOL"),
)
.arg(
Arg::with_name("faucet_per_request_sol_cap")
.long("faucet-per-request-sol-cap")
.takes_value(true)
.value_name("SOL")
.min_values(0)
.max_values(1)
.help("Per-request limit for faucet requests, in SOL"),
)
.arg(
Arg::with_name("geyser_plugin_config")
.long("geyser-plugin-config")
.alias("accountsdb-plugin-config")
.value_name("FILE")
.takes_value(true)
.multiple(true)
.help("Specify the configuration file for the Geyser plugin."),
)
.arg(
Arg::with_name("deactivate_feature")
.long("deactivate-feature")
.takes_value(true)
.value_name("FEATURE_PUBKEY")
.validator(is_pubkey)
.multiple(true)
.help("deactivate this feature in genesis."),
)
.arg(
Arg::with_name("compute_unit_limit")
.long("compute-unit-limit")
.alias("max-compute-units")
.value_name("COMPUTE_UNITS")
.validator(is_parsable::<u64>)
.takes_value(true)
.help("Override the runtime's compute unit limit per transaction"),
)
.arg(
Arg::with_name("log_messages_bytes_limit")
.long("log-messages-bytes-limit")
.value_name("BYTES")
.validator(is_parsable::<usize>)
.takes_value(true)
.help("Maximum number of bytes written to the program log before truncation"),
)
.arg(
Arg::with_name("transaction_account_lock_limit")
.long("transaction-account-lock-limit")
.value_name("NUM_ACCOUNTS")
.validator(is_parsable::<u64>)
.takes_value(true)
.help("Override the runtime's account lock limit per transaction"),
)
.arg(
Arg::with_name("clone_feature_set")
.long("clone-feature-set")
.takes_value(false)
.requires("json_rpc_url")
.help(
"Copy a feature set from the cluster referenced by the --url \
argument in the genesis configuration. If the ledger \
already exists then this parameter is silently ignored",
),
)
}
pub struct DefaultTestArgs {
pub rpc_port: String,
pub faucet_port: String,
pub limit_ledger_size: String,
pub faucet_sol: String,
pub faucet_time_slice_secs: String,
}
impl DefaultTestArgs {
pub fn new() -> Self {
DefaultTestArgs {
rpc_port: rpc_port::DEFAULT_RPC_PORT.to_string(),
faucet_port: FAUCET_PORT.to_string(),
limit_ledger_size: 10_000.to_string(),
faucet_sol: (1_000_000.).to_string(),
faucet_time_slice_secs: (faucet::TIME_SLICE).to_string(),
}
}
}
impl Default for DefaultTestArgs {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn make_sure_deprecated_arguments_are_sorted_alphabetically() {
let deprecated = deprecated_arguments();
for i in 0..deprecated.len().saturating_sub(1) {
let curr_name = deprecated[i].arg.b.name;
let next_name = deprecated[i + 1].arg.b.name;
assert!(
curr_name != next_name,
"Arguments in `deprecated_arguments()` should be distinct.\nArguments {} and {} \
use the same name: {}",
i,
i + 1,
curr_name,
);
assert!(
curr_name < next_name,
"To generate better diffs and for readability purposes, `deprecated_arguments()` \
should list arguments in alphabetical order.\nArguments {} and {} are \
not.\nArgument {} name: {}\nArgument {} name: {}",
i,
i + 1,
i,
curr_name,
i + 1,
next_name,
);
}
}
}