Skip to main content

agave_validator/
bootstrap.rs

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
45/// When downloading snapshots, wait at most this long for snapshot hashes from
46/// _all_ known validators.  Afterwards, wait for snapshot hashes from _any_
47/// known validator.
48const WAIT_FOR_ALL_KNOWN_VALIDATORS: Duration = Duration::from_secs(60);
49/// If we don't have any alternative peers after this long, better off trying
50/// blacklisted peers again.
51const BLACKLIST_CLEAR_THRESHOLD: Duration = Duration::from_secs(60);
52/// If we can't find a good snapshot download candidate after this time, just
53/// give up.
54const NEWER_SNAPSHOT_THRESHOLD: Duration = Duration::from_secs(180);
55/// If we haven't found any RPC peers after this time, just give up.
56const 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    // Filter out blacklisted nodes
207    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            // All nodes are blacklisted and no additional nodes recently discovered.
229            // Remove all nodes from the blacklist and try them again.
230            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    // Maybe we can calculate minimum voting fee; rather than 1 lamport
292    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/// Struct to wrap the return value from get_rpc_nodes().  The `rpc_contact_info` is the peer to
312/// download from, and `snapshot_hash` is the (optional) full and (optional) incremental
313/// snapshots to download.
314#[derive(Debug)]
315struct GetRpcNodeResult {
316    rpc_contact_info: ContactInfo,
317    snapshot_hash: Option<SnapshotHash>,
318}
319
320/// Struct to wrap the peers & snapshot hashes together.
321#[derive(Debug, PartialEq, Eq, Clone)]
322struct PeerSnapshotHash {
323    rpc_contact_info: ContactInfo,
324    snapshot_hash: SnapshotHash,
325}
326
327/// A snapshot hash.  In this context (bootstrap *with* incremental snapshots), a snapshot hash
328/// is _both_ a full snapshot hash and an (optional) incremental snapshot hash.
329#[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            // Consider failures here to be more likely due to user error (eg,
427            // incorrect `agave-validator` command-line arguments) rather than the
428            // RPC node failing.
429            //
430            // Power users can always use the `--no-check-vote-account` option to
431            // bypass this check entirely
432            error!("{err}");
433            exit(1);
434        });
435    }
436    Ok(())
437}
438
439/// simple ping helper function which returns the time to connect
440fn 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
448// Populates `vetted_rpc_nodes` with a list of RPC nodes that are ready to be
449// used for downloading latest snapshots and/or the genesis block. Guaranteed to
450// find at least one viable node or terminate the process.
451fn 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
683/// Get RPC peer node candidates to download from.
684///
685/// This function finds the highest compatible snapshots from the cluster and returns RPC peers.
686fn 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        // Give gossip some time to populate and not spin on grabbing the crds lock
698        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        // Reset timeouts if we found any viable RPC peers.
717        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
783/// Get the Slot and Hash of the local snapshot with the highest slot.  Can be either a full
784/// snapshot or an incremental snapshot.
785fn 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
811/// Get peer snapshot hashes
812///
813/// The result is a vector of peers with snapshot hashes that:
814/// 1. match a snapshot hash from the known validators
815/// 2. have the highest incremental snapshot slot
816/// 3. have the highest full snapshot slot of (2)
817fn 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        // Only filter by highest incremental snapshot slot if we're actually going to download an
838        // incremental snapshot.  Otherwise this could remove higher full snapshot slots from
839        // being selected.  For example, if there are two peer snapshot hashes:
840        // (A) full snapshot slot: 100, incremental snapshot slot: 160
841        // (B) full snapshot slot: 150, incremental snapshot slot: None
842        // Then (A) has the highest overall snapshot slot.  But if we're not downloading and
843        // incremental snapshot, (B) should be selected since it's full snapshot of 150 is highest.
844        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
853/// Map full snapshot hashes to a set of incremental snapshot hashes.  Each full snapshot hash
854/// is treated as the base for its set of incremental snapshot hashes.
855type KnownSnapshotHashes = HashMap<(Slot, Hash), HashSet<(Slot, Hash)>>;
856
857/// Get the snapshot hashes from known validators.
858///
859/// The snapshot hashes are put into a map from full snapshot hash to a set of incremental
860/// snapshot hashes.  This map will be used as the "known snapshot hashes"; when peers are
861/// queried for their individual snapshot hashes, their results will be checked against this
862/// map to verify correctness.
863///
864/// NOTE: Only a single snapshot hash is allowed per slot.  If somehow two known validators have
865/// a snapshot hash with the same slot and _different_ hashes, the second will be skipped.
866/// This applies to both full and incremental snapshot hashes.
867fn 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    // Get the snapshot hashes for a node from CRDS
873    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
890/// Check if we can discover snapshot hashes for the known validators.
891///
892/// This is a heuristic to ensure the gossip tables are populated enough so that the bootstrap
893/// process will download snapshots.
894///
895/// This function will return false if we do not yet have snapshot hashes from known validators;
896/// and true otherwise.  Either require snapshot hashes from *all* or *any* of the known validators
897/// based on the `KnownValidatorsToWaitFor` parameter.
898fn 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/// When waiting for snapshot hashes from the known validators, should we wait for *all* or *any*
912/// of them?
913#[derive(Debug, Copy, Clone, Eq, PartialEq)]
914enum KnownValidatorsToWaitFor {
915    All,
916    Any,
917}
918
919/// Build the known snapshot hashes from a set of nodes.
920///
921/// The `get_snapshot_hashes_for_node` parameter is a function that map a pubkey to its snapshot
922/// hashes.  This parameter exist to provide a way to test the inner algorithm without needing
923/// runtime information such as the ClusterInfo or ValidatorConfig.
924fn 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    /// Check to see if there exists another snapshot hash in the haystack with the *same* slot
931    /// but *different* hash as the needle.
932    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        // Do not add this snapshot hash if there's already a full snapshot hash with the
951        // same slot but with a _different_ hash.
952        // NOTE: Nodes should not produce snapshots at the same slot with _different_
953        // hashes.  So if it happens, keep the first and ignore the rest.
954        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        // Insert a new full snapshot hash into the known snapshot hashes IFF an entry
967        // doesn't already exist.  This is to ensure we don't overwrite existing
968        // incremental snapshot hashes that may be present for this full snapshot hash.
969        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            // Do not add this snapshot hash if there's already an incremental snapshot
974            // hash with the same slot, but with a _different_ hash.
975            // NOTE: Nodes should not produce snapshots at the same slot with _different_
976            // hashes.  So if it happens, keep the first and ignore the rest.
977            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
1002/// Get snapshot hashes from all eligible peers.
1003///
1004/// This fn will get only one snapshot hash per peer (the one with the highest slot).
1005/// This may be just a full snapshot hash, or a combo full snapshot hash and
1006/// incremental snapshot hash.
1007fn 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
1027/// Retain the peer snapshot hashes that match a snapshot hash from the known snapshot hashes
1028fn 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                    // If the peer's full snapshot hashes match, but doesn't have any
1040                    // incremental snapshots, that's fine; keep 'em!
1041                    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
1052/// Retain the peer snapshot hashes with the highest full snapshot slot
1053fn 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        // `max_by_key` will only be `None` IFF the input `peer_snapshot_hashes` is empty.
1062        // In that case there's nothing to do (additionally, without a valid 'max' value, there
1063        // will be nothing to compare against within the `retain()` predicate).
1064        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
1074/// Retain the peer snapshot hashes with the highest incremental snapshot slot
1075fn 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/// Check to see if we can use our local snapshots, otherwise download newer ones.
1094#[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 the local snapshots are new enough, then use 'em; no need to download new snapshots
1120    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    // Check and see if we've already got the full snapshot; if not, download it
1132    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        // Check and see if we've already got the incremental snapshot; if not, download it
1159        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/// Download a snapshot
1193#[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; // Do not abort download from the one-and-only known validator
1262                    }
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
1281/// Check to see if bootstrap should load from its local snapshots or not.  If not, then snapshots
1282/// will be downloaded.
1283fn 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
1327/// Get the node's highest snapshot hashes from CRDS
1328fn 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(), /*now:*/ 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        // simulate a set of known validators with various snapshot hashes
1376        let oracle = {
1377            let mut oracle = HashMap::new();
1378
1379            for (full, incr) in [
1380                // only a full snapshot
1381                (full_snapshot_hash1, None),
1382                // full and incremental snapshots
1383                (full_snapshot_hash1, Some(incremental_snapshot_hash1)),
1384                // full and incremental snapshots, with different incremental hash
1385                (full_snapshot_hash1, Some(incremental_snapshot_hash2)),
1386                // ...and now with different full hashes
1387                (full_snapshot_hash2, None),
1388                (full_snapshot_hash2, Some(incremental_snapshot_hash1)),
1389                (full_snapshot_hash2, Some(incremental_snapshot_hash2)),
1390            ] {
1391                // also simulate multiple known validators having the same snapshot hashes
1392                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            // no snapshots at all
1398            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        // ensure there's only one full snapshot hash, since they all used the same slot and there
1411        // can be only one snapshot hash per slot
1412        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        // and for the same reasons, ensure there is only one incremental snapshot hash
1417        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        // The resulting `known_snapshot_hashes` can be different from run-to-run due to how
1424        // `oracle.keys()` returns nodes during iteration.  Because of that, we cannot just assert
1425        // the full and incremental snapshot hashes are `full_snapshot_hash1` and
1426        // `incremental_snapshot_hash1`.  Instead, we assert that the full and incremental
1427        // snapshot hashes are exactly one or the other, since it depends on which nodes are seen
1428        // "first" when building the known snapshot hashes.
1429        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            // bad full snapshot hash, no incremental snapshot hash
1477            PeerSnapshotHash::new(contact_info.clone(), (111_000, Hash::default()), None),
1478            // bad everything
1479            PeerSnapshotHash::new(
1480                contact_info.clone(),
1481                (111_000, Hash::default()),
1482                Some((111_111, Hash::default())),
1483            ),
1484            // good full snapshot hash, no incremental snapshot hash
1485            PeerSnapshotHash::new(contact_info.clone(), *known_full_snapshot_hash, None),
1486            // bad full snapshot hash, good (not possible) incremental snapshot hash
1487            PeerSnapshotHash::new(
1488                contact_info.clone(),
1489                (111_000, Hash::default()),
1490                Some(*known_incremental_snapshot_hash),
1491            ),
1492            // good full snapshot hash, bad incremental snapshot hash
1493            PeerSnapshotHash::new(
1494                contact_info.clone(),
1495                *known_full_snapshot_hash,
1496                Some((111_111, Hash::default())),
1497            ),
1498            // good everything
1499            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            // old
1527            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            // new
1544            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    /// Ensure that retaining the highest incremental snapshot hashes works as expected even if
1633    /// there are *zero* peers with incremental snapshots.
1634    #[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    /// Ensure that retaining the highest snapshot hashes works (i.e. doesn't crash) even if the
1650    /// peer snapshot hashes input is empty.
1651    #[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}