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