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 if env::var_os("RUST_BACKTRACE").is_none() {
103 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 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 assert!(
332 required_caps.is_subset(&supported_caps),
333 "required_caps contains a cap not in supported_caps",
334 );
335
336 let current_permitted =
338 caps::read(None, CapSet::Permitted).expect("permitted capset to be readable");
339 let missing_caps = required_caps
340 .difference(¤t_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 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 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 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 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 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 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 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 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 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 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 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 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 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 )
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 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 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 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 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
1180fn 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 DISABLED_BAKING_TRACE_DIR
1235 } else {
1236 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 (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 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 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 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}