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