solana_runtime/
snapshot_utils.rs

1use {
2    crate::{
3        accounts_db::{
4            AccountShrinkThreshold, AccountsDbConfig, SnapshotStorage, SnapshotStorages,
5        },
6        accounts_index::AccountSecondaryIndexes,
7        accounts_update_notifier_interface::AccountsUpdateNotifier,
8        bank::{Bank, BankFieldsToDeserialize, BankSlotDelta},
9        builtins::Builtins,
10        hardened_unpack::{unpack_snapshot, ParallelSelector, UnpackError, UnpackedAppendVecMap},
11        serde_snapshot::{
12            bank_from_streams, bank_to_stream, fields_from_streams, SerdeStyle, SnapshotStreams,
13        },
14        shared_buffer_reader::{SharedBuffer, SharedBufferReader},
15        snapshot_archive_info::{
16            FullSnapshotArchiveInfo, IncrementalSnapshotArchiveInfo, SnapshotArchiveInfoGetter,
17        },
18        snapshot_package::{
19            AccountsPackage, PendingAccountsPackage, SnapshotPackage, SnapshotType,
20        },
21        status_cache,
22    },
23    bincode::{config::Options, serialize_into},
24    bzip2::bufread::BzDecoder,
25    flate2::read::GzDecoder,
26    lazy_static::lazy_static,
27    log::*,
28    rayon::prelude::*,
29    regex::Regex,
30    safecoin_measure::measure::Measure,
31    solana_sdk::{
32        clock::Slot,
33        genesis_config::GenesisConfig,
34        hash::Hash,
35        pubkey::Pubkey,
36        slot_history::{Check, SlotHistory},
37    },
38    std::{
39        cmp::Ordering,
40        collections::{HashMap, HashSet},
41        fmt,
42        fs::{self, File},
43        io::{BufReader, BufWriter, Error as IoError, ErrorKind, Read, Seek, Write},
44        path::{Path, PathBuf},
45        process::ExitStatus,
46        str::FromStr,
47        sync::Arc,
48    },
49    tar::{self, Archive},
50    tempfile::TempDir,
51    thiserror::Error,
52};
53
54mod archive_format;
55pub use archive_format::*;
56
57pub const SNAPSHOT_STATUS_CACHE_FILENAME: &str = "status_cache";
58pub const SNAPSHOT_ARCHIVE_DOWNLOAD_DIR: &str = "remote";
59pub const DEFAULT_FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS: Slot = 25_000;
60pub const DEFAULT_INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS: Slot = 100;
61const MAX_SNAPSHOT_DATA_FILE_SIZE: u64 = 32 * 1024 * 1024 * 1024; // 32 GiB
62const MAX_SNAPSHOT_VERSION_FILE_SIZE: u64 = 8; // byte
63const VERSION_STRING_V1_2_0: &str = "1.2.0";
64pub(crate) const TMP_BANK_SNAPSHOT_PREFIX: &str = "tmp-bank-snapshot-";
65pub const TMP_SNAPSHOT_ARCHIVE_PREFIX: &str = "tmp-snapshot-archive-";
66pub const BANK_SNAPSHOT_PRE_FILENAME_EXTENSION: &str = "pre";
67pub const MAX_BANK_SNAPSHOTS_TO_RETAIN: usize = 8; // Save some bank snapshots but not too many
68pub const DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN: usize = 2;
69pub const DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN: usize = 4;
70pub const FULL_SNAPSHOT_ARCHIVE_FILENAME_REGEX: &str = r"^snapshot-(?P<slot>[[:digit:]]+)-(?P<hash>[[:alnum:]]+)\.(?P<ext>tar|tar\.bz2|tar\.zst|tar\.gz|tar\.lz4)$";
71pub const INCREMENTAL_SNAPSHOT_ARCHIVE_FILENAME_REGEX: &str = r"^incremental-snapshot-(?P<base>[[:digit:]]+)-(?P<slot>[[:digit:]]+)-(?P<hash>[[:alnum:]]+)\.(?P<ext>tar|tar\.bz2|tar\.zst|tar\.gz|tar\.lz4)$";
72
73#[derive(Copy, Clone, Eq, PartialEq, Debug)]
74pub enum SnapshotVersion {
75    V1_2_0,
76}
77
78impl Default for SnapshotVersion {
79    fn default() -> Self {
80        SnapshotVersion::V1_2_0
81    }
82}
83
84impl fmt::Display for SnapshotVersion {
85    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
86        f.write_str(From::from(*self))
87    }
88}
89
90impl From<SnapshotVersion> for &'static str {
91    fn from(snapshot_version: SnapshotVersion) -> &'static str {
92        match snapshot_version {
93            SnapshotVersion::V1_2_0 => VERSION_STRING_V1_2_0,
94        }
95    }
96}
97
98impl FromStr for SnapshotVersion {
99    type Err = &'static str;
100
101    fn from_str(version_string: &str) -> std::result::Result<Self, Self::Err> {
102        // Remove leading 'v' or 'V' from slice
103        let version_string = if version_string
104            .get(..1)
105            .map_or(false, |s| s.eq_ignore_ascii_case("v"))
106        {
107            &version_string[1..]
108        } else {
109            version_string
110        };
111        match version_string {
112            VERSION_STRING_V1_2_0 => Ok(SnapshotVersion::V1_2_0),
113            _ => Err("unsupported snapshot version"),
114        }
115    }
116}
117
118impl SnapshotVersion {
119    pub fn as_str(self) -> &'static str {
120        <&str as From<Self>>::from(self)
121    }
122
123    fn maybe_from_string(version_string: &str) -> Option<SnapshotVersion> {
124        version_string.parse::<Self>().ok()
125    }
126}
127
128/// Information about a bank snapshot. Namely the slot of the bank, the path to the snapshot, and
129/// the type of the snapshot.
130#[derive(PartialEq, Eq, Debug)]
131pub struct BankSnapshotInfo {
132    /// Slot of the bank
133    pub slot: Slot,
134    /// Path to the snapshot
135    pub snapshot_path: PathBuf,
136    /// Type of the snapshot
137    pub snapshot_type: BankSnapshotType,
138}
139
140impl PartialOrd for BankSnapshotInfo {
141    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
142        Some(self.cmp(other))
143    }
144}
145
146// Order BankSnapshotInfo by slot (ascending), which practically is sorting chronologically
147impl Ord for BankSnapshotInfo {
148    fn cmp(&self, other: &Self) -> Ordering {
149        self.slot.cmp(&other.slot)
150    }
151}
152
153/// Bank snapshots traditionally had their accounts hash calculated prior to serialization.  Since
154/// the hash calculation takes a long time, an optimization has been put in to offload the accounts
155/// hash calculation.  The bank serialization format has not changed, so we need another way to
156/// identify if a bank snapshot contains the calculated accounts hash or not.
157///
158/// When a bank snapshot is first taken, it does not have the calculated accounts hash.  It is said
159/// that this bank snapshot is "pre" accounts hash.  Later, when the accounts hash is calculated,
160/// the bank snapshot is re-serialized, and is now "post" accounts hash.
161#[derive(Debug, Copy, Clone, Eq, PartialEq)]
162pub enum BankSnapshotType {
163    /// This bank snapshot has *not* yet had its accounts hash calculated
164    Pre,
165    /// This bank snapshot *has* had its accounts hash calculated
166    Post,
167}
168
169/// Helper type when rebuilding from snapshots.  Designed to handle when rebuilding from just a
170/// full snapshot, or from both a full snapshot and an incremental snapshot.
171#[derive(Debug)]
172struct SnapshotRootPaths {
173    full_snapshot_root_file_path: PathBuf,
174    incremental_snapshot_root_file_path: Option<PathBuf>,
175}
176
177/// Helper type to bundle up the results from `unarchive_snapshot()`
178#[derive(Debug)]
179struct UnarchivedSnapshot {
180    #[allow(dead_code)]
181    unpack_dir: TempDir,
182    unpacked_append_vec_map: UnpackedAppendVecMap,
183    unpacked_snapshots_dir_and_version: UnpackedSnapshotsDirAndVersion,
184    measure_untar: Measure,
185}
186
187/// Helper type for passing around the unpacked snapshots dir and the snapshot version together
188#[derive(Debug)]
189struct UnpackedSnapshotsDirAndVersion {
190    unpacked_snapshots_dir: PathBuf,
191    snapshot_version: String,
192}
193
194#[derive(Error, Debug)]
195#[allow(clippy::large_enum_variant)]
196pub enum SnapshotError {
197    #[error("I/O error: {0}")]
198    Io(#[from] std::io::Error),
199
200    #[error("serialization error: {0}")]
201    Serialize(#[from] bincode::Error),
202
203    #[error("archive generation failure {0}")]
204    ArchiveGenerationFailure(ExitStatus),
205
206    #[error("storage path symlink is invalid")]
207    StoragePathSymlinkInvalid,
208
209    #[error("Unpack error: {0}")]
210    UnpackError(#[from] UnpackError),
211
212    #[error("source({1}) - I/O error: {0}")]
213    IoWithSource(std::io::Error, &'static str),
214
215    #[error("could not get file name from path: {}", .0.display())]
216    PathToFileNameError(PathBuf),
217
218    #[error("could not get str from file name: {}", .0.display())]
219    FileNameToStrError(PathBuf),
220
221    #[error("could not parse snapshot archive's file name: {0}")]
222    ParseSnapshotArchiveFileNameError(String),
223
224    #[error("snapshots are incompatible: full snapshot slot ({0}) and incremental snapshot base slot ({1}) do not match")]
225    MismatchedBaseSlot(Slot, Slot),
226
227    #[error("no snapshot archives to load from")]
228    NoSnapshotArchives,
229
230    #[error("snapshot has mismatch: deserialized bank: {:?}, snapshot archive info: {:?}", .0, .1)]
231    MismatchedSlotHash((Slot, Hash), (Slot, Hash)),
232
233    #[error("snapshot slot deltas are invalid: {0}")]
234    VerifySlotDeltas(#[from] VerifySlotDeltasError),
235}
236pub type Result<T> = std::result::Result<T, SnapshotError>;
237
238/// Errors that can happen in `verify_slot_deltas()`
239#[derive(Error, Debug, PartialEq, Eq)]
240pub enum VerifySlotDeltasError {
241    #[error("too many entries: {0} (max: {1})")]
242    TooManyEntries(usize, usize),
243
244    #[error("slot {0} is not a root")]
245    SlotIsNotRoot(Slot),
246
247    #[error("slot {0} is greater than bank slot {1}")]
248    SlotGreaterThanMaxRoot(Slot, Slot),
249
250    #[error("slot {0} has multiple entries")]
251    SlotHasMultipleEntries(Slot),
252
253    #[error("slot {0} was not found in slot history")]
254    SlotNotFoundInHistory(Slot),
255
256    #[error("slot {0} was in history but missing from slot deltas")]
257    SlotNotFoundInDeltas(Slot),
258
259    #[error("slot history is bad and cannot be used to verify slot deltas")]
260    BadSlotHistory,
261}
262
263/// If the validator halts in the middle of `archive_snapshot_package()`, the temporary staging
264/// directory won't be cleaned up.  Call this function to clean them up.
265pub fn remove_tmp_snapshot_archives(snapshot_archives_dir: impl AsRef<Path>) {
266    if let Ok(entries) = fs::read_dir(snapshot_archives_dir) {
267        for entry in entries.filter_map(|entry| entry.ok()) {
268            let file_name = entry
269                .file_name()
270                .into_string()
271                .unwrap_or_else(|_| String::new());
272            if file_name.starts_with(TMP_SNAPSHOT_ARCHIVE_PREFIX) {
273                if entry.path().is_file() {
274                    fs::remove_file(entry.path())
275                } else {
276                    fs::remove_dir_all(entry.path())
277                }
278                .unwrap_or_else(|err| {
279                    warn!("Failed to remove {}: {}", entry.path().display(), err)
280                });
281            }
282        }
283    }
284}
285
286/// Make a snapshot archive out of the snapshot package
287pub fn archive_snapshot_package(
288    snapshot_package: &SnapshotPackage,
289    full_snapshot_archives_dir: impl AsRef<Path>,
290    incremental_snapshot_archives_dir: impl AsRef<Path>,
291    maximum_full_snapshot_archives_to_retain: usize,
292    maximum_incremental_snapshot_archives_to_retain: usize,
293) -> Result<()> {
294    info!(
295        "Generating snapshot archive for slot {}",
296        snapshot_package.slot()
297    );
298
299    serialize_status_cache(
300        snapshot_package.slot(),
301        &snapshot_package.slot_deltas,
302        &snapshot_package
303            .snapshot_links
304            .path()
305            .join(SNAPSHOT_STATUS_CACHE_FILENAME),
306    )?;
307
308    let mut timer = Measure::start("snapshot_package-package_snapshots");
309    let tar_dir = snapshot_package
310        .path()
311        .parent()
312        .expect("Tar output path is invalid");
313
314    fs::create_dir_all(tar_dir)
315        .map_err(|e| SnapshotError::IoWithSource(e, "create archive path"))?;
316
317    // Create the staging directories
318    let staging_dir_prefix = TMP_SNAPSHOT_ARCHIVE_PREFIX;
319    let staging_dir = tempfile::Builder::new()
320        .prefix(&format!(
321            "{}{}-",
322            staging_dir_prefix,
323            snapshot_package.slot()
324        ))
325        .tempdir_in(tar_dir)
326        .map_err(|e| SnapshotError::IoWithSource(e, "create archive tempdir"))?;
327
328    let staging_accounts_dir = staging_dir.path().join("accounts");
329    let staging_snapshots_dir = staging_dir.path().join("snapshots");
330    let staging_version_file = staging_dir.path().join("version");
331    fs::create_dir_all(&staging_accounts_dir)
332        .map_err(|e| SnapshotError::IoWithSource(e, "create staging path"))?;
333
334    // Add the snapshots to the staging directory
335    symlink::symlink_dir(
336        snapshot_package.snapshot_links.path(),
337        &staging_snapshots_dir,
338    )
339    .map_err(|e| SnapshotError::IoWithSource(e, "create staging symlinks"))?;
340
341    // Add the AppendVecs into the compressible list
342    for storage in snapshot_package.snapshot_storages.iter().flatten() {
343        storage.flush()?;
344        let storage_path = storage.get_path();
345        let output_path = staging_accounts_dir.join(crate::append_vec::AppendVec::file_name(
346            storage.slot(),
347            storage.append_vec_id(),
348        ));
349
350        // `storage_path` - The file path where the AppendVec itself is located
351        // `output_path` - The file path where the AppendVec will be placed in the staging directory.
352        let storage_path =
353            fs::canonicalize(storage_path).expect("Could not get absolute path for accounts");
354        symlink::symlink_file(storage_path, &output_path)
355            .map_err(|e| SnapshotError::IoWithSource(e, "create storage symlink"))?;
356        if !output_path.is_file() {
357            return Err(SnapshotError::StoragePathSymlinkInvalid);
358        }
359    }
360
361    // Write version file
362    {
363        let mut f = fs::File::create(staging_version_file)
364            .map_err(|e| SnapshotError::IoWithSource(e, "create version file"))?;
365        f.write_all(snapshot_package.snapshot_version.as_str().as_bytes())
366            .map_err(|e| SnapshotError::IoWithSource(e, "write version file"))?;
367    }
368
369    // Tar the staging directory into the archive at `archive_path`
370    let archive_path = tar_dir.join(format!(
371        "{}{}.{}",
372        staging_dir_prefix,
373        snapshot_package.slot(),
374        snapshot_package.archive_format().extension(),
375    ));
376
377    {
378        let mut archive_file = fs::File::create(&archive_path)?;
379
380        let do_archive_files = |encoder: &mut dyn Write| -> Result<()> {
381            let mut archive = tar::Builder::new(encoder);
382            // Serialize the version and snapshots files before accounts so we can quickly determine the version
383            // and other bank fields. This is necessary if we want to interleave unpacking with reconstruction
384            archive.append_path_with_name(staging_dir.as_ref().join("version"), "version")?;
385            for dir in ["snapshots", "accounts"] {
386                archive.append_dir_all(dir, staging_dir.as_ref().join(dir))?;
387            }
388            archive.into_inner()?;
389            Ok(())
390        };
391
392        match snapshot_package.archive_format() {
393            ArchiveFormat::TarBzip2 => {
394                let mut encoder =
395                    bzip2::write::BzEncoder::new(archive_file, bzip2::Compression::best());
396                do_archive_files(&mut encoder)?;
397                encoder.finish()?;
398            }
399            ArchiveFormat::TarGzip => {
400                let mut encoder =
401                    flate2::write::GzEncoder::new(archive_file, flate2::Compression::default());
402                do_archive_files(&mut encoder)?;
403                encoder.finish()?;
404            }
405            ArchiveFormat::TarZstd => {
406                let mut encoder = zstd::stream::Encoder::new(archive_file, 0)?;
407                do_archive_files(&mut encoder)?;
408                encoder.finish()?;
409            }
410            ArchiveFormat::TarLz4 => {
411                let mut encoder = lz4::EncoderBuilder::new().level(1).build(archive_file)?;
412                do_archive_files(&mut encoder)?;
413                let (_output, result) = encoder.finish();
414                result?
415            }
416            ArchiveFormat::Tar => {
417                do_archive_files(&mut archive_file)?;
418            }
419        };
420    }
421
422    // Atomically move the archive into position for other validators to find
423    let metadata = fs::metadata(&archive_path)
424        .map_err(|e| SnapshotError::IoWithSource(e, "archive path stat"))?;
425    fs::rename(&archive_path, snapshot_package.path())
426        .map_err(|e| SnapshotError::IoWithSource(e, "archive path rename"))?;
427
428    purge_old_snapshot_archives(
429        full_snapshot_archives_dir,
430        incremental_snapshot_archives_dir,
431        maximum_full_snapshot_archives_to_retain,
432        maximum_incremental_snapshot_archives_to_retain,
433    );
434
435    timer.stop();
436    info!(
437        "Successfully created {:?}. slot: {}, elapsed ms: {}, size={}",
438        snapshot_package.path(),
439        snapshot_package.slot(),
440        timer.as_ms(),
441        metadata.len()
442    );
443
444    datapoint_info!(
445        "archive-snapshot-package",
446        ("slot", snapshot_package.slot(), i64),
447        (
448            "archive_format",
449            snapshot_package.archive_format().to_string(),
450            String
451        ),
452        ("duration_ms", timer.as_ms(), i64),
453        (
454            if snapshot_package.snapshot_type.is_full_snapshot() {
455                "full-snapshot-archive-size"
456            } else {
457                "incremental-snapshot-archive-size"
458            },
459            metadata.len(),
460            i64
461        ),
462    );
463    Ok(())
464}
465
466/// Get the bank snapshots in a directory
467pub fn get_bank_snapshots(bank_snapshots_dir: impl AsRef<Path>) -> Vec<BankSnapshotInfo> {
468    let mut bank_snapshots = Vec::default();
469    match fs::read_dir(&bank_snapshots_dir) {
470        Err(err) => {
471            info!(
472                "Unable to read bank snapshots directory {}: {}",
473                bank_snapshots_dir.as_ref().display(),
474                err
475            );
476        }
477        Ok(paths) => paths
478            .filter_map(|entry| {
479                // check if this entry is a directory and only a Slot
480                // bank snapshots are bank_snapshots_dir/slot/slot(BANK_SNAPSHOT_PRE_FILENAME_EXTENSION)
481                entry
482                    .ok()
483                    .filter(|entry| entry.path().is_dir())
484                    .and_then(|entry| {
485                        entry
486                            .path()
487                            .file_name()
488                            .and_then(|file_name| file_name.to_str())
489                            .and_then(|file_name| file_name.parse::<Slot>().ok())
490                    })
491            })
492            .for_each(|slot| {
493                // check this directory to see if there is a BankSnapshotPre and/or
494                // BankSnapshotPost file
495                let bank_snapshot_outer_dir = get_bank_snapshots_dir(&bank_snapshots_dir, slot);
496                let bank_snapshot_post_path =
497                    bank_snapshot_outer_dir.join(get_snapshot_file_name(slot));
498                let mut bank_snapshot_pre_path = bank_snapshot_post_path.clone();
499                bank_snapshot_pre_path.set_extension(BANK_SNAPSHOT_PRE_FILENAME_EXTENSION);
500
501                if bank_snapshot_pre_path.is_file() {
502                    bank_snapshots.push(BankSnapshotInfo {
503                        slot,
504                        snapshot_path: bank_snapshot_pre_path,
505                        snapshot_type: BankSnapshotType::Pre,
506                    });
507                }
508
509                if bank_snapshot_post_path.is_file() {
510                    bank_snapshots.push(BankSnapshotInfo {
511                        slot,
512                        snapshot_path: bank_snapshot_post_path,
513                        snapshot_type: BankSnapshotType::Post,
514                    });
515                }
516            }),
517    }
518    bank_snapshots
519}
520
521/// Get the bank snapshots in a directory
522///
523/// This function retains only the bank snapshots of type BankSnapshotType::Pre
524pub fn get_bank_snapshots_pre(bank_snapshots_dir: impl AsRef<Path>) -> Vec<BankSnapshotInfo> {
525    let mut bank_snapshots = get_bank_snapshots(bank_snapshots_dir);
526    bank_snapshots.retain(|bank_snapshot| bank_snapshot.snapshot_type == BankSnapshotType::Pre);
527    bank_snapshots
528}
529
530/// Get the bank snapshots in a directory
531///
532/// This function retains only the bank snapshots of type BankSnapshotType::Post
533pub fn get_bank_snapshots_post(bank_snapshots_dir: impl AsRef<Path>) -> Vec<BankSnapshotInfo> {
534    let mut bank_snapshots = get_bank_snapshots(bank_snapshots_dir);
535    bank_snapshots.retain(|bank_snapshot| bank_snapshot.snapshot_type == BankSnapshotType::Post);
536    bank_snapshots
537}
538
539/// Get the bank snapshot with the highest slot in a directory
540///
541/// This function gets the highest bank snapshot of type BankSnapshotType::Pre
542pub fn get_highest_bank_snapshot_pre(
543    bank_snapshots_dir: impl AsRef<Path>,
544) -> Option<BankSnapshotInfo> {
545    do_get_highest_bank_snapshot(get_bank_snapshots_pre(bank_snapshots_dir))
546}
547
548/// Get the bank snapshot with the highest slot in a directory
549///
550/// This function gets the highest bank snapshot of type BankSnapshotType::Post
551pub fn get_highest_bank_snapshot_post(
552    bank_snapshots_dir: impl AsRef<Path>,
553) -> Option<BankSnapshotInfo> {
554    do_get_highest_bank_snapshot(get_bank_snapshots_post(bank_snapshots_dir))
555}
556
557fn do_get_highest_bank_snapshot(
558    mut bank_snapshots: Vec<BankSnapshotInfo>,
559) -> Option<BankSnapshotInfo> {
560    bank_snapshots.sort_unstable();
561    bank_snapshots.into_iter().rev().next()
562}
563
564pub fn serialize_snapshot_data_file<F>(data_file_path: &Path, serializer: F) -> Result<u64>
565where
566    F: FnOnce(&mut BufWriter<File>) -> Result<()>,
567{
568    serialize_snapshot_data_file_capped::<F>(
569        data_file_path,
570        MAX_SNAPSHOT_DATA_FILE_SIZE,
571        serializer,
572    )
573}
574
575pub fn deserialize_snapshot_data_file<T: Sized>(
576    data_file_path: &Path,
577    deserializer: impl FnOnce(&mut BufReader<File>) -> Result<T>,
578) -> Result<T> {
579    let wrapped_deserializer = move |streams: &mut SnapshotStreams<File>| -> Result<T> {
580        deserializer(streams.full_snapshot_stream)
581    };
582
583    let wrapped_data_file_path = SnapshotRootPaths {
584        full_snapshot_root_file_path: data_file_path.to_path_buf(),
585        incremental_snapshot_root_file_path: None,
586    };
587
588    deserialize_snapshot_data_files_capped(
589        &wrapped_data_file_path,
590        MAX_SNAPSHOT_DATA_FILE_SIZE,
591        wrapped_deserializer,
592    )
593}
594
595fn deserialize_snapshot_data_files<T: Sized>(
596    snapshot_root_paths: &SnapshotRootPaths,
597    deserializer: impl FnOnce(&mut SnapshotStreams<File>) -> Result<T>,
598) -> Result<T> {
599    deserialize_snapshot_data_files_capped(
600        snapshot_root_paths,
601        MAX_SNAPSHOT_DATA_FILE_SIZE,
602        deserializer,
603    )
604}
605
606fn serialize_snapshot_data_file_capped<F>(
607    data_file_path: &Path,
608    maximum_file_size: u64,
609    serializer: F,
610) -> Result<u64>
611where
612    F: FnOnce(&mut BufWriter<File>) -> Result<()>,
613{
614    let data_file = File::create(data_file_path)?;
615    let mut data_file_stream = BufWriter::new(data_file);
616    serializer(&mut data_file_stream)?;
617    data_file_stream.flush()?;
618
619    let consumed_size = data_file_stream.stream_position()?;
620    if consumed_size > maximum_file_size {
621        let error_message = format!(
622            "too large snapshot data file to serialize: {:?} has {} bytes",
623            data_file_path, consumed_size
624        );
625        return Err(get_io_error(&error_message));
626    }
627    Ok(consumed_size)
628}
629
630fn deserialize_snapshot_data_files_capped<T: Sized>(
631    snapshot_root_paths: &SnapshotRootPaths,
632    maximum_file_size: u64,
633    deserializer: impl FnOnce(&mut SnapshotStreams<File>) -> Result<T>,
634) -> Result<T> {
635    let (full_snapshot_file_size, mut full_snapshot_data_file_stream) =
636        create_snapshot_data_file_stream(
637            &snapshot_root_paths.full_snapshot_root_file_path,
638            maximum_file_size,
639        )?;
640
641    let (incremental_snapshot_file_size, mut incremental_snapshot_data_file_stream) =
642        if let Some(ref incremental_snapshot_root_file_path) =
643            snapshot_root_paths.incremental_snapshot_root_file_path
644        {
645            let (incremental_snapshot_file_size, incremental_snapshot_data_file_stream) =
646                create_snapshot_data_file_stream(
647                    incremental_snapshot_root_file_path,
648                    maximum_file_size,
649                )?;
650            (
651                Some(incremental_snapshot_file_size),
652                Some(incremental_snapshot_data_file_stream),
653            )
654        } else {
655            (None, None)
656        };
657
658    let mut snapshot_streams = SnapshotStreams {
659        full_snapshot_stream: &mut full_snapshot_data_file_stream,
660        incremental_snapshot_stream: incremental_snapshot_data_file_stream.as_mut(),
661    };
662    let ret = deserializer(&mut snapshot_streams)?;
663
664    check_deserialize_file_consumed(
665        full_snapshot_file_size,
666        &snapshot_root_paths.full_snapshot_root_file_path,
667        &mut full_snapshot_data_file_stream,
668    )?;
669
670    if let Some(ref incremental_snapshot_root_file_path) =
671        snapshot_root_paths.incremental_snapshot_root_file_path
672    {
673        check_deserialize_file_consumed(
674            incremental_snapshot_file_size.unwrap(),
675            incremental_snapshot_root_file_path,
676            incremental_snapshot_data_file_stream.as_mut().unwrap(),
677        )?;
678    }
679
680    Ok(ret)
681}
682
683/// Before running the deserializer function, perform common operations on the snapshot archive
684/// files, such as checking the file size and opening the file into a stream.
685fn create_snapshot_data_file_stream<P>(
686    snapshot_root_file_path: P,
687    maximum_file_size: u64,
688) -> Result<(u64, BufReader<File>)>
689where
690    P: AsRef<Path>,
691{
692    let snapshot_file_size = fs::metadata(&snapshot_root_file_path)?.len();
693
694    if snapshot_file_size > maximum_file_size {
695        let error_message =
696            format!(
697            "too large snapshot data file to deserialize: {} has {} bytes (max size is {} bytes)",
698            snapshot_root_file_path.as_ref().display(), snapshot_file_size, maximum_file_size
699        );
700        return Err(get_io_error(&error_message));
701    }
702
703    let snapshot_data_file = File::open(&snapshot_root_file_path)?;
704    let snapshot_data_file_stream = BufReader::new(snapshot_data_file);
705
706    Ok((snapshot_file_size, snapshot_data_file_stream))
707}
708
709/// After running the deserializer function, perform common checks to ensure the snapshot archive
710/// files were consumed correctly.
711fn check_deserialize_file_consumed<P>(
712    file_size: u64,
713    file_path: P,
714    file_stream: &mut BufReader<File>,
715) -> Result<()>
716where
717    P: AsRef<Path>,
718{
719    let consumed_size = file_stream.stream_position()?;
720
721    if consumed_size != file_size {
722        let error_message =
723            format!(
724            "invalid snapshot data file: {} has {} bytes, however consumed {} bytes to deserialize",
725            file_path.as_ref().display(), file_size, consumed_size
726        );
727        return Err(get_io_error(&error_message));
728    }
729
730    Ok(())
731}
732
733/// Serialize a bank to a snapshot
734pub fn add_bank_snapshot<P: AsRef<Path>>(
735    bank_snapshots_dir: P,
736    bank: &Bank,
737    snapshot_storages: &[SnapshotStorage],
738    snapshot_version: SnapshotVersion,
739) -> Result<BankSnapshotInfo> {
740    let slot = bank.slot();
741    // bank_snapshots_dir/slot
742    let bank_snapshots_dir = get_bank_snapshots_dir(bank_snapshots_dir, slot);
743    fs::create_dir_all(&bank_snapshots_dir)?;
744
745    // the bank snapshot is stored as bank_snapshots_dir/slot/slot.BANK_SNAPSHOT_PRE_FILENAME_EXTENSION
746    let mut bank_snapshot_path = bank_snapshots_dir.join(get_snapshot_file_name(slot));
747    bank_snapshot_path.set_extension(BANK_SNAPSHOT_PRE_FILENAME_EXTENSION);
748
749    info!(
750        "Creating bank snapshot for slot {}, path: {}",
751        slot,
752        bank_snapshot_path.display(),
753    );
754
755    let mut bank_serialize = Measure::start("bank-serialize-ms");
756    let bank_snapshot_serializer = move |stream: &mut BufWriter<File>| -> Result<()> {
757        let serde_style = match snapshot_version {
758            SnapshotVersion::V1_2_0 => SerdeStyle::Newer,
759        };
760        bank_to_stream(serde_style, stream.by_ref(), bank, snapshot_storages)?;
761        Ok(())
762    };
763    let consumed_size =
764        serialize_snapshot_data_file(&bank_snapshot_path, bank_snapshot_serializer)?;
765    bank_serialize.stop();
766
767    // Monitor sizes because they're capped to MAX_SNAPSHOT_DATA_FILE_SIZE
768    datapoint_info!(
769        "snapshot-bank-file",
770        ("slot", slot, i64),
771        ("size", consumed_size, i64)
772    );
773
774    inc_new_counter_info!("bank-serialize-ms", bank_serialize.as_ms() as usize);
775
776    info!(
777        "{} for slot {} at {}",
778        bank_serialize,
779        slot,
780        bank_snapshot_path.display(),
781    );
782
783    Ok(BankSnapshotInfo {
784        slot,
785        snapshot_path: bank_snapshot_path,
786        snapshot_type: BankSnapshotType::Pre,
787    })
788}
789
790fn serialize_status_cache(
791    slot: Slot,
792    slot_deltas: &[BankSlotDelta],
793    status_cache_path: &Path,
794) -> Result<()> {
795    let mut status_cache_serialize = Measure::start("status_cache_serialize-ms");
796    let consumed_size = serialize_snapshot_data_file(status_cache_path, |stream| {
797        serialize_into(stream, slot_deltas)?;
798        Ok(())
799    })?;
800    status_cache_serialize.stop();
801
802    // Monitor sizes because they're capped to MAX_SNAPSHOT_DATA_FILE_SIZE
803    datapoint_info!(
804        "snapshot-status-cache-file",
805        ("slot", slot, i64),
806        ("size", consumed_size, i64)
807    );
808
809    inc_new_counter_info!(
810        "serialize-status-cache-ms",
811        status_cache_serialize.as_ms() as usize
812    );
813    Ok(())
814}
815
816/// Remove the snapshot directory for this slot
817pub fn remove_bank_snapshot<P>(slot: Slot, bank_snapshots_dir: P) -> Result<()>
818where
819    P: AsRef<Path>,
820{
821    let bank_snapshot_dir = get_bank_snapshots_dir(&bank_snapshots_dir, slot);
822    fs::remove_dir_all(bank_snapshot_dir)?;
823    Ok(())
824}
825
826#[derive(Debug, Default)]
827pub struct BankFromArchiveTimings {
828    pub rebuild_bank_from_snapshots_us: u64,
829    pub full_snapshot_untar_us: u64,
830    pub incremental_snapshot_untar_us: u64,
831    pub verify_snapshot_bank_us: u64,
832}
833
834// From testing, 4 seems to be a sweet spot for ranges of 60M-360M accounts and 16-64 cores. This may need to be tuned later.
835const PARALLEL_UNTAR_READERS_DEFAULT: usize = 4;
836
837fn verify_and_unarchive_snapshots(
838    bank_snapshots_dir: impl AsRef<Path>,
839    full_snapshot_archive_info: &FullSnapshotArchiveInfo,
840    incremental_snapshot_archive_info: Option<&IncrementalSnapshotArchiveInfo>,
841    account_paths: &[PathBuf],
842) -> Result<(UnarchivedSnapshot, Option<UnarchivedSnapshot>)> {
843    check_are_snapshots_compatible(
844        full_snapshot_archive_info,
845        incremental_snapshot_archive_info,
846    )?;
847
848    let parallel_divisions = std::cmp::min(
849        PARALLEL_UNTAR_READERS_DEFAULT,
850        std::cmp::max(1, num_cpus::get() / 4),
851    );
852
853    let unarchived_full_snapshot = unarchive_snapshot(
854        &bank_snapshots_dir,
855        TMP_SNAPSHOT_ARCHIVE_PREFIX,
856        full_snapshot_archive_info.path(),
857        "snapshot untar",
858        account_paths,
859        full_snapshot_archive_info.archive_format(),
860        parallel_divisions,
861    )?;
862
863    let unarchived_incremental_snapshot =
864        if let Some(incremental_snapshot_archive_info) = incremental_snapshot_archive_info {
865            let unarchived_incremental_snapshot = unarchive_snapshot(
866                &bank_snapshots_dir,
867                TMP_SNAPSHOT_ARCHIVE_PREFIX,
868                incremental_snapshot_archive_info.path(),
869                "incremental snapshot untar",
870                account_paths,
871                incremental_snapshot_archive_info.archive_format(),
872                parallel_divisions,
873            )?;
874            Some(unarchived_incremental_snapshot)
875        } else {
876            None
877        };
878
879    Ok((unarchived_full_snapshot, unarchived_incremental_snapshot))
880}
881
882/// Utility for parsing out bank specific information from a snapshot archive. This utility can be used
883/// to parse out bank specific information like the leader schedule, epoch schedule, etc.
884pub fn bank_fields_from_snapshot_archives(
885    bank_snapshots_dir: impl AsRef<Path>,
886    full_snapshot_archives_dir: impl AsRef<Path>,
887    incremental_snapshot_archives_dir: impl AsRef<Path>,
888) -> Result<BankFieldsToDeserialize> {
889    let full_snapshot_archive_info =
890        get_highest_full_snapshot_archive_info(&full_snapshot_archives_dir)
891            .ok_or(SnapshotError::NoSnapshotArchives)?;
892
893    let incremental_snapshot_archive_info = get_highest_incremental_snapshot_archive_info(
894        &incremental_snapshot_archives_dir,
895        full_snapshot_archive_info.slot(),
896    );
897
898    let temp_dir = tempfile::Builder::new()
899        .prefix("dummy-accounts-path")
900        .tempdir()?;
901
902    let account_paths = vec![temp_dir.path().to_path_buf()];
903
904    let (unarchived_full_snapshot, unarchived_incremental_snapshot) =
905        verify_and_unarchive_snapshots(
906            &bank_snapshots_dir,
907            &full_snapshot_archive_info,
908            incremental_snapshot_archive_info.as_ref(),
909            &account_paths,
910        )?;
911
912    bank_fields_from_snapshots(
913        &unarchived_full_snapshot.unpacked_snapshots_dir_and_version,
914        unarchived_incremental_snapshot
915            .as_ref()
916            .map(|unarchive_preparation_result| {
917                &unarchive_preparation_result.unpacked_snapshots_dir_and_version
918            }),
919    )
920}
921
922/// Rebuild bank from snapshot archives.  Handles either just a full snapshot, or both a full
923/// snapshot and an incremental snapshot.
924#[allow(clippy::too_many_arguments)]
925pub fn bank_from_snapshot_archives(
926    account_paths: &[PathBuf],
927    bank_snapshots_dir: impl AsRef<Path>,
928    full_snapshot_archive_info: &FullSnapshotArchiveInfo,
929    incremental_snapshot_archive_info: Option<&IncrementalSnapshotArchiveInfo>,
930    genesis_config: &GenesisConfig,
931    debug_keys: Option<Arc<HashSet<Pubkey>>>,
932    additional_builtins: Option<&Builtins>,
933    account_secondary_indexes: AccountSecondaryIndexes,
934    accounts_db_caching_enabled: bool,
935    limit_load_slot_count_from_snapshot: Option<usize>,
936    shrink_ratio: AccountShrinkThreshold,
937    test_hash_calculation: bool,
938    accounts_db_skip_shrink: bool,
939    verify_index: bool,
940    accounts_db_config: Option<AccountsDbConfig>,
941    accounts_update_notifier: Option<AccountsUpdateNotifier>,
942) -> Result<(Bank, BankFromArchiveTimings)> {
943    let (unarchived_full_snapshot, mut unarchived_incremental_snapshot) =
944        verify_and_unarchive_snapshots(
945            bank_snapshots_dir,
946            full_snapshot_archive_info,
947            incremental_snapshot_archive_info,
948            account_paths,
949        )?;
950
951    let mut unpacked_append_vec_map = unarchived_full_snapshot.unpacked_append_vec_map;
952    if let Some(ref mut unarchive_preparation_result) = unarchived_incremental_snapshot {
953        let incremental_snapshot_unpacked_append_vec_map =
954            std::mem::take(&mut unarchive_preparation_result.unpacked_append_vec_map);
955        unpacked_append_vec_map.extend(incremental_snapshot_unpacked_append_vec_map.into_iter());
956    }
957
958    let mut measure_rebuild = Measure::start("rebuild bank from snapshots");
959    let bank = rebuild_bank_from_snapshots(
960        &unarchived_full_snapshot.unpacked_snapshots_dir_and_version,
961        unarchived_incremental_snapshot
962            .as_ref()
963            .map(|unarchive_preparation_result| {
964                &unarchive_preparation_result.unpacked_snapshots_dir_and_version
965            }),
966        account_paths,
967        unpacked_append_vec_map,
968        genesis_config,
969        debug_keys,
970        additional_builtins,
971        account_secondary_indexes,
972        accounts_db_caching_enabled,
973        limit_load_slot_count_from_snapshot,
974        shrink_ratio,
975        verify_index,
976        accounts_db_config,
977        accounts_update_notifier,
978    )?;
979    measure_rebuild.stop();
980    info!("{}", measure_rebuild);
981
982    let mut measure_verify = Measure::start("verify");
983    if !bank.verify_snapshot_bank(
984        test_hash_calculation,
985        accounts_db_skip_shrink || !full_snapshot_archive_info.is_remote(),
986        Some(full_snapshot_archive_info.slot()),
987    ) && limit_load_slot_count_from_snapshot.is_none()
988    {
989        panic!("Snapshot bank for slot {} failed to verify", bank.slot());
990    }
991    measure_verify.stop();
992
993    let timings = BankFromArchiveTimings {
994        rebuild_bank_from_snapshots_us: measure_rebuild.as_us(),
995        full_snapshot_untar_us: unarchived_full_snapshot.measure_untar.as_us(),
996        incremental_snapshot_untar_us: unarchived_incremental_snapshot
997            .map_or(0, |unarchive_preparation_result| {
998                unarchive_preparation_result.measure_untar.as_us()
999            }),
1000        verify_snapshot_bank_us: measure_verify.as_us(),
1001    };
1002    Ok((bank, timings))
1003}
1004
1005/// Rebuild bank from snapshot archives.  This function searches `full_snapshot_archives_dir` and `incremental_snapshot_archives_dir` for the
1006/// highest full snapshot and highest corresponding incremental snapshot, then rebuilds the bank.
1007#[allow(clippy::too_many_arguments)]
1008pub fn bank_from_latest_snapshot_archives(
1009    bank_snapshots_dir: impl AsRef<Path>,
1010    full_snapshot_archives_dir: impl AsRef<Path>,
1011    incremental_snapshot_archives_dir: impl AsRef<Path>,
1012    account_paths: &[PathBuf],
1013    genesis_config: &GenesisConfig,
1014    debug_keys: Option<Arc<HashSet<Pubkey>>>,
1015    additional_builtins: Option<&Builtins>,
1016    account_secondary_indexes: AccountSecondaryIndexes,
1017    accounts_db_caching_enabled: bool,
1018    limit_load_slot_count_from_snapshot: Option<usize>,
1019    shrink_ratio: AccountShrinkThreshold,
1020    test_hash_calculation: bool,
1021    accounts_db_skip_shrink: bool,
1022    verify_index: bool,
1023    accounts_db_config: Option<AccountsDbConfig>,
1024    accounts_update_notifier: Option<AccountsUpdateNotifier>,
1025) -> Result<(
1026    Bank,
1027    FullSnapshotArchiveInfo,
1028    Option<IncrementalSnapshotArchiveInfo>,
1029)> {
1030    let full_snapshot_archive_info =
1031        get_highest_full_snapshot_archive_info(&full_snapshot_archives_dir)
1032            .ok_or(SnapshotError::NoSnapshotArchives)?;
1033
1034    let incremental_snapshot_archive_info = get_highest_incremental_snapshot_archive_info(
1035        &incremental_snapshot_archives_dir,
1036        full_snapshot_archive_info.slot(),
1037    );
1038
1039    info!(
1040        "Loading bank from full snapshot: {}, and incremental snapshot: {:?}",
1041        full_snapshot_archive_info.path().display(),
1042        incremental_snapshot_archive_info
1043            .as_ref()
1044            .map(
1045                |incremental_snapshot_archive_info| incremental_snapshot_archive_info
1046                    .path()
1047                    .display()
1048            )
1049    );
1050
1051    let (bank, timings) = bank_from_snapshot_archives(
1052        account_paths,
1053        bank_snapshots_dir.as_ref(),
1054        &full_snapshot_archive_info,
1055        incremental_snapshot_archive_info.as_ref(),
1056        genesis_config,
1057        debug_keys,
1058        additional_builtins,
1059        account_secondary_indexes,
1060        accounts_db_caching_enabled,
1061        limit_load_slot_count_from_snapshot,
1062        shrink_ratio,
1063        test_hash_calculation,
1064        accounts_db_skip_shrink,
1065        verify_index,
1066        accounts_db_config,
1067        accounts_update_notifier,
1068    )?;
1069
1070    datapoint_info!(
1071        "bank_from_snapshot_archives",
1072        (
1073            "full_snapshot_untar_us",
1074            timings.full_snapshot_untar_us,
1075            i64
1076        ),
1077        (
1078            "incremental_snapshot_untar_us",
1079            timings.incremental_snapshot_untar_us,
1080            i64
1081        ),
1082        (
1083            "rebuild_bank_from_snapshots_us",
1084            timings.rebuild_bank_from_snapshots_us,
1085            i64
1086        ),
1087        (
1088            "verify_snapshot_bank_us",
1089            timings.verify_snapshot_bank_us,
1090            i64
1091        ),
1092    );
1093
1094    verify_bank_against_expected_slot_hash(
1095        &bank,
1096        incremental_snapshot_archive_info.as_ref().map_or(
1097            full_snapshot_archive_info.slot(),
1098            |incremental_snapshot_archive_info| incremental_snapshot_archive_info.slot(),
1099        ),
1100        incremental_snapshot_archive_info.as_ref().map_or(
1101            *full_snapshot_archive_info.hash(),
1102            |incremental_snapshot_archive_info| *incremental_snapshot_archive_info.hash(),
1103        ),
1104    )?;
1105
1106    Ok((
1107        bank,
1108        full_snapshot_archive_info,
1109        incremental_snapshot_archive_info,
1110    ))
1111}
1112
1113/// Check to make sure the deserialized bank's slot and hash matches the snapshot archive's slot
1114/// and hash
1115fn verify_bank_against_expected_slot_hash(
1116    bank: &Bank,
1117    expected_slot: Slot,
1118    expected_hash: Hash,
1119) -> Result<()> {
1120    let bank_slot = bank.slot();
1121    let bank_hash = bank.get_accounts_hash();
1122
1123    if bank_slot != expected_slot || bank_hash != expected_hash {
1124        return Err(SnapshotError::MismatchedSlotHash(
1125            (bank_slot, bank_hash),
1126            (expected_slot, expected_hash),
1127        ));
1128    }
1129
1130    Ok(())
1131}
1132
1133/// Perform the common tasks when unarchiving a snapshot.  Handles creating the temporary
1134/// directories, untaring, reading the version file, and then returning those fields plus the
1135/// unpacked append vec map.
1136fn unarchive_snapshot<P, Q>(
1137    bank_snapshots_dir: P,
1138    unpacked_snapshots_dir_prefix: &'static str,
1139    snapshot_archive_path: Q,
1140    measure_name: &'static str,
1141    account_paths: &[PathBuf],
1142    archive_format: ArchiveFormat,
1143    parallel_divisions: usize,
1144) -> Result<UnarchivedSnapshot>
1145where
1146    P: AsRef<Path>,
1147    Q: AsRef<Path>,
1148{
1149    let unpack_dir = tempfile::Builder::new()
1150        .prefix(unpacked_snapshots_dir_prefix)
1151        .tempdir_in(bank_snapshots_dir)?;
1152    let unpacked_snapshots_dir = unpack_dir.path().join("snapshots");
1153
1154    let mut measure_untar = Measure::start(measure_name);
1155    let unpacked_append_vec_map = untar_snapshot_in(
1156        snapshot_archive_path,
1157        unpack_dir.path(),
1158        account_paths,
1159        archive_format,
1160        parallel_divisions,
1161    )?;
1162    measure_untar.stop();
1163    info!("{}", measure_untar);
1164
1165    let unpacked_version_file = unpack_dir.path().join("version");
1166    let snapshot_version = snapshot_version_from_file(&unpacked_version_file)?;
1167
1168    Ok(UnarchivedSnapshot {
1169        unpack_dir,
1170        unpacked_append_vec_map,
1171        unpacked_snapshots_dir_and_version: UnpackedSnapshotsDirAndVersion {
1172            unpacked_snapshots_dir,
1173            snapshot_version,
1174        },
1175        measure_untar,
1176    })
1177}
1178
1179/// Reads the `snapshot_version` from a file. Before opening the file, its size
1180/// is compared to `MAX_SNAPSHOT_VERSION_FILE_SIZE`. If the size exceeds this
1181/// threshold, it is not opened and an error is returned.
1182fn snapshot_version_from_file(path: impl AsRef<Path>) -> Result<String> {
1183    // Check file size.
1184    let file_size = fs::metadata(&path)?.len();
1185    if file_size > MAX_SNAPSHOT_VERSION_FILE_SIZE {
1186        let error_message = format!(
1187            "snapshot version file too large: {} has {} bytes (max size is {} bytes)",
1188            path.as_ref().display(),
1189            file_size,
1190            MAX_SNAPSHOT_VERSION_FILE_SIZE,
1191        );
1192        return Err(get_io_error(&error_message));
1193    }
1194
1195    // Read snapshot_version from file.
1196    let mut snapshot_version = String::new();
1197    File::open(path).and_then(|mut f| f.read_to_string(&mut snapshot_version))?;
1198    Ok(snapshot_version.trim().to_string())
1199}
1200
1201/// Check if an incremental snapshot is compatible with a full snapshot.  This is done by checking
1202/// if the incremental snapshot's base slot is the same as the full snapshot's slot.
1203fn check_are_snapshots_compatible(
1204    full_snapshot_archive_info: &FullSnapshotArchiveInfo,
1205    incremental_snapshot_archive_info: Option<&IncrementalSnapshotArchiveInfo>,
1206) -> Result<()> {
1207    if incremental_snapshot_archive_info.is_none() {
1208        return Ok(());
1209    }
1210
1211    let incremental_snapshot_archive_info = incremental_snapshot_archive_info.unwrap();
1212
1213    (full_snapshot_archive_info.slot() == incremental_snapshot_archive_info.base_slot())
1214        .then(|| ())
1215        .ok_or_else(|| {
1216            SnapshotError::MismatchedBaseSlot(
1217                full_snapshot_archive_info.slot(),
1218                incremental_snapshot_archive_info.base_slot(),
1219            )
1220        })
1221}
1222
1223/// Get the `&str` from a `&Path`
1224pub fn path_to_file_name_str(path: &Path) -> Result<&str> {
1225    path.file_name()
1226        .ok_or_else(|| SnapshotError::PathToFileNameError(path.to_path_buf()))?
1227        .to_str()
1228        .ok_or_else(|| SnapshotError::FileNameToStrError(path.to_path_buf()))
1229}
1230
1231pub fn build_snapshot_archives_remote_dir(snapshot_archives_dir: impl AsRef<Path>) -> PathBuf {
1232    snapshot_archives_dir
1233        .as_ref()
1234        .join(SNAPSHOT_ARCHIVE_DOWNLOAD_DIR)
1235}
1236
1237/// Build the full snapshot archive path from its components: the snapshot archives directory, the
1238/// snapshot slot, the accounts hash, and the archive format.
1239pub fn build_full_snapshot_archive_path(
1240    full_snapshot_archives_dir: impl AsRef<Path>,
1241    slot: Slot,
1242    hash: &Hash,
1243    archive_format: ArchiveFormat,
1244) -> PathBuf {
1245    full_snapshot_archives_dir.as_ref().join(format!(
1246        "snapshot-{}-{}.{}",
1247        slot,
1248        hash,
1249        archive_format.extension(),
1250    ))
1251}
1252
1253/// Build the incremental snapshot archive path from its components: the snapshot archives
1254/// directory, the snapshot base slot, the snapshot slot, the accounts hash, and the archive
1255/// format.
1256pub fn build_incremental_snapshot_archive_path(
1257    incremental_snapshot_archives_dir: impl AsRef<Path>,
1258    base_slot: Slot,
1259    slot: Slot,
1260    hash: &Hash,
1261    archive_format: ArchiveFormat,
1262) -> PathBuf {
1263    incremental_snapshot_archives_dir.as_ref().join(format!(
1264        "incremental-snapshot-{}-{}-{}.{}",
1265        base_slot,
1266        slot,
1267        hash,
1268        archive_format.extension(),
1269    ))
1270}
1271
1272/// Parse a full snapshot archive filename into its Slot, Hash, and Archive Format
1273pub(crate) fn parse_full_snapshot_archive_filename(
1274    archive_filename: &str,
1275) -> Result<(Slot, Hash, ArchiveFormat)> {
1276    lazy_static! {
1277        static ref RE: Regex = Regex::new(FULL_SNAPSHOT_ARCHIVE_FILENAME_REGEX).unwrap();
1278    }
1279
1280    let do_parse = || {
1281        RE.captures(archive_filename).and_then(|captures| {
1282            let slot = captures
1283                .name("slot")
1284                .map(|x| x.as_str().parse::<Slot>())?
1285                .ok()?;
1286            let hash = captures
1287                .name("hash")
1288                .map(|x| x.as_str().parse::<Hash>())?
1289                .ok()?;
1290            let archive_format = captures
1291                .name("ext")
1292                .map(|x| x.as_str().parse::<ArchiveFormat>())?
1293                .ok()?;
1294
1295            Some((slot, hash, archive_format))
1296        })
1297    };
1298
1299    do_parse().ok_or_else(|| {
1300        SnapshotError::ParseSnapshotArchiveFileNameError(archive_filename.to_string())
1301    })
1302}
1303
1304/// Parse an incremental snapshot archive filename into its base Slot, actual Slot, Hash, and Archive Format
1305pub(crate) fn parse_incremental_snapshot_archive_filename(
1306    archive_filename: &str,
1307) -> Result<(Slot, Slot, Hash, ArchiveFormat)> {
1308    lazy_static! {
1309        static ref RE: Regex = Regex::new(INCREMENTAL_SNAPSHOT_ARCHIVE_FILENAME_REGEX).unwrap();
1310    }
1311
1312    let do_parse = || {
1313        RE.captures(archive_filename).and_then(|captures| {
1314            let base_slot = captures
1315                .name("base")
1316                .map(|x| x.as_str().parse::<Slot>())?
1317                .ok()?;
1318            let slot = captures
1319                .name("slot")
1320                .map(|x| x.as_str().parse::<Slot>())?
1321                .ok()?;
1322            let hash = captures
1323                .name("hash")
1324                .map(|x| x.as_str().parse::<Hash>())?
1325                .ok()?;
1326            let archive_format = captures
1327                .name("ext")
1328                .map(|x| x.as_str().parse::<ArchiveFormat>())?
1329                .ok()?;
1330
1331            Some((base_slot, slot, hash, archive_format))
1332        })
1333    };
1334
1335    do_parse().ok_or_else(|| {
1336        SnapshotError::ParseSnapshotArchiveFileNameError(archive_filename.to_string())
1337    })
1338}
1339
1340/// Walk down the snapshot archive to collect snapshot archive file info
1341fn get_snapshot_archives<T, F>(snapshot_archives_dir: &Path, cb: F) -> Vec<T>
1342where
1343    F: Fn(PathBuf) -> Result<T>,
1344{
1345    let walk_dir = |dir: &Path| -> Vec<T> {
1346        let entry_iter = fs::read_dir(dir);
1347        match entry_iter {
1348            Err(err) => {
1349                info!(
1350                    "Unable to read snapshot archives directory: err: {}, path: {}",
1351                    err,
1352                    dir.display()
1353                );
1354                vec![]
1355            }
1356            Ok(entries) => entries
1357                .filter_map(|entry| entry.map_or(None, |entry| cb(entry.path()).ok()))
1358                .collect(),
1359        }
1360    };
1361
1362    let mut ret = walk_dir(snapshot_archives_dir);
1363    let remote_dir = build_snapshot_archives_remote_dir(snapshot_archives_dir);
1364    if remote_dir.exists() {
1365        ret.append(&mut walk_dir(remote_dir.as_ref()));
1366    }
1367    ret
1368}
1369
1370/// Get a list of the full snapshot archives from a directory
1371pub fn get_full_snapshot_archives(
1372    full_snapshot_archives_dir: impl AsRef<Path>,
1373) -> Vec<FullSnapshotArchiveInfo> {
1374    get_snapshot_archives(
1375        full_snapshot_archives_dir.as_ref(),
1376        FullSnapshotArchiveInfo::new_from_path,
1377    )
1378}
1379
1380/// Get a list of the incremental snapshot archives from a directory
1381pub fn get_incremental_snapshot_archives(
1382    incremental_snapshot_archives_dir: impl AsRef<Path>,
1383) -> Vec<IncrementalSnapshotArchiveInfo> {
1384    get_snapshot_archives(
1385        incremental_snapshot_archives_dir.as_ref(),
1386        IncrementalSnapshotArchiveInfo::new_from_path,
1387    )
1388}
1389
1390/// Get the highest slot of the full snapshot archives in a directory
1391pub fn get_highest_full_snapshot_archive_slot(
1392    full_snapshot_archives_dir: impl AsRef<Path>,
1393) -> Option<Slot> {
1394    get_highest_full_snapshot_archive_info(full_snapshot_archives_dir)
1395        .map(|full_snapshot_archive_info| full_snapshot_archive_info.slot())
1396}
1397
1398/// Get the highest slot of the incremental snapshot archives in a directory, for a given full
1399/// snapshot slot
1400pub fn get_highest_incremental_snapshot_archive_slot(
1401    incremental_snapshot_archives_dir: impl AsRef<Path>,
1402    full_snapshot_slot: Slot,
1403) -> Option<Slot> {
1404    get_highest_incremental_snapshot_archive_info(
1405        incremental_snapshot_archives_dir,
1406        full_snapshot_slot,
1407    )
1408    .map(|incremental_snapshot_archive_info| incremental_snapshot_archive_info.slot())
1409}
1410
1411/// Get the path (and metadata) for the full snapshot archive with the highest slot in a directory
1412pub fn get_highest_full_snapshot_archive_info(
1413    full_snapshot_archives_dir: impl AsRef<Path>,
1414) -> Option<FullSnapshotArchiveInfo> {
1415    let mut full_snapshot_archives = get_full_snapshot_archives(full_snapshot_archives_dir);
1416    full_snapshot_archives.sort_unstable();
1417    full_snapshot_archives.into_iter().rev().next()
1418}
1419
1420/// Get the path for the incremental snapshot archive with the highest slot, for a given full
1421/// snapshot slot, in a directory
1422pub fn get_highest_incremental_snapshot_archive_info(
1423    incremental_snapshot_archives_dir: impl AsRef<Path>,
1424    full_snapshot_slot: Slot,
1425) -> Option<IncrementalSnapshotArchiveInfo> {
1426    // Since we want to filter down to only the incremental snapshot archives that have the same
1427    // full snapshot slot as the value passed in, perform the filtering before sorting to avoid
1428    // doing unnecessary work.
1429    let mut incremental_snapshot_archives =
1430        get_incremental_snapshot_archives(incremental_snapshot_archives_dir)
1431            .into_iter()
1432            .filter(|incremental_snapshot_archive_info| {
1433                incremental_snapshot_archive_info.base_slot() == full_snapshot_slot
1434            })
1435            .collect::<Vec<_>>();
1436    incremental_snapshot_archives.sort_unstable();
1437    incremental_snapshot_archives.into_iter().rev().next()
1438}
1439
1440pub fn purge_old_snapshot_archives(
1441    full_snapshot_archives_dir: impl AsRef<Path>,
1442    incremental_snapshot_archives_dir: impl AsRef<Path>,
1443    maximum_full_snapshot_archives_to_retain: usize,
1444    maximum_incremental_snapshot_archives_to_retain: usize,
1445) {
1446    info!(
1447        "Purging old full snapshot archives in {}, retaining up to {} full snapshots",
1448        full_snapshot_archives_dir.as_ref().display(),
1449        maximum_full_snapshot_archives_to_retain
1450    );
1451
1452    let mut full_snapshot_archives = get_full_snapshot_archives(&full_snapshot_archives_dir);
1453    full_snapshot_archives.sort_unstable();
1454    full_snapshot_archives.reverse();
1455
1456    let num_to_retain = full_snapshot_archives.len().min(
1457        maximum_full_snapshot_archives_to_retain
1458            .max(1 /* Always keep at least one full snapshot */),
1459    );
1460    trace!(
1461        "There are {} full snapshot archives, retaining {}",
1462        full_snapshot_archives.len(),
1463        num_to_retain,
1464    );
1465
1466    let (full_snapshot_archives_to_retain, full_snapshot_archives_to_remove) =
1467        if full_snapshot_archives.is_empty() {
1468            None
1469        } else {
1470            Some(full_snapshot_archives.split_at(num_to_retain))
1471        }
1472        .unwrap_or_default();
1473
1474    let retained_full_snapshot_slots = full_snapshot_archives_to_retain
1475        .iter()
1476        .map(|ai| ai.slot())
1477        .collect::<HashSet<_>>();
1478
1479    fn remove_archives<T: SnapshotArchiveInfoGetter>(archives: &[T]) {
1480        for path in archives.iter().map(|a| a.path()) {
1481            trace!("Removing snapshot archive: {}", path.display());
1482            fs::remove_file(path)
1483                .unwrap_or_else(|err| info!("Failed to remove {}: {}", path.display(), err));
1484        }
1485    }
1486    remove_archives(full_snapshot_archives_to_remove);
1487
1488    info!(
1489        "Purging old incremental snapshot archives in {}, retaining up to {} incremental snapshots",
1490        incremental_snapshot_archives_dir.as_ref().display(),
1491        maximum_incremental_snapshot_archives_to_retain
1492    );
1493    let mut incremental_snapshot_archives_by_base_slot = HashMap::<Slot, Vec<_>>::new();
1494    for incremental_snapshot_archive in
1495        get_incremental_snapshot_archives(&incremental_snapshot_archives_dir)
1496    {
1497        incremental_snapshot_archives_by_base_slot
1498            .entry(incremental_snapshot_archive.base_slot())
1499            .or_default()
1500            .push(incremental_snapshot_archive)
1501    }
1502
1503    let highest_full_snapshot_slot = retained_full_snapshot_slots.iter().max().copied();
1504    for (base_slot, mut incremental_snapshot_archives) in incremental_snapshot_archives_by_base_slot
1505    {
1506        incremental_snapshot_archives.sort_unstable();
1507        let num_to_retain = if Some(base_slot) == highest_full_snapshot_slot {
1508            maximum_incremental_snapshot_archives_to_retain
1509        } else {
1510            usize::from(retained_full_snapshot_slots.contains(&base_slot))
1511        };
1512        trace!(
1513            "There are {} incremental snapshot archives for base slot {}, removing {} of them",
1514            incremental_snapshot_archives.len(),
1515            base_slot,
1516            incremental_snapshot_archives
1517                .len()
1518                .saturating_sub(num_to_retain),
1519        );
1520
1521        incremental_snapshot_archives.truncate(
1522            incremental_snapshot_archives
1523                .len()
1524                .saturating_sub(num_to_retain),
1525        );
1526        remove_archives(&incremental_snapshot_archives);
1527    }
1528}
1529
1530fn unpack_snapshot_local(
1531    shared_buffer: SharedBuffer,
1532    ledger_dir: &Path,
1533    account_paths: &[PathBuf],
1534    parallel_divisions: usize,
1535) -> Result<UnpackedAppendVecMap> {
1536    assert!(parallel_divisions > 0);
1537
1538    // allocate all readers before any readers start reading
1539    let readers = (0..parallel_divisions)
1540        .into_iter()
1541        .map(|_| SharedBufferReader::new(&shared_buffer))
1542        .collect::<Vec<_>>();
1543
1544    // create 'parallel_divisions' # of parallel workers, each responsible for 1/parallel_divisions of all the files to extract.
1545    let all_unpacked_append_vec_map = readers
1546        .into_par_iter()
1547        .enumerate()
1548        .map(|(index, reader)| {
1549            let parallel_selector = Some(ParallelSelector {
1550                index,
1551                divisions: parallel_divisions,
1552            });
1553            let mut archive = Archive::new(reader);
1554            unpack_snapshot(&mut archive, ledger_dir, account_paths, parallel_selector)
1555        })
1556        .collect::<Vec<_>>();
1557
1558    let mut unpacked_append_vec_map = UnpackedAppendVecMap::new();
1559    for h in all_unpacked_append_vec_map {
1560        unpacked_append_vec_map.extend(h?);
1561    }
1562
1563    Ok(unpacked_append_vec_map)
1564}
1565
1566fn untar_snapshot_create_shared_buffer(
1567    snapshot_tar: &Path,
1568    archive_format: ArchiveFormat,
1569) -> SharedBuffer {
1570    let open_file = || File::open(snapshot_tar).unwrap();
1571    match archive_format {
1572        ArchiveFormat::TarBzip2 => SharedBuffer::new(BzDecoder::new(BufReader::new(open_file()))),
1573        ArchiveFormat::TarGzip => SharedBuffer::new(GzDecoder::new(BufReader::new(open_file()))),
1574        ArchiveFormat::TarZstd => SharedBuffer::new(
1575            zstd::stream::read::Decoder::new(BufReader::new(open_file())).unwrap(),
1576        ),
1577        ArchiveFormat::TarLz4 => {
1578            SharedBuffer::new(lz4::Decoder::new(BufReader::new(open_file())).unwrap())
1579        }
1580        ArchiveFormat::Tar => SharedBuffer::new(BufReader::new(open_file())),
1581    }
1582}
1583
1584fn untar_snapshot_in<P: AsRef<Path>>(
1585    snapshot_tar: P,
1586    unpack_dir: &Path,
1587    account_paths: &[PathBuf],
1588    archive_format: ArchiveFormat,
1589    parallel_divisions: usize,
1590) -> Result<UnpackedAppendVecMap> {
1591    let shared_buffer = untar_snapshot_create_shared_buffer(snapshot_tar.as_ref(), archive_format);
1592    unpack_snapshot_local(shared_buffer, unpack_dir, account_paths, parallel_divisions)
1593}
1594
1595fn verify_unpacked_snapshots_dir_and_version(
1596    unpacked_snapshots_dir_and_version: &UnpackedSnapshotsDirAndVersion,
1597) -> Result<(SnapshotVersion, BankSnapshotInfo)> {
1598    info!(
1599        "snapshot version: {}",
1600        &unpacked_snapshots_dir_and_version.snapshot_version
1601    );
1602
1603    let snapshot_version =
1604        SnapshotVersion::maybe_from_string(&unpacked_snapshots_dir_and_version.snapshot_version)
1605            .ok_or_else(|| {
1606                get_io_error(&format!(
1607                    "unsupported snapshot version: {}",
1608                    &unpacked_snapshots_dir_and_version.snapshot_version,
1609                ))
1610            })?;
1611    let mut bank_snapshots =
1612        get_bank_snapshots_post(&unpacked_snapshots_dir_and_version.unpacked_snapshots_dir);
1613    if bank_snapshots.len() > 1 {
1614        return Err(get_io_error("invalid snapshot format"));
1615    }
1616    let root_paths = bank_snapshots
1617        .pop()
1618        .ok_or_else(|| get_io_error("No snapshots found in snapshots directory"))?;
1619    Ok((snapshot_version, root_paths))
1620}
1621
1622fn bank_fields_from_snapshots(
1623    full_snapshot_unpacked_snapshots_dir_and_version: &UnpackedSnapshotsDirAndVersion,
1624    incremental_snapshot_unpacked_snapshots_dir_and_version: Option<
1625        &UnpackedSnapshotsDirAndVersion,
1626    >,
1627) -> Result<BankFieldsToDeserialize> {
1628    let (full_snapshot_version, full_snapshot_root_paths) =
1629        verify_unpacked_snapshots_dir_and_version(
1630            full_snapshot_unpacked_snapshots_dir_and_version,
1631        )?;
1632    let (incremental_snapshot_version, incremental_snapshot_root_paths) =
1633        if let Some(snapshot_unpacked_snapshots_dir_and_version) =
1634            incremental_snapshot_unpacked_snapshots_dir_and_version
1635        {
1636            let (snapshot_version, bank_snapshot_info) = verify_unpacked_snapshots_dir_and_version(
1637                snapshot_unpacked_snapshots_dir_and_version,
1638            )?;
1639            (Some(snapshot_version), Some(bank_snapshot_info))
1640        } else {
1641            (None, None)
1642        };
1643    info!(
1644        "Loading bank from full snapshot {} and incremental snapshot {:?}",
1645        full_snapshot_root_paths.snapshot_path.display(),
1646        incremental_snapshot_root_paths
1647            .as_ref()
1648            .map(|paths| paths.snapshot_path.display()),
1649    );
1650
1651    let snapshot_root_paths = SnapshotRootPaths {
1652        full_snapshot_root_file_path: full_snapshot_root_paths.snapshot_path,
1653        incremental_snapshot_root_file_path: incremental_snapshot_root_paths
1654            .map(|root_paths| root_paths.snapshot_path),
1655    };
1656
1657    deserialize_snapshot_data_files(&snapshot_root_paths, |snapshot_streams| {
1658        Ok(
1659            match incremental_snapshot_version.unwrap_or(full_snapshot_version) {
1660                SnapshotVersion::V1_2_0 => fields_from_streams(SerdeStyle::Newer, snapshot_streams)
1661                    .map(|(bank_fields, _accountsdb_fields)| bank_fields),
1662            }?,
1663        )
1664    })
1665}
1666
1667#[allow(clippy::too_many_arguments)]
1668fn rebuild_bank_from_snapshots(
1669    full_snapshot_unpacked_snapshots_dir_and_version: &UnpackedSnapshotsDirAndVersion,
1670    incremental_snapshot_unpacked_snapshots_dir_and_version: Option<
1671        &UnpackedSnapshotsDirAndVersion,
1672    >,
1673    account_paths: &[PathBuf],
1674    unpacked_append_vec_map: UnpackedAppendVecMap,
1675    genesis_config: &GenesisConfig,
1676    debug_keys: Option<Arc<HashSet<Pubkey>>>,
1677    additional_builtins: Option<&Builtins>,
1678    account_secondary_indexes: AccountSecondaryIndexes,
1679    accounts_db_caching_enabled: bool,
1680    limit_load_slot_count_from_snapshot: Option<usize>,
1681    shrink_ratio: AccountShrinkThreshold,
1682    verify_index: bool,
1683    accounts_db_config: Option<AccountsDbConfig>,
1684    accounts_update_notifier: Option<AccountsUpdateNotifier>,
1685) -> Result<Bank> {
1686    let (full_snapshot_version, full_snapshot_root_paths) =
1687        verify_unpacked_snapshots_dir_and_version(
1688            full_snapshot_unpacked_snapshots_dir_and_version,
1689        )?;
1690    let (incremental_snapshot_version, incremental_snapshot_root_paths) =
1691        if let Some(snapshot_unpacked_snapshots_dir_and_version) =
1692            incremental_snapshot_unpacked_snapshots_dir_and_version
1693        {
1694            let (snapshot_version, bank_snapshot_info) = verify_unpacked_snapshots_dir_and_version(
1695                snapshot_unpacked_snapshots_dir_and_version,
1696            )?;
1697            (Some(snapshot_version), Some(bank_snapshot_info))
1698        } else {
1699            (None, None)
1700        };
1701    info!(
1702        "Loading bank from full snapshot {} and incremental snapshot {:?}",
1703        full_snapshot_root_paths.snapshot_path.display(),
1704        incremental_snapshot_root_paths
1705            .as_ref()
1706            .map(|paths| paths.snapshot_path.display()),
1707    );
1708
1709    let snapshot_root_paths = SnapshotRootPaths {
1710        full_snapshot_root_file_path: full_snapshot_root_paths.snapshot_path,
1711        incremental_snapshot_root_file_path: incremental_snapshot_root_paths
1712            .map(|root_paths| root_paths.snapshot_path),
1713    };
1714
1715    let bank = deserialize_snapshot_data_files(&snapshot_root_paths, |snapshot_streams| {
1716        Ok(
1717            match incremental_snapshot_version.unwrap_or(full_snapshot_version) {
1718                SnapshotVersion::V1_2_0 => bank_from_streams(
1719                    SerdeStyle::Newer,
1720                    snapshot_streams,
1721                    account_paths,
1722                    unpacked_append_vec_map,
1723                    genesis_config,
1724                    debug_keys,
1725                    additional_builtins,
1726                    account_secondary_indexes,
1727                    accounts_db_caching_enabled,
1728                    limit_load_slot_count_from_snapshot,
1729                    shrink_ratio,
1730                    verify_index,
1731                    accounts_db_config,
1732                    accounts_update_notifier,
1733                ),
1734            }?,
1735        )
1736    })?;
1737
1738    // The status cache is rebuilt from the latest snapshot.  So, if there's an incremental
1739    // snapshot, use that.  Otherwise use the full snapshot.
1740    let status_cache_path = incremental_snapshot_unpacked_snapshots_dir_and_version
1741        .map_or_else(
1742            || {
1743                full_snapshot_unpacked_snapshots_dir_and_version
1744                    .unpacked_snapshots_dir
1745                    .as_path()
1746            },
1747            |unpacked_snapshots_dir_and_version| {
1748                unpacked_snapshots_dir_and_version
1749                    .unpacked_snapshots_dir
1750                    .as_path()
1751            },
1752        )
1753        .join(SNAPSHOT_STATUS_CACHE_FILENAME);
1754    let slot_deltas = deserialize_snapshot_data_file(&status_cache_path, |stream| {
1755        info!(
1756            "Rebuilding status cache from {}",
1757            status_cache_path.display()
1758        );
1759        let slot_deltas: Vec<BankSlotDelta> = bincode::options()
1760            .with_limit(MAX_SNAPSHOT_DATA_FILE_SIZE)
1761            .with_fixint_encoding()
1762            .allow_trailing_bytes()
1763            .deserialize_from(stream)?;
1764        Ok(slot_deltas)
1765    })?;
1766
1767    verify_slot_deltas(slot_deltas.as_slice(), &bank)?;
1768
1769    bank.status_cache.write().unwrap().append(&slot_deltas);
1770
1771    bank.prepare_rewrites_for_hash();
1772
1773    info!("Loaded bank for slot: {}", bank.slot());
1774    Ok(bank)
1775}
1776
1777/// Verify that the snapshot's slot deltas are not corrupt/invalid
1778fn verify_slot_deltas(
1779    slot_deltas: &[BankSlotDelta],
1780    bank: &Bank,
1781) -> std::result::Result<(), VerifySlotDeltasError> {
1782    let info = verify_slot_deltas_structural(slot_deltas, bank.slot())?;
1783    verify_slot_deltas_with_history(&info.slots, &bank.get_slot_history(), bank.slot())
1784}
1785
1786/// Verify that the snapshot's slot deltas are not corrupt/invalid
1787/// These checks are simple/structural
1788fn verify_slot_deltas_structural(
1789    slot_deltas: &[BankSlotDelta],
1790    bank_slot: Slot,
1791) -> std::result::Result<VerifySlotDeltasStructuralInfo, VerifySlotDeltasError> {
1792    // there should not be more entries than that status cache's max
1793    let num_entries = slot_deltas.len();
1794    if num_entries > status_cache::MAX_CACHE_ENTRIES {
1795        return Err(VerifySlotDeltasError::TooManyEntries(
1796            num_entries,
1797            status_cache::MAX_CACHE_ENTRIES,
1798        ));
1799    }
1800
1801    let mut slots_seen_so_far = HashSet::new();
1802    for &(slot, is_root, ..) in slot_deltas {
1803        // all entries should be roots
1804        if !is_root {
1805            return Err(VerifySlotDeltasError::SlotIsNotRoot(slot));
1806        }
1807
1808        // all entries should be for slots less than or equal to the bank's slot
1809        if slot > bank_slot {
1810            return Err(VerifySlotDeltasError::SlotGreaterThanMaxRoot(
1811                slot, bank_slot,
1812            ));
1813        }
1814
1815        // there should only be one entry per slot
1816        let is_duplicate = !slots_seen_so_far.insert(slot);
1817        if is_duplicate {
1818            return Err(VerifySlotDeltasError::SlotHasMultipleEntries(slot));
1819        }
1820    }
1821
1822    // detect serious logic error for future careless changes. :)
1823    assert_eq!(slots_seen_so_far.len(), slot_deltas.len());
1824
1825    Ok(VerifySlotDeltasStructuralInfo {
1826        slots: slots_seen_so_far,
1827    })
1828}
1829
1830/// Computed information from `verify_slot_deltas_structural()`, that may be reused/useful later.
1831#[derive(Debug, PartialEq, Eq)]
1832struct VerifySlotDeltasStructuralInfo {
1833    /// All the slots in the slot deltas
1834    slots: HashSet<Slot>,
1835}
1836
1837/// Verify that the snapshot's slot deltas are not corrupt/invalid
1838/// These checks use the slot history for verification
1839fn verify_slot_deltas_with_history(
1840    slots_from_slot_deltas: &HashSet<Slot>,
1841    slot_history: &SlotHistory,
1842    bank_slot: Slot,
1843) -> std::result::Result<(), VerifySlotDeltasError> {
1844    // ensure the slot history is valid (as much as possible), since we're using it to verify the
1845    // slot deltas
1846    if slot_history.newest() != bank_slot {
1847        return Err(VerifySlotDeltasError::BadSlotHistory);
1848    }
1849
1850    // all slots in the slot deltas should be in the bank's slot history
1851    let slot_missing_from_history = slots_from_slot_deltas
1852        .iter()
1853        .find(|slot| slot_history.check(**slot) != Check::Found);
1854    if let Some(slot) = slot_missing_from_history {
1855        return Err(VerifySlotDeltasError::SlotNotFoundInHistory(*slot));
1856    }
1857
1858    // all slots in the history should be in the slot deltas (up to MAX_CACHE_ENTRIES)
1859    // this ensures nothing was removed from the status cache
1860    //
1861    // go through the slot history and make sure there's an entry for each slot
1862    // note: it's important to go highest-to-lowest since the status cache removes
1863    // older entries first
1864    // note: we already checked above that `bank_slot == slot_history.newest()`
1865    let slot_missing_from_deltas = (slot_history.oldest()..=slot_history.newest())
1866        .rev()
1867        .filter(|slot| slot_history.check(*slot) == Check::Found)
1868        .take(status_cache::MAX_CACHE_ENTRIES)
1869        .find(|slot| !slots_from_slot_deltas.contains(slot));
1870    if let Some(slot) = slot_missing_from_deltas {
1871        return Err(VerifySlotDeltasError::SlotNotFoundInDeltas(slot));
1872    }
1873
1874    Ok(())
1875}
1876
1877pub(crate) fn get_snapshot_file_name(slot: Slot) -> String {
1878    slot.to_string()
1879}
1880
1881pub(crate) fn get_bank_snapshots_dir<P: AsRef<Path>>(path: P, slot: Slot) -> PathBuf {
1882    path.as_ref().join(slot.to_string())
1883}
1884
1885fn get_io_error(error: &str) -> SnapshotError {
1886    warn!("Snapshot Error: {:?}", error);
1887    SnapshotError::Io(IoError::new(ErrorKind::Other, error))
1888}
1889
1890#[derive(Debug, Copy, Clone)]
1891/// allow tests to specify what happened to the serialized format
1892pub enum VerifyBank {
1893    /// the bank's serialized format is expected to be identical to what we are comparing against
1894    Deterministic,
1895    /// the serialized bank was 'reserialized' into a non-deterministic format at the specified slot
1896    /// so, deserialize both files and compare deserialized results
1897    NonDeterministic(Slot),
1898}
1899
1900pub fn verify_snapshot_archive<P, Q, R>(
1901    snapshot_archive: P,
1902    snapshots_to_verify: Q,
1903    storages_to_verify: R,
1904    archive_format: ArchiveFormat,
1905    verify_bank: VerifyBank,
1906) where
1907    P: AsRef<Path>,
1908    Q: AsRef<Path>,
1909    R: AsRef<Path>,
1910{
1911    let temp_dir = tempfile::TempDir::new().unwrap();
1912    let unpack_dir = temp_dir.path();
1913    untar_snapshot_in(
1914        snapshot_archive,
1915        unpack_dir,
1916        &[unpack_dir.to_path_buf()],
1917        archive_format,
1918        1,
1919    )
1920    .unwrap();
1921
1922    // Check snapshots are the same
1923    let unpacked_snapshots = unpack_dir.join("snapshots");
1924    if let VerifyBank::NonDeterministic(slot) = verify_bank {
1925        // file contents may be different, but deserialized structs should be equal
1926        let slot = slot.to_string();
1927        let p1 = snapshots_to_verify.as_ref().join(&slot).join(&slot);
1928        let p2 = unpacked_snapshots.join(&slot).join(&slot);
1929
1930        assert!(crate::serde_snapshot::compare_two_serialized_banks(&p1, &p2).unwrap());
1931        std::fs::remove_file(p1).unwrap();
1932        std::fs::remove_file(p2).unwrap();
1933    }
1934
1935    assert!(!dir_diff::is_different(&snapshots_to_verify, unpacked_snapshots).unwrap());
1936
1937    // Check the account entries are the same
1938    let unpacked_accounts = unpack_dir.join("accounts");
1939    assert!(!dir_diff::is_different(&storages_to_verify, unpacked_accounts).unwrap());
1940}
1941
1942/// Remove outdated bank snapshots
1943pub fn purge_old_bank_snapshots(bank_snapshots_dir: impl AsRef<Path>) {
1944    let do_purge = |mut bank_snapshots: Vec<BankSnapshotInfo>| {
1945        bank_snapshots.sort_unstable();
1946        bank_snapshots
1947            .into_iter()
1948            .rev()
1949            .skip(MAX_BANK_SNAPSHOTS_TO_RETAIN)
1950            .for_each(|bank_snapshot| {
1951                let r = remove_bank_snapshot(bank_snapshot.slot, &bank_snapshots_dir);
1952                if r.is_err() {
1953                    warn!(
1954                        "Couldn't remove bank snapshot at: {}",
1955                        bank_snapshot.snapshot_path.display()
1956                    );
1957                }
1958            })
1959    };
1960
1961    do_purge(get_bank_snapshots_pre(&bank_snapshots_dir));
1962    do_purge(get_bank_snapshots_post(&bank_snapshots_dir));
1963}
1964
1965/// Gather the necessary elements for a snapshot of the given `root_bank`.
1966///
1967/// **DEVELOPER NOTE** Any error that is returned from this function may bring down the node!  This
1968/// function is called from AccountsBackgroundService to handle snapshot requests.  Since taking a
1969/// snapshot is not permitted to fail, any errors returned here will trigger the node to shutdown.
1970/// So, be careful whenever adding new code that may return errors.
1971#[allow(clippy::too_many_arguments)]
1972pub fn snapshot_bank(
1973    root_bank: &Bank,
1974    status_cache_slot_deltas: Vec<BankSlotDelta>,
1975    pending_accounts_package: &PendingAccountsPackage,
1976    bank_snapshots_dir: impl AsRef<Path>,
1977    full_snapshot_archives_dir: impl AsRef<Path>,
1978    incremental_snapshot_archives_dir: impl AsRef<Path>,
1979    snapshot_version: SnapshotVersion,
1980    archive_format: ArchiveFormat,
1981    hash_for_testing: Option<Hash>,
1982    snapshot_type: Option<SnapshotType>,
1983) -> Result<()> {
1984    let snapshot_storages = get_snapshot_storages(root_bank);
1985
1986    let mut add_snapshot_time = Measure::start("add-snapshot-ms");
1987    let bank_snapshot_info = add_bank_snapshot(
1988        &bank_snapshots_dir,
1989        root_bank,
1990        &snapshot_storages,
1991        snapshot_version,
1992    )?;
1993    add_snapshot_time.stop();
1994    inc_new_counter_info!("add-snapshot-ms", add_snapshot_time.as_ms() as usize);
1995
1996    let accounts_package = AccountsPackage::new(
1997        root_bank,
1998        &bank_snapshot_info,
1999        bank_snapshots_dir,
2000        status_cache_slot_deltas,
2001        full_snapshot_archives_dir,
2002        incremental_snapshot_archives_dir,
2003        snapshot_storages,
2004        archive_format,
2005        snapshot_version,
2006        hash_for_testing,
2007        snapshot_type,
2008    )
2009    .expect("failed to hard link bank snapshot into a tmpdir");
2010
2011    if can_submit_accounts_package(&accounts_package, pending_accounts_package) {
2012        let old_accounts_package = pending_accounts_package
2013            .lock()
2014            .unwrap()
2015            .replace(accounts_package);
2016        if let Some(old_accounts_package) = old_accounts_package {
2017            debug!(
2018                "The pending AccountsPackage has been overwritten: \
2019                \nNew AccountsPackage slot: {}, snapshot type: {:?} \
2020                \nOld AccountsPackage slot: {}, snapshot type: {:?}",
2021                root_bank.slot(),
2022                snapshot_type,
2023                old_accounts_package.slot,
2024                old_accounts_package.snapshot_type,
2025            );
2026        }
2027    }
2028
2029    Ok(())
2030}
2031
2032/// Get the snapshot storages for this bank
2033fn get_snapshot_storages(bank: &Bank) -> SnapshotStorages {
2034    let mut measure_snapshot_storages = Measure::start("snapshot-storages");
2035    let snapshot_storages = bank.get_snapshot_storages(None);
2036    measure_snapshot_storages.stop();
2037    let snapshot_storages_count = snapshot_storages.iter().map(Vec::len).sum::<usize>();
2038    datapoint_info!(
2039        "get_snapshot_storages",
2040        ("snapshot-storages-count", snapshot_storages_count, i64),
2041        (
2042            "snapshot-storages-time-ms",
2043            measure_snapshot_storages.as_ms(),
2044            i64
2045        ),
2046    );
2047
2048    snapshot_storages
2049}
2050
2051/// Convenience function to create a full snapshot archive out of any Bank, regardless of state.
2052/// The Bank will be frozen during the process.
2053/// This is only called from ledger-tool or tests. Warping is a special case as well.
2054///
2055/// Requires:
2056///     - `bank` is complete
2057pub fn bank_to_full_snapshot_archive(
2058    bank_snapshots_dir: impl AsRef<Path>,
2059    bank: &Bank,
2060    snapshot_version: Option<SnapshotVersion>,
2061    full_snapshot_archives_dir: impl AsRef<Path>,
2062    incremental_snapshot_archives_dir: impl AsRef<Path>,
2063    archive_format: ArchiveFormat,
2064    maximum_full_snapshot_archives_to_retain: usize,
2065    maximum_incremental_snapshot_archives_to_retain: usize,
2066) -> Result<FullSnapshotArchiveInfo> {
2067    let snapshot_version = snapshot_version.unwrap_or_default();
2068
2069    assert!(bank.is_complete());
2070    bank.squash(); // Bank may not be a root
2071    bank.force_flush_accounts_cache();
2072    bank.clean_accounts(true, false, Some(bank.slot()));
2073    bank.update_accounts_hash();
2074    bank.rehash(); // Bank accounts may have been manually modified by the caller
2075
2076    let temp_dir = tempfile::tempdir_in(bank_snapshots_dir)?;
2077    let snapshot_storages = bank.get_snapshot_storages(None);
2078    let bank_snapshot_info =
2079        add_bank_snapshot(&temp_dir, bank, &snapshot_storages, snapshot_version)?;
2080
2081    package_and_archive_full_snapshot(
2082        bank,
2083        &bank_snapshot_info,
2084        &temp_dir,
2085        full_snapshot_archives_dir,
2086        incremental_snapshot_archives_dir,
2087        snapshot_storages,
2088        archive_format,
2089        snapshot_version,
2090        maximum_full_snapshot_archives_to_retain,
2091        maximum_incremental_snapshot_archives_to_retain,
2092    )
2093}
2094
2095/// Convenience function to create an incremental snapshot archive out of any Bank, regardless of
2096/// state.  The Bank will be frozen during the process.
2097/// This is only called from ledger-tool or tests. Warping is a special case as well.
2098///
2099/// Requires:
2100///     - `bank` is complete
2101///     - `bank`'s slot is greater than `full_snapshot_slot`
2102pub fn bank_to_incremental_snapshot_archive(
2103    bank_snapshots_dir: impl AsRef<Path>,
2104    bank: &Bank,
2105    full_snapshot_slot: Slot,
2106    snapshot_version: Option<SnapshotVersion>,
2107    full_snapshot_archives_dir: impl AsRef<Path>,
2108    incremental_snapshot_archives_dir: impl AsRef<Path>,
2109    archive_format: ArchiveFormat,
2110    maximum_full_snapshot_archives_to_retain: usize,
2111    maximum_incremental_snapshot_archives_to_retain: usize,
2112) -> Result<IncrementalSnapshotArchiveInfo> {
2113    let snapshot_version = snapshot_version.unwrap_or_default();
2114
2115    assert!(bank.is_complete());
2116    assert!(bank.slot() > full_snapshot_slot);
2117    bank.squash(); // Bank may not be a root
2118    bank.force_flush_accounts_cache();
2119    bank.clean_accounts(true, false, Some(full_snapshot_slot));
2120    bank.update_accounts_hash();
2121    bank.rehash(); // Bank accounts may have been manually modified by the caller
2122
2123    let temp_dir = tempfile::tempdir_in(bank_snapshots_dir)?;
2124    let snapshot_storages = bank.get_snapshot_storages(Some(full_snapshot_slot));
2125    let bank_snapshot_info =
2126        add_bank_snapshot(&temp_dir, bank, &snapshot_storages, snapshot_version)?;
2127
2128    package_and_archive_incremental_snapshot(
2129        bank,
2130        full_snapshot_slot,
2131        &bank_snapshot_info,
2132        &temp_dir,
2133        full_snapshot_archives_dir,
2134        incremental_snapshot_archives_dir,
2135        snapshot_storages,
2136        archive_format,
2137        snapshot_version,
2138        maximum_full_snapshot_archives_to_retain,
2139        maximum_incremental_snapshot_archives_to_retain,
2140    )
2141}
2142
2143/// Helper function to hold shared code to package, process, and archive full snapshots
2144#[allow(clippy::too_many_arguments)]
2145pub fn package_and_archive_full_snapshot(
2146    bank: &Bank,
2147    bank_snapshot_info: &BankSnapshotInfo,
2148    bank_snapshots_dir: impl AsRef<Path>,
2149    full_snapshot_archives_dir: impl AsRef<Path>,
2150    incremental_snapshot_archives_dir: impl AsRef<Path>,
2151    snapshot_storages: SnapshotStorages,
2152    archive_format: ArchiveFormat,
2153    snapshot_version: SnapshotVersion,
2154    maximum_full_snapshot_archives_to_retain: usize,
2155    maximum_incremental_snapshot_archives_to_retain: usize,
2156) -> Result<FullSnapshotArchiveInfo> {
2157    let slot_deltas = bank.status_cache.read().unwrap().root_slot_deltas();
2158    let accounts_package = AccountsPackage::new(
2159        bank,
2160        bank_snapshot_info,
2161        bank_snapshots_dir,
2162        slot_deltas,
2163        &full_snapshot_archives_dir,
2164        &incremental_snapshot_archives_dir,
2165        snapshot_storages,
2166        archive_format,
2167        snapshot_version,
2168        None,
2169        Some(SnapshotType::FullSnapshot),
2170    )?;
2171
2172    crate::serde_snapshot::reserialize_bank_with_new_accounts_hash(
2173        accounts_package.snapshot_links.path(),
2174        accounts_package.slot,
2175        &bank.get_accounts_hash(),
2176        None,
2177    );
2178
2179    let snapshot_package = SnapshotPackage::new(accounts_package, bank.get_accounts_hash());
2180    archive_snapshot_package(
2181        &snapshot_package,
2182        full_snapshot_archives_dir,
2183        incremental_snapshot_archives_dir,
2184        maximum_full_snapshot_archives_to_retain,
2185        maximum_incremental_snapshot_archives_to_retain,
2186    )?;
2187
2188    Ok(FullSnapshotArchiveInfo::new(
2189        snapshot_package.snapshot_archive_info,
2190    ))
2191}
2192
2193/// Helper function to hold shared code to package, process, and archive incremental snapshots
2194#[allow(clippy::too_many_arguments)]
2195pub fn package_and_archive_incremental_snapshot(
2196    bank: &Bank,
2197    incremental_snapshot_base_slot: Slot,
2198    bank_snapshot_info: &BankSnapshotInfo,
2199    bank_snapshots_dir: impl AsRef<Path>,
2200    full_snapshot_archives_dir: impl AsRef<Path>,
2201    incremental_snapshot_archives_dir: impl AsRef<Path>,
2202    snapshot_storages: SnapshotStorages,
2203    archive_format: ArchiveFormat,
2204    snapshot_version: SnapshotVersion,
2205    maximum_full_snapshot_archives_to_retain: usize,
2206    maximum_incremental_snapshot_archives_to_retain: usize,
2207) -> Result<IncrementalSnapshotArchiveInfo> {
2208    let slot_deltas = bank.status_cache.read().unwrap().root_slot_deltas();
2209    let accounts_package = AccountsPackage::new(
2210        bank,
2211        bank_snapshot_info,
2212        bank_snapshots_dir,
2213        slot_deltas,
2214        &full_snapshot_archives_dir,
2215        &incremental_snapshot_archives_dir,
2216        snapshot_storages,
2217        archive_format,
2218        snapshot_version,
2219        None,
2220        Some(SnapshotType::IncrementalSnapshot(
2221            incremental_snapshot_base_slot,
2222        )),
2223    )?;
2224
2225    crate::serde_snapshot::reserialize_bank_with_new_accounts_hash(
2226        accounts_package.snapshot_links.path(),
2227        accounts_package.slot,
2228        &bank.get_accounts_hash(),
2229        None,
2230    );
2231
2232    let snapshot_package = SnapshotPackage::new(accounts_package, bank.get_accounts_hash());
2233    archive_snapshot_package(
2234        &snapshot_package,
2235        full_snapshot_archives_dir,
2236        incremental_snapshot_archives_dir,
2237        maximum_full_snapshot_archives_to_retain,
2238        maximum_incremental_snapshot_archives_to_retain,
2239    )?;
2240
2241    Ok(IncrementalSnapshotArchiveInfo::new(
2242        incremental_snapshot_base_slot,
2243        snapshot_package.snapshot_archive_info,
2244    ))
2245}
2246
2247pub fn should_take_full_snapshot(
2248    block_height: Slot,
2249    full_snapshot_archive_interval_slots: Slot,
2250) -> bool {
2251    block_height % full_snapshot_archive_interval_slots == 0
2252}
2253
2254pub fn should_take_incremental_snapshot(
2255    block_height: Slot,
2256    incremental_snapshot_archive_interval_slots: Slot,
2257    last_full_snapshot_slot: Option<Slot>,
2258) -> bool {
2259    block_height % incremental_snapshot_archive_interval_slots == 0
2260        && last_full_snapshot_slot.is_some()
2261}
2262
2263/// Decide if an accounts package can be submitted to the PendingAccountsPackage
2264///
2265/// This is based on the values for `snapshot_type` in both the `accounts_package` and the
2266/// `pending_accounts_package`:
2267/// - if the new AccountsPackage is for a full snapshot, always submit
2268/// - if the new AccountsPackage is for an incremental snapshot, submit as long as there isn't a
2269///   pending full snapshot
2270/// - otherwise, only submit the new AccountsPackage as long as there's not a pending package
2271///   destined for a snapshot archive
2272fn can_submit_accounts_package(
2273    accounts_package: &AccountsPackage,
2274    pending_accounts_package: &PendingAccountsPackage,
2275) -> bool {
2276    match accounts_package.snapshot_type {
2277        Some(SnapshotType::FullSnapshot) => true,
2278        Some(SnapshotType::IncrementalSnapshot(_)) => pending_accounts_package
2279            .lock()
2280            .unwrap()
2281            .as_ref()
2282            .and_then(|old_accounts_package| old_accounts_package.snapshot_type)
2283            .map(|old_snapshot_type| !old_snapshot_type.is_full_snapshot())
2284            .unwrap_or(true),
2285        None => pending_accounts_package
2286            .lock()
2287            .unwrap()
2288            .as_ref()
2289            .map(|old_accounts_package| old_accounts_package.snapshot_type.is_none())
2290            .unwrap_or(true),
2291    }
2292}
2293
2294#[cfg(test)]
2295mod tests {
2296    use {
2297        super::*,
2298        crate::{accounts_db::ACCOUNTS_DB_CONFIG_FOR_TESTING, status_cache::Status},
2299        assert_matches::assert_matches,
2300        bincode::{deserialize_from, serialize_into},
2301        solana_sdk::{
2302            genesis_config::create_genesis_config,
2303            native_token::sol_to_lamports,
2304            signature::{Keypair, Signer},
2305            slot_history::SlotHistory,
2306            system_transaction,
2307            transaction::SanitizedTransaction,
2308        },
2309        std::{convert::TryFrom, mem::size_of},
2310        tempfile::NamedTempFile,
2311    };
2312
2313    #[test]
2314    fn test_serialize_snapshot_data_file_under_limit() {
2315        let temp_dir = tempfile::TempDir::new().unwrap();
2316        let expected_consumed_size = size_of::<u32>() as u64;
2317        let consumed_size = serialize_snapshot_data_file_capped(
2318            &temp_dir.path().join("data-file"),
2319            expected_consumed_size,
2320            |stream| {
2321                serialize_into(stream, &2323_u32)?;
2322                Ok(())
2323            },
2324        )
2325        .unwrap();
2326        assert_eq!(consumed_size, expected_consumed_size);
2327    }
2328
2329    #[test]
2330    fn test_serialize_snapshot_data_file_over_limit() {
2331        let temp_dir = tempfile::TempDir::new().unwrap();
2332        let expected_consumed_size = size_of::<u32>() as u64;
2333        let result = serialize_snapshot_data_file_capped(
2334            &temp_dir.path().join("data-file"),
2335            expected_consumed_size - 1,
2336            |stream| {
2337                serialize_into(stream, &2323_u32)?;
2338                Ok(())
2339            },
2340        );
2341        assert_matches!(result, Err(SnapshotError::Io(ref message)) if message.to_string().starts_with("too large snapshot data file to serialize"));
2342    }
2343
2344    #[test]
2345    fn test_deserialize_snapshot_data_file_under_limit() {
2346        let expected_data = 2323_u32;
2347        let expected_consumed_size = size_of::<u32>() as u64;
2348
2349        let temp_dir = tempfile::TempDir::new().unwrap();
2350        serialize_snapshot_data_file_capped(
2351            &temp_dir.path().join("data-file"),
2352            expected_consumed_size,
2353            |stream| {
2354                serialize_into(stream, &expected_data)?;
2355                Ok(())
2356            },
2357        )
2358        .unwrap();
2359
2360        let snapshot_root_paths = SnapshotRootPaths {
2361            full_snapshot_root_file_path: temp_dir.path().join("data-file"),
2362            incremental_snapshot_root_file_path: None,
2363        };
2364
2365        let actual_data = deserialize_snapshot_data_files_capped(
2366            &snapshot_root_paths,
2367            expected_consumed_size,
2368            |stream| {
2369                Ok(deserialize_from::<_, u32>(
2370                    &mut stream.full_snapshot_stream,
2371                )?)
2372            },
2373        )
2374        .unwrap();
2375        assert_eq!(actual_data, expected_data);
2376    }
2377
2378    #[test]
2379    fn test_deserialize_snapshot_data_file_over_limit() {
2380        let expected_data = 2323_u32;
2381        let expected_consumed_size = size_of::<u32>() as u64;
2382
2383        let temp_dir = tempfile::TempDir::new().unwrap();
2384        serialize_snapshot_data_file_capped(
2385            &temp_dir.path().join("data-file"),
2386            expected_consumed_size,
2387            |stream| {
2388                serialize_into(stream, &expected_data)?;
2389                Ok(())
2390            },
2391        )
2392        .unwrap();
2393
2394        let snapshot_root_paths = SnapshotRootPaths {
2395            full_snapshot_root_file_path: temp_dir.path().join("data-file"),
2396            incremental_snapshot_root_file_path: None,
2397        };
2398
2399        let result = deserialize_snapshot_data_files_capped(
2400            &snapshot_root_paths,
2401            expected_consumed_size - 1,
2402            |stream| {
2403                Ok(deserialize_from::<_, u32>(
2404                    &mut stream.full_snapshot_stream,
2405                )?)
2406            },
2407        );
2408        assert_matches!(result, Err(SnapshotError::Io(ref message)) if message.to_string().starts_with("too large snapshot data file to deserialize"));
2409    }
2410
2411    #[test]
2412    fn test_deserialize_snapshot_data_file_extra_data() {
2413        let expected_data = 2323_u32;
2414        let expected_consumed_size = size_of::<u32>() as u64;
2415
2416        let temp_dir = tempfile::TempDir::new().unwrap();
2417        serialize_snapshot_data_file_capped(
2418            &temp_dir.path().join("data-file"),
2419            expected_consumed_size * 2,
2420            |stream| {
2421                serialize_into(stream.by_ref(), &expected_data)?;
2422                serialize_into(stream.by_ref(), &expected_data)?;
2423                Ok(())
2424            },
2425        )
2426        .unwrap();
2427
2428        let snapshot_root_paths = SnapshotRootPaths {
2429            full_snapshot_root_file_path: temp_dir.path().join("data-file"),
2430            incremental_snapshot_root_file_path: None,
2431        };
2432
2433        let result = deserialize_snapshot_data_files_capped(
2434            &snapshot_root_paths,
2435            expected_consumed_size * 2,
2436            |stream| {
2437                Ok(deserialize_from::<_, u32>(
2438                    &mut stream.full_snapshot_stream,
2439                )?)
2440            },
2441        );
2442        assert_matches!(result, Err(SnapshotError::Io(ref message)) if message.to_string().starts_with("invalid snapshot data file"));
2443    }
2444
2445    #[test]
2446    fn test_snapshot_version_from_file_under_limit() {
2447        let file_content = SnapshotVersion::default().as_str();
2448        let mut file = NamedTempFile::new().unwrap();
2449        file.write_all(file_content.as_bytes()).unwrap();
2450        let version_from_file = snapshot_version_from_file(file.path()).unwrap();
2451        assert_eq!(version_from_file, file_content);
2452    }
2453
2454    #[test]
2455    fn test_snapshot_version_from_file_over_limit() {
2456        let over_limit_size = usize::try_from(MAX_SNAPSHOT_VERSION_FILE_SIZE + 1).unwrap();
2457        let file_content = vec![7u8; over_limit_size];
2458        let mut file = NamedTempFile::new().unwrap();
2459        file.write_all(&file_content).unwrap();
2460        assert_matches!(
2461            snapshot_version_from_file(file.path()),
2462            Err(SnapshotError::Io(ref message)) if message.to_string().starts_with("snapshot version file too large")
2463        );
2464    }
2465
2466    #[test]
2467    fn test_parse_full_snapshot_archive_filename() {
2468        assert_eq!(
2469            parse_full_snapshot_archive_filename(&format!(
2470                "snapshot-42-{}.tar.bz2",
2471                Hash::default()
2472            ))
2473            .unwrap(),
2474            (42, Hash::default(), ArchiveFormat::TarBzip2)
2475        );
2476        assert_eq!(
2477            parse_full_snapshot_archive_filename(&format!(
2478                "snapshot-43-{}.tar.zst",
2479                Hash::default()
2480            ))
2481            .unwrap(),
2482            (43, Hash::default(), ArchiveFormat::TarZstd)
2483        );
2484        assert_eq!(
2485            parse_full_snapshot_archive_filename(&format!("snapshot-44-{}.tar", Hash::default()))
2486                .unwrap(),
2487            (44, Hash::default(), ArchiveFormat::Tar)
2488        );
2489        assert_eq!(
2490            parse_full_snapshot_archive_filename(&format!(
2491                "snapshot-45-{}.tar.lz4",
2492                Hash::default()
2493            ))
2494            .unwrap(),
2495            (45, Hash::default(), ArchiveFormat::TarLz4)
2496        );
2497
2498        assert!(parse_full_snapshot_archive_filename("invalid").is_err());
2499        assert!(
2500            parse_full_snapshot_archive_filename("snapshot-bad!slot-bad!hash.bad!ext").is_err()
2501        );
2502
2503        assert!(
2504            parse_full_snapshot_archive_filename("snapshot-12345678-bad!hash.bad!ext").is_err()
2505        );
2506        assert!(parse_full_snapshot_archive_filename(&format!(
2507            "snapshot-12345678-{}.bad!ext",
2508            Hash::new_unique()
2509        ))
2510        .is_err());
2511        assert!(parse_full_snapshot_archive_filename("snapshot-12345678-bad!hash.tar").is_err());
2512
2513        assert!(parse_full_snapshot_archive_filename(&format!(
2514            "snapshot-bad!slot-{}.bad!ext",
2515            Hash::new_unique()
2516        ))
2517        .is_err());
2518        assert!(parse_full_snapshot_archive_filename(&format!(
2519            "snapshot-12345678-{}.bad!ext",
2520            Hash::new_unique()
2521        ))
2522        .is_err());
2523        assert!(parse_full_snapshot_archive_filename(&format!(
2524            "snapshot-bad!slot-{}.tar",
2525            Hash::new_unique()
2526        ))
2527        .is_err());
2528
2529        assert!(parse_full_snapshot_archive_filename("snapshot-bad!slot-bad!hash.tar").is_err());
2530        assert!(parse_full_snapshot_archive_filename("snapshot-12345678-bad!hash.tar").is_err());
2531        assert!(parse_full_snapshot_archive_filename(&format!(
2532            "snapshot-bad!slot-{}.tar",
2533            Hash::new_unique()
2534        ))
2535        .is_err());
2536    }
2537
2538    #[test]
2539    fn test_parse_incremental_snapshot_archive_filename() {
2540        solana_logger::setup();
2541        assert_eq!(
2542            parse_incremental_snapshot_archive_filename(&format!(
2543                "incremental-snapshot-42-123-{}.tar.bz2",
2544                Hash::default()
2545            ))
2546            .unwrap(),
2547            (42, 123, Hash::default(), ArchiveFormat::TarBzip2)
2548        );
2549        assert_eq!(
2550            parse_incremental_snapshot_archive_filename(&format!(
2551                "incremental-snapshot-43-234-{}.tar.zst",
2552                Hash::default()
2553            ))
2554            .unwrap(),
2555            (43, 234, Hash::default(), ArchiveFormat::TarZstd)
2556        );
2557        assert_eq!(
2558            parse_incremental_snapshot_archive_filename(&format!(
2559                "incremental-snapshot-44-345-{}.tar",
2560                Hash::default()
2561            ))
2562            .unwrap(),
2563            (44, 345, Hash::default(), ArchiveFormat::Tar)
2564        );
2565        assert_eq!(
2566            parse_incremental_snapshot_archive_filename(&format!(
2567                "incremental-snapshot-45-456-{}.tar.lz4",
2568                Hash::default()
2569            ))
2570            .unwrap(),
2571            (45, 456, Hash::default(), ArchiveFormat::TarLz4)
2572        );
2573
2574        assert!(parse_incremental_snapshot_archive_filename("invalid").is_err());
2575        assert!(parse_incremental_snapshot_archive_filename(&format!(
2576            "snapshot-42-{}.tar",
2577            Hash::new_unique()
2578        ))
2579        .is_err());
2580        assert!(parse_incremental_snapshot_archive_filename(
2581            "incremental-snapshot-bad!slot-bad!slot-bad!hash.bad!ext"
2582        )
2583        .is_err());
2584
2585        assert!(parse_incremental_snapshot_archive_filename(&format!(
2586            "incremental-snapshot-bad!slot-56785678-{}.tar",
2587            Hash::new_unique()
2588        ))
2589        .is_err());
2590
2591        assert!(parse_incremental_snapshot_archive_filename(&format!(
2592            "incremental-snapshot-12345678-bad!slot-{}.tar",
2593            Hash::new_unique()
2594        ))
2595        .is_err());
2596
2597        assert!(parse_incremental_snapshot_archive_filename(
2598            "incremental-snapshot-12341234-56785678-bad!HASH.tar"
2599        )
2600        .is_err());
2601
2602        assert!(parse_incremental_snapshot_archive_filename(&format!(
2603            "incremental-snapshot-12341234-56785678-{}.bad!ext",
2604            Hash::new_unique()
2605        ))
2606        .is_err());
2607    }
2608
2609    #[test]
2610    fn test_check_are_snapshots_compatible() {
2611        solana_logger::setup();
2612        let slot1: Slot = 1234;
2613        let slot2: Slot = 5678;
2614        let slot3: Slot = 999_999;
2615
2616        let full_snapshot_archive_info = FullSnapshotArchiveInfo::new_from_path(PathBuf::from(
2617            format!("/dir/snapshot-{}-{}.tar", slot1, Hash::new_unique()),
2618        ))
2619        .unwrap();
2620
2621        assert!(check_are_snapshots_compatible(&full_snapshot_archive_info, None,).is_ok());
2622
2623        let incremental_snapshot_archive_info =
2624            IncrementalSnapshotArchiveInfo::new_from_path(PathBuf::from(format!(
2625                "/dir/incremental-snapshot-{}-{}-{}.tar",
2626                slot1,
2627                slot2,
2628                Hash::new_unique()
2629            )))
2630            .unwrap();
2631
2632        assert!(check_are_snapshots_compatible(
2633            &full_snapshot_archive_info,
2634            Some(&incremental_snapshot_archive_info)
2635        )
2636        .is_ok());
2637
2638        let incremental_snapshot_archive_info =
2639            IncrementalSnapshotArchiveInfo::new_from_path(PathBuf::from(format!(
2640                "/dir/incremental-snapshot-{}-{}-{}.tar",
2641                slot2,
2642                slot3,
2643                Hash::new_unique()
2644            )))
2645            .unwrap();
2646
2647        assert!(check_are_snapshots_compatible(
2648            &full_snapshot_archive_info,
2649            Some(&incremental_snapshot_archive_info)
2650        )
2651        .is_err());
2652    }
2653
2654    /// A test heler function that creates bank snapshot files
2655    fn common_create_bank_snapshot_files(
2656        bank_snapshots_dir: &Path,
2657        min_slot: Slot,
2658        max_slot: Slot,
2659    ) {
2660        for slot in min_slot..max_slot {
2661            let snapshot_dir = get_bank_snapshots_dir(bank_snapshots_dir, slot);
2662            fs::create_dir_all(&snapshot_dir).unwrap();
2663
2664            let snapshot_filename = get_snapshot_file_name(slot);
2665            let snapshot_path = snapshot_dir.join(snapshot_filename);
2666            File::create(snapshot_path).unwrap();
2667        }
2668    }
2669
2670    #[test]
2671    fn test_get_bank_snapshots() {
2672        solana_logger::setup();
2673        let temp_snapshots_dir = tempfile::TempDir::new().unwrap();
2674        let min_slot = 10;
2675        let max_slot = 20;
2676        common_create_bank_snapshot_files(temp_snapshots_dir.path(), min_slot, max_slot);
2677
2678        let bank_snapshots = get_bank_snapshots(temp_snapshots_dir.path());
2679        assert_eq!(bank_snapshots.len() as Slot, max_slot - min_slot);
2680    }
2681
2682    #[test]
2683    fn test_get_highest_bank_snapshot_post() {
2684        solana_logger::setup();
2685        let temp_snapshots_dir = tempfile::TempDir::new().unwrap();
2686        let min_slot = 99;
2687        let max_slot = 123;
2688        common_create_bank_snapshot_files(temp_snapshots_dir.path(), min_slot, max_slot);
2689
2690        let highest_bank_snapshot = get_highest_bank_snapshot_post(temp_snapshots_dir.path());
2691        assert!(highest_bank_snapshot.is_some());
2692        assert_eq!(highest_bank_snapshot.unwrap().slot, max_slot - 1);
2693    }
2694
2695    /// A test helper function that creates full and incremental snapshot archive files.  Creates
2696    /// full snapshot files in the range (`min_full_snapshot_slot`, `max_full_snapshot_slot`], and
2697    /// incremental snapshot files in the range (`min_incremental_snapshot_slot`,
2698    /// `max_incremental_snapshot_slot`].  Additionally, "bad" files are created for both full and
2699    /// incremental snapshots to ensure the tests properly filter them out.
2700    fn common_create_snapshot_archive_files(
2701        full_snapshot_archives_dir: &Path,
2702        incremental_snapshot_archives_dir: &Path,
2703        min_full_snapshot_slot: Slot,
2704        max_full_snapshot_slot: Slot,
2705        min_incremental_snapshot_slot: Slot,
2706        max_incremental_snapshot_slot: Slot,
2707    ) {
2708        fs::create_dir_all(full_snapshot_archives_dir).unwrap();
2709        fs::create_dir_all(incremental_snapshot_archives_dir).unwrap();
2710        for full_snapshot_slot in min_full_snapshot_slot..max_full_snapshot_slot {
2711            for incremental_snapshot_slot in
2712                min_incremental_snapshot_slot..max_incremental_snapshot_slot
2713            {
2714                let snapshot_filename = format!(
2715                    "incremental-snapshot-{}-{}-{}.tar",
2716                    full_snapshot_slot,
2717                    incremental_snapshot_slot,
2718                    Hash::default()
2719                );
2720                let snapshot_filepath = incremental_snapshot_archives_dir.join(snapshot_filename);
2721                File::create(snapshot_filepath).unwrap();
2722            }
2723
2724            let snapshot_filename =
2725                format!("snapshot-{}-{}.tar", full_snapshot_slot, Hash::default());
2726            let snapshot_filepath = full_snapshot_archives_dir.join(snapshot_filename);
2727            File::create(snapshot_filepath).unwrap();
2728
2729            // Add in an incremental snapshot with a bad filename and high slot to ensure filename are filtered and sorted correctly
2730            let bad_filename = format!(
2731                "incremental-snapshot-{}-{}-bad!hash.tar",
2732                full_snapshot_slot,
2733                max_incremental_snapshot_slot + 1,
2734            );
2735            let bad_filepath = incremental_snapshot_archives_dir.join(bad_filename);
2736            File::create(bad_filepath).unwrap();
2737        }
2738
2739        // Add in a snapshot with a bad filename and high slot to ensure filename are filtered and
2740        // sorted correctly
2741        let bad_filename = format!("snapshot-{}-bad!hash.tar", max_full_snapshot_slot + 1);
2742        let bad_filepath = full_snapshot_archives_dir.join(bad_filename);
2743        File::create(bad_filepath).unwrap();
2744    }
2745
2746    #[test]
2747    fn test_get_full_snapshot_archives() {
2748        solana_logger::setup();
2749        let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2750        let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2751        let min_slot = 123;
2752        let max_slot = 456;
2753        common_create_snapshot_archive_files(
2754            full_snapshot_archives_dir.path(),
2755            incremental_snapshot_archives_dir.path(),
2756            min_slot,
2757            max_slot,
2758            0,
2759            0,
2760        );
2761
2762        let snapshot_archives = get_full_snapshot_archives(full_snapshot_archives_dir);
2763        assert_eq!(snapshot_archives.len() as Slot, max_slot - min_slot);
2764    }
2765
2766    #[test]
2767    fn test_get_full_snapshot_archives_remote() {
2768        solana_logger::setup();
2769        let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2770        let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2771        let min_slot = 123;
2772        let max_slot = 456;
2773        common_create_snapshot_archive_files(
2774            &full_snapshot_archives_dir.path().join("remote"),
2775            &incremental_snapshot_archives_dir.path().join("remote"),
2776            min_slot,
2777            max_slot,
2778            0,
2779            0,
2780        );
2781
2782        let snapshot_archives = get_full_snapshot_archives(full_snapshot_archives_dir);
2783        assert_eq!(snapshot_archives.len() as Slot, max_slot - min_slot);
2784        assert!(snapshot_archives.iter().all(|info| info.is_remote()));
2785    }
2786
2787    #[test]
2788    fn test_get_incremental_snapshot_archives() {
2789        solana_logger::setup();
2790        let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2791        let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2792        let min_full_snapshot_slot = 12;
2793        let max_full_snapshot_slot = 23;
2794        let min_incremental_snapshot_slot = 34;
2795        let max_incremental_snapshot_slot = 45;
2796        common_create_snapshot_archive_files(
2797            full_snapshot_archives_dir.path(),
2798            incremental_snapshot_archives_dir.path(),
2799            min_full_snapshot_slot,
2800            max_full_snapshot_slot,
2801            min_incremental_snapshot_slot,
2802            max_incremental_snapshot_slot,
2803        );
2804
2805        let incremental_snapshot_archives =
2806            get_incremental_snapshot_archives(incremental_snapshot_archives_dir);
2807        assert_eq!(
2808            incremental_snapshot_archives.len() as Slot,
2809            (max_full_snapshot_slot - min_full_snapshot_slot)
2810                * (max_incremental_snapshot_slot - min_incremental_snapshot_slot)
2811        );
2812    }
2813
2814    #[test]
2815    fn test_get_incremental_snapshot_archives_remote() {
2816        solana_logger::setup();
2817        let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2818        let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2819        let min_full_snapshot_slot = 12;
2820        let max_full_snapshot_slot = 23;
2821        let min_incremental_snapshot_slot = 34;
2822        let max_incremental_snapshot_slot = 45;
2823        common_create_snapshot_archive_files(
2824            &full_snapshot_archives_dir.path().join("remote"),
2825            &incremental_snapshot_archives_dir.path().join("remote"),
2826            min_full_snapshot_slot,
2827            max_full_snapshot_slot,
2828            min_incremental_snapshot_slot,
2829            max_incremental_snapshot_slot,
2830        );
2831
2832        let incremental_snapshot_archives =
2833            get_incremental_snapshot_archives(incremental_snapshot_archives_dir);
2834        assert_eq!(
2835            incremental_snapshot_archives.len() as Slot,
2836            (max_full_snapshot_slot - min_full_snapshot_slot)
2837                * (max_incremental_snapshot_slot - min_incremental_snapshot_slot)
2838        );
2839        assert!(incremental_snapshot_archives
2840            .iter()
2841            .all(|info| info.is_remote()));
2842    }
2843
2844    #[test]
2845    fn test_get_highest_full_snapshot_archive_slot() {
2846        solana_logger::setup();
2847        let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2848        let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2849        let min_slot = 123;
2850        let max_slot = 456;
2851        common_create_snapshot_archive_files(
2852            full_snapshot_archives_dir.path(),
2853            incremental_snapshot_archives_dir.path(),
2854            min_slot,
2855            max_slot,
2856            0,
2857            0,
2858        );
2859
2860        assert_eq!(
2861            get_highest_full_snapshot_archive_slot(full_snapshot_archives_dir.path()),
2862            Some(max_slot - 1)
2863        );
2864    }
2865
2866    #[test]
2867    fn test_get_highest_incremental_snapshot_slot() {
2868        solana_logger::setup();
2869        let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2870        let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2871        let min_full_snapshot_slot = 12;
2872        let max_full_snapshot_slot = 23;
2873        let min_incremental_snapshot_slot = 34;
2874        let max_incremental_snapshot_slot = 45;
2875        common_create_snapshot_archive_files(
2876            full_snapshot_archives_dir.path(),
2877            incremental_snapshot_archives_dir.path(),
2878            min_full_snapshot_slot,
2879            max_full_snapshot_slot,
2880            min_incremental_snapshot_slot,
2881            max_incremental_snapshot_slot,
2882        );
2883
2884        for full_snapshot_slot in min_full_snapshot_slot..max_full_snapshot_slot {
2885            assert_eq!(
2886                get_highest_incremental_snapshot_archive_slot(
2887                    incremental_snapshot_archives_dir.path(),
2888                    full_snapshot_slot
2889                ),
2890                Some(max_incremental_snapshot_slot - 1)
2891            );
2892        }
2893
2894        assert_eq!(
2895            get_highest_incremental_snapshot_archive_slot(
2896                incremental_snapshot_archives_dir.path(),
2897                max_full_snapshot_slot
2898            ),
2899            None
2900        );
2901    }
2902
2903    fn common_test_purge_old_snapshot_archives(
2904        snapshot_names: &[&String],
2905        maximum_full_snapshot_archives_to_retain: usize,
2906        maximum_incremental_snapshot_archives_to_retain: usize,
2907        expected_snapshots: &[&String],
2908    ) {
2909        let temp_snap_dir = tempfile::TempDir::new().unwrap();
2910
2911        for snap_name in snapshot_names {
2912            let snap_path = temp_snap_dir.path().join(snap_name);
2913            let mut _snap_file = File::create(snap_path);
2914        }
2915        purge_old_snapshot_archives(
2916            temp_snap_dir.path(),
2917            temp_snap_dir.path(),
2918            maximum_full_snapshot_archives_to_retain,
2919            maximum_incremental_snapshot_archives_to_retain,
2920        );
2921
2922        let mut retained_snaps = HashSet::new();
2923        for entry in fs::read_dir(temp_snap_dir.path()).unwrap() {
2924            let entry_path_buf = entry.unwrap().path();
2925            let entry_path = entry_path_buf.as_path();
2926            let snapshot_name = entry_path
2927                .file_name()
2928                .unwrap()
2929                .to_str()
2930                .unwrap()
2931                .to_string();
2932            retained_snaps.insert(snapshot_name);
2933        }
2934
2935        for snap_name in expected_snapshots {
2936            assert!(
2937                retained_snaps.contains(snap_name.as_str()),
2938                "{} not found",
2939                snap_name
2940            );
2941        }
2942        assert_eq!(retained_snaps.len(), expected_snapshots.len());
2943    }
2944
2945    #[test]
2946    fn test_purge_old_full_snapshot_archives() {
2947        let snap1_name = format!("snapshot-1-{}.tar.zst", Hash::default());
2948        let snap2_name = format!("snapshot-3-{}.tar.zst", Hash::default());
2949        let snap3_name = format!("snapshot-50-{}.tar.zst", Hash::default());
2950        let snapshot_names = vec![&snap1_name, &snap2_name, &snap3_name];
2951
2952        // expecting only the newest to be retained
2953        let expected_snapshots = vec![&snap3_name];
2954        common_test_purge_old_snapshot_archives(
2955            &snapshot_names,
2956            1,
2957            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2958            &expected_snapshots,
2959        );
2960
2961        // retaining 0, but minimum to retain is 1
2962        common_test_purge_old_snapshot_archives(
2963            &snapshot_names,
2964            0,
2965            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2966            &expected_snapshots,
2967        );
2968
2969        // retaining 2, expecting the 2 newest to be retained
2970        let expected_snapshots = vec![&snap2_name, &snap3_name];
2971        common_test_purge_old_snapshot_archives(
2972            &snapshot_names,
2973            2,
2974            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2975            &expected_snapshots,
2976        );
2977
2978        // retaining 3, all three should be retained
2979        let expected_snapshots = vec![&snap1_name, &snap2_name, &snap3_name];
2980        common_test_purge_old_snapshot_archives(
2981            &snapshot_names,
2982            3,
2983            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2984            &expected_snapshots,
2985        );
2986    }
2987
2988    /// Mimic a running node's behavior w.r.t. purging old snapshot archives.  Take snapshots in a
2989    /// loop, and periodically purge old snapshot archives.  After purging, check to make sure the
2990    /// snapshot archives on disk are correct.
2991    #[test]
2992    fn test_purge_old_full_snapshot_archives_in_the_loop() {
2993        let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2994        let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2995        let maximum_snapshots_to_retain = 5;
2996        let starting_slot: Slot = 42;
2997
2998        for slot in (starting_slot..).take(100) {
2999            let full_snapshot_archive_file_name =
3000                format!("snapshot-{}-{}.tar", slot, Hash::default());
3001            let full_snapshot_archive_path = full_snapshot_archives_dir
3002                .as_ref()
3003                .join(full_snapshot_archive_file_name);
3004            File::create(full_snapshot_archive_path).unwrap();
3005
3006            // don't purge-and-check until enough snapshot archives have been created
3007            if slot < starting_slot + maximum_snapshots_to_retain as Slot {
3008                continue;
3009            }
3010
3011            // purge infrequently, so there will always be snapshot archives to purge
3012            if slot % (maximum_snapshots_to_retain as Slot * 2) != 0 {
3013                continue;
3014            }
3015
3016            purge_old_snapshot_archives(
3017                &full_snapshot_archives_dir,
3018                &incremental_snapshot_archives_dir,
3019                maximum_snapshots_to_retain,
3020                usize::MAX,
3021            );
3022            let mut full_snapshot_archives =
3023                get_full_snapshot_archives(&full_snapshot_archives_dir);
3024            full_snapshot_archives.sort_unstable();
3025            assert_eq!(full_snapshot_archives.len(), maximum_snapshots_to_retain);
3026            assert_eq!(full_snapshot_archives.last().unwrap().slot(), slot);
3027            for (i, full_snapshot_archive) in full_snapshot_archives.iter().rev().enumerate() {
3028                assert_eq!(full_snapshot_archive.slot(), slot - i as Slot);
3029            }
3030        }
3031    }
3032
3033    #[test]
3034    fn test_purge_old_incremental_snapshot_archives() {
3035        solana_logger::setup();
3036        let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
3037        let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
3038        let starting_slot = 100_000;
3039
3040        let maximum_incremental_snapshot_archives_to_retain =
3041            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN;
3042        let maximum_full_snapshot_archives_to_retain = DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN;
3043
3044        let incremental_snapshot_interval = 100;
3045        let num_incremental_snapshots_per_full_snapshot =
3046            maximum_incremental_snapshot_archives_to_retain * 2;
3047        let full_snapshot_interval =
3048            incremental_snapshot_interval * num_incremental_snapshots_per_full_snapshot;
3049
3050        let mut snapshot_filenames = vec![];
3051        (starting_slot..)
3052            .step_by(full_snapshot_interval)
3053            .take(maximum_full_snapshot_archives_to_retain * 2)
3054            .for_each(|full_snapshot_slot| {
3055                let snapshot_filename =
3056                    format!("snapshot-{}-{}.tar", full_snapshot_slot, Hash::default());
3057                let snapshot_path = full_snapshot_archives_dir.path().join(&snapshot_filename);
3058                File::create(snapshot_path).unwrap();
3059                snapshot_filenames.push(snapshot_filename);
3060
3061                (full_snapshot_slot..)
3062                    .step_by(incremental_snapshot_interval)
3063                    .take(num_incremental_snapshots_per_full_snapshot)
3064                    .skip(1)
3065                    .for_each(|incremental_snapshot_slot| {
3066                        let snapshot_filename = format!(
3067                            "incremental-snapshot-{}-{}-{}.tar",
3068                            full_snapshot_slot,
3069                            incremental_snapshot_slot,
3070                            Hash::default()
3071                        );
3072                        let snapshot_path = incremental_snapshot_archives_dir
3073                            .path()
3074                            .join(&snapshot_filename);
3075                        File::create(snapshot_path).unwrap();
3076                        snapshot_filenames.push(snapshot_filename);
3077                    });
3078            });
3079
3080        purge_old_snapshot_archives(
3081            full_snapshot_archives_dir.path(),
3082            incremental_snapshot_archives_dir.path(),
3083            maximum_full_snapshot_archives_to_retain,
3084            maximum_incremental_snapshot_archives_to_retain,
3085        );
3086
3087        // Ensure correct number of full snapshot archives are purged/retained
3088        // NOTE: One extra full snapshot is always kept (the oldest), hence the `+1`
3089        let mut remaining_full_snapshot_archives =
3090            get_full_snapshot_archives(full_snapshot_archives_dir.path());
3091        assert_eq!(
3092            remaining_full_snapshot_archives.len(),
3093            maximum_full_snapshot_archives_to_retain,
3094        );
3095        remaining_full_snapshot_archives.sort_unstable();
3096        let latest_full_snapshot_archive_slot =
3097            remaining_full_snapshot_archives.last().unwrap().slot();
3098
3099        // Ensure correct number of incremental snapshot archives are purged/retained
3100        let mut remaining_incremental_snapshot_archives =
3101            get_incremental_snapshot_archives(incremental_snapshot_archives_dir.path());
3102        assert_eq!(
3103            remaining_incremental_snapshot_archives.len(),
3104            maximum_incremental_snapshot_archives_to_retain
3105                + maximum_full_snapshot_archives_to_retain.saturating_sub(1)
3106        );
3107        remaining_incremental_snapshot_archives.sort_unstable();
3108        remaining_incremental_snapshot_archives.reverse();
3109
3110        // Ensure there exists one incremental snapshot all but the latest full snapshot
3111        for i in (1..maximum_full_snapshot_archives_to_retain).rev() {
3112            let incremental_snapshot_archive =
3113                remaining_incremental_snapshot_archives.pop().unwrap();
3114
3115            let expected_base_slot =
3116                latest_full_snapshot_archive_slot - (i * full_snapshot_interval) as u64;
3117            assert_eq!(incremental_snapshot_archive.base_slot(), expected_base_slot);
3118            let expected_slot = expected_base_slot
3119                + (full_snapshot_interval - incremental_snapshot_interval) as u64;
3120            assert_eq!(incremental_snapshot_archive.slot(), expected_slot);
3121        }
3122
3123        // Ensure all remaining incremental snapshots are only for the latest full snapshot
3124        for incremental_snapshot_archive in &remaining_incremental_snapshot_archives {
3125            assert_eq!(
3126                incremental_snapshot_archive.base_slot(),
3127                latest_full_snapshot_archive_slot
3128            );
3129        }
3130
3131        // Ensure the remaining incremental snapshots are at the right slot
3132        let expected_remaing_incremental_snapshot_archive_slots =
3133            (latest_full_snapshot_archive_slot..)
3134                .step_by(incremental_snapshot_interval)
3135                .take(num_incremental_snapshots_per_full_snapshot)
3136                .skip(
3137                    num_incremental_snapshots_per_full_snapshot
3138                        - maximum_incremental_snapshot_archives_to_retain,
3139                )
3140                .collect::<HashSet<_>>();
3141
3142        let actual_remaining_incremental_snapshot_archive_slots =
3143            remaining_incremental_snapshot_archives
3144                .iter()
3145                .map(|snapshot| snapshot.slot())
3146                .collect::<HashSet<_>>();
3147        assert_eq!(
3148            actual_remaining_incremental_snapshot_archive_slots,
3149            expected_remaing_incremental_snapshot_archive_slots
3150        );
3151    }
3152
3153    #[test]
3154    fn test_purge_all_incremental_snapshot_archives_when_no_full_snapshot_archives() {
3155        let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
3156        let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
3157
3158        for snapshot_filenames in [
3159            format!("incremental-snapshot-100-120-{}.tar", Hash::default()),
3160            format!("incremental-snapshot-100-140-{}.tar", Hash::default()),
3161            format!("incremental-snapshot-100-160-{}.tar", Hash::default()),
3162            format!("incremental-snapshot-100-180-{}.tar", Hash::default()),
3163            format!("incremental-snapshot-200-220-{}.tar", Hash::default()),
3164            format!("incremental-snapshot-200-240-{}.tar", Hash::default()),
3165            format!("incremental-snapshot-200-260-{}.tar", Hash::default()),
3166            format!("incremental-snapshot-200-280-{}.tar", Hash::default()),
3167        ] {
3168            let snapshot_path = incremental_snapshot_archives_dir
3169                .path()
3170                .join(&snapshot_filenames);
3171            File::create(snapshot_path).unwrap();
3172        }
3173
3174        purge_old_snapshot_archives(
3175            full_snapshot_archives_dir.path(),
3176            incremental_snapshot_archives_dir.path(),
3177            usize::MAX,
3178            usize::MAX,
3179        );
3180
3181        let remaining_incremental_snapshot_archives =
3182            get_incremental_snapshot_archives(incremental_snapshot_archives_dir.path());
3183        assert!(remaining_incremental_snapshot_archives.is_empty());
3184    }
3185
3186    /// Test roundtrip of bank to a full snapshot, then back again.  This test creates the simplest
3187    /// bank possible, so the contents of the snapshot archive will be quite minimal.
3188    #[test]
3189    fn test_roundtrip_bank_to_and_from_full_snapshot_simple() {
3190        solana_logger::setup();
3191        let genesis_config = GenesisConfig::default();
3192        let original_bank = Bank::new_for_tests(&genesis_config);
3193
3194        while !original_bank.is_complete() {
3195            original_bank.register_tick(&Hash::new_unique());
3196        }
3197
3198        let accounts_dir = tempfile::TempDir::new().unwrap();
3199        let bank_snapshots_dir = tempfile::TempDir::new().unwrap();
3200        let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
3201        let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
3202        let snapshot_archive_format = ArchiveFormat::Tar;
3203
3204        let snapshot_archive_info = bank_to_full_snapshot_archive(
3205            &bank_snapshots_dir,
3206            &original_bank,
3207            None,
3208            full_snapshot_archives_dir.path(),
3209            incremental_snapshot_archives_dir.path(),
3210            snapshot_archive_format,
3211            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3212            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3213        )
3214        .unwrap();
3215
3216        let (roundtrip_bank, _) = bank_from_snapshot_archives(
3217            &[PathBuf::from(accounts_dir.path())],
3218            bank_snapshots_dir.path(),
3219            &snapshot_archive_info,
3220            None,
3221            &genesis_config,
3222            None,
3223            None,
3224            AccountSecondaryIndexes::default(),
3225            false,
3226            None,
3227            AccountShrinkThreshold::default(),
3228            false,
3229            false,
3230            false,
3231            Some(ACCOUNTS_DB_CONFIG_FOR_TESTING),
3232            None,
3233        )
3234        .unwrap();
3235
3236        assert_eq!(original_bank, roundtrip_bank);
3237    }
3238
3239    /// Test roundtrip of bank to a full snapshot, then back again.  This test is more involved
3240    /// than the simple version above; creating multiple banks over multiple slots and doing
3241    /// multiple transfers.  So this full snapshot should contain more data.
3242    #[test]
3243    fn test_roundtrip_bank_to_and_from_snapshot_complex() {
3244        solana_logger::setup();
3245        let collector = Pubkey::new_unique();
3246        let key1 = Keypair::new();
3247        let key2 = Keypair::new();
3248        let key3 = Keypair::new();
3249        let key4 = Keypair::new();
3250        let key5 = Keypair::new();
3251
3252        let (genesis_config, mint_keypair) = create_genesis_config(sol_to_lamports(1_000_000.));
3253        let bank0 = Arc::new(Bank::new_for_tests(&genesis_config));
3254        bank0
3255            .transfer(sol_to_lamports(1.), &mint_keypair, &key1.pubkey())
3256            .unwrap();
3257        bank0
3258            .transfer(sol_to_lamports(2.), &mint_keypair, &key2.pubkey())
3259            .unwrap();
3260        bank0
3261            .transfer(sol_to_lamports(3.), &mint_keypair, &key3.pubkey())
3262            .unwrap();
3263        while !bank0.is_complete() {
3264            bank0.register_tick(&Hash::new_unique());
3265        }
3266
3267        let slot = 1;
3268        let bank1 = Arc::new(Bank::new_from_parent(&bank0, &collector, slot));
3269        bank1
3270            .transfer(sol_to_lamports(3.), &mint_keypair, &key3.pubkey())
3271            .unwrap();
3272        bank1
3273            .transfer(sol_to_lamports(4.), &mint_keypair, &key4.pubkey())
3274            .unwrap();
3275        bank1
3276            .transfer(sol_to_lamports(5.), &mint_keypair, &key5.pubkey())
3277            .unwrap();
3278        while !bank1.is_complete() {
3279            bank1.register_tick(&Hash::new_unique());
3280        }
3281
3282        let slot = slot + 1;
3283        let bank2 = Arc::new(Bank::new_from_parent(&bank1, &collector, slot));
3284        bank2
3285            .transfer(sol_to_lamports(1.), &mint_keypair, &key1.pubkey())
3286            .unwrap();
3287        while !bank2.is_complete() {
3288            bank2.register_tick(&Hash::new_unique());
3289        }
3290
3291        let slot = slot + 1;
3292        let bank3 = Arc::new(Bank::new_from_parent(&bank2, &collector, slot));
3293        bank3
3294            .transfer(sol_to_lamports(1.), &mint_keypair, &key1.pubkey())
3295            .unwrap();
3296        while !bank3.is_complete() {
3297            bank3.register_tick(&Hash::new_unique());
3298        }
3299
3300        let slot = slot + 1;
3301        let bank4 = Arc::new(Bank::new_from_parent(&bank3, &collector, slot));
3302        bank4
3303            .transfer(sol_to_lamports(1.), &mint_keypair, &key1.pubkey())
3304            .unwrap();
3305        while !bank4.is_complete() {
3306            bank4.register_tick(&Hash::new_unique());
3307        }
3308
3309        let accounts_dir = tempfile::TempDir::new().unwrap();
3310        let bank_snapshots_dir = tempfile::TempDir::new().unwrap();
3311        let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
3312        let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
3313        let snapshot_archive_format = ArchiveFormat::TarGzip;
3314
3315        let full_snapshot_archive_info = bank_to_full_snapshot_archive(
3316            bank_snapshots_dir.path(),
3317            &bank4,
3318            None,
3319            full_snapshot_archives_dir.path(),
3320            incremental_snapshot_archives_dir.path(),
3321            snapshot_archive_format,
3322            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3323            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3324        )
3325        .unwrap();
3326
3327        let (roundtrip_bank, _) = bank_from_snapshot_archives(
3328            &[PathBuf::from(accounts_dir.path())],
3329            bank_snapshots_dir.path(),
3330            &full_snapshot_archive_info,
3331            None,
3332            &genesis_config,
3333            None,
3334            None,
3335            AccountSecondaryIndexes::default(),
3336            false,
3337            None,
3338            AccountShrinkThreshold::default(),
3339            false,
3340            false,
3341            false,
3342            Some(ACCOUNTS_DB_CONFIG_FOR_TESTING),
3343            None,
3344        )
3345        .unwrap();
3346
3347        assert_eq!(*bank4, roundtrip_bank);
3348    }
3349
3350    /// Test roundtrip of bank to snapshots, then back again, with incremental snapshots.  In this
3351    /// version, build up a few slots and take a full snapshot.  Continue on a few more slots and
3352    /// take an incremental snapshot.  Rebuild the bank from both the incremental snapshot and full
3353    /// snapshot.
3354    ///
3355    /// For the full snapshot, touch all the accounts, but only one for the incremental snapshot.
3356    /// This is intended to mimic the real behavior of transactions, where only a small number of
3357    /// accounts are modified often, which are captured by the incremental snapshot.  The majority
3358    /// of the accounts are not modified often, and are captured by the full snapshot.
3359    #[test]
3360    fn test_roundtrip_bank_to_and_from_incremental_snapshot() {
3361        solana_logger::setup();
3362        let collector = Pubkey::new_unique();
3363        let key1 = Keypair::new();
3364        let key2 = Keypair::new();
3365        let key3 = Keypair::new();
3366        let key4 = Keypair::new();
3367        let key5 = Keypair::new();
3368
3369        let (genesis_config, mint_keypair) = create_genesis_config(sol_to_lamports(1_000_000.));
3370        let bank0 = Arc::new(Bank::new_for_tests(&genesis_config));
3371        bank0
3372            .transfer(sol_to_lamports(1.), &mint_keypair, &key1.pubkey())
3373            .unwrap();
3374        bank0
3375            .transfer(sol_to_lamports(2.), &mint_keypair, &key2.pubkey())
3376            .unwrap();
3377        bank0
3378            .transfer(sol_to_lamports(3.), &mint_keypair, &key3.pubkey())
3379            .unwrap();
3380        while !bank0.is_complete() {
3381            bank0.register_tick(&Hash::new_unique());
3382        }
3383
3384        let slot = 1;
3385        let bank1 = Arc::new(Bank::new_from_parent(&bank0, &collector, slot));
3386        bank1
3387            .transfer(sol_to_lamports(3.), &mint_keypair, &key3.pubkey())
3388            .unwrap();
3389        bank1
3390            .transfer(sol_to_lamports(4.), &mint_keypair, &key4.pubkey())
3391            .unwrap();
3392        bank1
3393            .transfer(sol_to_lamports(5.), &mint_keypair, &key5.pubkey())
3394            .unwrap();
3395        while !bank1.is_complete() {
3396            bank1.register_tick(&Hash::new_unique());
3397        }
3398
3399        let accounts_dir = tempfile::TempDir::new().unwrap();
3400        let bank_snapshots_dir = tempfile::TempDir::new().unwrap();
3401        let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
3402        let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
3403        let snapshot_archive_format = ArchiveFormat::TarZstd;
3404
3405        let full_snapshot_slot = slot;
3406        let full_snapshot_archive_info = bank_to_full_snapshot_archive(
3407            bank_snapshots_dir.path(),
3408            &bank1,
3409            None,
3410            full_snapshot_archives_dir.path(),
3411            incremental_snapshot_archives_dir.path(),
3412            snapshot_archive_format,
3413            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3414            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3415        )
3416        .unwrap();
3417
3418        let slot = slot + 1;
3419        let bank2 = Arc::new(Bank::new_from_parent(&bank1, &collector, slot));
3420        bank2
3421            .transfer(sol_to_lamports(1.), &mint_keypair, &key1.pubkey())
3422            .unwrap();
3423        while !bank2.is_complete() {
3424            bank2.register_tick(&Hash::new_unique());
3425        }
3426
3427        let slot = slot + 1;
3428        let bank3 = Arc::new(Bank::new_from_parent(&bank2, &collector, slot));
3429        bank3
3430            .transfer(sol_to_lamports(1.), &mint_keypair, &key1.pubkey())
3431            .unwrap();
3432        while !bank3.is_complete() {
3433            bank3.register_tick(&Hash::new_unique());
3434        }
3435
3436        let slot = slot + 1;
3437        let bank4 = Arc::new(Bank::new_from_parent(&bank3, &collector, slot));
3438        bank4
3439            .transfer(sol_to_lamports(1.), &mint_keypair, &key1.pubkey())
3440            .unwrap();
3441        while !bank4.is_complete() {
3442            bank4.register_tick(&Hash::new_unique());
3443        }
3444
3445        let incremental_snapshot_archive_info = bank_to_incremental_snapshot_archive(
3446            bank_snapshots_dir.path(),
3447            &bank4,
3448            full_snapshot_slot,
3449            None,
3450            full_snapshot_archives_dir.path(),
3451            incremental_snapshot_archives_dir.path(),
3452            snapshot_archive_format,
3453            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3454            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3455        )
3456        .unwrap();
3457
3458        let (roundtrip_bank, _) = bank_from_snapshot_archives(
3459            &[PathBuf::from(accounts_dir.path())],
3460            bank_snapshots_dir.path(),
3461            &full_snapshot_archive_info,
3462            Some(&incremental_snapshot_archive_info),
3463            &genesis_config,
3464            None,
3465            None,
3466            AccountSecondaryIndexes::default(),
3467            false,
3468            None,
3469            AccountShrinkThreshold::default(),
3470            false,
3471            false,
3472            false,
3473            Some(ACCOUNTS_DB_CONFIG_FOR_TESTING),
3474            None,
3475        )
3476        .unwrap();
3477
3478        assert_eq!(*bank4, roundtrip_bank);
3479    }
3480
3481    /// Test rebuilding bank from the latest snapshot archives
3482    #[test]
3483    fn test_bank_from_latest_snapshot_archives() {
3484        solana_logger::setup();
3485        let collector = Pubkey::new_unique();
3486        let key1 = Keypair::new();
3487        let key2 = Keypair::new();
3488        let key3 = Keypair::new();
3489
3490        let (genesis_config, mint_keypair) = create_genesis_config(sol_to_lamports(1_000_000.));
3491        let bank0 = Arc::new(Bank::new_for_tests(&genesis_config));
3492        bank0
3493            .transfer(sol_to_lamports(1.), &mint_keypair, &key1.pubkey())
3494            .unwrap();
3495        bank0
3496            .transfer(sol_to_lamports(2.), &mint_keypair, &key2.pubkey())
3497            .unwrap();
3498        bank0
3499            .transfer(sol_to_lamports(3.), &mint_keypair, &key3.pubkey())
3500            .unwrap();
3501        while !bank0.is_complete() {
3502            bank0.register_tick(&Hash::new_unique());
3503        }
3504
3505        let slot = 1;
3506        let bank1 = Arc::new(Bank::new_from_parent(&bank0, &collector, slot));
3507        bank1
3508            .transfer(sol_to_lamports(1.), &mint_keypair, &key1.pubkey())
3509            .unwrap();
3510        bank1
3511            .transfer(sol_to_lamports(2.), &mint_keypair, &key2.pubkey())
3512            .unwrap();
3513        bank1
3514            .transfer(sol_to_lamports(3.), &mint_keypair, &key3.pubkey())
3515            .unwrap();
3516        while !bank1.is_complete() {
3517            bank1.register_tick(&Hash::new_unique());
3518        }
3519
3520        let accounts_dir = tempfile::TempDir::new().unwrap();
3521        let bank_snapshots_dir = tempfile::TempDir::new().unwrap();
3522        let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
3523        let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
3524        let snapshot_archive_format = ArchiveFormat::Tar;
3525
3526        let full_snapshot_slot = slot;
3527        bank_to_full_snapshot_archive(
3528            &bank_snapshots_dir,
3529            &bank1,
3530            None,
3531            &full_snapshot_archives_dir,
3532            &incremental_snapshot_archives_dir,
3533            snapshot_archive_format,
3534            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3535            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3536        )
3537        .unwrap();
3538
3539        let slot = slot + 1;
3540        let bank2 = Arc::new(Bank::new_from_parent(&bank1, &collector, slot));
3541        bank2
3542            .transfer(sol_to_lamports(1.), &mint_keypair, &key1.pubkey())
3543            .unwrap();
3544        while !bank2.is_complete() {
3545            bank2.register_tick(&Hash::new_unique());
3546        }
3547
3548        let slot = slot + 1;
3549        let bank3 = Arc::new(Bank::new_from_parent(&bank2, &collector, slot));
3550        bank3
3551            .transfer(sol_to_lamports(2.), &mint_keypair, &key2.pubkey())
3552            .unwrap();
3553        while !bank3.is_complete() {
3554            bank3.register_tick(&Hash::new_unique());
3555        }
3556
3557        let slot = slot + 1;
3558        let bank4 = Arc::new(Bank::new_from_parent(&bank3, &collector, slot));
3559        bank4
3560            .transfer(sol_to_lamports(3.), &mint_keypair, &key3.pubkey())
3561            .unwrap();
3562        while !bank4.is_complete() {
3563            bank4.register_tick(&Hash::new_unique());
3564        }
3565
3566        bank_to_incremental_snapshot_archive(
3567            &bank_snapshots_dir,
3568            &bank4,
3569            full_snapshot_slot,
3570            None,
3571            &full_snapshot_archives_dir,
3572            &incremental_snapshot_archives_dir,
3573            snapshot_archive_format,
3574            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3575            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3576        )
3577        .unwrap();
3578
3579        let (deserialized_bank, ..) = bank_from_latest_snapshot_archives(
3580            &bank_snapshots_dir,
3581            &full_snapshot_archives_dir,
3582            &incremental_snapshot_archives_dir,
3583            &[accounts_dir.as_ref().to_path_buf()],
3584            &genesis_config,
3585            None,
3586            None,
3587            AccountSecondaryIndexes::default(),
3588            false,
3589            None,
3590            AccountShrinkThreshold::default(),
3591            false,
3592            false,
3593            false,
3594            Some(ACCOUNTS_DB_CONFIG_FOR_TESTING),
3595            None,
3596        )
3597        .unwrap();
3598
3599        assert_eq!(deserialized_bank, *bank4);
3600    }
3601
3602    /// Test that cleaning works well in the edge cases of zero-lamport accounts and snapshots.
3603    /// Here's the scenario:
3604    ///
3605    /// slot 1:
3606    ///     - send some lamports to Account1 (from Account2) to bring it to life
3607    ///     - take a full snapshot
3608    /// slot 2:
3609    ///     - make Account1 have zero lamports (send back to Account2)
3610    ///     - take an incremental snapshot
3611    ///     - ensure deserializing from this snapshot is equal to this bank
3612    /// slot 3:
3613    ///     - remove Account2's reference back to slot 2 by transfering from the mint to Account2
3614    /// slot 4:
3615    ///     - ensure `clean_accounts()` has run and that Account1 is gone
3616    ///     - take another incremental snapshot
3617    ///     - ensure deserializing from this snapshots is equal to this bank
3618    ///     - ensure Account1 hasn't come back from the dead
3619    ///
3620    /// The check at slot 4 will fail with the pre-incremental-snapshot cleaning logic.  Because
3621    /// of the cleaning/purging at slot 4, the incremental snapshot at slot 4 will no longer have
3622    /// information about Account1, but the full snapshost _does_ have info for Account1, which is
3623    /// no longer correct!
3624    #[test]
3625    fn test_incremental_snapshots_handle_zero_lamport_accounts() {
3626        solana_logger::setup();
3627
3628        let collector = Pubkey::new_unique();
3629        let key1 = Keypair::new();
3630        let key2 = Keypair::new();
3631
3632        let accounts_dir = tempfile::TempDir::new().unwrap();
3633        let bank_snapshots_dir = tempfile::TempDir::new().unwrap();
3634        let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
3635        let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
3636        let snapshot_archive_format = ArchiveFormat::Tar;
3637
3638        let (genesis_config, mint_keypair) = create_genesis_config(sol_to_lamports(1_000_000.));
3639
3640        let lamports_to_transfer = sol_to_lamports(123_456.);
3641        let bank0 = Arc::new(Bank::new_with_paths_for_tests(
3642            &genesis_config,
3643            vec![accounts_dir.path().to_path_buf()],
3644            None,
3645            None,
3646            AccountSecondaryIndexes::default(),
3647            false,
3648            AccountShrinkThreshold::default(),
3649            false,
3650            None,
3651        ));
3652        bank0
3653            .transfer(lamports_to_transfer, &mint_keypair, &key2.pubkey())
3654            .unwrap();
3655        while !bank0.is_complete() {
3656            bank0.register_tick(&Hash::new_unique());
3657        }
3658
3659        let slot = 1;
3660        let bank1 = Arc::new(Bank::new_from_parent(&bank0, &collector, slot));
3661        bank1
3662            .transfer(lamports_to_transfer, &key2, &key1.pubkey())
3663            .unwrap();
3664        while !bank1.is_complete() {
3665            bank1.register_tick(&Hash::new_unique());
3666        }
3667
3668        let full_snapshot_slot = slot;
3669        let full_snapshot_archive_info = bank_to_full_snapshot_archive(
3670            bank_snapshots_dir.path(),
3671            &bank1,
3672            None,
3673            full_snapshot_archives_dir.path(),
3674            incremental_snapshot_archives_dir.path(),
3675            snapshot_archive_format,
3676            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3677            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3678        )
3679        .unwrap();
3680
3681        let slot = slot + 1;
3682        let bank2 = Arc::new(Bank::new_from_parent(&bank1, &collector, slot));
3683        let blockhash = bank2.last_blockhash();
3684        let tx = SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer(
3685            &key1,
3686            &key2.pubkey(),
3687            lamports_to_transfer,
3688            blockhash,
3689        ));
3690        let fee = bank2.get_fee_for_message(tx.message()).unwrap();
3691        let tx = system_transaction::transfer(
3692            &key1,
3693            &key2.pubkey(),
3694            lamports_to_transfer - fee,
3695            blockhash,
3696        );
3697        bank2.process_transaction(&tx).unwrap();
3698        assert_eq!(
3699            bank2.get_balance(&key1.pubkey()),
3700            0,
3701            "Ensure Account1's balance is zero"
3702        );
3703        while !bank2.is_complete() {
3704            bank2.register_tick(&Hash::new_unique());
3705        }
3706
3707        // Take an incremental snapshot and then do a roundtrip on the bank and ensure it
3708        // deserializes correctly.
3709        let incremental_snapshot_archive_info = bank_to_incremental_snapshot_archive(
3710            bank_snapshots_dir.path(),
3711            &bank2,
3712            full_snapshot_slot,
3713            None,
3714            full_snapshot_archives_dir.path(),
3715            incremental_snapshot_archives_dir.path(),
3716            snapshot_archive_format,
3717            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3718            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3719        )
3720        .unwrap();
3721        let (deserialized_bank, _) = bank_from_snapshot_archives(
3722            &[accounts_dir.path().to_path_buf()],
3723            bank_snapshots_dir.path(),
3724            &full_snapshot_archive_info,
3725            Some(&incremental_snapshot_archive_info),
3726            &genesis_config,
3727            None,
3728            None,
3729            AccountSecondaryIndexes::default(),
3730            false,
3731            None,
3732            AccountShrinkThreshold::default(),
3733            false,
3734            false,
3735            false,
3736            Some(ACCOUNTS_DB_CONFIG_FOR_TESTING),
3737            None,
3738        )
3739        .unwrap();
3740        assert_eq!(
3741            deserialized_bank, *bank2,
3742            "Ensure rebuilding from an incremental snapshot works"
3743        );
3744
3745        let slot = slot + 1;
3746        let bank3 = Arc::new(Bank::new_from_parent(&bank2, &collector, slot));
3747        // Update Account2 so that it no longer holds a reference to slot2
3748        bank3
3749            .transfer(lamports_to_transfer, &mint_keypair, &key2.pubkey())
3750            .unwrap();
3751        while !bank3.is_complete() {
3752            bank3.register_tick(&Hash::new_unique());
3753        }
3754
3755        let slot = slot + 1;
3756        let bank4 = Arc::new(Bank::new_from_parent(&bank3, &collector, slot));
3757        while !bank4.is_complete() {
3758            bank4.register_tick(&Hash::new_unique());
3759        }
3760
3761        // Ensure account1 has been cleaned/purged from everywhere
3762        bank4.squash();
3763        bank4.clean_accounts(true, false, Some(full_snapshot_slot));
3764        assert!(
3765            bank4.get_account_modified_slot(&key1.pubkey()).is_none(),
3766            "Ensure Account1 has been cleaned and purged from AccountsDb"
3767        );
3768
3769        // Take an incremental snapshot and then do a roundtrip on the bank and ensure it
3770        // deserializes correctly
3771        let incremental_snapshot_archive_info = bank_to_incremental_snapshot_archive(
3772            bank_snapshots_dir.path(),
3773            &bank4,
3774            full_snapshot_slot,
3775            None,
3776            full_snapshot_archives_dir.path(),
3777            incremental_snapshot_archives_dir.path(),
3778            snapshot_archive_format,
3779            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3780            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3781        )
3782        .unwrap();
3783
3784        let (deserialized_bank, _) = bank_from_snapshot_archives(
3785            &[accounts_dir.path().to_path_buf()],
3786            bank_snapshots_dir.path(),
3787            &full_snapshot_archive_info,
3788            Some(&incremental_snapshot_archive_info),
3789            &genesis_config,
3790            None,
3791            None,
3792            AccountSecondaryIndexes::default(),
3793            false,
3794            None,
3795            AccountShrinkThreshold::default(),
3796            false,
3797            false,
3798            false,
3799            Some(ACCOUNTS_DB_CONFIG_FOR_TESTING),
3800            None,
3801        )
3802        .unwrap();
3803        assert_eq!(
3804            deserialized_bank, *bank4,
3805            "Ensure rebuilding from an incremental snapshot works",
3806        );
3807        assert!(
3808            deserialized_bank
3809                .get_account_modified_slot(&key1.pubkey())
3810                .is_none(),
3811            "Ensure Account1 has not been brought back from the dead"
3812        );
3813    }
3814
3815    #[test]
3816    fn test_bank_fields_from_snapshot() {
3817        solana_logger::setup();
3818        let collector = Pubkey::new_unique();
3819        let key1 = Keypair::new();
3820
3821        let (genesis_config, mint_keypair) = create_genesis_config(sol_to_lamports(1_000_000.));
3822        let bank0 = Arc::new(Bank::new_for_tests(&genesis_config));
3823        while !bank0.is_complete() {
3824            bank0.register_tick(&Hash::new_unique());
3825        }
3826
3827        let slot = 1;
3828        let bank1 = Arc::new(Bank::new_from_parent(&bank0, &collector, slot));
3829        while !bank1.is_complete() {
3830            bank1.register_tick(&Hash::new_unique());
3831        }
3832
3833        let all_snapshots_dir = tempfile::TempDir::new().unwrap();
3834        let snapshot_archive_format = ArchiveFormat::Tar;
3835
3836        let full_snapshot_slot = slot;
3837        bank_to_full_snapshot_archive(
3838            &all_snapshots_dir,
3839            &bank1,
3840            None,
3841            &all_snapshots_dir,
3842            &all_snapshots_dir,
3843            snapshot_archive_format,
3844            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3845            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3846        )
3847        .unwrap();
3848
3849        let slot = slot + 1;
3850        let bank2 = Arc::new(Bank::new_from_parent(&bank1, &collector, slot));
3851        bank2
3852            .transfer(sol_to_lamports(1.), &mint_keypair, &key1.pubkey())
3853            .unwrap();
3854        while !bank2.is_complete() {
3855            bank2.register_tick(&Hash::new_unique());
3856        }
3857
3858        bank_to_incremental_snapshot_archive(
3859            &all_snapshots_dir,
3860            &bank2,
3861            full_snapshot_slot,
3862            None,
3863            &all_snapshots_dir,
3864            &all_snapshots_dir,
3865            snapshot_archive_format,
3866            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3867            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3868        )
3869        .unwrap();
3870
3871        let bank_fields = bank_fields_from_snapshot_archives(
3872            &all_snapshots_dir,
3873            &all_snapshots_dir,
3874            &all_snapshots_dir,
3875        )
3876        .unwrap();
3877        assert_eq!(bank_fields.slot, bank2.slot());
3878        assert_eq!(bank_fields.parent_slot, bank2.parent_slot());
3879    }
3880
3881    /// All the permutations of `snapshot_type` for the new-and-old accounts packages:
3882    ///
3883    ///  new      | old      |
3884    ///  snapshot | snapshot |
3885    ///  type     | type     | result
3886    /// ----------+----------+--------
3887    ///   FSS     |  FSS     |  true
3888    ///   FSS     |  ISS     |  true
3889    ///   FSS     |  None    |  true
3890    ///   ISS     |  FSS     |  false
3891    ///   ISS     |  ISS     |  true
3892    ///   ISS     |  None    |  true
3893    ///   None    |  FSS     |  false
3894    ///   None    |  ISS     |  false
3895    ///   None    |  None    |  true
3896    #[test]
3897    fn test_can_submit_accounts_package() {
3898        /// helper function to create an AccountsPackage that's good enough for this test
3899        fn new_accounts_package_with(snapshot_type: Option<SnapshotType>) -> AccountsPackage {
3900            AccountsPackage {
3901                slot: Slot::default(),
3902                block_height: Slot::default(),
3903                slot_deltas: Vec::default(),
3904                snapshot_links: TempDir::new().unwrap(),
3905                snapshot_storages: SnapshotStorages::default(),
3906                archive_format: ArchiveFormat::Tar,
3907                snapshot_version: SnapshotVersion::default(),
3908                full_snapshot_archives_dir: PathBuf::default(),
3909                incremental_snapshot_archives_dir: PathBuf::default(),
3910                expected_capitalization: u64::default(),
3911                accounts_hash_for_testing: None,
3912                cluster_type: solana_sdk::genesis_config::ClusterType::Development,
3913                snapshot_type,
3914                accounts: Arc::new(crate::accounts::Accounts::default_for_tests()),
3915                epoch_schedule: solana_sdk::epoch_schedule::EpochSchedule::default(),
3916                rent_collector: crate::rent_collector::RentCollector::default(),
3917            }
3918        }
3919
3920        let pending_accounts_package = PendingAccountsPackage::default();
3921        for (new_snapshot_type, old_snapshot_type, expected_result) in [
3922            (
3923                Some(SnapshotType::FullSnapshot),
3924                Some(SnapshotType::FullSnapshot),
3925                true,
3926            ),
3927            (
3928                Some(SnapshotType::FullSnapshot),
3929                Some(SnapshotType::IncrementalSnapshot(0)),
3930                true,
3931            ),
3932            (Some(SnapshotType::FullSnapshot), None, true),
3933            (
3934                Some(SnapshotType::IncrementalSnapshot(0)),
3935                Some(SnapshotType::FullSnapshot),
3936                false,
3937            ),
3938            (
3939                Some(SnapshotType::IncrementalSnapshot(0)),
3940                Some(SnapshotType::IncrementalSnapshot(0)),
3941                true,
3942            ),
3943            (Some(SnapshotType::IncrementalSnapshot(0)), None, true),
3944            (None, Some(SnapshotType::FullSnapshot), false),
3945            (None, Some(SnapshotType::IncrementalSnapshot(0)), false),
3946            (None, None, true),
3947        ] {
3948            let new_accounts_package = new_accounts_package_with(new_snapshot_type);
3949            let old_accounts_package = new_accounts_package_with(old_snapshot_type);
3950            pending_accounts_package
3951                .lock()
3952                .unwrap()
3953                .replace(old_accounts_package);
3954
3955            let actual_result =
3956                can_submit_accounts_package(&new_accounts_package, &pending_accounts_package);
3957            assert_eq!(expected_result, actual_result);
3958        }
3959    }
3960
3961    #[test]
3962    fn test_verify_slot_deltas_structural_good() {
3963        // NOTE: slot deltas do not need to be sorted
3964        let slot_deltas = vec![
3965            (222, true, Status::default()),
3966            (333, true, Status::default()),
3967            (111, true, Status::default()),
3968        ];
3969
3970        let bank_slot = 333;
3971        let result = verify_slot_deltas_structural(slot_deltas.as_slice(), bank_slot);
3972        assert_eq!(
3973            result,
3974            Ok(VerifySlotDeltasStructuralInfo {
3975                slots: HashSet::from([111, 222, 333])
3976            })
3977        );
3978    }
3979
3980    #[test]
3981    fn test_verify_slot_deltas_structural_bad_too_many_entries() {
3982        let bank_slot = status_cache::MAX_CACHE_ENTRIES as Slot + 1;
3983        let slot_deltas: Vec<_> = (0..bank_slot)
3984            .map(|slot| (slot, true, Status::default()))
3985            .collect();
3986
3987        let result = verify_slot_deltas_structural(slot_deltas.as_slice(), bank_slot);
3988        assert_eq!(
3989            result,
3990            Err(VerifySlotDeltasError::TooManyEntries(
3991                status_cache::MAX_CACHE_ENTRIES + 1,
3992                status_cache::MAX_CACHE_ENTRIES
3993            )),
3994        );
3995    }
3996
3997    #[test]
3998    fn test_verify_slot_deltas_structural_bad_slot_not_root() {
3999        let slot_deltas = vec![
4000            (111, true, Status::default()),
4001            (222, false, Status::default()), // <-- slot is not a root
4002            (333, true, Status::default()),
4003        ];
4004
4005        let bank_slot = 333;
4006        let result = verify_slot_deltas_structural(slot_deltas.as_slice(), bank_slot);
4007        assert_eq!(result, Err(VerifySlotDeltasError::SlotIsNotRoot(222)));
4008    }
4009
4010    #[test]
4011    fn test_verify_slot_deltas_structural_bad_slot_greater_than_bank() {
4012        let slot_deltas = vec![
4013            (222, true, Status::default()),
4014            (111, true, Status::default()),
4015            (555, true, Status::default()), // <-- slot is greater than the bank slot
4016        ];
4017
4018        let bank_slot = 444;
4019        let result = verify_slot_deltas_structural(slot_deltas.as_slice(), bank_slot);
4020        assert_eq!(
4021            result,
4022            Err(VerifySlotDeltasError::SlotGreaterThanMaxRoot(
4023                555, bank_slot
4024            )),
4025        );
4026    }
4027
4028    #[test]
4029    fn test_verify_slot_deltas_structural_bad_slot_has_multiple_entries() {
4030        let slot_deltas = vec![
4031            (111, true, Status::default()),
4032            (222, true, Status::default()),
4033            (111, true, Status::default()), // <-- slot is a duplicate
4034        ];
4035
4036        let bank_slot = 222;
4037        let result = verify_slot_deltas_structural(slot_deltas.as_slice(), bank_slot);
4038        assert_eq!(
4039            result,
4040            Err(VerifySlotDeltasError::SlotHasMultipleEntries(111)),
4041        );
4042    }
4043
4044    #[test]
4045    fn test_verify_slot_deltas_with_history_good() {
4046        let mut slots_from_slot_deltas = HashSet::default();
4047        let mut slot_history = SlotHistory::default();
4048        // note: slot history expects slots to be added in numeric order
4049        for slot in [0, 111, 222, 333, 444] {
4050            slots_from_slot_deltas.insert(slot);
4051            slot_history.add(slot);
4052        }
4053
4054        let bank_slot = 444;
4055        let result =
4056            verify_slot_deltas_with_history(&slots_from_slot_deltas, &slot_history, bank_slot);
4057        assert_eq!(result, Ok(()));
4058    }
4059
4060    #[test]
4061    fn test_verify_slot_deltas_with_history_bad_slot_history() {
4062        let bank_slot = 444;
4063        let result = verify_slot_deltas_with_history(
4064            &HashSet::default(),
4065            &SlotHistory::default(), // <-- will only have an entry for slot 0
4066            bank_slot,
4067        );
4068        assert_eq!(result, Err(VerifySlotDeltasError::BadSlotHistory));
4069    }
4070
4071    #[test]
4072    fn test_verify_slot_deltas_with_history_bad_slot_not_in_history() {
4073        let slots_from_slot_deltas = HashSet::from([
4074            0, // slot history has slot 0 added by default
4075            444, 222,
4076        ]);
4077        let mut slot_history = SlotHistory::default();
4078        slot_history.add(444); // <-- slot history is missing slot 222
4079
4080        let bank_slot = 444;
4081        let result =
4082            verify_slot_deltas_with_history(&slots_from_slot_deltas, &slot_history, bank_slot);
4083
4084        assert_eq!(
4085            result,
4086            Err(VerifySlotDeltasError::SlotNotFoundInHistory(222)),
4087        );
4088    }
4089
4090    #[test]
4091    fn test_verify_slot_deltas_with_history_bad_slot_not_in_deltas() {
4092        let slots_from_slot_deltas = HashSet::from([
4093            0, // slot history has slot 0 added by default
4094            444, 222,
4095            // <-- slot deltas is missing slot 333
4096        ]);
4097        let mut slot_history = SlotHistory::default();
4098        slot_history.add(222);
4099        slot_history.add(333);
4100        slot_history.add(444);
4101
4102        let bank_slot = 444;
4103        let result =
4104            verify_slot_deltas_with_history(&slots_from_slot_deltas, &slot_history, bank_slot);
4105
4106        assert_eq!(
4107            result,
4108            Err(VerifySlotDeltasError::SlotNotFoundInDeltas(333)),
4109        );
4110    }
4111}