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