1use {
2 agave_snapshots::{
3 SnapshotArchiveKind, paths as snapshot_paths,
4 snapshot_archive_info::SnapshotArchiveInfoGetter as _,
5 },
6 itertools::Itertools,
7 log::*,
8 rand::{Rng, rng, seq::SliceRandom},
9 rayon::prelude::*,
10 solana_account::ReadableAccount,
11 solana_clock::Slot,
12 solana_commitment_config::CommitmentConfig,
13 solana_core::validator::{ValidatorConfig, ValidatorStartProgress},
14 solana_download_utils::{DownloadProgressRecord, download_snapshot_archive},
15 solana_genesis_utils::download_then_check_genesis_hash,
16 solana_gossip::{
17 cluster_info::ClusterInfo,
18 contact_info::{ContactInfo, Protocol},
19 crds_data,
20 gossip_service::GossipService,
21 node::Node,
22 },
23 solana_hash::Hash,
24 solana_keypair::Keypair,
25 solana_metrics::datapoint_info,
26 solana_net_utils::SocketAddrSpace,
27 solana_pubkey::Pubkey,
28 solana_rpc_client::rpc_client::RpcClient,
29 solana_signer::Signer,
30 solana_vote_program::vote_state::VoteStateV4,
31 std::{
32 collections::{HashMap, HashSet, hash_map::RandomState},
33 net::{SocketAddr, TcpListener, TcpStream, UdpSocket},
34 path::Path,
35 process::exit,
36 sync::{
37 Arc, RwLock,
38 atomic::{AtomicBool, Ordering},
39 },
40 time::{Duration, Instant},
41 },
42 thiserror::Error,
43};
44
45const WAIT_FOR_ALL_KNOWN_VALIDATORS: Duration = Duration::from_secs(60);
49const BLACKLIST_CLEAR_THRESHOLD: Duration = Duration::from_secs(60);
52const NEWER_SNAPSHOT_THRESHOLD: Duration = Duration::from_secs(180);
55const GET_RPC_PEERS_TIMEOUT: Duration = Duration::from_secs(300);
57
58pub const MAX_RPC_CONNECTIONS_EVALUATED_PER_ITERATION: usize = 32;
59
60pub const PING_TIMEOUT: Duration = Duration::from_secs(2);
61
62#[derive(Debug, PartialEq, Clone)]
63pub struct RpcBootstrapConfig {
64 pub no_genesis_fetch: bool,
65 pub no_snapshot_fetch: bool,
66 pub only_known_rpc: bool,
67 pub max_genesis_archive_unpacked_size: u64,
68 pub check_vote_account: Option<String>,
69 pub incremental_snapshot_fetch: bool,
70}
71
72fn verify_reachable_ports(
73 node: &Node,
74 cluster_entrypoint: &ContactInfo,
75 validator_config: &ValidatorConfig,
76 socket_addr_space: &SocketAddrSpace,
77) -> bool {
78 let verify_address = |addr: &Option<SocketAddr>| -> bool {
79 addr.as_ref()
80 .map(|addr| socket_addr_space.check(addr))
81 .unwrap_or_default()
82 };
83
84 let mut udp_sockets = vec![&node.sockets.repair];
85 udp_sockets.extend(node.sockets.gossip.iter());
86
87 if verify_address(&node.info.serve_repair(Protocol::UDP)) {
88 udp_sockets.push(&node.sockets.serve_repair);
89 }
90 if verify_address(&node.info.tpu_vote(Protocol::UDP)) {
91 udp_sockets.extend(node.sockets.tpu_vote.iter());
92 }
93 if verify_address(&node.info.tvu(Protocol::UDP)) {
94 udp_sockets.extend(node.sockets.tvu.iter());
95 udp_sockets.extend(node.sockets.broadcast.iter());
96 udp_sockets.extend(node.sockets.retransmit_sockets.iter());
97 }
98 if !solana_net_utils::verify_all_reachable_udp(
99 &cluster_entrypoint.gossip().unwrap(),
100 &udp_sockets,
101 ) {
102 return false;
103 }
104
105 let mut tcp_listeners = vec![];
106 if let Some((rpc_addr, rpc_pubsub_addr)) = validator_config.rpc_addrs {
107 for (purpose, bind_addr, public_addr) in &[
108 ("RPC", rpc_addr, node.info.rpc()),
109 ("RPC pubsub", rpc_pubsub_addr, node.info.rpc_pubsub()),
110 ] {
111 if verify_address(public_addr) {
112 tcp_listeners.push(TcpListener::bind(bind_addr).unwrap_or_else(|err| {
113 error!("Unable to bind to tcp {bind_addr:?} for {purpose}: {err}");
114 exit(1);
115 }));
116 }
117 }
118 }
119
120 if let Some(ip_echo) = &node.sockets.ip_echo {
121 let ip_echo = ip_echo.try_clone().expect("unable to clone tcp_listener");
122 tcp_listeners.push(ip_echo);
123 }
124
125 solana_net_utils::verify_all_reachable_tcp(&cluster_entrypoint.gossip().unwrap(), tcp_listeners)
126}
127
128fn is_known_validator(id: &Pubkey, known_validators: &Option<HashSet<Pubkey>>) -> bool {
129 if let Some(known_validators) = known_validators {
130 known_validators.contains(id)
131 } else {
132 false
133 }
134}
135
136#[allow(clippy::too_many_arguments)]
137fn start_gossip_node(
138 identity_keypair: Arc<Keypair>,
139 cluster_entrypoints: &[ContactInfo],
140 known_validators: Option<HashSet<Pubkey>>,
141 ledger_path: &Path,
142 gossip_addr: &SocketAddr,
143 gossip_sockets: Arc<[UdpSocket]>,
144 expected_shred_version: u16,
145 gossip_validators: Option<HashSet<Pubkey>>,
146 should_check_duplicate_instance: bool,
147 socket_addr_space: SocketAddrSpace,
148) -> (Arc<ClusterInfo>, Arc<AtomicBool>, GossipService) {
149 let contact_info = ClusterInfo::gossip_contact_info(
150 identity_keypair.pubkey(),
151 *gossip_addr,
152 expected_shred_version,
153 );
154 let mut cluster_info = ClusterInfo::new(contact_info, identity_keypair, socket_addr_space);
155 if let Some(known_validators) = known_validators {
156 cluster_info
157 .set_trim_keep_pubkeys(known_validators)
158 .expect("set_trim_keep_pubkeys should succeed as ClusterInfo was just created");
159 }
160 cluster_info.set_entrypoints(cluster_entrypoints.to_vec());
161 cluster_info.restore_contact_info(ledger_path, 0);
162 let cluster_info = Arc::new(cluster_info);
163
164 let gossip_exit_flag = Arc::new(AtomicBool::new(false));
165 let gossip_service = GossipService::new(
166 &cluster_info,
167 None,
168 gossip_sockets,
169 None,
170 gossip_validators,
171 should_check_duplicate_instance,
172 None,
173 gossip_exit_flag.clone(),
174 );
175 (cluster_info, gossip_exit_flag, gossip_service)
176}
177
178fn get_rpc_peers(
179 cluster_info: &ClusterInfo,
180 validator_config: &ValidatorConfig,
181 blacklisted_rpc_nodes: &mut HashSet<Pubkey>,
182 blacklist_timeout: &Instant,
183 retry_reason: &mut Option<String>,
184 bootstrap_config: &RpcBootstrapConfig,
185) -> Vec<ContactInfo> {
186 let shred_version = validator_config
187 .expected_shred_version
188 .unwrap_or_else(|| cluster_info.my_shred_version());
189
190 info!(
191 "Searching for an RPC service with shred version {shred_version}{}...",
192 retry_reason
193 .as_ref()
194 .map(|s| format!(" (Retrying: {s})"))
195 .unwrap_or_default()
196 );
197
198 let mut rpc_peers = cluster_info.rpc_peers();
199 if bootstrap_config.only_known_rpc {
200 rpc_peers.retain(|rpc_peer| {
201 is_known_validator(rpc_peer.pubkey(), &validator_config.known_validators)
202 });
203 }
204
205 let rpc_peers_total = rpc_peers.len();
206
207 let rpc_peers: Vec<_> = rpc_peers
209 .into_iter()
210 .filter(|rpc_peer| !blacklisted_rpc_nodes.contains(rpc_peer.pubkey()))
211 .collect();
212 let rpc_peers_blacklisted = rpc_peers_total - rpc_peers.len();
213 let rpc_known_peers = rpc_peers
214 .iter()
215 .filter(|rpc_peer| {
216 is_known_validator(rpc_peer.pubkey(), &validator_config.known_validators)
217 })
218 .count();
219
220 info!(
221 "Total {rpc_peers_total} RPC nodes found. {rpc_known_peers} known, \
222 {rpc_peers_blacklisted} blacklisted"
223 );
224
225 if rpc_peers_blacklisted == rpc_peers_total {
226 *retry_reason = if !blacklisted_rpc_nodes.is_empty()
227 && blacklist_timeout.elapsed() > BLACKLIST_CLEAR_THRESHOLD
228 {
229 blacklisted_rpc_nodes.clear();
232 Some("Blacklist timeout expired".to_owned())
233 } else {
234 Some("Wait for known rpc peers".to_owned())
235 };
236 return vec![];
237 }
238 rpc_peers
239}
240
241fn check_vote_account(
242 rpc_client: &RpcClient,
243 identity_pubkey: &Pubkey,
244 vote_account_address: &Pubkey,
245 authorized_voter_pubkeys: &[Pubkey],
246) -> Result<(), String> {
247 let vote_account = rpc_client
248 .get_account_with_commitment(vote_account_address, CommitmentConfig::confirmed())
249 .map_err(|err| format!("failed to fetch vote account: {err}"))?
250 .value
251 .ok_or_else(|| format!("vote account does not exist: {vote_account_address}"))?;
252
253 if vote_account.owner != solana_vote_program::id() {
254 return Err(format!(
255 "not a vote account (owned by {}): {}",
256 vote_account.owner, vote_account_address
257 ));
258 }
259
260 let identity_account = rpc_client
261 .get_account_with_commitment(identity_pubkey, CommitmentConfig::confirmed())
262 .map_err(|err| format!("failed to fetch identity account: {err}"))?
263 .value
264 .ok_or_else(|| format!("identity account does not exist: {identity_pubkey}"))?;
265
266 let vote_state = VoteStateV4::deserialize(vote_account.data(), vote_account_address).ok();
267 if let Some(vote_state) = vote_state {
268 if vote_state.authorized_voters.is_empty() {
269 return Err("Vote account not yet initialized".to_string());
270 }
271
272 if vote_state.node_pubkey != *identity_pubkey {
273 return Err(format!(
274 "vote account's identity ({}) does not match the validator's identity {}).",
275 vote_state.node_pubkey, identity_pubkey
276 ));
277 }
278
279 for (_, vote_account_authorized_voter_pubkey) in vote_state.authorized_voters.iter() {
280 if !authorized_voter_pubkeys.contains(vote_account_authorized_voter_pubkey) {
281 return Err(format!(
282 "authorized voter {vote_account_authorized_voter_pubkey} not available"
283 ));
284 }
285 }
286 } else {
287 return Err(format!(
288 "invalid vote account data for {vote_account_address}"
289 ));
290 }
291
292 if identity_account.lamports <= 1 {
294 return Err(format!(
295 "underfunded identity account ({}): only {} lamports available",
296 identity_pubkey, identity_account.lamports
297 ));
298 }
299
300 Ok(())
301}
302
303#[derive(Error, Debug)]
304pub enum GetRpcNodeError {
305 #[error("Unable to find any RPC peers")]
306 NoRpcPeersFound,
307
308 #[error("Giving up, did not get newer snapshots from the cluster")]
309 NoNewerSnapshots,
310}
311
312#[derive(Debug)]
316struct GetRpcNodeResult {
317 rpc_contact_info: ContactInfo,
318 snapshot_hash: Option<SnapshotHash>,
319}
320
321#[derive(Debug, PartialEq, Eq, Clone)]
323struct PeerSnapshotHash {
324 rpc_contact_info: ContactInfo,
325 snapshot_hash: SnapshotHash,
326}
327
328#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
331pub struct SnapshotHash {
332 full: (Slot, Hash),
333 incr: Option<(Slot, Hash)>,
334}
335
336pub fn fail_rpc_node(
337 err: String,
338 known_validators: &Option<HashSet<Pubkey, RandomState>>,
339 rpc_id: &Pubkey,
340 blacklisted_rpc_nodes: &mut HashSet<Pubkey, RandomState>,
341) {
342 warn!("{err}");
343 if let Some(known_validators) = known_validators
344 && known_validators.contains(rpc_id)
345 {
346 return;
347 }
348
349 info!("Excluding {rpc_id} as a future RPC candidate");
350 blacklisted_rpc_nodes.insert(*rpc_id);
351}
352
353fn shutdown_gossip_service(gossip: (Arc<ClusterInfo>, Arc<AtomicBool>, GossipService)) {
354 let (cluster_info, gossip_exit_flag, gossip_service) = gossip;
355 cluster_info.save_contact_info();
356 gossip_exit_flag.store(true, Ordering::Relaxed);
357 gossip_service.join().unwrap();
358}
359
360#[allow(clippy::too_many_arguments)]
361pub fn attempt_download_genesis_and_snapshot(
362 rpc_contact_info: &ContactInfo,
363 ledger_path: &Path,
364 validator_config: &mut ValidatorConfig,
365 bootstrap_config: &RpcBootstrapConfig,
366 use_progress_bar: bool,
367 gossip: &mut Option<(Arc<ClusterInfo>, Arc<AtomicBool>, GossipService)>,
368 rpc_client: &RpcClient,
369 maximum_local_snapshot_age: Slot,
370 start_progress: &Arc<RwLock<ValidatorStartProgress>>,
371 minimal_snapshot_download_speed: f32,
372 maximum_snapshot_download_abort: u64,
373 download_abort_count: &mut u64,
374 snapshot_hash: Option<SnapshotHash>,
375 identity_keypair: &Arc<Keypair>,
376 vote_account: &Pubkey,
377 authorized_voter_keypairs: Arc<RwLock<Vec<Arc<Keypair>>>>,
378) -> Result<(), String> {
379 download_then_check_genesis_hash(
380 &rpc_contact_info
381 .rpc()
382 .ok_or_else(|| String::from("Invalid RPC address"))?,
383 ledger_path,
384 &mut validator_config.expected_genesis_hash,
385 bootstrap_config.max_genesis_archive_unpacked_size,
386 bootstrap_config.no_genesis_fetch,
387 use_progress_bar,
388 rpc_client,
389 )?;
390
391 if let Some(gossip) = gossip.take() {
392 shutdown_gossip_service(gossip);
393 }
394
395 let rpc_client_slot = rpc_client
396 .get_slot_with_commitment(CommitmentConfig::finalized())
397 .map_err(|err| format!("Failed to get RPC node slot: {err}"))?;
398 info!("RPC node root slot: {rpc_client_slot}");
399
400 download_snapshots(
401 validator_config,
402 bootstrap_config,
403 use_progress_bar,
404 maximum_local_snapshot_age,
405 start_progress,
406 minimal_snapshot_download_speed,
407 maximum_snapshot_download_abort,
408 download_abort_count,
409 snapshot_hash,
410 rpc_contact_info,
411 )?;
412
413 if let Some(url) = bootstrap_config.check_vote_account.as_ref() {
414 let rpc_client = RpcClient::new(url);
415 check_vote_account(
416 &rpc_client,
417 &identity_keypair.pubkey(),
418 vote_account,
419 &authorized_voter_keypairs
420 .read()
421 .unwrap()
422 .iter()
423 .map(|k| k.pubkey())
424 .collect::<Vec<_>>(),
425 )
426 .unwrap_or_else(|err| {
427 error!("{err}");
434 exit(1);
435 });
436 }
437 Ok(())
438}
439
440fn ping(addr: &SocketAddr) -> Option<Duration> {
442 let start = Instant::now();
443 match TcpStream::connect_timeout(addr, PING_TIMEOUT) {
444 Ok(_) => Some(start.elapsed()),
445 Err(_) => None,
446 }
447}
448
449fn get_vetted_rpc_nodes(
453 vetted_rpc_nodes: &mut Vec<(ContactInfo, Option<SnapshotHash>, RpcClient)>,
454 cluster_info: &Arc<ClusterInfo>,
455 validator_config: &ValidatorConfig,
456 blacklisted_rpc_nodes: &mut HashSet<Pubkey>,
457 bootstrap_config: &RpcBootstrapConfig,
458) {
459 while vetted_rpc_nodes.is_empty() {
460 let rpc_node_details = match get_rpc_nodes(
461 cluster_info,
462 validator_config,
463 blacklisted_rpc_nodes,
464 bootstrap_config,
465 ) {
466 Ok(rpc_node_details) => rpc_node_details,
467 Err(err) => {
468 error!(
469 "Failed to get RPC nodes: {err}. Consider checking system clock, removing \
470 `--no-port-check`, or adjusting `--known-validator ...` arguments as \
471 applicable"
472 );
473 exit(1);
474 }
475 };
476
477 let newly_blacklisted_rpc_nodes = RwLock::new(HashSet::new());
478 vetted_rpc_nodes.extend(
479 rpc_node_details
480 .into_par_iter()
481 .filter_map(|rpc_node_details| {
482 let GetRpcNodeResult {
483 rpc_contact_info,
484 snapshot_hash,
485 } = rpc_node_details;
486
487 info!(
488 "Using RPC service from node {}: {:?}",
489 rpc_contact_info.pubkey(),
490 rpc_contact_info.rpc()
491 );
492
493 let rpc_addr = rpc_contact_info.rpc()?;
494 let ping_time = ping(&rpc_addr);
495
496 let rpc_client =
497 RpcClient::new_socket_with_timeout(rpc_addr, Duration::from_secs(5));
498
499 Some((rpc_contact_info, snapshot_hash, rpc_client, ping_time))
500 })
501 .filter(
502 |(rpc_contact_info, _snapshot_hash, rpc_client, ping_time)| match rpc_client
503 .get_version()
504 {
505 Ok(rpc_version) => {
506 if let Some(ping_time) = ping_time {
507 info!(
508 "RPC node version: {} Ping: {}ms",
509 rpc_version.solana_core,
510 ping_time.as_millis()
511 );
512 true
513 } else {
514 fail_rpc_node(
515 "Failed to ping RPC".to_string(),
516 &validator_config.known_validators,
517 rpc_contact_info.pubkey(),
518 &mut newly_blacklisted_rpc_nodes.write().unwrap(),
519 );
520 false
521 }
522 }
523 Err(err) => {
524 fail_rpc_node(
525 format!("Failed to get RPC node version: {err}"),
526 &validator_config.known_validators,
527 rpc_contact_info.pubkey(),
528 &mut newly_blacklisted_rpc_nodes.write().unwrap(),
529 );
530 false
531 }
532 },
533 )
534 .collect::<Vec<(
535 ContactInfo,
536 Option<SnapshotHash>,
537 RpcClient,
538 Option<Duration>,
539 )>>()
540 .into_iter()
541 .sorted_by_key(|(_, _, _, ping_time)| ping_time.unwrap())
542 .map(|(rpc_contact_info, snapshot_hash, rpc_client, _)| {
543 (rpc_contact_info, snapshot_hash, rpc_client)
544 })
545 .collect::<Vec<(ContactInfo, Option<SnapshotHash>, RpcClient)>>(),
546 );
547 blacklisted_rpc_nodes.extend(newly_blacklisted_rpc_nodes.into_inner().unwrap());
548 }
549}
550
551#[allow(clippy::too_many_arguments)]
552pub fn rpc_bootstrap(
553 node: &Node,
554 identity_keypair: &Arc<Keypair>,
555 ledger_path: &Path,
556 vote_account: &Pubkey,
557 authorized_voter_keypairs: Arc<RwLock<Vec<Arc<Keypair>>>>,
558 cluster_entrypoints: &[ContactInfo],
559 validator_config: &mut ValidatorConfig,
560 bootstrap_config: RpcBootstrapConfig,
561 do_port_check: bool,
562 use_progress_bar: bool,
563 maximum_local_snapshot_age: Slot,
564 start_progress: &Arc<RwLock<ValidatorStartProgress>>,
565 minimal_snapshot_download_speed: f32,
566 maximum_snapshot_download_abort: u64,
567 socket_addr_space: SocketAddrSpace,
568) {
569 if do_port_check {
570 let mut order: Vec<_> = (0..cluster_entrypoints.len()).collect();
571 order.shuffle(&mut rng());
572 if order.into_iter().all(|i| {
573 !verify_reachable_ports(
574 node,
575 &cluster_entrypoints[i],
576 validator_config,
577 &socket_addr_space,
578 )
579 }) {
580 exit(1);
581 }
582 }
583
584 if bootstrap_config.no_genesis_fetch && bootstrap_config.no_snapshot_fetch {
585 return;
586 }
587
588 let total_snapshot_download_time = Instant::now();
589 let mut get_rpc_nodes_time = Duration::new(0, 0);
590 let mut snapshot_download_time = Duration::new(0, 0);
591 let mut blacklisted_rpc_nodes = HashSet::new();
592 let mut gossip = None;
593 let mut vetted_rpc_nodes = vec![];
594 let mut download_abort_count = 0;
595 loop {
596 if gossip.is_none() {
597 *start_progress.write().unwrap() = ValidatorStartProgress::SearchingForRpcService;
598
599 gossip = Some(start_gossip_node(
600 identity_keypair.clone(),
601 cluster_entrypoints,
602 validator_config.known_validators.clone(),
603 ledger_path,
604 &node
605 .info
606 .gossip()
607 .expect("Operator must spin up node with valid gossip address"),
608 node.sockets.gossip.clone(),
609 validator_config
610 .expected_shred_version
611 .expect("expected_shred_version should not be None"),
612 validator_config.gossip_validators.clone(),
613 validator_config.should_check_duplicate_instance,
614 socket_addr_space,
615 ));
616 }
617
618 let get_rpc_nodes_start = Instant::now();
619 get_vetted_rpc_nodes(
620 &mut vetted_rpc_nodes,
621 &gossip.as_ref().unwrap().0,
622 validator_config,
623 &mut blacklisted_rpc_nodes,
624 &bootstrap_config,
625 );
626 let (rpc_contact_info, snapshot_hash, rpc_client) = vetted_rpc_nodes.remove(0);
629 get_rpc_nodes_time += get_rpc_nodes_start.elapsed();
630
631 let snapshot_download_start = Instant::now();
632 let download_result = attempt_download_genesis_and_snapshot(
633 &rpc_contact_info,
634 ledger_path,
635 validator_config,
636 &bootstrap_config,
637 use_progress_bar,
638 &mut gossip,
639 &rpc_client,
640 maximum_local_snapshot_age,
641 start_progress,
642 minimal_snapshot_download_speed,
643 maximum_snapshot_download_abort,
644 &mut download_abort_count,
645 snapshot_hash,
646 identity_keypair,
647 vote_account,
648 authorized_voter_keypairs.clone(),
649 );
650 snapshot_download_time += snapshot_download_start.elapsed();
651 match download_result {
652 Ok(()) => break,
653 Err(err) => {
654 fail_rpc_node(
655 err,
656 &validator_config.known_validators,
657 rpc_contact_info.pubkey(),
658 &mut blacklisted_rpc_nodes,
659 );
660 }
661 }
662 }
663
664 if let Some(gossip) = gossip.take() {
665 shutdown_gossip_service(gossip);
666 }
667
668 datapoint_info!(
669 "bootstrap-snapshot-download",
670 (
671 "total_time_secs",
672 total_snapshot_download_time.elapsed().as_secs(),
673 i64
674 ),
675 ("get_rpc_nodes_time_secs", get_rpc_nodes_time.as_secs(), i64),
676 (
677 "snapshot_download_time_secs",
678 snapshot_download_time.as_secs(),
679 i64
680 ),
681 ("download_abort_count", download_abort_count, i64),
682 ("blacklisted_nodes_count", blacklisted_rpc_nodes.len(), i64),
683 );
684}
685
686fn get_rpc_nodes(
690 cluster_info: &ClusterInfo,
691 validator_config: &ValidatorConfig,
692 blacklisted_rpc_nodes: &mut HashSet<Pubkey>,
693 bootstrap_config: &RpcBootstrapConfig,
694) -> Result<Vec<GetRpcNodeResult>, GetRpcNodeError> {
695 let mut blacklist_timeout = Instant::now();
696 let mut get_rpc_peers_timout = Instant::now();
697 let mut newer_cluster_snapshot_timeout = None;
698 let mut retry_reason = None;
699 loop {
700 std::thread::sleep(Duration::from_secs(1));
702 info!("\n{}", cluster_info.rpc_info_trace());
703
704 let rpc_peers = get_rpc_peers(
705 cluster_info,
706 validator_config,
707 blacklisted_rpc_nodes,
708 &blacklist_timeout,
709 &mut retry_reason,
710 bootstrap_config,
711 );
712 if rpc_peers.is_empty() {
713 if get_rpc_peers_timout.elapsed() > GET_RPC_PEERS_TIMEOUT {
714 return Err(GetRpcNodeError::NoRpcPeersFound);
715 }
716 continue;
717 }
718
719 blacklist_timeout = Instant::now();
721 get_rpc_peers_timout = Instant::now();
722 if bootstrap_config.no_snapshot_fetch {
723 let random_peer = &rpc_peers[rng().random_range(0..rpc_peers.len())];
724 return Ok(vec![GetRpcNodeResult {
725 rpc_contact_info: random_peer.clone(),
726 snapshot_hash: None,
727 }]);
728 }
729
730 let known_validators_to_wait_for = if newer_cluster_snapshot_timeout
731 .as_ref()
732 .map(|timer: &Instant| timer.elapsed() < WAIT_FOR_ALL_KNOWN_VALIDATORS)
733 .unwrap_or(true)
734 {
735 KnownValidatorsToWaitFor::All
736 } else {
737 KnownValidatorsToWaitFor::Any
738 };
739 let peer_snapshot_hashes = get_peer_snapshot_hashes(
740 cluster_info,
741 &rpc_peers,
742 validator_config.known_validators.as_ref(),
743 known_validators_to_wait_for,
744 bootstrap_config.incremental_snapshot_fetch,
745 );
746 if peer_snapshot_hashes.is_empty() {
747 match newer_cluster_snapshot_timeout {
748 None => newer_cluster_snapshot_timeout = Some(Instant::now()),
749 Some(newer_cluster_snapshot_timeout) => {
750 if newer_cluster_snapshot_timeout.elapsed() > NEWER_SNAPSHOT_THRESHOLD {
751 return Err(GetRpcNodeError::NoNewerSnapshots);
752 }
753 }
754 }
755 retry_reason = Some("No snapshots available".to_owned());
756 continue;
757 } else {
758 let rpc_peers = peer_snapshot_hashes
759 .iter()
760 .map(|peer_snapshot_hash| peer_snapshot_hash.rpc_contact_info.pubkey())
761 .collect::<Vec<_>>();
762 let final_snapshot_hash = peer_snapshot_hashes[0].snapshot_hash;
763 info!(
764 "Highest available snapshot slot is {}, available from {} node{}: {:?}",
765 final_snapshot_hash
766 .incr
767 .map(|(slot, _hash)| slot)
768 .unwrap_or(final_snapshot_hash.full.0),
769 rpc_peers.len(),
770 if rpc_peers.len() > 1 { "s" } else { "" },
771 rpc_peers,
772 );
773 let rpc_node_results = peer_snapshot_hashes
774 .iter()
775 .map(|peer_snapshot_hash| GetRpcNodeResult {
776 rpc_contact_info: peer_snapshot_hash.rpc_contact_info.clone(),
777 snapshot_hash: Some(peer_snapshot_hash.snapshot_hash),
778 })
779 .take(MAX_RPC_CONNECTIONS_EVALUATED_PER_ITERATION)
780 .collect();
781 return Ok(rpc_node_results);
782 }
783 }
784}
785
786fn get_highest_local_snapshot_hash(
789 full_snapshot_archives_dir: impl AsRef<Path>,
790 incremental_snapshot_archives_dir: impl AsRef<Path>,
791 incremental_snapshot_fetch: bool,
792) -> Option<(Slot, Hash)> {
793 snapshot_paths::get_highest_full_snapshot_archive_info(full_snapshot_archives_dir)
794 .and_then(|full_snapshot_info| {
795 if incremental_snapshot_fetch {
796 snapshot_paths::get_highest_incremental_snapshot_archive_info(
797 incremental_snapshot_archives_dir,
798 full_snapshot_info.slot(),
799 )
800 .map(|incremental_snapshot_info| {
801 (
802 incremental_snapshot_info.slot(),
803 *incremental_snapshot_info.hash(),
804 )
805 })
806 } else {
807 None
808 }
809 .or_else(|| Some((full_snapshot_info.slot(), *full_snapshot_info.hash())))
810 })
811 .map(|(slot, snapshot_hash)| (slot, snapshot_hash.0))
812}
813
814fn get_peer_snapshot_hashes(
821 cluster_info: &ClusterInfo,
822 rpc_peers: &[ContactInfo],
823 known_validators: Option<&HashSet<Pubkey>>,
824 known_validators_to_wait_for: KnownValidatorsToWaitFor,
825 incremental_snapshot_fetch: bool,
826) -> Vec<PeerSnapshotHash> {
827 let mut peer_snapshot_hashes = get_eligible_peer_snapshot_hashes(cluster_info, rpc_peers);
828 if let Some(known_validators) = known_validators {
829 let known_snapshot_hashes = get_snapshot_hashes_from_known_validators(
830 cluster_info,
831 known_validators,
832 known_validators_to_wait_for,
833 );
834 retain_peer_snapshot_hashes_that_match_known_snapshot_hashes(
835 &known_snapshot_hashes,
836 &mut peer_snapshot_hashes,
837 );
838 }
839 if incremental_snapshot_fetch {
840 retain_peer_snapshot_hashes_with_highest_incremental_snapshot_slot(
848 &mut peer_snapshot_hashes,
849 );
850 }
851 retain_peer_snapshot_hashes_with_highest_full_snapshot_slot(&mut peer_snapshot_hashes);
852
853 peer_snapshot_hashes
854}
855
856type KnownSnapshotHashes = HashMap<(Slot, Hash), HashSet<(Slot, Hash)>>;
859
860fn get_snapshot_hashes_from_known_validators(
871 cluster_info: &ClusterInfo,
872 known_validators: &HashSet<Pubkey>,
873 known_validators_to_wait_for: KnownValidatorsToWaitFor,
874) -> KnownSnapshotHashes {
875 let get_snapshot_hashes_for_node = |node| get_snapshot_hashes_for_node(cluster_info, node);
877
878 if !do_known_validators_have_all_snapshot_hashes(
879 known_validators,
880 known_validators_to_wait_for,
881 get_snapshot_hashes_for_node,
882 ) {
883 debug!(
884 "Snapshot hashes have not been discovered from known validators. This likely means \
885 the gossip tables are not fully populated. We will sleep and retry..."
886 );
887 return KnownSnapshotHashes::default();
888 }
889
890 build_known_snapshot_hashes(known_validators, get_snapshot_hashes_for_node)
891}
892
893fn do_known_validators_have_all_snapshot_hashes<'a>(
902 known_validators: impl IntoIterator<Item = &'a Pubkey>,
903 known_validators_to_wait_for: KnownValidatorsToWaitFor,
904 get_snapshot_hashes_for_node: impl Fn(&'a Pubkey) -> Option<SnapshotHash>,
905) -> bool {
906 let node_has_snapshot_hashes = |node| get_snapshot_hashes_for_node(node).is_some();
907
908 match known_validators_to_wait_for {
909 KnownValidatorsToWaitFor::All => known_validators.into_iter().all(node_has_snapshot_hashes),
910 KnownValidatorsToWaitFor::Any => known_validators.into_iter().any(node_has_snapshot_hashes),
911 }
912}
913
914#[derive(Debug, Copy, Clone, Eq, PartialEq)]
917enum KnownValidatorsToWaitFor {
918 All,
919 Any,
920}
921
922fn build_known_snapshot_hashes<'a>(
928 nodes: impl IntoIterator<Item = &'a Pubkey>,
929 get_snapshot_hashes_for_node: impl Fn(&'a Pubkey) -> Option<SnapshotHash>,
930) -> KnownSnapshotHashes {
931 let mut known_snapshot_hashes = KnownSnapshotHashes::new();
932
933 fn is_any_same_slot_and_different_hash<'a>(
936 needle: &(Slot, Hash),
937 haystack: impl IntoIterator<Item = &'a (Slot, Hash)>,
938 ) -> bool {
939 haystack
940 .into_iter()
941 .any(|hay| needle.0 == hay.0 && needle.1 != hay.1)
942 }
943
944 'to_next_node: for node in nodes {
945 let Some(SnapshotHash {
946 full: full_snapshot_hash,
947 incr: incremental_snapshot_hash,
948 }) = get_snapshot_hashes_for_node(node)
949 else {
950 continue 'to_next_node;
951 };
952
953 if is_any_same_slot_and_different_hash(&full_snapshot_hash, known_snapshot_hashes.keys()) {
958 warn!(
959 "Ignoring all snapshot hashes from node {node} since we've seen a different full \
960 snapshot hash with this slot. full snapshot hash: {full_snapshot_hash:?}"
961 );
962 debug!(
963 "known full snapshot hashes: {:#?}",
964 known_snapshot_hashes.keys(),
965 );
966 continue 'to_next_node;
967 }
968
969 let known_incremental_snapshot_hashes =
973 known_snapshot_hashes.entry(full_snapshot_hash).or_default();
974
975 if let Some(incremental_snapshot_hash) = incremental_snapshot_hash {
976 if is_any_same_slot_and_different_hash(
981 &incremental_snapshot_hash,
982 known_incremental_snapshot_hashes.iter(),
983 ) {
984 warn!(
985 "Ignoring incremental snapshot hash from node {node} since we've seen a \
986 different incremental snapshot hash with this slot. full snapshot hash: \
987 {full_snapshot_hash:?}, incremental snapshot hash: \
988 {incremental_snapshot_hash:?}"
989 );
990 debug!(
991 "known incremental snapshot hashes based on this slot: {:#?}",
992 known_incremental_snapshot_hashes.iter(),
993 );
994 continue 'to_next_node;
995 }
996
997 known_incremental_snapshot_hashes.insert(incremental_snapshot_hash);
998 };
999 }
1000
1001 trace!("known snapshot hashes: {known_snapshot_hashes:?}");
1002 known_snapshot_hashes
1003}
1004
1005fn get_eligible_peer_snapshot_hashes(
1011 cluster_info: &ClusterInfo,
1012 rpc_peers: &[ContactInfo],
1013) -> Vec<PeerSnapshotHash> {
1014 let peer_snapshot_hashes = rpc_peers
1015 .iter()
1016 .flat_map(|rpc_peer| {
1017 get_snapshot_hashes_for_node(cluster_info, rpc_peer.pubkey()).map(|snapshot_hash| {
1018 PeerSnapshotHash {
1019 rpc_contact_info: rpc_peer.clone(),
1020 snapshot_hash,
1021 }
1022 })
1023 })
1024 .collect();
1025
1026 trace!("peer snapshot hashes: {peer_snapshot_hashes:?}");
1027 peer_snapshot_hashes
1028}
1029
1030fn retain_peer_snapshot_hashes_that_match_known_snapshot_hashes(
1032 known_snapshot_hashes: &KnownSnapshotHashes,
1033 peer_snapshot_hashes: &mut Vec<PeerSnapshotHash>,
1034) {
1035 peer_snapshot_hashes.retain(|peer_snapshot_hash| {
1036 known_snapshot_hashes
1037 .get(&peer_snapshot_hash.snapshot_hash.full)
1038 .map(|known_incremental_hashes| {
1039 if let Some(incr) = peer_snapshot_hash.snapshot_hash.incr.as_ref() {
1040 known_incremental_hashes.contains(incr)
1041 } else {
1042 true
1045 }
1046 })
1047 .unwrap_or(false)
1048 });
1049
1050 trace!(
1051 "retain peer snapshot hashes that match known snapshot hashes: {peer_snapshot_hashes:?}"
1052 );
1053}
1054
1055fn retain_peer_snapshot_hashes_with_highest_full_snapshot_slot(
1057 peer_snapshot_hashes: &mut Vec<PeerSnapshotHash>,
1058) {
1059 let highest_full_snapshot_hash = peer_snapshot_hashes
1060 .iter()
1061 .map(|peer_snapshot_hash| peer_snapshot_hash.snapshot_hash.full)
1062 .max_by_key(|(slot, _hash)| *slot);
1063 let Some(highest_full_snapshot_hash) = highest_full_snapshot_hash else {
1064 return;
1068 };
1069
1070 peer_snapshot_hashes.retain(|peer_snapshot_hash| {
1071 peer_snapshot_hash.snapshot_hash.full == highest_full_snapshot_hash
1072 });
1073
1074 trace!("retain peer snapshot hashes with highest full snapshot slot: {peer_snapshot_hashes:?}");
1075}
1076
1077fn retain_peer_snapshot_hashes_with_highest_incremental_snapshot_slot(
1079 peer_snapshot_hashes: &mut Vec<PeerSnapshotHash>,
1080) {
1081 let highest_incremental_snapshot_hash = peer_snapshot_hashes
1082 .iter()
1083 .flat_map(|peer_snapshot_hash| peer_snapshot_hash.snapshot_hash.incr)
1084 .max_by_key(|(slot, _hash)| *slot);
1085
1086 peer_snapshot_hashes.retain(|peer_snapshot_hash| {
1087 peer_snapshot_hash.snapshot_hash.incr == highest_incremental_snapshot_hash
1088 });
1089
1090 trace!(
1091 "retain peer snapshot hashes with highest incremental snapshot slot: \
1092 {peer_snapshot_hashes:?}"
1093 );
1094}
1095
1096#[allow(clippy::too_many_arguments)]
1098fn download_snapshots(
1099 validator_config: &ValidatorConfig,
1100 bootstrap_config: &RpcBootstrapConfig,
1101 use_progress_bar: bool,
1102 maximum_local_snapshot_age: Slot,
1103 start_progress: &Arc<RwLock<ValidatorStartProgress>>,
1104 minimal_snapshot_download_speed: f32,
1105 maximum_snapshot_download_abort: u64,
1106 download_abort_count: &mut u64,
1107 snapshot_hash: Option<SnapshotHash>,
1108 rpc_contact_info: &ContactInfo,
1109) -> Result<(), String> {
1110 if snapshot_hash.is_none() {
1111 return Ok(());
1112 }
1113 let SnapshotHash {
1114 full: full_snapshot_hash,
1115 incr: incremental_snapshot_hash,
1116 } = snapshot_hash.unwrap();
1117 let full_snapshot_archives_dir = &validator_config.snapshot_config.full_snapshot_archives_dir;
1118 let incremental_snapshot_archives_dir = &validator_config
1119 .snapshot_config
1120 .incremental_snapshot_archives_dir;
1121
1122 if should_use_local_snapshot(
1124 full_snapshot_archives_dir,
1125 incremental_snapshot_archives_dir,
1126 maximum_local_snapshot_age,
1127 full_snapshot_hash,
1128 incremental_snapshot_hash,
1129 bootstrap_config.incremental_snapshot_fetch,
1130 ) {
1131 return Ok(());
1132 }
1133
1134 if snapshot_paths::full_snapshot_archives_iter(full_snapshot_archives_dir).any(
1136 |snapshot_archive| {
1137 snapshot_archive.slot() == full_snapshot_hash.0
1138 && snapshot_archive.hash().0 == full_snapshot_hash.1
1139 },
1140 ) {
1141 info!(
1142 "Full snapshot archive already exists locally. Skipping download. slot: {}, hash: {}",
1143 full_snapshot_hash.0, full_snapshot_hash.1
1144 );
1145 } else {
1146 download_snapshot(
1147 validator_config,
1148 bootstrap_config,
1149 use_progress_bar,
1150 start_progress,
1151 minimal_snapshot_download_speed,
1152 maximum_snapshot_download_abort,
1153 download_abort_count,
1154 rpc_contact_info,
1155 full_snapshot_hash,
1156 SnapshotArchiveKind::Full,
1157 )?;
1158 }
1159
1160 if bootstrap_config.incremental_snapshot_fetch {
1161 if let Some(incremental_snapshot_hash) = incremental_snapshot_hash {
1163 if snapshot_paths::incremental_snapshot_archives_iter(incremental_snapshot_archives_dir)
1164 .any(|snapshot_archive| {
1165 snapshot_archive.slot() == incremental_snapshot_hash.0
1166 && snapshot_archive.hash().0 == incremental_snapshot_hash.1
1167 && snapshot_archive.base_slot() == full_snapshot_hash.0
1168 })
1169 {
1170 info!(
1171 "Incremental snapshot archive already exists locally. Skipping download. \
1172 slot: {}, hash: {}",
1173 incremental_snapshot_hash.0, incremental_snapshot_hash.1
1174 );
1175 } else {
1176 download_snapshot(
1177 validator_config,
1178 bootstrap_config,
1179 use_progress_bar,
1180 start_progress,
1181 minimal_snapshot_download_speed,
1182 maximum_snapshot_download_abort,
1183 download_abort_count,
1184 rpc_contact_info,
1185 incremental_snapshot_hash,
1186 SnapshotArchiveKind::Incremental(full_snapshot_hash.0),
1187 )?;
1188 }
1189 }
1190 }
1191
1192 Ok(())
1193}
1194
1195#[allow(clippy::too_many_arguments)]
1197fn download_snapshot(
1198 validator_config: &ValidatorConfig,
1199 bootstrap_config: &RpcBootstrapConfig,
1200 use_progress_bar: bool,
1201 start_progress: &Arc<RwLock<ValidatorStartProgress>>,
1202 minimal_snapshot_download_speed: f32,
1203 maximum_snapshot_download_abort: u64,
1204 download_abort_count: &mut u64,
1205 rpc_contact_info: &ContactInfo,
1206 desired_snapshot_hash: (Slot, Hash),
1207 snapshot_kind: SnapshotArchiveKind,
1208) -> Result<(), String> {
1209 let maximum_full_snapshot_archives_to_retain = validator_config
1210 .snapshot_config
1211 .maximum_full_snapshot_archives_to_retain;
1212 let maximum_incremental_snapshot_archives_to_retain = validator_config
1213 .snapshot_config
1214 .maximum_incremental_snapshot_archives_to_retain;
1215 let full_snapshot_archives_dir = &validator_config.snapshot_config.full_snapshot_archives_dir;
1216 let incremental_snapshot_archives_dir = &validator_config
1217 .snapshot_config
1218 .incremental_snapshot_archives_dir;
1219
1220 *start_progress.write().unwrap() = ValidatorStartProgress::DownloadingSnapshot {
1221 slot: desired_snapshot_hash.0,
1222 rpc_addr: rpc_contact_info
1223 .rpc()
1224 .ok_or_else(|| String::from("Invalid RPC address"))?,
1225 };
1226 let desired_snapshot_hash = (
1227 desired_snapshot_hash.0,
1228 agave_snapshots::snapshot_hash::SnapshotHash(desired_snapshot_hash.1),
1229 );
1230 download_snapshot_archive(
1231 &rpc_contact_info
1232 .rpc()
1233 .ok_or_else(|| String::from("Invalid RPC address"))?,
1234 full_snapshot_archives_dir,
1235 incremental_snapshot_archives_dir,
1236 desired_snapshot_hash,
1237 snapshot_kind,
1238 maximum_full_snapshot_archives_to_retain,
1239 maximum_incremental_snapshot_archives_to_retain,
1240 use_progress_bar,
1241 &mut Some(Box::new(|download_progress: &DownloadProgressRecord| {
1242 debug!("Download progress: {download_progress:?}");
1243 if download_progress.last_throughput < minimal_snapshot_download_speed
1244 && download_progress.notification_count <= 1
1245 && download_progress.percentage_done <= 2_f32
1246 && download_progress.estimated_remaining_time > 60_f32
1247 && *download_abort_count < maximum_snapshot_download_abort
1248 {
1249 if let Some(ref known_validators) = validator_config.known_validators
1250 && known_validators.contains(rpc_contact_info.pubkey())
1251 && known_validators.len() == 1
1252 && bootstrap_config.only_known_rpc
1253 {
1254 warn!(
1255 "The snapshot download is too slow, throughput: {} < min speed {} \
1256 bytes/sec, but will NOT abort and try a different node as it is the only \
1257 known validator and the --only-known-rpc flag is set. Abort count: {}, \
1258 Progress detail: {:?}",
1259 download_progress.last_throughput,
1260 minimal_snapshot_download_speed,
1261 download_abort_count,
1262 download_progress,
1263 );
1264 return true; }
1266 warn!(
1267 "The snapshot download is too slow, throughput: {} < min speed {} bytes/sec, \
1268 will abort and try a different node. Abort count: {}, Progress detail: {:?}",
1269 download_progress.last_throughput,
1270 minimal_snapshot_download_speed,
1271 download_abort_count,
1272 download_progress,
1273 );
1274 *download_abort_count += 1;
1275 false
1276 } else {
1277 true
1278 }
1279 })),
1280 )
1281}
1282
1283fn should_use_local_snapshot(
1286 full_snapshot_archives_dir: &Path,
1287 incremental_snapshot_archives_dir: &Path,
1288 maximum_local_snapshot_age: Slot,
1289 full_snapshot_hash: (Slot, Hash),
1290 incremental_snapshot_hash: Option<(Slot, Hash)>,
1291 incremental_snapshot_fetch: bool,
1292) -> bool {
1293 let cluster_snapshot_slot = incremental_snapshot_hash
1294 .map(|(slot, _)| slot)
1295 .unwrap_or(full_snapshot_hash.0);
1296
1297 match get_highest_local_snapshot_hash(
1298 full_snapshot_archives_dir,
1299 incremental_snapshot_archives_dir,
1300 incremental_snapshot_fetch,
1301 ) {
1302 None => {
1303 info!(
1304 "Downloading a snapshot for slot {cluster_snapshot_slot} since there is not a \
1305 local snapshot."
1306 );
1307 false
1308 }
1309 Some((local_snapshot_slot, _)) => {
1310 if local_snapshot_slot
1311 >= cluster_snapshot_slot.saturating_sub(maximum_local_snapshot_age)
1312 {
1313 info!(
1314 "Reusing local snapshot at slot {local_snapshot_slot} instead of downloading \
1315 a snapshot for slot {cluster_snapshot_slot}."
1316 );
1317 true
1318 } else {
1319 info!(
1320 "Local snapshot from slot {local_snapshot_slot} is too old. Downloading a \
1321 newer snapshot for slot {cluster_snapshot_slot}."
1322 );
1323 false
1324 }
1325 }
1326 }
1327}
1328
1329fn get_snapshot_hashes_for_node(cluster_info: &ClusterInfo, node: &Pubkey) -> Option<SnapshotHash> {
1331 cluster_info.get_snapshot_hashes_for_node(node).map(
1332 |crds_data::SnapshotHashes {
1333 full, incremental, ..
1334 }| {
1335 let highest_incremental_snapshot_hash = incremental.into_iter().max();
1336 SnapshotHash {
1337 full,
1338 incr: highest_incremental_snapshot_hash,
1339 }
1340 },
1341 )
1342}
1343
1344#[cfg(test)]
1345mod tests {
1346 use super::*;
1347
1348 impl PeerSnapshotHash {
1349 fn new(
1350 rpc_contact_info: ContactInfo,
1351 full_snapshot_hash: (Slot, Hash),
1352 incremental_snapshot_hash: Option<(Slot, Hash)>,
1353 ) -> Self {
1354 Self {
1355 rpc_contact_info,
1356 snapshot_hash: SnapshotHash {
1357 full: full_snapshot_hash,
1358 incr: incremental_snapshot_hash,
1359 },
1360 }
1361 }
1362 }
1363
1364 fn default_contact_info_for_tests() -> ContactInfo {
1365 ContactInfo::new_localhost(&Pubkey::default(), 1_681_834_947_321)
1366 }
1367
1368 #[test]
1369 fn test_build_known_snapshot_hashes() {
1370 agave_logger::setup();
1371 let full_snapshot_hash1 = (400_000, Hash::new_unique());
1372 let full_snapshot_hash2 = (400_000, Hash::new_unique());
1373
1374 let incremental_snapshot_hash1 = (400_800, Hash::new_unique());
1375 let incremental_snapshot_hash2 = (400_800, Hash::new_unique());
1376
1377 let oracle = {
1379 let mut oracle = HashMap::new();
1380
1381 for (full, incr) in [
1382 (full_snapshot_hash1, None),
1384 (full_snapshot_hash1, Some(incremental_snapshot_hash1)),
1386 (full_snapshot_hash1, Some(incremental_snapshot_hash2)),
1388 (full_snapshot_hash2, None),
1390 (full_snapshot_hash2, Some(incremental_snapshot_hash1)),
1391 (full_snapshot_hash2, Some(incremental_snapshot_hash2)),
1392 ] {
1393 oracle.insert(Pubkey::new_unique(), Some(SnapshotHash { full, incr }));
1395 oracle.insert(Pubkey::new_unique(), Some(SnapshotHash { full, incr }));
1396 oracle.insert(Pubkey::new_unique(), Some(SnapshotHash { full, incr }));
1397 }
1398
1399 oracle.insert(Pubkey::new_unique(), None);
1401 oracle.insert(Pubkey::new_unique(), None);
1402 oracle.insert(Pubkey::new_unique(), None);
1403
1404 oracle
1405 };
1406
1407 let node_to_snapshot_hashes = |node| *oracle.get(node).unwrap();
1408
1409 let known_snapshot_hashes =
1410 build_known_snapshot_hashes(oracle.keys(), node_to_snapshot_hashes);
1411
1412 let known_full_snapshot_hashes = known_snapshot_hashes.keys();
1415 assert_eq!(known_full_snapshot_hashes.len(), 1);
1416 let known_full_snapshot_hash = known_full_snapshot_hashes.into_iter().next().unwrap();
1417
1418 let known_incremental_snapshot_hashes =
1420 known_snapshot_hashes.get(known_full_snapshot_hash).unwrap();
1421 assert_eq!(known_incremental_snapshot_hashes.len(), 1);
1422 let known_incremental_snapshot_hash =
1423 known_incremental_snapshot_hashes.iter().next().unwrap();
1424
1425 assert!(
1432 known_full_snapshot_hash == &full_snapshot_hash1
1433 || known_full_snapshot_hash == &full_snapshot_hash2
1434 );
1435 assert!(
1436 known_incremental_snapshot_hash == &incremental_snapshot_hash1
1437 || known_incremental_snapshot_hash == &incremental_snapshot_hash2
1438 );
1439 }
1440
1441 #[test]
1442 fn test_retain_peer_snapshot_hashes_that_match_known_snapshot_hashes() {
1443 let known_snapshot_hashes: KnownSnapshotHashes = [
1444 (
1445 (200_000, Hash::new_unique()),
1446 [
1447 (200_200, Hash::new_unique()),
1448 (200_400, Hash::new_unique()),
1449 (200_600, Hash::new_unique()),
1450 (200_800, Hash::new_unique()),
1451 ]
1452 .iter()
1453 .cloned()
1454 .collect(),
1455 ),
1456 (
1457 (300_000, Hash::new_unique()),
1458 [
1459 (300_200, Hash::new_unique()),
1460 (300_400, Hash::new_unique()),
1461 (300_600, Hash::new_unique()),
1462 ]
1463 .iter()
1464 .cloned()
1465 .collect(),
1466 ),
1467 ]
1468 .iter()
1469 .cloned()
1470 .collect();
1471
1472 let known_snapshot_hash = known_snapshot_hashes.iter().next().unwrap();
1473 let known_full_snapshot_hash = known_snapshot_hash.0;
1474 let known_incremental_snapshot_hash = known_snapshot_hash.1.iter().next().unwrap();
1475
1476 let contact_info = default_contact_info_for_tests();
1477 let peer_snapshot_hashes = vec![
1478 PeerSnapshotHash::new(contact_info.clone(), (111_000, Hash::default()), None),
1480 PeerSnapshotHash::new(
1482 contact_info.clone(),
1483 (111_000, Hash::default()),
1484 Some((111_111, Hash::default())),
1485 ),
1486 PeerSnapshotHash::new(contact_info.clone(), *known_full_snapshot_hash, None),
1488 PeerSnapshotHash::new(
1490 contact_info.clone(),
1491 (111_000, Hash::default()),
1492 Some(*known_incremental_snapshot_hash),
1493 ),
1494 PeerSnapshotHash::new(
1496 contact_info.clone(),
1497 *known_full_snapshot_hash,
1498 Some((111_111, Hash::default())),
1499 ),
1500 PeerSnapshotHash::new(
1502 contact_info.clone(),
1503 *known_full_snapshot_hash,
1504 Some(*known_incremental_snapshot_hash),
1505 ),
1506 ];
1507
1508 let expected = vec![
1509 PeerSnapshotHash::new(contact_info.clone(), *known_full_snapshot_hash, None),
1510 PeerSnapshotHash::new(
1511 contact_info,
1512 *known_full_snapshot_hash,
1513 Some(*known_incremental_snapshot_hash),
1514 ),
1515 ];
1516 let mut actual = peer_snapshot_hashes;
1517 retain_peer_snapshot_hashes_that_match_known_snapshot_hashes(
1518 &known_snapshot_hashes,
1519 &mut actual,
1520 );
1521 assert_eq!(expected, actual);
1522 }
1523
1524 #[test]
1525 fn test_retain_peer_snapshot_hashes_with_highest_full_snapshot_slot() {
1526 let contact_info = default_contact_info_for_tests();
1527 let peer_snapshot_hashes = vec![
1528 PeerSnapshotHash::new(contact_info.clone(), (100_000, Hash::default()), None),
1530 PeerSnapshotHash::new(
1531 contact_info.clone(),
1532 (100_000, Hash::default()),
1533 Some((100_100, Hash::default())),
1534 ),
1535 PeerSnapshotHash::new(
1536 contact_info.clone(),
1537 (100_000, Hash::default()),
1538 Some((100_200, Hash::default())),
1539 ),
1540 PeerSnapshotHash::new(
1541 contact_info.clone(),
1542 (100_000, Hash::default()),
1543 Some((100_300, Hash::default())),
1544 ),
1545 PeerSnapshotHash::new(contact_info.clone(), (200_000, Hash::default()), None),
1547 PeerSnapshotHash::new(
1548 contact_info.clone(),
1549 (200_000, Hash::default()),
1550 Some((200_100, Hash::default())),
1551 ),
1552 PeerSnapshotHash::new(
1553 contact_info.clone(),
1554 (200_000, Hash::default()),
1555 Some((200_200, Hash::default())),
1556 ),
1557 PeerSnapshotHash::new(
1558 contact_info.clone(),
1559 (200_000, Hash::default()),
1560 Some((200_300, Hash::default())),
1561 ),
1562 ];
1563
1564 let expected = vec![
1565 PeerSnapshotHash::new(contact_info.clone(), (200_000, Hash::default()), None),
1566 PeerSnapshotHash::new(
1567 contact_info.clone(),
1568 (200_000, Hash::default()),
1569 Some((200_100, Hash::default())),
1570 ),
1571 PeerSnapshotHash::new(
1572 contact_info.clone(),
1573 (200_000, Hash::default()),
1574 Some((200_200, Hash::default())),
1575 ),
1576 PeerSnapshotHash::new(
1577 contact_info,
1578 (200_000, Hash::default()),
1579 Some((200_300, Hash::default())),
1580 ),
1581 ];
1582 let mut actual = peer_snapshot_hashes;
1583 retain_peer_snapshot_hashes_with_highest_full_snapshot_slot(&mut actual);
1584 assert_eq!(expected, actual);
1585 }
1586
1587 #[test]
1588 fn test_retain_peer_snapshot_hashes_with_highest_incremental_snapshot_slot_some() {
1589 let contact_info = default_contact_info_for_tests();
1590 let peer_snapshot_hashes = vec![
1591 PeerSnapshotHash::new(contact_info.clone(), (200_000, Hash::default()), None),
1592 PeerSnapshotHash::new(
1593 contact_info.clone(),
1594 (200_000, Hash::default()),
1595 Some((200_100, Hash::default())),
1596 ),
1597 PeerSnapshotHash::new(
1598 contact_info.clone(),
1599 (200_000, Hash::default()),
1600 Some((200_200, Hash::default())),
1601 ),
1602 PeerSnapshotHash::new(
1603 contact_info.clone(),
1604 (200_000, Hash::default()),
1605 Some((200_300, Hash::default())),
1606 ),
1607 PeerSnapshotHash::new(
1608 contact_info.clone(),
1609 (200_000, Hash::default()),
1610 Some((200_010, Hash::default())),
1611 ),
1612 PeerSnapshotHash::new(
1613 contact_info.clone(),
1614 (200_000, Hash::default()),
1615 Some((200_020, Hash::default())),
1616 ),
1617 PeerSnapshotHash::new(
1618 contact_info.clone(),
1619 (200_000, Hash::default()),
1620 Some((200_030, Hash::default())),
1621 ),
1622 ];
1623
1624 let expected = vec![PeerSnapshotHash::new(
1625 contact_info,
1626 (200_000, Hash::default()),
1627 Some((200_300, Hash::default())),
1628 )];
1629 let mut actual = peer_snapshot_hashes;
1630 retain_peer_snapshot_hashes_with_highest_incremental_snapshot_slot(&mut actual);
1631 assert_eq!(expected, actual);
1632 }
1633
1634 #[test]
1637 fn test_retain_peer_snapshot_hashes_with_highest_incremental_snapshot_slot_none() {
1638 let contact_info = default_contact_info_for_tests();
1639 let peer_snapshot_hashes = vec![
1640 PeerSnapshotHash::new(contact_info.clone(), (200_000, Hash::new_unique()), None),
1641 PeerSnapshotHash::new(contact_info.clone(), (200_000, Hash::new_unique()), None),
1642 PeerSnapshotHash::new(contact_info, (200_000, Hash::new_unique()), None),
1643 ];
1644
1645 let expected = peer_snapshot_hashes.clone();
1646 let mut actual = peer_snapshot_hashes;
1647 retain_peer_snapshot_hashes_with_highest_incremental_snapshot_slot(&mut actual);
1648 assert_eq!(expected, actual);
1649 }
1650
1651 #[test]
1654 fn test_retain_peer_snapshot_hashes_with_highest_slot_empty() {
1655 {
1656 let mut actual = vec![];
1657 let expected = actual.clone();
1658 retain_peer_snapshot_hashes_with_highest_full_snapshot_slot(&mut actual);
1659 assert_eq!(expected, actual);
1660 }
1661 {
1662 let mut actual = vec![];
1663 let expected = actual.clone();
1664 retain_peer_snapshot_hashes_with_highest_incremental_snapshot_slot(&mut actual);
1665 assert_eq!(expected, actual);
1666 }
1667 }
1668}