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