Skip to main content

agave_validator/commands/run/
execute.rs

1use {
2    crate::{
3        admin_rpc_service::{self, StakedNodesOverrides, load_staked_nodes_overrides},
4        bootstrap,
5        cli::{self},
6        commands::{FromClapArgMatches, run::args::RunArgs},
7        ledger_lockfile, lock_ledger,
8    },
9    agave_snapshots::{
10        ArchiveFormat, SnapshotInterval, SnapshotVersion,
11        paths::BANK_SNAPSHOTS_DIR,
12        snapshot_config::{SnapshotConfig, SnapshotUsage},
13    },
14    agave_votor::vote_history_storage,
15    agave_xdp::{set_cpu_affinity, transmitter::XdpConfig},
16    clap::{ArgMatches, crate_name, value_t, value_t_or_exit, values_t, values_t_or_exit},
17    crossbeam_channel::unbounded,
18    log::*,
19    rand::{rng, seq::SliceRandom},
20    solana_accounts_db::{
21        accounts_db::{AccountShrinkThreshold, AccountsDbConfig},
22        accounts_file::StorageAccess,
23        accounts_index::{
24            AccountSecondaryIndexes, AccountsIndexConfig, DEFAULT_NUM_ENTRIES_OVERHEAD,
25            DEFAULT_NUM_ENTRIES_TO_EVICT, IndexLimit, IndexLimitThreshold, ScanFilter,
26        },
27        partitioned_rewards::PartitionedEpochRewardsConfig,
28        utils::{
29            create_all_accounts_run_and_snapshot_dirs, create_and_canonicalize_directories,
30            create_and_canonicalize_directory,
31        },
32    },
33    solana_clap_utils::input_parsers::{
34        keypair_of, keypairs_of, parse_cpu_ranges, pubkey_of, value_of, values_of,
35    },
36    solana_clock::{DEFAULT_SLOTS_PER_EPOCH, Slot},
37    solana_core::{
38        banking_stage::transaction_scheduler::scheduler_controller::SchedulerConfig,
39        banking_trace::DISABLED_BAKING_TRACE_DIR,
40        consensus::tower_storage,
41        repair::repair_handler::RepairHandlerType,
42        resource_limits,
43        snapshot_packager_service::SnapshotPackagerService,
44        system_monitor_service::SystemMonitorService,
45        tpu::MAX_VOTES_PER_SECOND,
46        validator::{
47            BlockProductionMethod, BlockVerificationMethod, SchedulerPacing, Validator,
48            ValidatorConfig, ValidatorLogConfig, ValidatorStartProgress, ValidatorTpuConfig,
49            is_snapshot_config_valid,
50        },
51    },
52    solana_genesis_utils::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE,
53    solana_gossip::{
54        cluster_info::{DEFAULT_CONTACT_SAVE_INTERVAL_MILLIS, NodeConfig},
55        contact_info::ContactInfo,
56        node::Node,
57    },
58    solana_hash::Hash,
59    solana_keypair::Keypair,
60    solana_ledger::{
61        blockstore_cleanup_service::{DEFAULT_MAX_LEDGER_SHREDS, DEFAULT_MIN_MAX_LEDGER_SHREDS},
62        shred::filter::TurbineMode,
63        use_snapshot_archives_at_startup::{self, UseSnapshotArchivesAtStartup},
64    },
65    solana_net_utils::multihomed_sockets::BindIpAddrs,
66    solana_poh::poh_service,
67    solana_pubkey::Pubkey,
68    solana_runtime::{runtime_config::RuntimeConfig, snapshot_utils},
69    solana_signer::Signer,
70    solana_streamer::{
71        nonblocking::{simple_qos::SimpleQosConfig, swqos::SwQosConfig},
72        quic::{QuicStreamerConfig, SimpleQosQuicStreamerConfig, SwQosQuicStreamerConfig},
73    },
74    solana_tpu_client::tpu_client::DEFAULT_TPU_CONNECTION_POOL_SIZE,
75    solana_turbine::broadcast_stage::BroadcastStageType,
76    solana_validator_exit::Exit,
77    std::{
78        collections::HashSet,
79        env,
80        fs::{self, File},
81        net::{IpAddr, Ipv4Addr, SocketAddr},
82        num::{NonZeroU64, NonZeroUsize},
83        path::{Path, PathBuf},
84        str::{self, FromStr},
85        sync::{Arc, RwLock, atomic::AtomicBool},
86    },
87};
88
89#[derive(Debug, PartialEq, Eq)]
90pub enum Operation {
91    Initialize,
92    Run,
93}
94
95pub fn execute(
96    matches: &ArgMatches,
97    solana_version: &str,
98    operation: Operation,
99    config: super::Config,
100) -> Result<(), Box<dyn std::error::Error>> {
101    // Debugging panics is easier with a backtrace
102    if env::var_os("RUST_BACKTRACE").is_none() {
103        // Safety: env update is made before any spawned threads might access the environment
104        unsafe { env::set_var("RUST_BACKTRACE", "1") }
105    }
106
107    let run_args = RunArgs::from_clap_arg_match(matches)?;
108
109    let cli::thread_args::NumThreadConfig {
110        accounts_db_background_threads,
111        accounts_db_foreground_threads,
112        accounts_index_flush_threads,
113        block_production_num_workers,
114        ip_echo_server_threads,
115        rayon_global_threads,
116        replay_forks_threads,
117        replay_transactions_threads,
118        tpu_sigverify_threads,
119        tpu_transaction_forward_receive_threads,
120        tpu_transaction_receive_threads,
121        tpu_vote_transaction_receive_threads,
122        tvu_receive_threads,
123        tvu_retransmit_threads,
124        tvu_sigverify_threads,
125        tvu_bls_sigverify_threads,
126    } = cli::thread_args::parse_num_threads_args(matches);
127
128    let identity_keypair = Arc::new(run_args.identity_keypair);
129
130    let logfile = run_args.logfile;
131    let log_config = if let Some(ref logfile) = logfile {
132        println!("log file: {}", logfile.display());
133        let logrotate_flag = Validator::register_logrotate_signal_handler()?;
134
135        Some(ValidatorLogConfig {
136            logfile: logfile.clone(),
137            logrotate_flag,
138        })
139    } else {
140        None
141    };
142    let use_progress_bar = log_config.is_none();
143    agave_logger::initialize_logging(logfile);
144
145    cli::warn_for_deprecated_arguments(matches);
146
147    info!("{} {}", crate_name!(), solana_version);
148    info!("Starting validator with: {:#?}", std::env::args_os());
149
150    solana_metrics::set_host_id(identity_keypair.pubkey().to_string());
151    solana_metrics::set_panic_hook("validator", Some(String::from(solana_version)));
152    solana_entry::entry::init_poh();
153
154    let bind_addresses = {
155        let parsed = matches
156            .values_of("bind_address")
157            .expect("bind_address should always be present due to default")
158            .map(solana_net_utils::parse_host)
159            .collect::<Result<Vec<_>, _>>()?;
160        BindIpAddrs::new(parsed).map_err(|err| format!("invalid bind_addresses: {err}"))?
161    };
162
163    let entrypoint_addrs = run_args.entrypoints;
164    for addr in &entrypoint_addrs {
165        if !run_args.socket_addr_space.check(addr) {
166            Err(format!("invalid entrypoint address: {addr}"))?;
167        }
168    }
169
170    let xdp_interface = matches
171        .value_of("xdp_interface")
172        .or_else(|| matches.value_of("experimental_retransmit_xdp_interface"));
173    let xdp_zero_copy = matches.is_present("xdp_zero_copy")
174        || matches.is_present("experimental_retransmit_xdp_zero_copy");
175    let retransmit_xdp = matches
176        .value_of("xdp_cpu_cores")
177        .or_else(|| matches.value_of("experimental_retransmit_xdp_cpu_cores"))
178        .map(|cpus| {
179            XdpConfig::new(
180                xdp_interface,
181                parse_cpu_ranges(cpus).unwrap(),
182                xdp_zero_copy,
183            )
184        });
185
186    let dynamic_port_range =
187        solana_net_utils::parse_port_range(matches.value_of("dynamic_port_range").unwrap())
188            .expect("invalid dynamic_port_range");
189
190    let advertised_ip = matches
191        .value_of("advertised_ip")
192        .map(|advertised_ip| {
193            solana_net_utils::parse_host(advertised_ip)
194                .map_err(|err| format!("failed to parse --advertised-ip: {err}"))
195        })
196        .transpose()?;
197
198    let advertised_ip = if let Some(cli_ip) = advertised_ip {
199        cli_ip
200    } else if !bind_addresses.active().is_unspecified() && !bind_addresses.active().is_loopback() {
201        bind_addresses.active()
202    } else if !entrypoint_addrs.is_empty() {
203        let mut order: Vec<_> = (0..entrypoint_addrs.len()).collect();
204        order.shuffle(&mut rng());
205
206        order
207            .into_iter()
208            .find_map(|i| {
209                let entrypoint_addr = &entrypoint_addrs[i];
210                info!(
211                    "Contacting {entrypoint_addr} to determine the validator's public IP address"
212                );
213                solana_net_utils::get_public_ip_addr_with_binding(
214                    entrypoint_addr,
215                    bind_addresses.active(),
216                )
217                .map_or_else(
218                    |err| {
219                        warn!("Failed to contact cluster entrypoint {entrypoint_addr}: {err}");
220                        None
221                    },
222                    Some,
223                )
224            })
225            .ok_or_else(|| "unable to determine the validator's public IP address".to_string())?
226    } else {
227        IpAddr::V4(Ipv4Addr::LOCALHOST)
228    };
229    let gossip_port = value_t!(matches, "gossip_port", u16).or_else(|_| {
230        solana_net_utils::find_available_port_in_range(bind_addresses.active(), (0, 1))
231            .map_err(|err| format!("unable to find an available gossip port: {err}"))
232    })?;
233
234    let public_tpu_addr = matches
235        .value_of("public_tpu_addr")
236        .map(|public_tpu_addr| {
237            solana_net_utils::parse_host_port(public_tpu_addr)
238                .map_err(|err| format!("failed to parse --public-tpu-address: {err}"))
239        })
240        .transpose()?;
241
242    let public_tpu_forwards_addr = matches
243        .value_of("public_tpu_forwards_addr")
244        .map(|public_tpu_forwards_addr| {
245            solana_net_utils::parse_host_port(public_tpu_forwards_addr)
246                .map_err(|err| format!("failed to parse --public-tpu-forwards-address: {err}"))
247        })
248        .transpose()?;
249
250    let public_tvu_addr = matches
251        .value_of("public_tvu_addr")
252        .map(|public_tvu_addr| {
253            solana_net_utils::parse_host_port(public_tvu_addr)
254                .map_err(|err| format!("failed to parse --public-tvu-address: {err}"))
255        })
256        .transpose()?;
257
258    if bind_addresses.len() > 1 && public_tvu_addr.is_some() {
259        Err(String::from(
260            "--public-tvu-address can not be used in a multihoming context",
261        ))?;
262    }
263
264    let num_quic_endpoints = value_t_or_exit!(matches, "num_quic_endpoints", NonZeroUsize);
265
266    let node_config = NodeConfig {
267        advertised_ip,
268        gossip_port,
269        port_range: dynamic_port_range,
270        bind_ip_addrs: bind_addresses.clone(),
271        public_tpu_addr,
272        public_tpu_forwards_addr,
273        public_tvu_addr,
274        num_tvu_receive_sockets: tvu_receive_threads,
275        num_tvu_retransmit_sockets: tvu_retransmit_threads,
276        num_quic_endpoints,
277    };
278
279    let mut node = Node::new_with_external_ip(&identity_keypair.pubkey(), node_config);
280
281    let exit = Arc::new(AtomicBool::new(false));
282
283    #[cfg(not(target_os = "linux"))]
284    let _ = config;
285
286    #[cfg(target_os = "linux")]
287    let xdp_builder_with_src_addr = {
288        use {
289            agave_xdp::transmitter::TransmitterBuilder,
290            caps::{
291                CapSet,
292                Capability::{CAP_BPF, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON, CAP_SYS_NICE},
293            },
294        };
295
296        let super::Config { primordial_caps } = config;
297
298        let mut required_caps = HashSet::new();
299        let mut retained_caps = HashSet::new();
300        let mut supported_caps = HashSet::from_iter([
301            CAP_BPF,
302            CAP_NET_ADMIN,
303            CAP_NET_RAW,
304            CAP_PERFMON,
305            CAP_SYS_NICE,
306        ]);
307
308        // make sure we keep any primordial caps
309        supported_caps.extend(primordial_caps.clone());
310        required_caps.extend(primordial_caps.clone());
311        retained_caps.extend(primordial_caps.clone());
312
313        if let Some(xdp_config) = retransmit_xdp.as_ref() {
314            required_caps.insert(CAP_NET_ADMIN);
315            required_caps.insert(CAP_NET_RAW);
316            if xdp_config.zero_copy {
317                required_caps.insert(CAP_BPF);
318                required_caps.insert(CAP_PERFMON);
319            }
320        }
321
322        let snapshot_packager_niceness_adj =
323            value_t_or_exit!(matches, "snapshot_packager_niceness_adj", i8);
324
325        if snapshot_packager_niceness_adj != 0 || run_args.json_rpc_config.rpc_niceness_adj != 0 {
326            required_caps.insert(CAP_SYS_NICE);
327            retained_caps.insert(CAP_SYS_NICE);
328        }
329
330        // lazy dev check
331        assert!(
332            required_caps.is_subset(&supported_caps),
333            "required_caps contains a cap not in supported_caps",
334        );
335
336        // validate and minimize the permitted set
337        let current_permitted =
338            caps::read(None, CapSet::Permitted).expect("permitted capset to be readable");
339        let missing_caps = required_caps
340            .difference(&current_permitted)
341            .collect::<Vec<_>>();
342        if !missing_caps.is_empty() {
343            error!(
344                "the current configuration requires the following capabilities, which have not \
345                 been permitted to the current process: {missing_caps:?}",
346            );
347            std::process::exit(1);
348        }
349        // warn about extraneous caps that no configuration requires
350        let extra_caps = current_permitted
351            .difference(&supported_caps)
352            .collect::<Vec<_>>();
353        if !extra_caps.is_empty() {
354            warn!(
355                "dropping extraneous capabilities ({extra_caps:?}) from the current process. \
356                 consider removing them from your operational configuration.",
357            );
358        }
359
360        // drop all caps that the current configuration does not require
361        caps::set(None, CapSet::Effective, &required_caps)
362            .expect("linux allows effective capset to be set");
363        caps::set(None, CapSet::Permitted, &required_caps)
364            .expect("linux allows permitted capset to be set");
365
366        // XDP _MUST_ be setup _BEFORE_ the app spawns any threads to ensure linux
367        // capabilities do not leak, leaving the process in a state where it could
368        // potentially be used as a privilege escalation gadget
369        let xdp_builder_with_src_addr = retransmit_xdp.clone().map(|xdp_config| {
370            use {
371                agave_xdp::{default_device_ipv4, interface_ipv4},
372                std::net::SocketAddrV4,
373            };
374
375            let src_port = node.sockets.retransmit_sockets[0]
376                .local_addr()
377                .expect("failed to get local address")
378                .port();
379            let src_ip = match node.bind_ip_addrs.active() {
380                IpAddr::V4(ip) if !ip.is_unspecified() => ip,
381                IpAddr::V4(_unspecified) => {
382                    if let Some(interface) = xdp_config.interface.as_ref() {
383                        interface_ipv4(interface).expect(
384                            "configured interface should exist and have an IPv4 address assigned",
385                        )
386                    } else {
387                        default_device_ipv4().expect(
388                            "default route device should exist and have an IPv4 address assigned",
389                        )
390                    }
391                }
392                _ => panic!("IPv6 not supported"),
393            };
394            (
395                TransmitterBuilder::new(xdp_config, exit.clone())
396                    .expect("failed to create xdp transmitter"),
397                SocketAddrV4::new(src_ip, src_port),
398            )
399        });
400
401        // we're done with caps needed to init xdp now. remove them from our process
402        caps::set(None, CapSet::Effective, &retained_caps)
403            .expect("linux allows effective capset to be set");
404        caps::set(None, CapSet::Permitted, &retained_caps)
405            .expect("linux allows permitted capset to be set");
406
407        xdp_builder_with_src_addr
408    };
409
410    #[cfg(not(target_os = "linux"))]
411    let xdp_builder_with_src_addr = None;
412
413    let reserved = retransmit_xdp
414        .map(|xdp| xdp.cpus.clone())
415        .unwrap_or_default()
416        .iter()
417        .cloned()
418        .collect::<HashSet<_>>();
419    if !reserved.is_empty() {
420        let available = core_affinity::get_core_ids()
421            .unwrap_or_default()
422            .into_iter()
423            .map(|core_id| core_id.id)
424            .collect::<HashSet<_>>();
425        let available = available.difference(&reserved);
426        set_cpu_affinity(available.into_iter().copied()).unwrap();
427    }
428
429    solana_core::validator::report_target_features();
430
431    let authorized_voter_keypairs = keypairs_of(matches, "authorized_voter_keypairs")
432        .map(|keypairs| keypairs.into_iter().map(Arc::new).collect())
433        .unwrap_or_else(|| vec![Arc::new(keypair_of(matches, "identity").expect("identity"))]);
434    let authorized_voter_keypairs = Arc::new(RwLock::new(authorized_voter_keypairs));
435
436    let staked_nodes_overrides_path = matches
437        .value_of("staked_nodes_overrides")
438        .map(str::to_string);
439    let staked_nodes_overrides = Arc::new(RwLock::new(
440        match &staked_nodes_overrides_path {
441            None => StakedNodesOverrides::default(),
442            Some(p) => load_staked_nodes_overrides(p).unwrap_or_else(|err| {
443                error!("Failed to load stake-nodes-overrides from {p}: {err}");
444                clap::Error::with_description(
445                    "Failed to load configuration of stake-nodes-overrides argument",
446                    clap::ErrorKind::InvalidValue,
447                )
448                .exit()
449            }),
450        }
451        .staked_map_id,
452    ));
453
454    let init_complete_file = matches.value_of("init_complete_file");
455
456    let private_rpc = matches.is_present("private_rpc");
457    let do_port_check = !matches.is_present("no_port_check");
458
459    let ledger_path = run_args.ledger_path;
460
461    let max_ledger_shreds = if matches.is_present("limit_ledger_size") {
462        let limit_ledger_size = match matches.value_of("limit_ledger_size") {
463            Some(_) => value_t_or_exit!(matches, "limit_ledger_size", u64),
464            None => DEFAULT_MAX_LEDGER_SHREDS,
465        };
466        if limit_ledger_size < DEFAULT_MIN_MAX_LEDGER_SHREDS {
467            Err(format!(
468                "The provided --limit-ledger-size value was too small, the minimum value is \
469                 {DEFAULT_MIN_MAX_LEDGER_SHREDS}"
470            ))?;
471        }
472        Some(limit_ledger_size)
473    } else {
474        None
475    };
476
477    let debug_keys: Option<Arc<HashSet<_>>> = if matches.is_present("debug_key") {
478        Some(Arc::new(
479            values_t_or_exit!(matches, "debug_key", Pubkey)
480                .into_iter()
481                .collect(),
482        ))
483    } else {
484        None
485    };
486
487    let repair_validators = validators_set(
488        &identity_keypair.pubkey(),
489        matches,
490        "repair_validators",
491        "--repair-validator",
492    )?;
493    let repair_whitelist = validators_set(
494        &identity_keypair.pubkey(),
495        matches,
496        "repair_whitelist",
497        "--repair-whitelist",
498    )?;
499    let repair_whitelist = Arc::new(RwLock::new(repair_whitelist.unwrap_or_default()));
500    let gossip_validators = validators_set(
501        &identity_keypair.pubkey(),
502        matches,
503        "gossip_validators",
504        "--gossip-validator",
505    )?;
506
507    if bind_addresses.len() > 1 {
508        for (flag, msg) in [
509            (
510                "advertised_ip",
511                "--advertised-ip cannot be used in a multihoming context. In multihoming, the \
512                 validator will advertise the first --bind-address as this node's public IP \
513                 address.",
514            ),
515            (
516                "public_tpu_addr",
517                "--public-tpu-address can not be used in a multihoming context",
518            ),
519        ] {
520            if matches.is_present(flag) {
521                Err(String::from(msg))?;
522            }
523        }
524    }
525
526    let rpc_bind_address = if matches.is_present("rpc_bind_address") {
527        solana_net_utils::parse_host(matches.value_of("rpc_bind_address").unwrap())
528            .expect("invalid rpc_bind_address")
529    } else if private_rpc {
530        solana_net_utils::parse_host("127.0.0.1").unwrap()
531    } else {
532        bind_addresses.active()
533    };
534
535    let contact_debug_interval = value_t_or_exit!(matches, "contact_debug_interval", u64);
536
537    let account_indexes = AccountSecondaryIndexes::from_clap_arg_match(matches)?;
538
539    let restricted_repair_only_mode = matches.is_present("restricted_repair_only_mode");
540    let accounts_shrink_optimize_total_space =
541        value_t_or_exit!(matches, "accounts_shrink_optimize_total_space", bool);
542    let vote_use_quic = value_t_or_exit!(matches, "vote_use_quic", bool);
543
544    let tpu_connection_pool_size = matches
545        .value_of("tpu_connection_pool_size")
546        .unwrap_or("")
547        .parse()
548        .unwrap_or(DEFAULT_TPU_CONNECTION_POOL_SIZE);
549
550    let shrink_ratio = value_t_or_exit!(matches, "accounts_shrink_ratio", f64);
551    if !(0.0..=1.0).contains(&shrink_ratio) {
552        Err(format!(
553            "the specified account-shrink-ratio is invalid, it must be between 0. and 1.0 \
554             inclusive: {shrink_ratio}"
555        ))?;
556    }
557
558    let shrink_ratio = if accounts_shrink_optimize_total_space {
559        AccountShrinkThreshold::TotalSpace { shrink_ratio }
560    } else {
561        AccountShrinkThreshold::IndividualStore { shrink_ratio }
562    };
563    // TODO: Once entrypoints are updated to return shred-version, this should
564    // abort if it fails to obtain a shred-version, so that nodes always join
565    // gossip with a valid shred-version. The code to adopt entrypoint shred
566    // version can then be deleted from gossip and get_rpc_node above.
567    let expected_shred_version = value_t!(matches, "expected_shred_version", u16)
568        .ok()
569        .or_else(|| get_cluster_shred_version(&entrypoint_addrs, bind_addresses.active()));
570
571    let tower_path = value_t!(matches, "tower", PathBuf)
572        .ok()
573        .unwrap_or_else(|| ledger_path.clone());
574    let tower_storage: Arc<dyn tower_storage::TowerStorage> =
575        Arc::new(tower_storage::FileTowerStorage::new(tower_path));
576
577    let vote_history_storage: Arc<dyn vote_history_storage::VoteHistoryStorage> = Arc::new(
578        vote_history_storage::FileVoteHistoryStorage::new(ledger_path.clone()),
579    );
580
581    let accounts_index_limit =
582        value_t!(matches, "accounts_index_limit", String).unwrap_or_else(|err| err.exit());
583    let index_limit = {
584        enum CliIndexLimit {
585            // deprecated in v4.1.0
586            Minimal,
587            Unlimited,
588            Threshold(u64),
589        }
590        let cli_index_limit = match accounts_index_limit.as_str() {
591            "minimal" => {
592                warn!("Using `minimal` for `--accounts-index-limit` is deprecated.");
593                CliIndexLimit::Minimal
594            }
595            "unlimited" => CliIndexLimit::Unlimited,
596            "25GB" => CliIndexLimit::Threshold(25_000_000_000),
597            "50GB" => CliIndexLimit::Threshold(50_000_000_000),
598            "100GB" => CliIndexLimit::Threshold(100_000_000_000),
599            "200GB" => CliIndexLimit::Threshold(200_000_000_000),
600            "400GB" => CliIndexLimit::Threshold(400_000_000_000),
601            "800GB" => CliIndexLimit::Threshold(800_000_000_000),
602            x => {
603                // clap will enforce only the above values are possible
604                unreachable!("invalid value given to `--accounts-index-limit`: '{x}'")
605            }
606        };
607        match cli_index_limit {
608            CliIndexLimit::Minimal => IndexLimit::Minimal,
609            CliIndexLimit::Unlimited => IndexLimit::InMemOnly,
610            CliIndexLimit::Threshold(num_bytes) => IndexLimit::Threshold(IndexLimitThreshold {
611                num_bytes,
612                num_entries_overhead: DEFAULT_NUM_ENTRIES_OVERHEAD,
613                num_entries_to_evict: DEFAULT_NUM_ENTRIES_TO_EVICT,
614            }),
615        }
616    };
617    // Note: need to still handle --enable-accounts-disk-index until it is removed
618    let index_limit = if matches.is_present("enable_accounts_disk_index") {
619        IndexLimit::Minimal
620    } else {
621        index_limit
622    };
623
624    let mut accounts_index_config = AccountsIndexConfig {
625        num_flush_threads: Some(accounts_index_flush_threads),
626        index_limit,
627        ..AccountsIndexConfig::default()
628    };
629    if let Ok(bins) = value_t!(matches, "accounts_index_bins", usize) {
630        accounts_index_config.bins = Some(bins);
631    }
632    if let Ok(num_initial_accounts) =
633        value_t!(matches, "accounts_index_initial_accounts_count", usize)
634    {
635        accounts_index_config.num_initial_accounts = Some(num_initial_accounts);
636    }
637
638    {
639        let mut accounts_index_paths: Vec<PathBuf> = if matches.is_present("accounts_index_path") {
640            values_t_or_exit!(matches, "accounts_index_path", String)
641                .into_iter()
642                .map(PathBuf::from)
643                .collect()
644        } else {
645            vec![]
646        };
647        if accounts_index_paths.is_empty() {
648            accounts_index_paths = vec![ledger_path.join("accounts_index")];
649        }
650        accounts_index_config.drives = Some(accounts_index_paths);
651    }
652
653    const MB: usize = 1_024 * 1_024;
654
655    let account_shrink_paths: Option<Vec<PathBuf>> =
656        values_t!(matches, "account_shrink_path", String)
657            .map(|shrink_paths| shrink_paths.into_iter().map(PathBuf::from).collect())
658            .ok();
659    let account_shrink_paths = account_shrink_paths
660        .as_ref()
661        .map(|paths| {
662            create_and_canonicalize_directories(paths)
663                .map_err(|err| format!("unable to access account shrink path: {err}"))
664        })
665        .transpose()?;
666
667    let (account_shrink_run_paths, account_shrink_snapshot_paths) = account_shrink_paths
668        .map(|paths| {
669            create_all_accounts_run_and_snapshot_dirs(&paths)
670                .map_err(|err| format!("unable to create account subdirectories: {err}"))
671        })
672        .transpose()?
673        .unzip();
674
675    let read_cache_limit_bytes =
676        values_of::<usize>(matches, "accounts_db_read_cache_limit").map(|limits| {
677            match limits.len() {
678                2 => (limits[0], limits[1]),
679                _ => {
680                    // clap will enforce two values are given
681                    unreachable!("invalid number of values given to accounts-db-read-cache-limit")
682                }
683            }
684        });
685
686    let storage_access = matches
687        .value_of("accounts_db_access_storages_method")
688        .map(|method| match method {
689            "mmap" => {
690                warn!("Using `mmap` for `--accounts-db-access-storages-method` is now deprecated.");
691                #[allow(deprecated)]
692                StorageAccess::Mmap
693            }
694            "file" => StorageAccess::File,
695            _ => {
696                // clap will enforce one of the above values is given
697                unreachable!("invalid value given to accounts-db-access-storages-method")
698            }
699        })
700        .unwrap_or_default();
701
702    let scan_filter_for_shrinking = matches
703        .value_of("accounts_db_scan_filter_for_shrinking")
704        .map(|filter| match filter {
705            "all" => ScanFilter::All,
706            "only-abnormal" => ScanFilter::OnlyAbnormal,
707            "only-abnormal-with-verify" => ScanFilter::OnlyAbnormalWithVerify,
708            _ => {
709                // clap will enforce one of the above values is given
710                unreachable!("invalid value given to accounts_db_scan_filter_for_shrinking")
711            }
712        })
713        .unwrap_or_default();
714
715    let accounts_db_config = AccountsDbConfig {
716        index: Some(accounts_index_config),
717        account_indexes: Some(account_indexes.clone()),
718        bank_hash_details_dir: ledger_path.clone(),
719        shrink_paths: account_shrink_run_paths,
720        shrink_ratio,
721        read_cache_limit_bytes,
722        read_cache_evict_sample_size: None,
723        read_cache_num_shards: None,
724        write_cache_limit_bytes: value_t!(matches, "accounts_db_cache_limit_mb", u64)
725            .ok()
726            .map(|mb| mb * MB as u64),
727        ancient_append_vec_offset: value_t!(matches, "accounts_db_ancient_append_vecs", i64).ok(),
728        ancient_storage_ideal_size: value_t!(
729            matches,
730            "accounts_db_ancient_storage_ideal_size",
731            u64
732        )
733        .ok(),
734        max_ancient_storages: value_t!(matches, "accounts_db_max_ancient_storages", usize).ok(),
735        skip_initial_hash_calc: false,
736        exhaustively_verify_refcounts: matches.is_present("accounts_db_verify_refcounts"),
737        partitioned_epoch_rewards_config: PartitionedEpochRewardsConfig::default(),
738        storage_access,
739        scan_filter_for_shrinking,
740        num_background_threads: Some(accounts_db_background_threads),
741        num_foreground_threads: Some(accounts_db_foreground_threads),
742    };
743
744    let on_start_geyser_plugin_config_files = if matches.is_present("geyser_plugin_config") {
745        Some(
746            values_t_or_exit!(matches, "geyser_plugin_config", String)
747                .into_iter()
748                .map(PathBuf::from)
749                .collect(),
750        )
751    } else {
752        None
753    };
754    let starting_with_geyser_plugins: bool = on_start_geyser_plugin_config_files.is_some()
755        || matches.is_present("geyser_plugin_always_enabled");
756
757    let account_paths: Vec<PathBuf> =
758        if let Ok(account_paths) = values_t!(matches, "account_paths", String) {
759            account_paths
760                .join(",")
761                .split(',')
762                .map(PathBuf::from)
763                .collect()
764        } else {
765            vec![ledger_path.join("accounts")]
766        };
767    let account_paths = create_and_canonicalize_directories(account_paths)
768        .map_err(|err| format!("unable to access account path: {err}"))?;
769
770    // From now on, use run/ paths in the same way as the previous account_paths.
771    let (account_run_paths, account_snapshot_paths) =
772        create_all_accounts_run_and_snapshot_dirs(&account_paths)
773            .map_err(|err| format!("unable to create account directories: {err}"))?;
774
775    // These snapshot paths are only used for initial clean up, add in shrink paths if they exist.
776    let account_snapshot_paths =
777        if let Some(account_shrink_snapshot_paths) = account_shrink_snapshot_paths {
778            account_snapshot_paths
779                .into_iter()
780                .chain(account_shrink_snapshot_paths)
781                .collect()
782        } else {
783            account_snapshot_paths
784        };
785
786    let snapshot_config = new_snapshot_config(
787        matches,
788        &ledger_path,
789        &account_paths,
790        run_args.rpc_bootstrap_config.incremental_snapshot_fetch,
791    )?;
792
793    let use_snapshot_archives_at_startup = value_t_or_exit!(
794        matches,
795        use_snapshot_archives_at_startup::cli::NAME,
796        UseSnapshotArchivesAtStartup
797    );
798
799    let mut validator_config = ValidatorConfig {
800        log_config,
801        require_tower: matches.is_present("require_tower"),
802        tower_storage,
803        vote_history_storage,
804        max_genesis_archive_unpacked_size: MAX_GENESIS_ARCHIVE_UNPACKED_SIZE,
805        expected_genesis_hash: matches
806            .value_of("expected_genesis_hash")
807            .map(|s| Hash::from_str(s).unwrap()),
808        fixed_leader_schedule: None,
809        expected_bank_hash: matches
810            .value_of("expected_bank_hash")
811            .map(|s| Hash::from_str(s).unwrap()),
812        expected_shred_version,
813        new_hard_forks: hardforks_of(matches, "hard_forks"),
814        rpc_config: run_args.json_rpc_config,
815        on_start_geyser_plugin_config_files,
816        geyser_plugin_always_enabled: matches.is_present("geyser_plugin_always_enabled"),
817        rpc_addrs: value_t!(matches, "rpc_port", u16).ok().map(|rpc_port| {
818            (
819                SocketAddr::new(rpc_bind_address, rpc_port),
820                SocketAddr::new(rpc_bind_address, rpc_port + 1),
821                // If additional ports are added, +2 needs to be skipped to avoid a conflict with
822                // the websocket port (which is +2) in web3.js This odd port shifting is tracked at
823                // https://github.com/solana-labs/solana/issues/12250
824            )
825        }),
826        pubsub_config: run_args.pub_sub_config,
827        voting_disabled: matches.is_present("no_voting") || restricted_repair_only_mode,
828        wait_for_supermajority: value_t!(matches, "wait_for_supermajority", Slot).ok(),
829        known_validators: run_args.known_validators,
830        repair_validators,
831        should_check_duplicate_instance: true,
832        repair_whitelist,
833        repair_handler_type: RepairHandlerType::default(),
834        gossip_validators,
835        max_ledger_shreds,
836        blockstore_options: run_args.blockstore_options,
837        run_verification: !matches.is_present("skip_startup_ledger_verification"),
838        debug_keys,
839        filter_keys: Arc::new(run_args.filter_keys),
840        warp_slot: None,
841        generator_config: None,
842        contact_debug_interval,
843        contact_save_interval: DEFAULT_CONTACT_SAVE_INTERVAL_MILLIS,
844        send_transaction_service_config: run_args.send_transaction_service_config,
845        no_poh_speed_test: matches.is_present("no_poh_speed_test"),
846        no_os_memory_stats_reporting: matches.is_present("no_os_memory_stats_reporting"),
847        no_os_network_stats_reporting: matches.is_present("no_os_network_stats_reporting"),
848        no_os_cpu_stats_reporting: matches.is_present("no_os_cpu_stats_reporting"),
849        no_os_disk_stats_reporting: matches.is_present("no_os_disk_stats_reporting"),
850        // The validator needs to open many files, check that the process has
851        // permission to do so in order to fail quickly and give a direct error
852        enforce_ulimit_nofile: true,
853        poh_pinned_cpu_core: value_of(matches, "poh_pinned_cpu_core")
854            .unwrap_or(poh_service::DEFAULT_PINNED_CPU_CORE),
855        poh_hashes_per_batch: value_of(matches, "poh_hashes_per_batch")
856            .unwrap_or(poh_service::DEFAULT_HASHES_PER_BATCH),
857        process_ledger_before_services: matches.is_present("process_ledger_before_services"),
858        account_paths: account_run_paths,
859        account_snapshot_paths,
860        accounts_db_config,
861        accounts_db_skip_shrink: true,
862        accounts_db_force_initial_clean: matches.is_present("no_skip_initial_accounts_db_clean"),
863        snapshot_config,
864        no_wait_for_vote_to_start_leader: matches.is_present("no_wait_for_vote_to_start_leader"),
865        wait_to_vote_slot: None,
866        runtime_config: RuntimeConfig {
867            log_messages_bytes_limit: value_of(matches, "log_messages_bytes_limit"),
868            ..RuntimeConfig::default()
869        },
870        staked_nodes_overrides: staked_nodes_overrides.clone(),
871        use_snapshot_archives_at_startup,
872        ip_echo_server_threads,
873        rayon_global_threads,
874        replay_forks_threads,
875        replay_transactions_threads,
876        tvu_shred_sigverify_threads: tvu_sigverify_threads,
877        tvu_bls_sigverify_threads,
878        delay_leader_block_for_pending_fork: matches
879            .is_present("delay_leader_block_for_pending_fork"),
880        turbine_mode: TurbineMode::default(),
881        broadcast_stage_type: BroadcastStageType::Standard,
882        block_verification_method: value_t_or_exit!(
883            matches,
884            "block_verification_method",
885            BlockVerificationMethod
886        ),
887        unified_scheduler_handler_threads: value_t!(
888            matches,
889            "unified_scheduler_handler_threads",
890            usize
891        )
892        .ok(),
893        block_production_method: value_t_or_exit!(
894            matches,
895            "block_production_method",
896            BlockProductionMethod
897        ),
898        block_production_num_workers,
899        block_production_scheduler_config: SchedulerConfig {
900            scheduler_pacing: value_t_or_exit!(
901                matches,
902                "block_production_pacing_fill_time_millis",
903                SchedulerPacing
904            ),
905        },
906        enable_block_production_forwarding: staked_nodes_overrides_path.is_some(),
907        enable_scheduler_bindings: matches.is_present("enable_scheduler_bindings"),
908        banking_trace_dir_byte_limit: parse_banking_trace_dir_byte_limit(matches),
909        validator_exit: Arc::new(RwLock::new(Exit::default())),
910        validator_exit_backpressure: [(
911            SnapshotPackagerService::NAME.to_string(),
912            Arc::new(AtomicBool::new(false)),
913        )]
914        .into(),
915        voting_service_test_override: None,
916        snapshot_packager_niceness_adj: value_t_or_exit!(
917            matches,
918            "snapshot_packager_niceness_adj",
919            i8
920        ),
921    };
922    validator_config
923        .block_production_method
924        .warn_if_deprecated_value();
925
926    let vote_account = pubkey_of(matches, "vote_account").unwrap_or_else(|| {
927        if !validator_config.voting_disabled {
928            warn!("--vote-account not specified, validator will not vote");
929            validator_config.voting_disabled = true;
930        }
931        Keypair::new().pubkey()
932    });
933
934    let maximum_local_snapshot_age = value_t_or_exit!(matches, "maximum_local_snapshot_age", u64);
935    let minimal_snapshot_download_speed =
936        value_t_or_exit!(matches, "minimal_snapshot_download_speed", f32);
937    let maximum_snapshot_download_abort =
938        value_t_or_exit!(matches, "maximum_snapshot_download_abort", u64);
939
940    let public_rpc_addr = matches
941        .value_of("public_rpc_addr")
942        .map(|addr| {
943            solana_net_utils::parse_host_port(addr)
944                .map_err(|err| format!("failed to parse public rpc address: {err}"))
945        })
946        .transpose()?;
947
948    if !matches.is_present("no_os_network_limits_test") {
949        if SystemMonitorService::check_os_network_limits() {
950            info!("OS network limits test passed.");
951        } else {
952            Err("OS network limit test failed. See \
953                https://docs.anza.xyz/operations/guides/validator-start#system-tuning"
954                .to_string())?;
955        }
956    }
957
958    let mut ledger_lock = ledger_lockfile(&ledger_path);
959    let _ledger_write_guard = lock_ledger(&ledger_path, &mut ledger_lock);
960
961    let start_progress = Arc::new(RwLock::new(ValidatorStartProgress::default()));
962    let admin_service_post_init = Arc::new(RwLock::new(None));
963    let (rpc_to_plugin_manager_sender, rpc_to_plugin_manager_receiver) =
964        if starting_with_geyser_plugins {
965            let (sender, receiver) = unbounded();
966            (Some(sender), Some(receiver))
967        } else {
968            (None, None)
969        };
970    admin_rpc_service::run(
971        &ledger_path,
972        admin_rpc_service::AdminRpcRequestMetadata {
973            rpc_addr: validator_config.rpc_addrs.map(|(rpc_addr, _)| rpc_addr),
974            start_time: std::time::SystemTime::now(),
975            validator_exit: validator_config.validator_exit.clone(),
976            validator_exit_backpressure: validator_config.validator_exit_backpressure.clone(),
977            start_progress: start_progress.clone(),
978            authorized_voter_keypairs: authorized_voter_keypairs.clone(),
979            post_init: admin_service_post_init.clone(),
980            tower_storage: validator_config.tower_storage.clone(),
981            staked_nodes_overrides,
982            rpc_to_plugin_manager_sender,
983        },
984    );
985
986    let tpu_max_connections_per_peer: Option<u64> = matches
987        .value_of("tpu_max_connections_per_peer")
988        .and_then(|v| v.parse().ok());
989    let tpu_max_connections_per_unstaked_peer = tpu_max_connections_per_peer
990        .unwrap_or_else(|| value_t_or_exit!(matches, "tpu_max_connections_per_unstaked_peer", u64));
991    let tpu_max_connections_per_staked_peer = tpu_max_connections_per_peer
992        .unwrap_or_else(|| value_t_or_exit!(matches, "tpu_max_connections_per_staked_peer", u64));
993    let tpu_max_staked_connections = value_t_or_exit!(matches, "tpu_max_staked_connections", u64);
994    let tpu_max_unstaked_connections =
995        value_t_or_exit!(matches, "tpu_max_unstaked_connections", u64);
996
997    let tpu_max_fwd_staked_connections =
998        value_t_or_exit!(matches, "tpu_max_fwd_staked_connections", u64);
999    let tpu_max_fwd_unstaked_connections =
1000        value_t_or_exit!(matches, "tpu_max_fwd_unstaked_connections", u64);
1001
1002    let tpu_max_connections_per_ipaddr_per_minute: u64 =
1003        value_t_or_exit!(matches, "tpu_max_connections_per_ipaddr_per_minute", u64);
1004    let max_streams_per_ms = value_t_or_exit!(matches, "tpu_max_streams_per_ms", u64);
1005
1006    let cluster_entrypoints = entrypoint_addrs
1007        .iter()
1008        .map(ContactInfo::new_gossip_entry_point)
1009        .collect::<Vec<_>>();
1010
1011    if restricted_repair_only_mode {
1012        // When in --restricted_repair_only_mode is enabled only the gossip and repair ports
1013        // need to be reachable by the entrypoint to respond to gossip pull requests and repair
1014        // requests initiated by the node.  All other ports are unused.
1015        node.info.remove_tpu();
1016        node.info.remove_tpu_forwards();
1017        node.info.remove_tvu();
1018        node.info.remove_serve_repair();
1019        node.info.remove_alpenglow();
1020
1021        // A node in this configuration shouldn't be an entrypoint to other nodes
1022        node.sockets.ip_echo = None;
1023    }
1024
1025    if !private_rpc {
1026        macro_rules! set_socket {
1027            ($method:ident, $addr:expr, $name:literal) => {
1028                node.info.$method($addr).expect(&format!(
1029                    "Operator must spin up node with valid {} address",
1030                    $name
1031                ))
1032            };
1033        }
1034        if let Some(public_rpc_addr) = public_rpc_addr {
1035            set_socket!(set_rpc, public_rpc_addr, "RPC");
1036            set_socket!(set_rpc_pubsub, public_rpc_addr, "RPC-pubsub");
1037        } else if let Some((rpc_addr, rpc_pubsub_addr)) = validator_config.rpc_addrs {
1038            let addr = node
1039                .info
1040                .gossip()
1041                .expect("Operator must spin up node with valid gossip address")
1042                .ip();
1043            set_socket!(set_rpc, (addr, rpc_addr.port()), "RPC");
1044            set_socket!(set_rpc_pubsub, (addr, rpc_pubsub_addr.port()), "RPC-pubsub");
1045        }
1046    }
1047
1048    snapshot_utils::remove_tmp_snapshot_archives(
1049        &validator_config.snapshot_config.full_snapshot_archives_dir,
1050    );
1051    snapshot_utils::remove_tmp_snapshot_archives(
1052        &validator_config
1053            .snapshot_config
1054            .incremental_snapshot_archives_dir,
1055    );
1056
1057    if !cluster_entrypoints.is_empty() {
1058        bootstrap::rpc_bootstrap(
1059            &node,
1060            &identity_keypair,
1061            &ledger_path,
1062            &vote_account,
1063            authorized_voter_keypairs.clone(),
1064            &cluster_entrypoints,
1065            &mut validator_config,
1066            run_args.rpc_bootstrap_config,
1067            do_port_check,
1068            use_progress_bar,
1069            maximum_local_snapshot_age,
1070            &start_progress,
1071            minimal_snapshot_download_speed,
1072            maximum_snapshot_download_abort,
1073            run_args.socket_addr_space,
1074        );
1075        *start_progress.write().unwrap() = ValidatorStartProgress::Initializing;
1076    }
1077
1078    if operation == Operation::Initialize {
1079        info!("Validator ledger initialization complete");
1080        return Ok(());
1081    }
1082
1083    // Bootstrap code above pushes a contact-info with more recent timestamp to
1084    // gossip. If the node is staked the contact-info lingers in gossip causing
1085    // false duplicate nodes error.
1086    // Below line refreshes the timestamp on contact-info so that it overrides
1087    // the one pushed by bootstrap.
1088    node.info.hot_swap_pubkey(identity_keypair.pubkey());
1089
1090    let tpu_quic_server_config = SwQosQuicStreamerConfig {
1091        quic_streamer_config: QuicStreamerConfig {
1092            max_connections_per_ipaddr_per_min: tpu_max_connections_per_ipaddr_per_minute,
1093            num_threads: tpu_transaction_receive_threads,
1094            stream_receive_window_size: solana_message::v1::MAX_TRANSACTION_SIZE as u32,
1095            max_stream_data_bytes: solana_message::v1::MAX_TRANSACTION_SIZE as u32,
1096            ..Default::default()
1097        },
1098        qos_config: SwQosConfig {
1099            max_connections_per_unstaked_peer: tpu_max_connections_per_unstaked_peer
1100                .try_into()
1101                .unwrap(),
1102            max_connections_per_staked_peer: tpu_max_connections_per_staked_peer
1103                .try_into()
1104                .unwrap(),
1105            max_staked_connections: tpu_max_staked_connections.try_into().unwrap(),
1106            max_unstaked_connections: tpu_max_unstaked_connections.try_into().unwrap(),
1107            max_streams_per_ms,
1108        },
1109    };
1110
1111    let tpu_fwd_quic_server_config = SwQosQuicStreamerConfig {
1112        quic_streamer_config: QuicStreamerConfig {
1113            max_connections_per_ipaddr_per_min: tpu_max_connections_per_ipaddr_per_minute,
1114            num_threads: tpu_transaction_forward_receive_threads,
1115            stream_receive_window_size: solana_message::v1::MAX_TRANSACTION_SIZE as u32,
1116            max_stream_data_bytes: solana_message::v1::MAX_TRANSACTION_SIZE as u32,
1117            ..Default::default()
1118        },
1119        qos_config: SwQosConfig {
1120            max_connections_per_staked_peer: tpu_max_connections_per_staked_peer
1121                .try_into()
1122                .unwrap(),
1123            max_connections_per_unstaked_peer: tpu_max_connections_per_unstaked_peer
1124                .try_into()
1125                .unwrap(),
1126            max_staked_connections: tpu_max_fwd_staked_connections.try_into().unwrap(),
1127            max_unstaked_connections: tpu_max_fwd_unstaked_connections.try_into().unwrap(),
1128            max_streams_per_ms,
1129        },
1130    };
1131
1132    let vote_quic_server_config = SimpleQosQuicStreamerConfig {
1133        quic_streamer_config: QuicStreamerConfig {
1134            max_connections_per_ipaddr_per_min: tpu_max_connections_per_ipaddr_per_minute,
1135            num_threads: tpu_vote_transaction_receive_threads,
1136            ..Default::default()
1137        },
1138        qos_config: SimpleQosConfig {
1139            max_streams_per_second: MAX_VOTES_PER_SECOND,
1140            ..Default::default()
1141        },
1142    };
1143
1144    let validator = Validator::new_with_exit(
1145        node,
1146        identity_keypair,
1147        &ledger_path,
1148        &vote_account,
1149        authorized_voter_keypairs,
1150        cluster_entrypoints,
1151        &validator_config,
1152        rpc_to_plugin_manager_receiver,
1153        start_progress,
1154        run_args.socket_addr_space,
1155        ValidatorTpuConfig {
1156            vote_use_quic,
1157            tpu_connection_pool_size,
1158            tpu_quic_server_config,
1159            tpu_fwd_quic_server_config,
1160            vote_quic_server_config,
1161            sigverify_threads: tpu_sigverify_threads,
1162        },
1163        admin_service_post_init,
1164        xdp_builder_with_src_addr,
1165        exit,
1166    )
1167    .map_err(|err| format!("{err:?}"))?;
1168
1169    if let Some(filename) = init_complete_file {
1170        File::create(filename).map_err(|err| format!("unable to create {filename}: {err}"))?;
1171    }
1172    info!("Validator initialized");
1173    validator.listen_for_signals()?;
1174    validator.join();
1175    info!("Validator exiting...");
1176
1177    Ok(())
1178}
1179
1180// This function is duplicated in ledger-tool/src/main.rs...
1181fn hardforks_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<Slot>> {
1182    if matches.is_present(name) {
1183        Some(values_t_or_exit!(matches, name, Slot))
1184    } else {
1185        None
1186    }
1187}
1188
1189fn validators_set(
1190    identity_pubkey: &Pubkey,
1191    matches: &ArgMatches<'_>,
1192    matches_name: &str,
1193    arg_name: &str,
1194) -> Result<Option<HashSet<Pubkey>>, String> {
1195    if matches.is_present(matches_name) {
1196        let validators_set: HashSet<_> = values_t_or_exit!(matches, matches_name, Pubkey)
1197            .into_iter()
1198            .collect();
1199        if validators_set.contains(identity_pubkey) {
1200            Err(format!(
1201                "the validator's identity pubkey cannot be a {arg_name}: {identity_pubkey}"
1202            ))?;
1203        }
1204        Ok(Some(validators_set))
1205    } else {
1206        Ok(None)
1207    }
1208}
1209
1210fn get_cluster_shred_version(entrypoints: &[SocketAddr], bind_address: IpAddr) -> Option<u16> {
1211    let entrypoints = {
1212        let mut index: Vec<_> = (0..entrypoints.len()).collect();
1213        index.shuffle(&mut rand::rng());
1214        index.into_iter().map(|i| &entrypoints[i])
1215    };
1216    for entrypoint in entrypoints {
1217        match solana_net_utils::get_cluster_shred_version_with_binding(entrypoint, bind_address) {
1218            Err(err) => eprintln!("get_cluster_shred_version failed: {entrypoint}, {err}"),
1219            Ok(0) => eprintln!("entrypoint {entrypoint} returned shred-version zero"),
1220            Ok(shred_version) => {
1221                info!("obtained shred-version {shred_version} from {entrypoint}");
1222                return Some(shred_version);
1223            }
1224        }
1225    }
1226    None
1227}
1228
1229fn parse_banking_trace_dir_byte_limit(matches: &ArgMatches) -> u64 {
1230    if matches.is_present("disable_banking_trace") {
1231        // disable with an explicit flag; This effectively becomes `opt-out` by resetting to
1232        // DISABLED_BAKING_TRACE_DIR, while allowing us to specify a default sensible limit in clap
1233        // configuration for cli help.
1234        DISABLED_BAKING_TRACE_DIR
1235    } else {
1236        // a default value in clap configuration (BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT) or
1237        // explicit user-supplied override value
1238        value_t_or_exit!(matches, "banking_trace_dir_byte_limit", u64)
1239    }
1240}
1241
1242fn new_snapshot_config(
1243    matches: &ArgMatches,
1244    ledger_path: &Path,
1245    account_paths: &[PathBuf],
1246    incremental_snapshot_fetch: bool,
1247) -> Result<SnapshotConfig, Box<dyn std::error::Error>> {
1248    let (full_snapshot_archive_interval, incremental_snapshot_archive_interval) =
1249        if matches.is_present("no_snapshots") {
1250            // snapshots are disabled
1251            (SnapshotInterval::Disabled, SnapshotInterval::Disabled)
1252        } else {
1253            match (
1254                incremental_snapshot_fetch,
1255                value_t_or_exit!(matches, "snapshot_interval_slots", NonZeroU64),
1256            ) {
1257                (true, incremental_snapshot_interval_slots) => {
1258                    // incremental snapshots are enabled
1259                    // use --snapshot-interval-slots for the incremental snapshot interval
1260                    let full_snapshot_interval_slots =
1261                        value_t_or_exit!(matches, "full_snapshot_interval_slots", NonZeroU64);
1262                    (
1263                        SnapshotInterval::Slots(full_snapshot_interval_slots),
1264                        SnapshotInterval::Slots(incremental_snapshot_interval_slots),
1265                    )
1266                }
1267                (false, full_snapshot_interval_slots) => {
1268                    // incremental snapshots are *disabled*
1269                    // use --snapshot-interval-slots for the *full* snapshot interval
1270                    // also warn if --full-snapshot-interval-slots was specified
1271                    if matches.occurrences_of("full_snapshot_interval_slots") > 0 {
1272                        warn!(
1273                            "Incremental snapshots are disabled, yet \
1274                             --full-snapshot-interval-slots was specified! Note that \
1275                             --full-snapshot-interval-slots is *ignored* when incremental \
1276                             snapshots are disabled. Use --snapshot-interval-slots instead.",
1277                        );
1278                    }
1279                    (
1280                        SnapshotInterval::Slots(full_snapshot_interval_slots),
1281                        SnapshotInterval::Disabled,
1282                    )
1283                }
1284            }
1285        };
1286
1287    info!(
1288        "Snapshot configuration: full snapshot interval: {}, incremental snapshot interval: {}",
1289        match full_snapshot_archive_interval {
1290            SnapshotInterval::Disabled => "disabled".to_string(),
1291            SnapshotInterval::Slots(interval) => format!("{interval} slots"),
1292        },
1293        match incremental_snapshot_archive_interval {
1294            SnapshotInterval::Disabled => "disabled".to_string(),
1295            SnapshotInterval::Slots(interval) => format!("{interval} slots"),
1296        },
1297    );
1298    // It is unlikely that a full snapshot interval greater than an epoch is a good idea.
1299    // Minimally we should warn the user in case this was a mistake.
1300    if let SnapshotInterval::Slots(full_snapshot_interval_slots) = full_snapshot_archive_interval {
1301        let full_snapshot_interval_slots = full_snapshot_interval_slots.get();
1302        if full_snapshot_interval_slots > DEFAULT_SLOTS_PER_EPOCH {
1303            warn!(
1304                "The full snapshot interval is excessively large: {full_snapshot_interval_slots}! \
1305                 This will negatively impact the background cleanup tasks in accounts-db. \
1306                 Consider a smaller value.",
1307            );
1308        }
1309    }
1310
1311    let snapshots_dir = matches
1312        .value_of("snapshots")
1313        .map(Path::new)
1314        .unwrap_or(ledger_path);
1315    let snapshots_dir = create_and_canonicalize_directory(snapshots_dir).map_err(|err| {
1316        format!(
1317            "failed to create snapshots directory '{}': {err}",
1318            snapshots_dir.display(),
1319        )
1320    })?;
1321    if account_paths
1322        .iter()
1323        .any(|account_path| account_path == &snapshots_dir)
1324    {
1325        Err(
1326            "the --accounts and --snapshots paths must be unique since they both create \
1327             'snapshots' subdirectories, otherwise there may be collisions"
1328                .to_string(),
1329        )?;
1330    }
1331
1332    let bank_snapshots_dir = snapshots_dir.join(BANK_SNAPSHOTS_DIR);
1333    fs::create_dir_all(&bank_snapshots_dir).map_err(|err| {
1334        format!(
1335            "failed to create bank snapshots directory '{}': {err}",
1336            bank_snapshots_dir.display(),
1337        )
1338    })?;
1339
1340    let full_snapshot_archives_dir = matches
1341        .value_of("full_snapshot_archive_path")
1342        .map(PathBuf::from)
1343        .unwrap_or_else(|| snapshots_dir.clone());
1344    fs::create_dir_all(&full_snapshot_archives_dir).map_err(|err| {
1345        format!(
1346            "failed to create full snapshot archives directory '{}': {err}",
1347            full_snapshot_archives_dir.display(),
1348        )
1349    })?;
1350
1351    let incremental_snapshot_archives_dir = matches
1352        .value_of("incremental_snapshot_archive_path")
1353        .map(PathBuf::from)
1354        .unwrap_or_else(|| snapshots_dir.clone());
1355    fs::create_dir_all(&incremental_snapshot_archives_dir).map_err(|err| {
1356        format!(
1357            "failed to create incremental snapshot archives directory '{}': {err}",
1358            incremental_snapshot_archives_dir.display(),
1359        )
1360    })?;
1361
1362    let archive_format = {
1363        let archive_format_str = value_t_or_exit!(matches, "snapshot_archive_format", String);
1364        let mut archive_format = ArchiveFormat::from_cli_arg(&archive_format_str)
1365            .unwrap_or_else(|| panic!("Archive format not recognized: {archive_format_str}"));
1366        if let ArchiveFormat::TarZstd { config } = &mut archive_format {
1367            config.compression_level =
1368                value_t_or_exit!(matches, "snapshot_zstd_compression_level", i32);
1369        }
1370        archive_format
1371    };
1372
1373    let snapshot_version = matches
1374        .value_of("snapshot_version")
1375        .map(|value| {
1376            value
1377                .parse::<SnapshotVersion>()
1378                .map_err(|err| format!("unable to parse snapshot version: {err}"))
1379        })
1380        .transpose()?
1381        .unwrap_or(SnapshotVersion::default());
1382
1383    let maximum_full_snapshot_archives_to_retain =
1384        value_t_or_exit!(matches, "maximum_full_snapshots_to_retain", NonZeroUsize);
1385    let maximum_incremental_snapshot_archives_to_retain = value_t_or_exit!(
1386        matches,
1387        "maximum_incremental_snapshots_to_retain",
1388        NonZeroUsize
1389    );
1390
1391    let snapshot_config = SnapshotConfig {
1392        usage: if full_snapshot_archive_interval == SnapshotInterval::Disabled {
1393            SnapshotUsage::LoadOnly
1394        } else {
1395            SnapshotUsage::LoadAndGenerate
1396        },
1397        full_snapshot_archive_interval,
1398        incremental_snapshot_archive_interval,
1399        bank_snapshots_dir,
1400        full_snapshot_archives_dir,
1401        incremental_snapshot_archives_dir,
1402        archive_format,
1403        snapshot_version,
1404        maximum_full_snapshot_archives_to_retain,
1405        maximum_incremental_snapshot_archives_to_retain,
1406        use_registered_io_uring_buffers: resource_limits::check_memlock_limit_for_disk_io(
1407            solana_accounts_db::accounts_db::TOTAL_IO_URING_BUFFERS_SIZE_LIMIT,
1408        ),
1409        use_direct_io: !matches.is_present("no_accounts_db_snapshots_direct_io"),
1410    };
1411
1412    if !is_snapshot_config_valid(&snapshot_config) {
1413        Err(
1414            "invalid snapshot configuration provided: snapshot intervals are incompatible. full \
1415             snapshot interval MUST be larger than incremental snapshot interval (if enabled)"
1416                .to_string(),
1417        )?;
1418    }
1419
1420    Ok(snapshot_config)
1421}