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_index::{
23 AccountSecondaryIndexes, AccountsIndexConfig, DEFAULT_NUM_ENTRIES_OVERHEAD,
24 DEFAULT_NUM_ENTRIES_TO_EVICT, IndexLimit, IndexLimitThreshold, ScanFilter,
25 },
26 partitioned_rewards::PartitionedEpochRewardsConfig,
27 utils::{
28 create_all_accounts_run_and_snapshot_dirs, create_and_canonicalize_directories,
29 create_and_canonicalize_directory,
30 },
31 },
32 solana_clap_utils::input_parsers::{keypair_of, keypairs_of, pubkey_of, value_of, values_of},
33 solana_clock::{DEFAULT_SLOTS_PER_EPOCH, Slot},
34 solana_core::{
35 banking_stage::transaction_scheduler::scheduler_controller::SchedulerConfig,
36 banking_trace::DISABLED_BAKING_TRACE_DIR,
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 };
717
718 let on_start_geyser_plugin_config_files = if matches.is_present("geyser_plugin_config") {
719 Some(
720 values_t_or_exit!(matches, "geyser_plugin_config", String)
721 .into_iter()
722 .map(PathBuf::from)
723 .collect(),
724 )
725 } else {
726 None
727 };
728 let starting_with_geyser_plugins: bool = on_start_geyser_plugin_config_files.is_some()
729 || matches.is_present("geyser_plugin_always_enabled");
730
731 let account_paths: Vec<PathBuf> =
732 if let Ok(account_paths) = values_t!(matches, "account_paths", String) {
733 account_paths
734 .join(",")
735 .split(',')
736 .map(PathBuf::from)
737 .collect()
738 } else {
739 vec![ledger_path.join("accounts")]
740 };
741 let account_paths = create_and_canonicalize_directories(account_paths)
742 .map_err(|err| format!("unable to access account path: {err}"))?;
743
744 let (account_run_paths, account_snapshot_paths) =
746 create_all_accounts_run_and_snapshot_dirs(&account_paths)
747 .map_err(|err| format!("unable to create account directories: {err}"))?;
748
749 let snapshot_config = new_snapshot_config(
750 matches,
751 &ledger_path,
752 &account_paths,
753 run_args.rpc_bootstrap_config.incremental_snapshot_fetch,
754 )?;
755
756 let use_snapshot_archives_at_startup = value_t_or_exit!(
757 matches,
758 use_snapshot_archives_at_startup::cli::NAME,
759 UseSnapshotArchivesAtStartup
760 );
761
762 let mut validator_config = ValidatorConfig {
763 log_config,
764 require_tower: matches.is_present("require_tower"),
765 require_vote_history: !matches.is_present("do_not_require_vote_history"),
766 tower_storage,
767 vote_history_storage,
768 max_genesis_archive_unpacked_size: MAX_GENESIS_ARCHIVE_UNPACKED_SIZE,
769 expected_genesis_hash: matches
770 .value_of("expected_genesis_hash")
771 .map(|s| Hash::from_str(s).unwrap()),
772 fixed_leader_schedule: None,
773 expected_bank_hash: matches
774 .value_of("expected_bank_hash")
775 .map(|s| Hash::from_str(s).unwrap()),
776 expected_shred_version,
777 new_hard_forks: hardforks_of(matches, "hard_forks"),
778 rpc_config: run_args.json_rpc_config,
779 on_start_geyser_plugin_config_files,
780 geyser_plugin_always_enabled: matches.is_present("geyser_plugin_always_enabled"),
781 rpc_addrs: value_t!(matches, "rpc_port", u16).ok().map(|rpc_port| {
782 (
783 SocketAddr::new(rpc_bind_address, rpc_port),
784 SocketAddr::new(rpc_bind_address, rpc_port + 1),
785 )
789 }),
790 pubsub_config: run_args.pub_sub_config,
791 voting_disabled: matches.is_present("no_voting") || restricted_repair_only_mode,
792 wait_for_supermajority: value_t!(matches, "wait_for_supermajority", Slot).ok(),
793 known_validators: run_args.known_validators,
794 repair_validators,
795 should_check_duplicate_instance: true,
796 repair_whitelist,
797 repair_handler_type: RepairHandlerType::default(),
798 gossip_validators,
799 max_ledger_shreds,
800 blockstore_options: run_args.blockstore_options,
801 run_verification: !matches.is_present("skip_startup_ledger_verification"),
802 debug_keys,
803 filter_keys: Arc::new(run_args.filter_keys),
804 warp_slot: None,
805 generator_config: None,
806 contact_debug_interval,
807 contact_save_interval: DEFAULT_CONTACT_SAVE_INTERVAL_MILLIS,
808 send_transaction_service_config: run_args.send_transaction_service_config,
809 no_poh_speed_test: matches.is_present("no_poh_speed_test"),
810 no_os_memory_stats_reporting: matches.is_present("no_os_memory_stats_reporting"),
811 no_os_network_stats_reporting: matches.is_present("no_os_network_stats_reporting"),
812 xdp_network_config_report,
813 no_os_cpu_stats_reporting: matches.is_present("no_os_cpu_stats_reporting"),
814 no_os_disk_stats_reporting: matches.is_present("no_os_disk_stats_reporting"),
815 enforce_ulimit_nofile: true,
818 poh_pinned_cpu_core,
819 poh_hashes_per_batch: value_of(matches, "poh_hashes_per_batch")
820 .unwrap_or(poh_service::DEFAULT_HASHES_PER_BATCH),
821 process_ledger_before_services: matches.is_present("process_ledger_before_services"),
822 account_paths: account_run_paths,
823 account_snapshot_paths,
824 accounts_db_config,
825 accounts_db_skip_shrink: true,
826 accounts_db_force_initial_clean: matches.is_present("no_skip_initial_accounts_db_clean"),
827 snapshot_config,
828 no_wait_for_vote_to_start_leader: matches.is_present("no_wait_for_vote_to_start_leader"),
829 wait_to_vote_slot: None,
830 runtime_config: RuntimeConfig {
831 log_messages_bytes_limit: value_of(matches, "log_messages_bytes_limit"),
832 ..RuntimeConfig::default()
833 },
834 staked_nodes_overrides: staked_nodes_overrides.clone(),
835 use_snapshot_archives_at_startup,
836 ip_echo_server_threads,
837 rayon_global_threads,
838 replay_forks_threads,
839 replay_transactions_threads,
840 tvu_shred_sigverify_threads: tvu_sigverify_threads,
841 tvu_bls_sigverify_threads,
842 delay_leader_block_for_pending_fork: !matches
843 .is_present("no_delay_leader_block_for_pending_fork"),
844 turbine_mode: TurbineMode::default(),
845 broadcast_stage_type: BroadcastStageType::Standard,
846 block_verification_method: value_t_or_exit!(
847 matches,
848 "block_verification_method",
849 BlockVerificationMethod
850 ),
851 unified_scheduler_handler_threads: value_t!(
852 matches,
853 "unified_scheduler_handler_threads",
854 usize
855 )
856 .ok(),
857 block_production_method: value_t_or_exit!(
858 matches,
859 "block_production_method",
860 BlockProductionMethod
861 ),
862 block_production_num_workers,
863 block_production_scheduler_config: SchedulerConfig {
864 scheduler_pacing: value_t_or_exit!(
865 matches,
866 "block_production_pacing_fill_time_millis",
867 SchedulerPacing
868 ),
869 },
870 enable_block_production_forwarding: staked_nodes_overrides_path.is_some(),
871 enable_scheduler_bindings: matches.is_present("enable_scheduler_bindings"),
872 banking_trace_dir_byte_limit: parse_banking_trace_dir_byte_limit(matches),
873 validator_exit: Arc::new(RwLock::new(Exit::default())),
874 validator_exit_backpressure: [(
875 SnapshotPackagerService::NAME.to_string(),
876 Arc::new(AtomicBool::new(false)),
877 )]
878 .into(),
879 voting_service_test_override: None,
880 snapshot_packager_niceness_adj: value_t_or_exit!(
881 matches,
882 "snapshot_packager_niceness_adj",
883 i8
884 ),
885 };
886 validator_config
887 .block_production_method
888 .warn_if_deprecated_value();
889
890 let vote_account = pubkey_of(matches, "vote_account").unwrap_or_else(|| {
891 if !validator_config.voting_disabled {
892 warn!("--vote-account not specified, validator will not vote");
893 validator_config.voting_disabled = true;
894 }
895 Keypair::new().pubkey()
896 });
897
898 let maximum_local_snapshot_age = value_t_or_exit!(matches, "maximum_local_snapshot_age", u64);
899 let minimal_snapshot_download_speed =
900 value_t_or_exit!(matches, "minimal_snapshot_download_speed", f32);
901 let maximum_snapshot_download_abort =
902 value_t_or_exit!(matches, "maximum_snapshot_download_abort", u64);
903
904 let public_rpc_addr = matches
905 .value_of("public_rpc_addr")
906 .map(|addr| {
907 solana_net_utils::parse_host_port(addr)
908 .map_err(|err| format!("failed to parse public rpc address: {err}"))
909 })
910 .transpose()?;
911
912 if !matches.is_present("no_os_network_limits_test") {
913 if SystemMonitorService::check_os_network_limits() {
914 info!("OS network limits test passed.");
915 } else {
916 Err("OS network limit test failed. See \
917 https://docs.anza.xyz/operations/guides/validator-start#system-tuning"
918 .to_string())?;
919 }
920 }
921
922 let mut ledger_lock = ledger_lockfile(&ledger_path);
923 let _ledger_write_guard = lock_ledger(&ledger_path, &mut ledger_lock);
924
925 let start_progress = Arc::new(RwLock::new(ValidatorStartProgress::default()));
926 let admin_service_post_init = Arc::new(RwLock::new(None));
927 let (rpc_to_plugin_manager_sender, rpc_to_plugin_manager_receiver) =
928 if starting_with_geyser_plugins {
929 let (sender, receiver) = unbounded();
930 (Some(sender), Some(receiver))
931 } else {
932 (None, None)
933 };
934 admin_rpc_service::run(
935 &ledger_path,
936 admin_rpc_service::AdminRpcRequestMetadata {
937 rpc_addr: validator_config.rpc_addrs.map(|(rpc_addr, _)| rpc_addr),
938 start_time: std::time::SystemTime::now(),
939 validator_exit: validator_config.validator_exit.clone(),
940 validator_exit_backpressure: validator_config.validator_exit_backpressure.clone(),
941 start_progress: start_progress.clone(),
942 authorized_voter_keypairs: authorized_voter_keypairs.clone(),
943 post_init: admin_service_post_init.clone(),
944 tower_storage: validator_config.tower_storage.clone(),
945 vote_history_storage: validator_config.vote_history_storage.clone(),
946 staked_nodes_overrides,
947 rpc_to_plugin_manager_sender,
948 },
949 );
950
951 let tpu_max_connections_per_peer: Option<u64> = matches
952 .value_of("tpu_max_connections_per_peer")
953 .and_then(|v| v.parse().ok());
954 let tpu_max_connections_per_unstaked_peer = tpu_max_connections_per_peer
955 .unwrap_or_else(|| value_t_or_exit!(matches, "tpu_max_connections_per_unstaked_peer", u64));
956 let tpu_max_connections_per_staked_peer = tpu_max_connections_per_peer
957 .unwrap_or_else(|| value_t_or_exit!(matches, "tpu_max_connections_per_staked_peer", u64));
958 let tpu_max_staked_connections = value_t_or_exit!(matches, "tpu_max_staked_connections", u64);
959 let tpu_max_unstaked_connections =
960 value_t_or_exit!(matches, "tpu_max_unstaked_connections", u64);
961
962 let tpu_max_fwd_staked_connections =
963 value_t_or_exit!(matches, "tpu_max_fwd_staked_connections", u64);
964 let tpu_max_fwd_unstaked_connections =
965 value_t_or_exit!(matches, "tpu_max_fwd_unstaked_connections", u64);
966
967 let tpu_max_connections_per_ipaddr_per_minute: u64 =
968 value_t_or_exit!(matches, "tpu_max_connections_per_ipaddr_per_minute", u64);
969 let max_streams_per_ms = value_t_or_exit!(matches, "tpu_max_streams_per_ms", u64);
970
971 let cluster_entrypoints = entrypoint_addrs
972 .iter()
973 .map(ContactInfo::new_gossip_entry_point)
974 .collect::<Vec<_>>();
975
976 if restricted_repair_only_mode {
977 node.info.remove_tpu();
981 node.info.remove_tpu_forwards();
982 node.info.remove_tvu();
983 node.info.remove_serve_repair();
984 node.info.remove_alpenglow();
985
986 node.sockets.ip_echo = None;
988 }
989
990 if !private_rpc {
991 macro_rules! set_socket {
992 ($method:ident, $addr:expr, $name:literal) => {
993 node.info.$method($addr).expect(&format!(
994 "Operator must spin up node with valid {} address",
995 $name
996 ))
997 };
998 }
999 if let Some(public_rpc_addr) = public_rpc_addr {
1000 set_socket!(set_rpc, public_rpc_addr, "RPC");
1001 set_socket!(set_rpc_pubsub, public_rpc_addr, "RPC-pubsub");
1002 } else if let Some((rpc_addr, rpc_pubsub_addr)) = validator_config.rpc_addrs {
1003 let addr = node
1004 .info
1005 .gossip()
1006 .expect("Operator must spin up node with valid gossip address")
1007 .ip();
1008 set_socket!(set_rpc, (addr, rpc_addr.port()), "RPC");
1009 set_socket!(set_rpc_pubsub, (addr, rpc_pubsub_addr.port()), "RPC-pubsub");
1010 }
1011 }
1012
1013 snapshot_utils::remove_tmp_snapshot_archives(
1014 &validator_config.snapshot_config.full_snapshot_archives_dir,
1015 );
1016 snapshot_utils::remove_tmp_snapshot_archives(
1017 &validator_config
1018 .snapshot_config
1019 .incremental_snapshot_archives_dir,
1020 );
1021
1022 if !cluster_entrypoints.is_empty() {
1023 bootstrap::rpc_bootstrap(
1024 &node,
1025 &identity_keypair,
1026 &ledger_path,
1027 &vote_account,
1028 authorized_voter_keypairs.clone(),
1029 &cluster_entrypoints,
1030 &mut validator_config,
1031 run_args.rpc_bootstrap_config,
1032 do_port_check,
1033 use_progress_bar,
1034 maximum_local_snapshot_age,
1035 &start_progress,
1036 minimal_snapshot_download_speed,
1037 maximum_snapshot_download_abort,
1038 run_args.socket_addr_space,
1039 );
1040 *start_progress.write().unwrap() = ValidatorStartProgress::Initializing;
1041 }
1042
1043 if operation == Operation::Initialize {
1044 info!("Validator ledger initialization complete");
1045 return Ok(());
1046 }
1047
1048 node.info.hot_swap_pubkey(identity_keypair.pubkey());
1054
1055 let tpu_quic_server_config = SwQosQuicStreamerConfig {
1056 quic_streamer_config: QuicStreamerConfig {
1057 max_connections_per_ipaddr_per_min: tpu_max_connections_per_ipaddr_per_minute,
1058 num_threads: tpu_transaction_receive_threads,
1059 stream_receive_window_size: solana_message::v1::MAX_TRANSACTION_SIZE as u32,
1060 max_stream_data_bytes: solana_message::v1::MAX_TRANSACTION_SIZE as u32,
1061 ..Default::default()
1062 },
1063 qos_config: SwQosConfig {
1064 max_connections_per_unstaked_peer: tpu_max_connections_per_unstaked_peer
1065 .try_into()
1066 .unwrap(),
1067 max_connections_per_staked_peer: tpu_max_connections_per_staked_peer
1068 .try_into()
1069 .unwrap(),
1070 max_staked_connections: tpu_max_staked_connections.try_into().unwrap(),
1071 max_unstaked_connections: tpu_max_unstaked_connections.try_into().unwrap(),
1072 max_streams_per_ms,
1073 },
1074 };
1075
1076 let tpu_fwd_quic_server_config = SwQosQuicStreamerConfig {
1077 quic_streamer_config: QuicStreamerConfig {
1078 max_connections_per_ipaddr_per_min: tpu_max_connections_per_ipaddr_per_minute,
1079 num_threads: tpu_transaction_forward_receive_threads,
1080 stream_receive_window_size: solana_message::v1::MAX_TRANSACTION_SIZE as u32,
1081 max_stream_data_bytes: solana_message::v1::MAX_TRANSACTION_SIZE as u32,
1082 ..Default::default()
1083 },
1084 qos_config: SwQosConfig {
1085 max_connections_per_staked_peer: tpu_max_connections_per_staked_peer
1086 .try_into()
1087 .unwrap(),
1088 max_connections_per_unstaked_peer: tpu_max_connections_per_unstaked_peer
1089 .try_into()
1090 .unwrap(),
1091 max_staked_connections: tpu_max_fwd_staked_connections.try_into().unwrap(),
1092 max_unstaked_connections: tpu_max_fwd_unstaked_connections.try_into().unwrap(),
1093 max_streams_per_ms,
1094 },
1095 };
1096
1097 let vote_quic_server_config = SimpleQosQuicStreamerConfig {
1098 quic_streamer_config: QuicStreamerConfig {
1099 max_connections_per_ipaddr_per_min: tpu_max_connections_per_ipaddr_per_minute,
1100 num_threads: tpu_vote_transaction_receive_threads,
1101 ..Default::default()
1102 },
1103 qos_config: SimpleQosConfig {
1104 max_streams_per_second: MAX_VOTES_PER_SECOND,
1105 ..Default::default()
1106 },
1107 };
1108
1109 let validator = Validator::new_with_exit(
1110 node,
1111 identity_keypair,
1112 &ledger_path,
1113 &vote_account,
1114 authorized_voter_keypairs,
1115 cluster_entrypoints,
1116 &validator_config,
1117 rpc_to_plugin_manager_receiver,
1118 start_progress,
1119 run_args.socket_addr_space,
1120 ValidatorTpuConfig {
1121 vote_use_quic,
1122 tpu_connection_pool_size,
1123 tpu_quic_server_config,
1124 tpu_fwd_quic_server_config,
1125 vote_quic_server_config,
1126 sigverify_threads: tpu_sigverify_threads,
1127 },
1128 admin_service_post_init,
1129 xdp_transmit_setup,
1130 exit,
1131 )
1132 .map_err(|err| format!("{err:?}"))?;
1133
1134 if let Some(filename) = init_complete_file {
1135 File::create(filename).map_err(|err| format!("unable to create {filename}: {err}"))?;
1136 }
1137 info!("Validator initialized");
1138 validator.listen_for_signals()?;
1139 validator.join();
1140 info!("Validator exiting...");
1141
1142 Ok(())
1143}
1144
1145fn hardforks_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<Slot>> {
1147 if matches.is_present(name) {
1148 Some(values_t_or_exit!(matches, name, Slot))
1149 } else {
1150 None
1151 }
1152}
1153
1154fn validators_set(
1155 identity_pubkey: &Pubkey,
1156 matches: &ArgMatches<'_>,
1157 matches_name: &str,
1158 arg_name: &str,
1159) -> Result<Option<HashSet<Pubkey>>, String> {
1160 if matches.is_present(matches_name) {
1161 let validators_set: HashSet<_> = values_t_or_exit!(matches, matches_name, Pubkey)
1162 .into_iter()
1163 .collect();
1164 if validators_set.contains(identity_pubkey) {
1165 Err(format!(
1166 "the validator's identity pubkey cannot be a {arg_name}: {identity_pubkey}"
1167 ))?;
1168 }
1169 Ok(Some(validators_set))
1170 } else {
1171 Ok(None)
1172 }
1173}
1174
1175fn get_cluster_shred_version(entrypoints: &[SocketAddr], bind_address: IpAddr) -> Option<u16> {
1176 let entrypoints = {
1177 let mut index: Vec<_> = (0..entrypoints.len()).collect();
1178 index.shuffle(&mut rand::rng());
1179 index.into_iter().map(|i| &entrypoints[i])
1180 };
1181 for entrypoint in entrypoints {
1182 match solana_net_utils::get_cluster_shred_version_with_binding(entrypoint, bind_address) {
1183 Err(err) => eprintln!("get_cluster_shred_version failed: {entrypoint}, {err}"),
1184 Ok(0) => eprintln!("entrypoint {entrypoint} returned shred-version zero"),
1185 Ok(shred_version) => {
1186 info!("obtained shred-version {shred_version} from {entrypoint}");
1187 return Some(shred_version);
1188 }
1189 }
1190 }
1191 None
1192}
1193
1194fn parse_banking_trace_dir_byte_limit(matches: &ArgMatches) -> u64 {
1195 if matches.is_present("disable_banking_trace") {
1196 DISABLED_BAKING_TRACE_DIR
1200 } else {
1201 value_t_or_exit!(matches, "banking_trace_dir_byte_limit", u64)
1204 }
1205}
1206
1207fn new_snapshot_config(
1208 matches: &ArgMatches,
1209 ledger_path: &Path,
1210 account_paths: &[PathBuf],
1211 incremental_snapshot_fetch: bool,
1212) -> Result<SnapshotConfig, Box<dyn std::error::Error>> {
1213 let (full_snapshot_archive_interval, incremental_snapshot_archive_interval) =
1214 if matches.is_present("no_snapshots") {
1215 (SnapshotInterval::Disabled, SnapshotInterval::Disabled)
1217 } else {
1218 match (
1219 incremental_snapshot_fetch,
1220 value_t_or_exit!(matches, "snapshot_interval_slots", NonZeroU64),
1221 ) {
1222 (true, incremental_snapshot_interval_slots) => {
1223 let full_snapshot_interval_slots =
1226 value_t_or_exit!(matches, "full_snapshot_interval_slots", NonZeroU64);
1227 (
1228 SnapshotInterval::Slots(full_snapshot_interval_slots),
1229 SnapshotInterval::Slots(incremental_snapshot_interval_slots),
1230 )
1231 }
1232 (false, full_snapshot_interval_slots) => {
1233 if matches.occurrences_of("full_snapshot_interval_slots") > 0 {
1237 warn!(
1238 "Incremental snapshots are disabled, yet \
1239 --full-snapshot-interval-slots was specified! Note that \
1240 --full-snapshot-interval-slots is *ignored* when incremental \
1241 snapshots are disabled. Use --snapshot-interval-slots instead.",
1242 );
1243 }
1244 (
1245 SnapshotInterval::Slots(full_snapshot_interval_slots),
1246 SnapshotInterval::Disabled,
1247 )
1248 }
1249 }
1250 };
1251
1252 info!(
1253 "Snapshot configuration: full snapshot interval: {}, incremental snapshot interval: {}",
1254 match full_snapshot_archive_interval {
1255 SnapshotInterval::Disabled => "disabled".to_string(),
1256 SnapshotInterval::Slots(interval) => format!("{interval} slots"),
1257 },
1258 match incremental_snapshot_archive_interval {
1259 SnapshotInterval::Disabled => "disabled".to_string(),
1260 SnapshotInterval::Slots(interval) => format!("{interval} slots"),
1261 },
1262 );
1263 if let SnapshotInterval::Slots(full_snapshot_interval_slots) = full_snapshot_archive_interval {
1266 let full_snapshot_interval_slots = full_snapshot_interval_slots.get();
1267 if full_snapshot_interval_slots > DEFAULT_SLOTS_PER_EPOCH {
1268 warn!(
1269 "The full snapshot interval is excessively large: {full_snapshot_interval_slots}! \
1270 This will negatively impact the background cleanup tasks in accounts-db. \
1271 Consider a smaller value.",
1272 );
1273 }
1274 }
1275
1276 let snapshots_dir = matches
1277 .value_of("snapshots")
1278 .map(Path::new)
1279 .unwrap_or(ledger_path);
1280 let snapshots_dir = create_and_canonicalize_directory(snapshots_dir).map_err(|err| {
1281 format!(
1282 "failed to create snapshots directory '{}': {err}",
1283 snapshots_dir.display(),
1284 )
1285 })?;
1286 if account_paths
1287 .iter()
1288 .any(|account_path| account_path == &snapshots_dir)
1289 {
1290 Err(
1291 "the --accounts and --snapshots paths must be unique since they both create \
1292 'snapshots' subdirectories, otherwise there may be collisions"
1293 .to_string(),
1294 )?;
1295 }
1296
1297 let bank_snapshots_dir = snapshots_dir.join(BANK_SNAPSHOTS_DIR);
1298 fs::create_dir_all(&bank_snapshots_dir).map_err(|err| {
1299 format!(
1300 "failed to create bank snapshots directory '{}': {err}",
1301 bank_snapshots_dir.display(),
1302 )
1303 })?;
1304
1305 let full_snapshot_archives_dir = matches
1306 .value_of("full_snapshot_archive_path")
1307 .map(PathBuf::from)
1308 .unwrap_or_else(|| snapshots_dir.clone());
1309 fs::create_dir_all(&full_snapshot_archives_dir).map_err(|err| {
1310 format!(
1311 "failed to create full snapshot archives directory '{}': {err}",
1312 full_snapshot_archives_dir.display(),
1313 )
1314 })?;
1315
1316 let incremental_snapshot_archives_dir = matches
1317 .value_of("incremental_snapshot_archive_path")
1318 .map(PathBuf::from)
1319 .unwrap_or_else(|| snapshots_dir.clone());
1320 fs::create_dir_all(&incremental_snapshot_archives_dir).map_err(|err| {
1321 format!(
1322 "failed to create incremental snapshot archives directory '{}': {err}",
1323 incremental_snapshot_archives_dir.display(),
1324 )
1325 })?;
1326
1327 let archive_format = {
1328 let archive_format_str = value_t_or_exit!(matches, "snapshot_archive_format", String);
1329 let mut archive_format = ArchiveFormat::from_cli_arg(&archive_format_str)
1330 .unwrap_or_else(|| panic!("Archive format not recognized: {archive_format_str}"));
1331 if let ArchiveFormat::TarZstd { config } = &mut archive_format {
1332 config.compression_level =
1333 value_t_or_exit!(matches, "snapshot_zstd_compression_level", i32);
1334 }
1335 archive_format
1336 };
1337
1338 let snapshot_version = matches
1339 .value_of("snapshot_version")
1340 .map(|value| {
1341 value
1342 .parse::<SnapshotVersion>()
1343 .map_err(|err| format!("unable to parse snapshot version: {err}"))
1344 })
1345 .transpose()?
1346 .unwrap_or(SnapshotVersion::default());
1347
1348 let maximum_full_snapshot_archives_to_retain =
1349 value_t_or_exit!(matches, "maximum_full_snapshots_to_retain", NonZeroUsize);
1350 let maximum_incremental_snapshot_archives_to_retain = value_t_or_exit!(
1351 matches,
1352 "maximum_incremental_snapshots_to_retain",
1353 NonZeroUsize
1354 );
1355
1356 let snapshot_config = SnapshotConfig {
1357 usage: if full_snapshot_archive_interval == SnapshotInterval::Disabled {
1358 SnapshotUsage::LoadOnly
1359 } else {
1360 SnapshotUsage::LoadAndGenerate
1361 },
1362 full_snapshot_archive_interval,
1363 incremental_snapshot_archive_interval,
1364 bank_snapshots_dir,
1365 full_snapshot_archives_dir,
1366 incremental_snapshot_archives_dir,
1367 archive_format,
1368 snapshot_version,
1369 maximum_full_snapshot_archives_to_retain,
1370 maximum_incremental_snapshot_archives_to_retain,
1371 use_registered_io_uring_buffers: resource_limits::check_memlock_limit_for_disk_io(
1372 solana_accounts_db::accounts_db::TOTAL_IO_URING_BUFFERS_SIZE_LIMIT,
1373 ),
1374 use_direct_io: !matches.is_present("no_accounts_db_snapshots_direct_io"),
1375 };
1376
1377 if !is_snapshot_config_valid(&snapshot_config) {
1378 Err(
1379 "invalid snapshot configuration provided: snapshot intervals are incompatible. full \
1380 snapshot interval MUST be larger than incremental snapshot interval (if enabled)"
1381 .to_string(),
1382 )?;
1383 }
1384
1385 Ok(snapshot_config)
1386}
1387
1388#[cfg(target_os = "linux")]
1389fn build_xdp_config(
1390 matches: &ArgMatches,
1391 operation: &Operation,
1392 bind_addresses: &BindIpAddrs,
1393) -> Result<Option<XdpConfig>, String> {
1394 if matches.is_present("no_xdp") || *operation == Operation::Initialize {
1395 return Ok(None);
1396 }
1397 if bind_addresses.len() > 1 {
1398 return Err(
1399 "XDP cannot be used in a multihoming context; pass --no-xdp to disable XDP".to_string(),
1400 );
1401 }
1402 let xdp_interface = matches
1403 .value_of("xdp_interface")
1404 .or_else(|| matches.value_of("experimental_retransmit_xdp_interface"));
1405 let xdp_zero_copy = matches.is_present("xdp_zero_copy")
1406 || matches.is_present("experimental_retransmit_xdp_zero_copy");
1407 let poh_pinned_cpu_core = value_of(matches, "poh_pinned_cpu_core")
1408 .or_else(|| value_of(matches, "experimental_poh_pinned_cpu_core"))
1409 .or(poh_service::DEFAULT_PINNED_CPU_CORE);
1410 let xdp_cpu_cores = matches
1411 .value_of("xdp_cpu_cores")
1412 .or_else(|| matches.value_of("experimental_retransmit_xdp_cpu_cores"));
1413 let cpus = if let Some(cpu_str) = xdp_cpu_cores {
1414 let parsed =
1415 parse_cpu_ranges(cpu_str).expect("clap validator already accepted this CPU list");
1416 if let Some(poh_core) = poh_pinned_cpu_core
1417 && parsed.contains(&poh_core)
1418 {
1419 return Err(format!(
1420 "--xdp-cpu-cores includes PoH core {poh_core}; XDP and PoH must not share a CPU \
1421 core"
1422 ));
1423 }
1424 Some(parsed)
1425 } else {
1426 match cpu_affinity(None) {
1428 Ok(allowed) => {
1429 match allowed
1430 .iter()
1431 .rev()
1432 .map(|cpu| **cpu)
1433 .find(|cpu| Some(*cpu) != poh_pinned_cpu_core)
1434 {
1435 Some(cpu) => Some(vec![cpu]),
1436 None => {
1437 return Err(format!(
1438 "XDP requires a dedicated CPU core separate from PoH (core \
1439 {poh_pinned_cpu_core:?}), but none is available. Pass --no-xdp to \
1440 disable XDP."
1441 ));
1442 }
1443 }
1444 }
1445 Err(e) => {
1446 return Err(format!(
1447 "failed to query CPU affinity: {e}. Pass --no-xdp to disable XDP, or provide \
1448 --xdp-cpu-cores explicitly."
1449 ));
1450 }
1451 }
1452 };
1453 Ok(cpus.map(|cpus| {
1454 info!("XDP enabled on CPU cores: {cpus:?}");
1455 let queues = cpus
1457 .into_iter()
1458 .enumerate()
1459 .map(|(queue, cpu)| QueueCpuBinding {
1460 queue: queue as u32,
1461 cpu,
1462 })
1463 .collect();
1464 XdpConfig::new(xdp_interface, queues, xdp_zero_copy)
1465 }))
1466}
1467
1468#[cfg(all(target_os = "linux", test))]
1469mod xdp_tests {
1470 use {
1471 super::*,
1472 crate::{cli::DefaultArgs, commands::run::args::add_args},
1473 solana_net_utils::multihomed_sockets::BindIpAddrs,
1474 std::net::{IpAddr, Ipv4Addr},
1475 };
1476
1477 fn single_ip_bind() -> BindIpAddrs {
1478 BindIpAddrs::new(vec![Ipv4Addr::UNSPECIFIED.into()]).unwrap()
1479 }
1480
1481 fn multihoming_bind() -> BindIpAddrs {
1482 BindIpAddrs::new(vec![
1483 IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)),
1484 IpAddr::V4(Ipv4Addr::new(2, 2, 2, 2)),
1485 ])
1486 .unwrap()
1487 }
1488
1489 #[test]
1490 fn test_no_xdp_flag_disables_xdp() {
1491 let default_args = DefaultArgs::default();
1492 let app = add_args(clap::App::new("agave-validator"), &default_args);
1493 let matches = app.get_matches_from(vec!["agave-validator", "--no-xdp"]);
1494 let result = build_xdp_config(&matches, &Operation::Run, &single_ip_bind());
1495 assert!(result.unwrap().is_none(), "--no-xdp must disable XDP");
1496 }
1497
1498 #[test]
1499 fn test_init_disables_xdp() {
1500 let default_args = DefaultArgs::default();
1501 let app = add_args(clap::App::new("agave-validator"), &default_args);
1502 let matches = app.get_matches_from(vec!["agave-validator"]);
1503 let result = build_xdp_config(&matches, &Operation::Initialize, &single_ip_bind());
1504 assert!(result.unwrap().is_none(), "init operation must disable XDP");
1505 }
1506
1507 #[test]
1508 fn test_multihoming_is_error() {
1509 let default_args = DefaultArgs::default();
1510 let app = add_args(clap::App::new("agave-validator"), &default_args);
1511 let matches = app.get_matches_from(vec!["agave-validator"]);
1512 let result = build_xdp_config(&matches, &Operation::Run, &multihoming_bind());
1513 assert!(
1514 result.unwrap_err().contains("multihoming"),
1515 "multihoming context must produce an error"
1516 );
1517 }
1518
1519 #[test]
1520 fn test_explicit_xdp_core_conflicts_with_poh_core_is_error() {
1521 let default_args = DefaultArgs::default();
1522 let app = add_args(clap::App::new("agave-validator"), &default_args);
1523 let poh_core = solana_poh::poh_service::DEFAULT_PINNED_CPU_CORE
1524 .unwrap_or(0)
1525 .to_string();
1526 let matches = app.get_matches_from(vec!["agave-validator", "--xdp-cpu-cores", &poh_core]);
1527 let result = build_xdp_config(&matches, &Operation::Run, &single_ip_bind());
1528 assert!(
1529 result.unwrap_err().contains("PoH core"),
1530 "XDP core overlapping PoH core must produce an error"
1531 );
1532 }
1533}