gemachain_runtime/
snapshot_utils.rs

1use {
2    crate::{
3        accounts_db::{AccountShrinkThreshold, AccountsDbConfig},
4        accounts_index::AccountSecondaryIndexes,
5        bank::{Bank, BankSlotDelta},
6        builtins::Builtins,
7        hardened_unpack::{unpack_snapshot, ParallelSelector, UnpackError, UnpackedAppendVecMap},
8        serde_snapshot::{
9            bank_from_streams, bank_to_stream, SerdeStyle, SnapshotStorage, SnapshotStorages,
10            SnapshotStreams,
11        },
12        shared_buffer_reader::{SharedBuffer, SharedBufferReader},
13        snapshot_archive_info::{
14            FullSnapshotArchiveInfo, IncrementalSnapshotArchiveInfo, SnapshotArchiveInfoGetter,
15        },
16        snapshot_package::{
17            AccountsPackage, AccountsPackageSendError, AccountsPackageSender, SnapshotPackage,
18            SnapshotType,
19        },
20    },
21    bincode::{config::Options, serialize_into},
22    bzip2::bufread::BzDecoder,
23    flate2::read::GzDecoder,
24    lazy_static::lazy_static,
25    log::*,
26    rayon::prelude::*,
27    regex::Regex,
28    gemachain_measure::measure::Measure,
29    gemachain_sdk::{clock::Slot, genesis_config::GenesisConfig, hash::Hash, pubkey::Pubkey},
30    std::{
31        cmp::{max, Ordering},
32        collections::HashSet,
33        fmt,
34        fs::{self, File},
35        io::{BufReader, BufWriter, Error as IoError, ErrorKind, Read, Seek, Write},
36        path::{Path, PathBuf},
37        process::ExitStatus,
38        str::FromStr,
39        sync::Arc,
40    },
41    tar::{self, Archive},
42    tempfile::TempDir,
43    thiserror::Error,
44};
45
46pub const SNAPSHOT_STATUS_CACHE_FILE_NAME: &str = "status_cache";
47pub const DEFAULT_FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS: Slot = 100_000;
48pub const DEFAULT_INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS: Slot = 100;
49const MAX_SNAPSHOT_DATA_FILE_SIZE: u64 = 32 * 1024 * 1024 * 1024; // 32 GiB
50const VERSION_STRING_V1_2_0: &str = "1.2.0";
51const DEFAULT_SNAPSHOT_VERSION: SnapshotVersion = SnapshotVersion::V1_2_0;
52pub(crate) const TMP_BANK_SNAPSHOT_PREFIX: &str = "tmp-bank-snapshot-";
53pub const TMP_SNAPSHOT_ARCHIVE_PREFIX: &str = "tmp-snapshot-archive-";
54pub const MAX_BANK_SNAPSHOTS_TO_RETAIN: usize = 8; // Save some bank snapshots but not too many
55pub const DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN: usize = 2;
56pub const DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN: usize = 4;
57pub const FULL_SNAPSHOT_ARCHIVE_FILENAME_REGEX: &str = r"^snapshot-(?P<slot>[[:digit:]]+)-(?P<hash>[[:alnum:]]+)\.(?P<ext>tar|tar\.bz2|tar\.zst|tar\.gz)$";
58pub 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)$";
59
60#[derive(Copy, Clone, Eq, PartialEq, Debug)]
61pub enum SnapshotVersion {
62    V1_2_0,
63}
64
65impl Default for SnapshotVersion {
66    fn default() -> Self {
67        DEFAULT_SNAPSHOT_VERSION
68    }
69}
70
71impl fmt::Display for SnapshotVersion {
72    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
73        f.write_str(From::from(*self))
74    }
75}
76
77impl From<SnapshotVersion> for &'static str {
78    fn from(snapshot_version: SnapshotVersion) -> &'static str {
79        match snapshot_version {
80            SnapshotVersion::V1_2_0 => VERSION_STRING_V1_2_0,
81        }
82    }
83}
84
85impl FromStr for SnapshotVersion {
86    type Err = &'static str;
87
88    fn from_str(version_string: &str) -> std::result::Result<Self, Self::Err> {
89        // Remove leading 'v' or 'V' from slice
90        let version_string = if version_string
91            .get(..1)
92            .map_or(false, |s| s.eq_ignore_ascii_case("v"))
93        {
94            &version_string[1..]
95        } else {
96            version_string
97        };
98        match version_string {
99            VERSION_STRING_V1_2_0 => Ok(SnapshotVersion::V1_2_0),
100            _ => Err("unsupported snapshot version"),
101        }
102    }
103}
104
105impl SnapshotVersion {
106    pub fn as_str(self) -> &'static str {
107        <&str as From<Self>>::from(self)
108    }
109
110    fn maybe_from_string(version_string: &str) -> Option<SnapshotVersion> {
111        version_string.parse::<Self>().ok()
112    }
113}
114
115/// The different archive formats used for snapshots
116#[derive(Copy, Clone, Debug, Eq, PartialEq)]
117pub enum ArchiveFormat {
118    TarBzip2,
119    TarGzip,
120    TarZstd,
121    Tar,
122}
123
124/// A slot and the path to its bank snapshot
125#[derive(PartialEq, Eq, Debug)]
126pub struct BankSnapshotInfo {
127    pub slot: Slot,
128    pub snapshot_path: PathBuf,
129}
130
131impl PartialOrd for BankSnapshotInfo {
132    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
133        Some(self.cmp(other))
134    }
135}
136
137// Order BankSnapshotInfo by slot (ascending), which practially is sorting chronologically
138impl Ord for BankSnapshotInfo {
139    fn cmp(&self, other: &Self) -> Ordering {
140        self.slot.cmp(&other.slot)
141    }
142}
143
144/// Helper type when rebuilding from snapshots.  Designed to handle when rebuilding from just a
145/// full snapshot, or from both a full snapshot and an incremental snapshot.
146#[derive(Debug)]
147struct SnapshotRootPaths {
148    full_snapshot_root_file_path: PathBuf,
149    incremental_snapshot_root_file_path: Option<PathBuf>,
150}
151
152/// Helper type to bundle up the results from `unarchive_snapshot()`
153#[derive(Debug)]
154struct UnarchivedSnapshot {
155    unpack_dir: TempDir,
156    unpacked_append_vec_map: UnpackedAppendVecMap,
157    unpacked_snapshots_dir_and_version: UnpackedSnapshotsDirAndVersion,
158    measure_untar: Measure,
159}
160
161/// Helper type for passing around the unpacked snapshots dir and the snapshot version together
162#[derive(Debug)]
163struct UnpackedSnapshotsDirAndVersion {
164    unpacked_snapshots_dir: PathBuf,
165    snapshot_version: String,
166}
167
168#[derive(Error, Debug)]
169pub enum SnapshotError {
170    #[error("I/O error: {0}")]
171    Io(#[from] std::io::Error),
172
173    #[error("serialization error: {0}")]
174    Serialize(#[from] bincode::Error),
175
176    #[error("archive generation failure {0}")]
177    ArchiveGenerationFailure(ExitStatus),
178
179    #[error("storage path symlink is invalid")]
180    StoragePathSymlinkInvalid,
181
182    #[error("Unpack error: {0}")]
183    UnpackError(#[from] UnpackError),
184
185    #[error("accounts package send error")]
186    AccountsPackageSendError(#[from] AccountsPackageSendError),
187
188    #[error("source({1}) - I/O error: {0}")]
189    IoWithSource(std::io::Error, &'static str),
190
191    #[error("could not get file name from path: {}", .0.display())]
192    PathToFileNameError(PathBuf),
193
194    #[error("could not get str from file name: {}", .0.display())]
195    FileNameToStrError(PathBuf),
196
197    #[error("could not parse snapshot archive's file name: {0}")]
198    ParseSnapshotArchiveFileNameError(String),
199
200    #[error("snapshots are incompatible: full snapshot slot ({0}) and incremental snapshot base slot ({1}) do not match")]
201    MismatchedBaseSlot(Slot, Slot),
202
203    #[error("no snapshot archives to load from")]
204    NoSnapshotArchives,
205
206    #[error("snapshot has mismatch: deserialized bank: {:?}, snapshot archive info: {:?}", .0, .1)]
207    MismatchedSlotHash((Slot, Hash), (Slot, Hash)),
208}
209pub type Result<T> = std::result::Result<T, SnapshotError>;
210
211fn get_archive_ext(archive_format: ArchiveFormat) -> &'static str {
212    match archive_format {
213        ArchiveFormat::TarBzip2 => "tar.bz2",
214        ArchiveFormat::TarGzip => "tar.gz",
215        ArchiveFormat::TarZstd => "tar.zst",
216        ArchiveFormat::Tar => "tar",
217    }
218}
219
220/// If the validator halts in the middle of `archive_snapshot_package()`, the temporary staging
221/// directory won't be cleaned up.  Call this function to clean them up.
222pub fn remove_tmp_snapshot_archives(snapshot_archives_dir: impl AsRef<Path>) {
223    if let Ok(entries) = fs::read_dir(snapshot_archives_dir) {
224        for entry in entries.filter_map(|entry| entry.ok()) {
225            let file_name = entry
226                .file_name()
227                .into_string()
228                .unwrap_or_else(|_| String::new());
229            if file_name.starts_with(TMP_SNAPSHOT_ARCHIVE_PREFIX) {
230                if entry.path().is_file() {
231                    fs::remove_file(entry.path())
232                } else {
233                    fs::remove_dir_all(entry.path())
234                }
235                .unwrap_or_else(|err| {
236                    warn!("Failed to remove {}: {}", entry.path().display(), err)
237                });
238            }
239        }
240    }
241}
242
243/// Make a snapshot archive out of the snapshot package
244pub fn archive_snapshot_package(
245    snapshot_package: &SnapshotPackage,
246    maximum_full_snapshot_archives_to_retain: usize,
247    maximum_incremental_snapshot_archives_to_retain: usize,
248) -> Result<()> {
249    info!(
250        "Generating snapshot archive for slot {}",
251        snapshot_package.slot()
252    );
253
254    serialize_status_cache(
255        snapshot_package.slot(),
256        &snapshot_package.slot_deltas,
257        &snapshot_package
258            .snapshot_links
259            .path()
260            .join(SNAPSHOT_STATUS_CACHE_FILE_NAME),
261    )?;
262
263    let mut timer = Measure::start("snapshot_package-package_snapshots");
264    let tar_dir = snapshot_package
265        .path()
266        .parent()
267        .expect("Tar output path is invalid");
268
269    fs::create_dir_all(tar_dir)
270        .map_err(|e| SnapshotError::IoWithSource(e, "create archive path"))?;
271
272    // Create the staging directories
273    let staging_dir_prefix = TMP_SNAPSHOT_ARCHIVE_PREFIX;
274    let staging_dir = tempfile::Builder::new()
275        .prefix(&format!(
276            "{}{}-",
277            staging_dir_prefix,
278            snapshot_package.slot()
279        ))
280        .tempdir_in(tar_dir)
281        .map_err(|e| SnapshotError::IoWithSource(e, "create archive tempdir"))?;
282
283    let staging_accounts_dir = staging_dir.path().join("accounts");
284    let staging_snapshots_dir = staging_dir.path().join("snapshots");
285    let staging_version_file = staging_dir.path().join("version");
286    fs::create_dir_all(&staging_accounts_dir)
287        .map_err(|e| SnapshotError::IoWithSource(e, "create staging path"))?;
288
289    // Add the snapshots to the staging directory
290    symlink::symlink_dir(
291        snapshot_package.snapshot_links.path(),
292        &staging_snapshots_dir,
293    )
294    .map_err(|e| SnapshotError::IoWithSource(e, "create staging symlinks"))?;
295
296    // Add the AppendVecs into the compressible list
297    for storage in snapshot_package.snapshot_storages.iter().flatten() {
298        storage.flush()?;
299        let storage_path = storage.get_path();
300        let output_path = staging_accounts_dir.join(crate::append_vec::AppendVec::file_name(
301            storage.slot(),
302            storage.append_vec_id(),
303        ));
304
305        // `storage_path` - The file path where the AppendVec itself is located
306        // `output_path` - The file path where the AppendVec will be placed in the staging directory.
307        let storage_path =
308            fs::canonicalize(storage_path).expect("Could not get absolute path for accounts");
309        symlink::symlink_file(storage_path, &output_path)
310            .map_err(|e| SnapshotError::IoWithSource(e, "create storage symlink"))?;
311        if !output_path.is_file() {
312            return Err(SnapshotError::StoragePathSymlinkInvalid);
313        }
314    }
315
316    // Write version file
317    {
318        let mut f = fs::File::create(staging_version_file)
319            .map_err(|e| SnapshotError::IoWithSource(e, "create version file"))?;
320        f.write_all(snapshot_package.snapshot_version.as_str().as_bytes())
321            .map_err(|e| SnapshotError::IoWithSource(e, "write version file"))?;
322    }
323
324    let file_ext = get_archive_ext(snapshot_package.archive_format());
325
326    // Tar the staging directory into the archive at `archive_path`
327    let archive_path = tar_dir.join(format!(
328        "{}{}.{}",
329        staging_dir_prefix,
330        snapshot_package.slot(),
331        file_ext
332    ));
333
334    {
335        let mut archive_file = fs::File::create(&archive_path)?;
336
337        let do_archive_files = |encoder: &mut dyn Write| -> Result<()> {
338            let mut archive = tar::Builder::new(encoder);
339            for dir in ["accounts", "snapshots"] {
340                archive.append_dir_all(dir, staging_dir.as_ref().join(dir))?;
341            }
342            archive.append_path_with_name(staging_dir.as_ref().join("version"), "version")?;
343            archive.into_inner()?;
344            Ok(())
345        };
346
347        match snapshot_package.archive_format() {
348            ArchiveFormat::TarBzip2 => {
349                let mut encoder =
350                    bzip2::write::BzEncoder::new(archive_file, bzip2::Compression::best());
351                do_archive_files(&mut encoder)?;
352                encoder.finish()?;
353            }
354            ArchiveFormat::TarGzip => {
355                let mut encoder =
356                    flate2::write::GzEncoder::new(archive_file, flate2::Compression::default());
357                do_archive_files(&mut encoder)?;
358                encoder.finish()?;
359            }
360            ArchiveFormat::TarZstd => {
361                let mut encoder = zstd::stream::Encoder::new(archive_file, 0)?;
362                do_archive_files(&mut encoder)?;
363                encoder.finish()?;
364            }
365            ArchiveFormat::Tar => {
366                do_archive_files(&mut archive_file)?;
367            }
368        };
369    }
370
371    // Atomically move the archive into position for other validators to find
372    let metadata = fs::metadata(&archive_path)
373        .map_err(|e| SnapshotError::IoWithSource(e, "archive path stat"))?;
374    fs::rename(&archive_path, &snapshot_package.path())
375        .map_err(|e| SnapshotError::IoWithSource(e, "archive path rename"))?;
376
377    purge_old_snapshot_archives(
378        tar_dir,
379        maximum_full_snapshot_archives_to_retain,
380        maximum_incremental_snapshot_archives_to_retain,
381    );
382
383    timer.stop();
384    info!(
385        "Successfully created {:?}. slot: {}, elapsed ms: {}, size={}",
386        snapshot_package.path(),
387        snapshot_package.slot(),
388        timer.as_ms(),
389        metadata.len()
390    );
391    datapoint_info!(
392        "snapshot-package",
393        ("slot", snapshot_package.slot(), i64),
394        ("duration_ms", timer.as_ms(), i64),
395        ("size", metadata.len(), i64)
396    );
397    Ok(())
398}
399
400/// Get a list of bank snapshots in a directory
401pub fn get_bank_snapshots<P>(bank_snapshots_dir: P) -> Vec<BankSnapshotInfo>
402where
403    P: AsRef<Path>,
404{
405    match fs::read_dir(&bank_snapshots_dir) {
406        Ok(paths) => paths
407            .filter_map(|entry| {
408                entry.ok().and_then(|e| {
409                    e.path()
410                        .file_name()
411                        .and_then(|n| n.to_str().map(|s| s.parse::<Slot>().ok()))
412                        .unwrap_or(None)
413                })
414            })
415            .map(|slot| {
416                let snapshot_file_name = get_snapshot_file_name(slot);
417                // So nice I join-ed it twice!  The redundant `snapshot_file_name` is unintentional
418                // and should be simplified.  Kept for compatibility.
419                let snapshot_path = bank_snapshots_dir
420                    .as_ref()
421                    .join(&snapshot_file_name)
422                    .join(snapshot_file_name);
423                BankSnapshotInfo {
424                    slot,
425                    snapshot_path,
426                }
427            })
428            .collect::<Vec<BankSnapshotInfo>>(),
429        Err(err) => {
430            info!(
431                "Unable to read snapshots directory {}: {}",
432                bank_snapshots_dir.as_ref().display(),
433                err
434            );
435            vec![]
436        }
437    }
438}
439
440/// Get the bank snapshot with the highest slot in a directory
441pub fn get_highest_bank_snapshot_info<P>(bank_snapshots_dir: P) -> Option<BankSnapshotInfo>
442where
443    P: AsRef<Path>,
444{
445    let mut bank_snapshot_infos = get_bank_snapshots(bank_snapshots_dir);
446    bank_snapshot_infos.sort_unstable();
447    bank_snapshot_infos.into_iter().rev().next()
448}
449
450pub fn serialize_snapshot_data_file<F>(data_file_path: &Path, serializer: F) -> Result<u64>
451where
452    F: FnOnce(&mut BufWriter<File>) -> Result<()>,
453{
454    serialize_snapshot_data_file_capped::<F>(
455        data_file_path,
456        MAX_SNAPSHOT_DATA_FILE_SIZE,
457        serializer,
458    )
459}
460
461pub fn deserialize_snapshot_data_file<T: Sized>(
462    data_file_path: &Path,
463    deserializer: impl FnOnce(&mut BufReader<File>) -> Result<T>,
464) -> Result<T> {
465    let wrapped_deserializer = move |streams: &mut SnapshotStreams<File>| -> Result<T> {
466        deserializer(&mut streams.full_snapshot_stream)
467    };
468
469    let wrapped_data_file_path = SnapshotRootPaths {
470        full_snapshot_root_file_path: data_file_path.to_path_buf(),
471        incremental_snapshot_root_file_path: None,
472    };
473
474    deserialize_snapshot_data_files_capped(
475        &wrapped_data_file_path,
476        MAX_SNAPSHOT_DATA_FILE_SIZE,
477        wrapped_deserializer,
478    )
479}
480
481fn deserialize_snapshot_data_files<T: Sized>(
482    snapshot_root_paths: &SnapshotRootPaths,
483    deserializer: impl FnOnce(&mut SnapshotStreams<File>) -> Result<T>,
484) -> Result<T> {
485    deserialize_snapshot_data_files_capped(
486        snapshot_root_paths,
487        MAX_SNAPSHOT_DATA_FILE_SIZE,
488        deserializer,
489    )
490}
491
492fn serialize_snapshot_data_file_capped<F>(
493    data_file_path: &Path,
494    maximum_file_size: u64,
495    serializer: F,
496) -> Result<u64>
497where
498    F: FnOnce(&mut BufWriter<File>) -> Result<()>,
499{
500    let data_file = File::create(data_file_path)?;
501    let mut data_file_stream = BufWriter::new(data_file);
502    serializer(&mut data_file_stream)?;
503    data_file_stream.flush()?;
504
505    let consumed_size = data_file_stream.stream_position()?;
506    if consumed_size > maximum_file_size {
507        let error_message = format!(
508            "too large snapshot data file to serialize: {:?} has {} bytes",
509            data_file_path, consumed_size
510        );
511        return Err(get_io_error(&error_message));
512    }
513    Ok(consumed_size)
514}
515
516fn deserialize_snapshot_data_files_capped<T: Sized>(
517    snapshot_root_paths: &SnapshotRootPaths,
518    maximum_file_size: u64,
519    deserializer: impl FnOnce(&mut SnapshotStreams<File>) -> Result<T>,
520) -> Result<T> {
521    let (full_snapshot_file_size, mut full_snapshot_data_file_stream) =
522        create_snapshot_data_file_stream(
523            &snapshot_root_paths.full_snapshot_root_file_path,
524            maximum_file_size,
525        )?;
526
527    let (incremental_snapshot_file_size, mut incremental_snapshot_data_file_stream) =
528        if let Some(ref incremental_snapshot_root_file_path) =
529            snapshot_root_paths.incremental_snapshot_root_file_path
530        {
531            let (incremental_snapshot_file_size, incremental_snapshot_data_file_stream) =
532                create_snapshot_data_file_stream(
533                    incremental_snapshot_root_file_path,
534                    maximum_file_size,
535                )?;
536            (
537                Some(incremental_snapshot_file_size),
538                Some(incremental_snapshot_data_file_stream),
539            )
540        } else {
541            (None, None)
542        };
543
544    let mut snapshot_streams = SnapshotStreams {
545        full_snapshot_stream: &mut full_snapshot_data_file_stream,
546        incremental_snapshot_stream: incremental_snapshot_data_file_stream.as_mut(),
547    };
548    let ret = deserializer(&mut snapshot_streams)?;
549
550    check_deserialize_file_consumed(
551        full_snapshot_file_size,
552        &snapshot_root_paths.full_snapshot_root_file_path,
553        &mut full_snapshot_data_file_stream,
554    )?;
555
556    if let Some(ref incremental_snapshot_root_file_path) =
557        snapshot_root_paths.incremental_snapshot_root_file_path
558    {
559        check_deserialize_file_consumed(
560            incremental_snapshot_file_size.unwrap(),
561            incremental_snapshot_root_file_path,
562            incremental_snapshot_data_file_stream.as_mut().unwrap(),
563        )?;
564    }
565
566    Ok(ret)
567}
568
569/// Before running the deserializer function, perform common operations on the snapshot archive
570/// files, such as checking the file size and opening the file into a stream.
571fn create_snapshot_data_file_stream<P>(
572    snapshot_root_file_path: P,
573    maximum_file_size: u64,
574) -> Result<(u64, BufReader<File>)>
575where
576    P: AsRef<Path>,
577{
578    let snapshot_file_size = fs::metadata(&snapshot_root_file_path)?.len();
579
580    if snapshot_file_size > maximum_file_size {
581        let error_message =
582            format!(
583            "too large snapshot data file to deserialize: {} has {} bytes (max size is {} bytes)",
584            snapshot_root_file_path.as_ref().display(), snapshot_file_size, maximum_file_size
585        );
586        return Err(get_io_error(&error_message));
587    }
588
589    let snapshot_data_file = File::open(&snapshot_root_file_path)?;
590    let snapshot_data_file_stream = BufReader::new(snapshot_data_file);
591
592    Ok((snapshot_file_size, snapshot_data_file_stream))
593}
594
595/// After running the deserializer function, perform common checks to ensure the snapshot archive
596/// files were consumed correctly.
597fn check_deserialize_file_consumed<P>(
598    file_size: u64,
599    file_path: P,
600    file_stream: &mut BufReader<File>,
601) -> Result<()>
602where
603    P: AsRef<Path>,
604{
605    let consumed_size = file_stream.stream_position()?;
606
607    if consumed_size != file_size {
608        let error_message =
609            format!(
610            "invalid snapshot data file: {} has {} bytes, however consumed {} bytes to deserialize",
611            file_path.as_ref().display(), file_size, consumed_size
612        );
613        return Err(get_io_error(&error_message));
614    }
615
616    Ok(())
617}
618
619/// Serialize a bank to a snapshot
620pub fn add_bank_snapshot<P: AsRef<Path>>(
621    bank_snapshots_dir: P,
622    bank: &Bank,
623    snapshot_storages: &[SnapshotStorage],
624    snapshot_version: SnapshotVersion,
625) -> Result<BankSnapshotInfo> {
626    let slot = bank.slot();
627    // bank_snapshots_dir/slot
628    let bank_snapshots_dir = get_bank_snapshots_dir(bank_snapshots_dir, slot);
629    fs::create_dir_all(&bank_snapshots_dir)?;
630
631    // the bank snapshot is stored as bank_snapshots_dir/slot/slot
632    let snapshot_bank_file_path = bank_snapshots_dir.join(get_snapshot_file_name(slot));
633    info!(
634        "Creating snapshot for slot {}, path: {:?}",
635        slot, snapshot_bank_file_path,
636    );
637
638    let mut bank_serialize = Measure::start("bank-serialize-ms");
639    let bank_snapshot_serializer = move |stream: &mut BufWriter<File>| -> Result<()> {
640        let serde_style = match snapshot_version {
641            SnapshotVersion::V1_2_0 => SerdeStyle::Newer,
642        };
643        bank_to_stream(serde_style, stream.by_ref(), bank, snapshot_storages)?;
644        Ok(())
645    };
646    let consumed_size =
647        serialize_snapshot_data_file(&snapshot_bank_file_path, bank_snapshot_serializer)?;
648    bank_serialize.stop();
649
650    // Monitor sizes because they're capped to MAX_SNAPSHOT_DATA_FILE_SIZE
651    datapoint_info!(
652        "snapshot-bank-file",
653        ("slot", slot, i64),
654        ("size", consumed_size, i64)
655    );
656
657    inc_new_counter_info!("bank-serialize-ms", bank_serialize.as_ms() as usize);
658
659    info!(
660        "{} for slot {} at {:?}",
661        bank_serialize, slot, snapshot_bank_file_path,
662    );
663
664    Ok(BankSnapshotInfo {
665        slot,
666        snapshot_path: snapshot_bank_file_path,
667    })
668}
669
670fn serialize_status_cache(
671    slot: Slot,
672    slot_deltas: &[BankSlotDelta],
673    status_cache_path: &Path,
674) -> Result<()> {
675    let mut status_cache_serialize = Measure::start("status_cache_serialize-ms");
676    let consumed_size = serialize_snapshot_data_file(status_cache_path, |stream| {
677        serialize_into(stream, slot_deltas)?;
678        Ok(())
679    })?;
680    status_cache_serialize.stop();
681
682    // Monitor sizes because they're capped to MAX_SNAPSHOT_DATA_FILE_SIZE
683    datapoint_info!(
684        "snapshot-status-cache-file",
685        ("slot", slot, i64),
686        ("size", consumed_size, i64)
687    );
688
689    inc_new_counter_info!(
690        "serialize-status-cache-ms",
691        status_cache_serialize.as_ms() as usize
692    );
693    Ok(())
694}
695
696/// Remove the snapshot directory for this slot
697pub fn remove_bank_snapshot<P>(slot: Slot, bank_snapshots_dir: P) -> Result<()>
698where
699    P: AsRef<Path>,
700{
701    let bank_snapshot_dir = get_bank_snapshots_dir(&bank_snapshots_dir, slot);
702    fs::remove_dir_all(bank_snapshot_dir)?;
703    Ok(())
704}
705
706#[derive(Debug, Default)]
707pub struct BankFromArchiveTimings {
708    pub rebuild_bank_from_snapshots_us: u64,
709    pub full_snapshot_untar_us: u64,
710    pub incremental_snapshot_untar_us: u64,
711    pub verify_snapshot_bank_us: u64,
712}
713
714// 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.
715const PARALLEL_UNTAR_READERS_DEFAULT: usize = 4;
716
717/// Rebuild bank from snapshot archives.  Handles either just a full snapshot, or both a full
718/// snapshot and an incremental snapshot.
719#[allow(clippy::too_many_arguments)]
720pub fn bank_from_snapshot_archives(
721    account_paths: &[PathBuf],
722    frozen_account_pubkeys: &[Pubkey],
723    bank_snapshots_dir: impl AsRef<Path>,
724    full_snapshot_archive_info: &FullSnapshotArchiveInfo,
725    incremental_snapshot_archive_info: Option<&IncrementalSnapshotArchiveInfo>,
726    genesis_config: &GenesisConfig,
727    debug_keys: Option<Arc<HashSet<Pubkey>>>,
728    additional_builtins: Option<&Builtins>,
729    account_secondary_indexes: AccountSecondaryIndexes,
730    accounts_db_caching_enabled: bool,
731    limit_load_slot_count_from_snapshot: Option<usize>,
732    shrink_ratio: AccountShrinkThreshold,
733    test_hash_calculation: bool,
734    accounts_db_skip_shrink: bool,
735    verify_index: bool,
736    accounts_db_config: Option<AccountsDbConfig>,
737) -> Result<(Bank, BankFromArchiveTimings)> {
738    check_are_snapshots_compatible(
739        full_snapshot_archive_info,
740        incremental_snapshot_archive_info,
741    )?;
742
743    let parallel_divisions = std::cmp::min(
744        PARALLEL_UNTAR_READERS_DEFAULT,
745        std::cmp::max(1, num_cpus::get() / 4),
746    );
747
748    let unarchived_full_snapshot = unarchive_snapshot(
749        &bank_snapshots_dir,
750        TMP_SNAPSHOT_ARCHIVE_PREFIX,
751        full_snapshot_archive_info.path(),
752        "snapshot untar",
753        account_paths,
754        full_snapshot_archive_info.archive_format(),
755        parallel_divisions,
756    )?;
757
758    let mut unarchived_incremental_snapshot =
759        if let Some(incremental_snapshot_archive_info) = incremental_snapshot_archive_info {
760            let unarchived_incremental_snapshot = unarchive_snapshot(
761                &bank_snapshots_dir,
762                TMP_SNAPSHOT_ARCHIVE_PREFIX,
763                incremental_snapshot_archive_info.path(),
764                "incremental snapshot untar",
765                account_paths,
766                incremental_snapshot_archive_info.archive_format(),
767                parallel_divisions,
768            )?;
769            Some(unarchived_incremental_snapshot)
770        } else {
771            None
772        };
773
774    let mut unpacked_append_vec_map = unarchived_full_snapshot.unpacked_append_vec_map;
775    if let Some(ref mut unarchive_preparation_result) = unarchived_incremental_snapshot {
776        let incremental_snapshot_unpacked_append_vec_map =
777            std::mem::take(&mut unarchive_preparation_result.unpacked_append_vec_map);
778        unpacked_append_vec_map.extend(incremental_snapshot_unpacked_append_vec_map.into_iter());
779    }
780
781    let mut measure_rebuild = Measure::start("rebuild bank from snapshots");
782    let bank = rebuild_bank_from_snapshots(
783        &unarchived_full_snapshot.unpacked_snapshots_dir_and_version,
784        unarchived_incremental_snapshot
785            .as_ref()
786            .map(|unarchive_preparation_result| {
787                &unarchive_preparation_result.unpacked_snapshots_dir_and_version
788            }),
789        frozen_account_pubkeys,
790        account_paths,
791        unpacked_append_vec_map,
792        genesis_config,
793        debug_keys,
794        additional_builtins,
795        account_secondary_indexes,
796        accounts_db_caching_enabled,
797        limit_load_slot_count_from_snapshot,
798        shrink_ratio,
799        verify_index,
800        accounts_db_config,
801    )?;
802    measure_rebuild.stop();
803    info!("{}", measure_rebuild);
804
805    let mut measure_verify = Measure::start("verify");
806    if !bank.verify_snapshot_bank(
807        test_hash_calculation,
808        accounts_db_skip_shrink,
809        Some(full_snapshot_archive_info.slot()),
810    ) && limit_load_slot_count_from_snapshot.is_none()
811    {
812        panic!("Snapshot bank for slot {} failed to verify", bank.slot());
813    }
814    measure_verify.stop();
815
816    let timings = BankFromArchiveTimings {
817        rebuild_bank_from_snapshots_us: measure_rebuild.as_us(),
818        full_snapshot_untar_us: unarchived_full_snapshot.measure_untar.as_us(),
819        incremental_snapshot_untar_us: unarchived_incremental_snapshot
820            .map_or(0, |unarchive_preparation_result| {
821                unarchive_preparation_result.measure_untar.as_us()
822            }),
823        verify_snapshot_bank_us: measure_verify.as_us(),
824    };
825    Ok((bank, timings))
826}
827
828/// Rebuild bank from snapshot archives.  This function searches `snapshot_archives_dir` for the
829/// highest full snapshot and highest corresponding incremental snapshot, then rebuilds the bank.
830#[allow(clippy::too_many_arguments)]
831pub fn bank_from_latest_snapshot_archives(
832    bank_snapshots_dir: impl AsRef<Path>,
833    snapshot_archives_dir: impl AsRef<Path>,
834    account_paths: &[PathBuf],
835    frozen_account_pubkeys: &[Pubkey],
836    genesis_config: &GenesisConfig,
837    debug_keys: Option<Arc<HashSet<Pubkey>>>,
838    additional_builtins: Option<&Builtins>,
839    account_secondary_indexes: AccountSecondaryIndexes,
840    accounts_db_caching_enabled: bool,
841    limit_load_slot_count_from_snapshot: Option<usize>,
842    shrink_ratio: AccountShrinkThreshold,
843    test_hash_calculation: bool,
844    accounts_db_skip_shrink: bool,
845    verify_index: bool,
846    accounts_db_config: Option<AccountsDbConfig>,
847) -> Result<(
848    Bank,
849    BankFromArchiveTimings,
850    FullSnapshotArchiveInfo,
851    Option<IncrementalSnapshotArchiveInfo>,
852)> {
853    let full_snapshot_archive_info = get_highest_full_snapshot_archive_info(&snapshot_archives_dir)
854        .ok_or(SnapshotError::NoSnapshotArchives)?;
855
856    let incremental_snapshot_archive_info = get_highest_incremental_snapshot_archive_info(
857        &snapshot_archives_dir,
858        full_snapshot_archive_info.slot(),
859    );
860
861    info!(
862        "Loading bank from full snapshot: {}, and incremental snapshot: {:?}",
863        full_snapshot_archive_info.path().display(),
864        incremental_snapshot_archive_info
865            .as_ref()
866            .map(
867                |incremental_snapshot_archive_info| incremental_snapshot_archive_info
868                    .path()
869                    .display()
870            )
871    );
872
873    let (bank, timings) = bank_from_snapshot_archives(
874        account_paths,
875        frozen_account_pubkeys,
876        bank_snapshots_dir.as_ref(),
877        &full_snapshot_archive_info,
878        incremental_snapshot_archive_info.as_ref(),
879        genesis_config,
880        debug_keys,
881        additional_builtins,
882        account_secondary_indexes,
883        accounts_db_caching_enabled,
884        limit_load_slot_count_from_snapshot,
885        shrink_ratio,
886        test_hash_calculation,
887        accounts_db_skip_shrink,
888        verify_index,
889        accounts_db_config,
890    )?;
891
892    verify_bank_against_expected_slot_hash(
893        &bank,
894        incremental_snapshot_archive_info.as_ref().map_or(
895            full_snapshot_archive_info.slot(),
896            |incremental_snapshot_archive_info| incremental_snapshot_archive_info.slot(),
897        ),
898        incremental_snapshot_archive_info.as_ref().map_or(
899            *full_snapshot_archive_info.hash(),
900            |incremental_snapshot_archive_info| *incremental_snapshot_archive_info.hash(),
901        ),
902    )?;
903
904    Ok((
905        bank,
906        timings,
907        full_snapshot_archive_info,
908        incremental_snapshot_archive_info,
909    ))
910}
911
912/// Check to make sure the deserialized bank's slot and hash matches the snapshot archive's slot
913/// and hash
914fn verify_bank_against_expected_slot_hash(
915    bank: &Bank,
916    expected_slot: Slot,
917    expected_hash: Hash,
918) -> Result<()> {
919    let bank_slot = bank.slot();
920    let bank_hash = bank.get_accounts_hash();
921
922    if bank_slot != expected_slot || bank_hash != expected_hash {
923        return Err(SnapshotError::MismatchedSlotHash(
924            (bank_slot, bank_hash),
925            (expected_slot, expected_hash),
926        ));
927    }
928
929    Ok(())
930}
931
932/// Perform the common tasks when unarchiving a snapshot.  Handles creating the temporary
933/// directories, untaring, reading the version file, and then returning those fields plus the
934/// unpacked append vec map.
935fn unarchive_snapshot<P, Q>(
936    bank_snapshots_dir: P,
937    unpacked_snapshots_dir_prefix: &'static str,
938    snapshot_archive_path: Q,
939    measure_name: &'static str,
940    account_paths: &[PathBuf],
941    archive_format: ArchiveFormat,
942    parallel_divisions: usize,
943) -> Result<UnarchivedSnapshot>
944where
945    P: AsRef<Path>,
946    Q: AsRef<Path>,
947{
948    let unpack_dir = tempfile::Builder::new()
949        .prefix(unpacked_snapshots_dir_prefix)
950        .tempdir_in(bank_snapshots_dir)?;
951    let unpacked_snapshots_dir = unpack_dir.path().join("snapshots");
952
953    let mut measure_untar = Measure::start(measure_name);
954    let unpacked_append_vec_map = untar_snapshot_in(
955        snapshot_archive_path,
956        unpack_dir.path(),
957        account_paths,
958        archive_format,
959        parallel_divisions,
960    )?;
961    measure_untar.stop();
962    info!("{}", measure_untar);
963
964    let unpacked_version_file = unpack_dir.path().join("version");
965    let snapshot_version = {
966        let mut snapshot_version = String::new();
967        File::open(unpacked_version_file)
968            .and_then(|mut f| f.read_to_string(&mut snapshot_version))?;
969        snapshot_version.trim().to_string()
970    };
971
972    Ok(UnarchivedSnapshot {
973        unpack_dir,
974        unpacked_append_vec_map,
975        unpacked_snapshots_dir_and_version: UnpackedSnapshotsDirAndVersion {
976            unpacked_snapshots_dir,
977            snapshot_version,
978        },
979        measure_untar,
980    })
981}
982
983/// Check if an incremental snapshot is compatible with a full snapshot.  This is done by checking
984/// if the incremental snapshot's base slot is the same as the full snapshot's slot.
985fn check_are_snapshots_compatible(
986    full_snapshot_archive_info: &FullSnapshotArchiveInfo,
987    incremental_snapshot_archive_info: Option<&IncrementalSnapshotArchiveInfo>,
988) -> Result<()> {
989    if incremental_snapshot_archive_info.is_none() {
990        return Ok(());
991    }
992
993    let incremental_snapshot_archive_info = incremental_snapshot_archive_info.unwrap();
994
995    (full_snapshot_archive_info.slot() == incremental_snapshot_archive_info.base_slot())
996        .then(|| ())
997        .ok_or_else(|| {
998            SnapshotError::MismatchedBaseSlot(
999                full_snapshot_archive_info.slot(),
1000                incremental_snapshot_archive_info.base_slot(),
1001            )
1002        })
1003}
1004
1005/// Get the `&str` from a `&Path`
1006pub fn path_to_file_name_str(path: &Path) -> Result<&str> {
1007    path.file_name()
1008        .ok_or_else(|| SnapshotError::PathToFileNameError(path.to_path_buf()))?
1009        .to_str()
1010        .ok_or_else(|| SnapshotError::FileNameToStrError(path.to_path_buf()))
1011}
1012
1013/// Build the full snapshot archive path from its components: the snapshot archives directory, the
1014/// snapshot slot, the accounts hash, and the archive format.
1015pub fn build_full_snapshot_archive_path(
1016    snapshot_archives_dir: PathBuf,
1017    slot: Slot,
1018    hash: &Hash,
1019    archive_format: ArchiveFormat,
1020) -> PathBuf {
1021    snapshot_archives_dir.join(format!(
1022        "snapshot-{}-{}.{}",
1023        slot,
1024        hash,
1025        get_archive_ext(archive_format),
1026    ))
1027}
1028
1029/// Build the incremental snapshot archive path from its components: the snapshot archives
1030/// directory, the snapshot base slot, the snapshot slot, the accounts hash, and the archive
1031/// format.
1032pub fn build_incremental_snapshot_archive_path(
1033    snapshot_archives_dir: PathBuf,
1034    base_slot: Slot,
1035    slot: Slot,
1036    hash: &Hash,
1037    archive_format: ArchiveFormat,
1038) -> PathBuf {
1039    snapshot_archives_dir.join(format!(
1040        "incremental-snapshot-{}-{}-{}.{}",
1041        base_slot,
1042        slot,
1043        hash,
1044        get_archive_ext(archive_format),
1045    ))
1046}
1047
1048fn archive_format_from_str(archive_format: &str) -> Option<ArchiveFormat> {
1049    match archive_format {
1050        "tar.bz2" => Some(ArchiveFormat::TarBzip2),
1051        "tar.gz" => Some(ArchiveFormat::TarGzip),
1052        "tar.zst" => Some(ArchiveFormat::TarZstd),
1053        "tar" => Some(ArchiveFormat::Tar),
1054        _ => None,
1055    }
1056}
1057
1058/// Parse a full snapshot archive filename into its Slot, Hash, and Archive Format
1059pub fn parse_full_snapshot_archive_filename(
1060    archive_filename: &str,
1061) -> Result<(Slot, Hash, ArchiveFormat)> {
1062    lazy_static! {
1063        static ref RE: Regex = Regex::new(FULL_SNAPSHOT_ARCHIVE_FILENAME_REGEX).unwrap();
1064    }
1065
1066    let do_parse = || {
1067        RE.captures(archive_filename).and_then(|captures| {
1068            let slot = captures
1069                .name("slot")
1070                .map(|x| x.as_str().parse::<Slot>())?
1071                .ok()?;
1072            let hash = captures
1073                .name("hash")
1074                .map(|x| x.as_str().parse::<Hash>())?
1075                .ok()?;
1076            let archive_format = captures
1077                .name("ext")
1078                .map(|x| archive_format_from_str(x.as_str()))??;
1079
1080            Some((slot, hash, archive_format))
1081        })
1082    };
1083
1084    do_parse().ok_or_else(|| {
1085        SnapshotError::ParseSnapshotArchiveFileNameError(archive_filename.to_string())
1086    })
1087}
1088
1089/// Parse an incremental snapshot archive filename into its base Slot, actual Slot, Hash, and Archive Format
1090pub fn parse_incremental_snapshot_archive_filename(
1091    archive_filename: &str,
1092) -> Result<(Slot, Slot, Hash, ArchiveFormat)> {
1093    lazy_static! {
1094        static ref RE: Regex = Regex::new(INCREMENTAL_SNAPSHOT_ARCHIVE_FILENAME_REGEX).unwrap();
1095    }
1096
1097    let do_parse = || {
1098        RE.captures(archive_filename).and_then(|captures| {
1099            let base_slot = captures
1100                .name("base")
1101                .map(|x| x.as_str().parse::<Slot>())?
1102                .ok()?;
1103            let slot = captures
1104                .name("slot")
1105                .map(|x| x.as_str().parse::<Slot>())?
1106                .ok()?;
1107            let hash = captures
1108                .name("hash")
1109                .map(|x| x.as_str().parse::<Hash>())?
1110                .ok()?;
1111            let archive_format = captures
1112                .name("ext")
1113                .map(|x| archive_format_from_str(x.as_str()))??;
1114
1115            Some((base_slot, slot, hash, archive_format))
1116        })
1117    };
1118
1119    do_parse().ok_or_else(|| {
1120        SnapshotError::ParseSnapshotArchiveFileNameError(archive_filename.to_string())
1121    })
1122}
1123
1124/// Get a list of the full snapshot archives in a directory
1125pub fn get_full_snapshot_archives<P>(snapshot_archives_dir: P) -> Vec<FullSnapshotArchiveInfo>
1126where
1127    P: AsRef<Path>,
1128{
1129    match fs::read_dir(&snapshot_archives_dir) {
1130        Err(err) => {
1131            info!(
1132                "Unable to read snapshot archives directory: err: {}, path: {}",
1133                err,
1134                snapshot_archives_dir.as_ref().display()
1135            );
1136            vec![]
1137        }
1138        Ok(files) => files
1139            .filter_map(|entry| {
1140                entry.map_or(None, |entry| {
1141                    FullSnapshotArchiveInfo::new_from_path(entry.path()).ok()
1142                })
1143            })
1144            .collect(),
1145    }
1146}
1147
1148/// Get a list of the incremental snapshot archives in a directory
1149fn get_incremental_snapshot_archives<P>(
1150    snapshot_archives_dir: P,
1151) -> Vec<IncrementalSnapshotArchiveInfo>
1152where
1153    P: AsRef<Path>,
1154{
1155    match fs::read_dir(&snapshot_archives_dir) {
1156        Err(err) => {
1157            info!(
1158                "Unable to read snapshot archives directory: err: {}, path: {}",
1159                err,
1160                snapshot_archives_dir.as_ref().display()
1161            );
1162            vec![]
1163        }
1164        Ok(files) => files
1165            .filter_map(|entry| {
1166                entry.map_or(None, |entry| {
1167                    IncrementalSnapshotArchiveInfo::new_from_path(entry.path()).ok()
1168                })
1169            })
1170            .collect(),
1171    }
1172}
1173
1174/// Get the highest slot of the full snapshot archives in a directory
1175pub fn get_highest_full_snapshot_archive_slot<P>(snapshot_archives_dir: P) -> Option<Slot>
1176where
1177    P: AsRef<Path>,
1178{
1179    get_highest_full_snapshot_archive_info(snapshot_archives_dir)
1180        .map(|full_snapshot_archive_info| full_snapshot_archive_info.slot())
1181}
1182
1183/// Get the highest slot of the incremental snapshot archives in a directory, for a given full
1184/// snapshot slot
1185pub fn get_highest_incremental_snapshot_archive_slot<P: AsRef<Path>>(
1186    snapshot_archives_dir: P,
1187    full_snapshot_slot: Slot,
1188) -> Option<Slot> {
1189    get_highest_incremental_snapshot_archive_info(snapshot_archives_dir, full_snapshot_slot)
1190        .map(|incremental_snapshot_archive_info| incremental_snapshot_archive_info.slot())
1191}
1192
1193/// Get the path (and metadata) for the full snapshot archive with the highest slot in a directory
1194pub fn get_highest_full_snapshot_archive_info<P>(
1195    snapshot_archives_dir: P,
1196) -> Option<FullSnapshotArchiveInfo>
1197where
1198    P: AsRef<Path>,
1199{
1200    let mut full_snapshot_archives = get_full_snapshot_archives(snapshot_archives_dir);
1201    full_snapshot_archives.sort_unstable();
1202    full_snapshot_archives.into_iter().rev().next()
1203}
1204
1205/// Get the path for the incremental snapshot archive with the highest slot, for a given full
1206/// snapshot slot, in a directory
1207pub fn get_highest_incremental_snapshot_archive_info<P>(
1208    snapshot_archives_dir: P,
1209    full_snapshot_slot: Slot,
1210) -> Option<IncrementalSnapshotArchiveInfo>
1211where
1212    P: AsRef<Path>,
1213{
1214    // Since we want to filter down to only the incremental snapshot archives that have the same
1215    // full snapshot slot as the value passed in, perform the filtering before sorting to avoid
1216    // doing unnecessary work.
1217    let mut incremental_snapshot_archives =
1218        get_incremental_snapshot_archives(snapshot_archives_dir)
1219            .into_iter()
1220            .filter(|incremental_snapshot_archive_info| {
1221                incremental_snapshot_archive_info.base_slot() == full_snapshot_slot
1222            })
1223            .collect::<Vec<_>>();
1224    incremental_snapshot_archives.sort_unstable();
1225    incremental_snapshot_archives.into_iter().rev().next()
1226}
1227
1228pub fn purge_old_snapshot_archives<P>(
1229    snapshot_archives_dir: P,
1230    maximum_full_snapshot_archives_to_retain: usize,
1231    maximum_incremental_snapshot_archives_to_retain: usize,
1232) where
1233    P: AsRef<Path>,
1234{
1235    info!(
1236        "Purging old snapshot archives in {}, retaining {} full snapshots and {} incremental snapshots",
1237        snapshot_archives_dir.as_ref().display(),
1238        maximum_full_snapshot_archives_to_retain,
1239        maximum_incremental_snapshot_archives_to_retain
1240    );
1241    let mut snapshot_archives = get_full_snapshot_archives(&snapshot_archives_dir);
1242    snapshot_archives.sort_unstable();
1243    snapshot_archives.reverse();
1244    // Keep the oldest snapshot so we can always play the ledger from it.
1245    snapshot_archives.pop();
1246    let max_snaps = max(1, maximum_full_snapshot_archives_to_retain);
1247    for old_archive in snapshot_archives.into_iter().skip(max_snaps) {
1248        trace!(
1249            "Purging old full snapshot archive: {}",
1250            old_archive.path().display()
1251        );
1252        fs::remove_file(old_archive.path())
1253            .unwrap_or_else(|err| info!("Failed to remove old full snapshot archive: {}", err));
1254    }
1255
1256    // Purge incremental snapshots with a different base slot than the highest full snapshot slot.
1257    // Of the incremental snapshots with the same base slot, purge the oldest ones and retain the
1258    // latest.
1259    //
1260    // First split the incremental snapshot archives into two vectors:
1261    // - One vector will be all the incremental snapshot archives with a _different_ base slot than
1262    // the highest full snapshot slot.
1263    // - The other vector will be all the incremental snapshot archives with the _same_ base slot
1264    // as the highest full snapshot slot.
1265    //
1266    // To find the incremental snapshot archives to retain, first sort the second vector (the
1267    // _same_ base slot), then reverse (so highest slots are first) and skip the first
1268    // `maximum_incremental_snapshot_archives_to_retain`.
1269    //
1270    // Purge all the rest.
1271    let highest_full_snapshot_slot = get_highest_full_snapshot_archive_slot(&snapshot_archives_dir);
1272    let mut incremental_snapshot_archives_with_same_base_slot = vec![];
1273    let mut incremental_snapshot_archives_with_different_base_slot = vec![];
1274    get_incremental_snapshot_archives(&snapshot_archives_dir)
1275        .drain(..)
1276        .for_each(|incremental_snapshot_archive| {
1277            if Some(incremental_snapshot_archive.base_slot()) == highest_full_snapshot_slot {
1278                incremental_snapshot_archives_with_same_base_slot
1279                    .push(incremental_snapshot_archive);
1280            } else {
1281                incremental_snapshot_archives_with_different_base_slot
1282                    .push(incremental_snapshot_archive);
1283            }
1284        });
1285
1286    incremental_snapshot_archives_with_same_base_slot.sort_unstable();
1287
1288    incremental_snapshot_archives_with_different_base_slot
1289        .iter()
1290        .chain(
1291            incremental_snapshot_archives_with_same_base_slot
1292                .iter()
1293                .rev()
1294                .skip(maximum_incremental_snapshot_archives_to_retain),
1295        )
1296        .for_each(|incremental_snapshot_archive| {
1297            trace!(
1298                "Purging old incremental snapshot archive: {}",
1299                incremental_snapshot_archive.path().display()
1300            );
1301            fs::remove_file(incremental_snapshot_archive.path()).unwrap_or_else(|err| {
1302                info!("Failed to remove old incremental snapshot archive: {}", err)
1303            })
1304        });
1305}
1306
1307fn unpack_snapshot_local<T: 'static + Read + std::marker::Send, F: Fn() -> T>(
1308    reader: F,
1309    ledger_dir: &Path,
1310    account_paths: &[PathBuf],
1311    parallel_archivers: usize,
1312) -> Result<UnpackedAppendVecMap> {
1313    assert!(parallel_archivers > 0);
1314    // a shared 'reader' that reads the decompressed stream once, keeps some history, and acts as a reader for multiple parallel archive readers
1315    let shared_buffer = SharedBuffer::new(reader());
1316
1317    // allocate all readers before any readers start reading
1318    let readers = (0..parallel_archivers)
1319        .into_iter()
1320        .map(|_| SharedBufferReader::new(&shared_buffer))
1321        .collect::<Vec<_>>();
1322
1323    // create 'parallel_archivers' # of parallel workers, each responsible for 1/parallel_archivers of all the files to extract.
1324    let all_unpacked_append_vec_map = readers
1325        .into_par_iter()
1326        .enumerate()
1327        .map(|(index, reader)| {
1328            let parallel_selector = Some(ParallelSelector {
1329                index,
1330                divisions: parallel_archivers,
1331            });
1332            let mut archive = Archive::new(reader);
1333            unpack_snapshot(&mut archive, ledger_dir, account_paths, parallel_selector)
1334        })
1335        .collect::<Vec<_>>();
1336    let mut unpacked_append_vec_map = UnpackedAppendVecMap::new();
1337    for h in all_unpacked_append_vec_map {
1338        unpacked_append_vec_map.extend(h?);
1339    }
1340
1341    Ok(unpacked_append_vec_map)
1342}
1343
1344fn untar_snapshot_in<P: AsRef<Path>>(
1345    snapshot_tar: P,
1346    unpack_dir: &Path,
1347    account_paths: &[PathBuf],
1348    archive_format: ArchiveFormat,
1349    parallel_divisions: usize,
1350) -> Result<UnpackedAppendVecMap> {
1351    let open_file = || File::open(&snapshot_tar).unwrap();
1352    let account_paths_map = match archive_format {
1353        ArchiveFormat::TarBzip2 => unpack_snapshot_local(
1354            || BzDecoder::new(BufReader::new(open_file())),
1355            unpack_dir,
1356            account_paths,
1357            parallel_divisions,
1358        )?,
1359        ArchiveFormat::TarGzip => unpack_snapshot_local(
1360            || GzDecoder::new(BufReader::new(open_file())),
1361            unpack_dir,
1362            account_paths,
1363            parallel_divisions,
1364        )?,
1365        ArchiveFormat::TarZstd => unpack_snapshot_local(
1366            || zstd::stream::read::Decoder::new(BufReader::new(open_file())).unwrap(),
1367            unpack_dir,
1368            account_paths,
1369            parallel_divisions,
1370        )?,
1371        ArchiveFormat::Tar => unpack_snapshot_local(
1372            || BufReader::new(open_file()),
1373            unpack_dir,
1374            account_paths,
1375            parallel_divisions,
1376        )?,
1377    };
1378    Ok(account_paths_map)
1379}
1380
1381fn verify_unpacked_snapshots_dir_and_version(
1382    unpacked_snapshots_dir_and_version: &UnpackedSnapshotsDirAndVersion,
1383) -> Result<(SnapshotVersion, BankSnapshotInfo)> {
1384    info!(
1385        "snapshot version: {}",
1386        &unpacked_snapshots_dir_and_version.snapshot_version
1387    );
1388
1389    let snapshot_version =
1390        SnapshotVersion::maybe_from_string(&unpacked_snapshots_dir_and_version.snapshot_version)
1391            .ok_or_else(|| {
1392                get_io_error(&format!(
1393                    "unsupported snapshot version: {}",
1394                    &unpacked_snapshots_dir_and_version.snapshot_version,
1395                ))
1396            })?;
1397    let mut bank_snapshot_infos =
1398        get_bank_snapshots(&unpacked_snapshots_dir_and_version.unpacked_snapshots_dir);
1399    if bank_snapshot_infos.len() > 1 {
1400        return Err(get_io_error("invalid snapshot format"));
1401    }
1402    bank_snapshot_infos.sort_unstable();
1403    let root_paths = bank_snapshot_infos
1404        .pop()
1405        .ok_or_else(|| get_io_error("No snapshots found in snapshots directory"))?;
1406    Ok((snapshot_version, root_paths))
1407}
1408
1409#[allow(clippy::too_many_arguments)]
1410fn rebuild_bank_from_snapshots(
1411    full_snapshot_unpacked_snapshots_dir_and_version: &UnpackedSnapshotsDirAndVersion,
1412    incremental_snapshot_unpacked_snapshots_dir_and_version: Option<
1413        &UnpackedSnapshotsDirAndVersion,
1414    >,
1415    frozen_account_pubkeys: &[Pubkey],
1416    account_paths: &[PathBuf],
1417    unpacked_append_vec_map: UnpackedAppendVecMap,
1418    genesis_config: &GenesisConfig,
1419    debug_keys: Option<Arc<HashSet<Pubkey>>>,
1420    additional_builtins: Option<&Builtins>,
1421    account_secondary_indexes: AccountSecondaryIndexes,
1422    accounts_db_caching_enabled: bool,
1423    limit_load_slot_count_from_snapshot: Option<usize>,
1424    shrink_ratio: AccountShrinkThreshold,
1425    verify_index: bool,
1426    accounts_db_config: Option<AccountsDbConfig>,
1427) -> Result<Bank> {
1428    let (full_snapshot_version, full_snapshot_root_paths) =
1429        verify_unpacked_snapshots_dir_and_version(
1430            full_snapshot_unpacked_snapshots_dir_and_version,
1431        )?;
1432    let (incremental_snapshot_version, incremental_snapshot_root_paths) =
1433        if let Some(snapshot_unpacked_snapshots_dir_and_version) =
1434            incremental_snapshot_unpacked_snapshots_dir_and_version
1435        {
1436            let (snapshot_version, bank_snapshot_info) = verify_unpacked_snapshots_dir_and_version(
1437                snapshot_unpacked_snapshots_dir_and_version,
1438            )?;
1439            (Some(snapshot_version), Some(bank_snapshot_info))
1440        } else {
1441            (None, None)
1442        };
1443    info!(
1444        "Loading bank from full snapshot {} and incremental snapshot {:?}",
1445        full_snapshot_root_paths.snapshot_path.display(),
1446        incremental_snapshot_root_paths
1447            .as_ref()
1448            .map(|paths| paths.snapshot_path.display()),
1449    );
1450
1451    let snapshot_root_paths = SnapshotRootPaths {
1452        full_snapshot_root_file_path: full_snapshot_root_paths.snapshot_path,
1453        incremental_snapshot_root_file_path: incremental_snapshot_root_paths
1454            .map(|root_paths| root_paths.snapshot_path),
1455    };
1456
1457    let bank = deserialize_snapshot_data_files(&snapshot_root_paths, |mut snapshot_streams| {
1458        Ok(
1459            match incremental_snapshot_version.unwrap_or(full_snapshot_version) {
1460                SnapshotVersion::V1_2_0 => bank_from_streams(
1461                    SerdeStyle::Newer,
1462                    &mut snapshot_streams,
1463                    account_paths,
1464                    unpacked_append_vec_map,
1465                    genesis_config,
1466                    frozen_account_pubkeys,
1467                    debug_keys,
1468                    additional_builtins,
1469                    account_secondary_indexes,
1470                    accounts_db_caching_enabled,
1471                    limit_load_slot_count_from_snapshot,
1472                    shrink_ratio,
1473                    verify_index,
1474                    accounts_db_config,
1475                ),
1476            }?,
1477        )
1478    })?;
1479
1480    // The status cache is rebuilt from the latest snapshot.  So, if there's an incremental
1481    // snapshot, use that.  Otherwise use the full snapshot.
1482    let status_cache_path = incremental_snapshot_unpacked_snapshots_dir_and_version
1483        .map_or_else(
1484            || {
1485                full_snapshot_unpacked_snapshots_dir_and_version
1486                    .unpacked_snapshots_dir
1487                    .as_path()
1488            },
1489            |unpacked_snapshots_dir_and_version| {
1490                unpacked_snapshots_dir_and_version
1491                    .unpacked_snapshots_dir
1492                    .as_path()
1493            },
1494        )
1495        .join(SNAPSHOT_STATUS_CACHE_FILE_NAME);
1496    let slot_deltas = deserialize_snapshot_data_file(&status_cache_path, |stream| {
1497        info!(
1498            "Rebuilding status cache from {}",
1499            status_cache_path.display()
1500        );
1501        let slot_deltas: Vec<BankSlotDelta> = bincode::options()
1502            .with_limit(MAX_SNAPSHOT_DATA_FILE_SIZE)
1503            .with_fixint_encoding()
1504            .allow_trailing_bytes()
1505            .deserialize_from(stream)?;
1506        Ok(slot_deltas)
1507    })?;
1508
1509    bank.src.append(&slot_deltas);
1510
1511    info!("Loaded bank for slot: {}", bank.slot());
1512    Ok(bank)
1513}
1514
1515fn get_snapshot_file_name(slot: Slot) -> String {
1516    slot.to_string()
1517}
1518
1519fn get_bank_snapshots_dir<P: AsRef<Path>>(path: P, slot: Slot) -> PathBuf {
1520    path.as_ref().join(slot.to_string())
1521}
1522
1523fn get_io_error(error: &str) -> SnapshotError {
1524    warn!("Snapshot Error: {:?}", error);
1525    SnapshotError::Io(IoError::new(ErrorKind::Other, error))
1526}
1527
1528pub fn verify_snapshot_archive<P, Q, R>(
1529    snapshot_archive: P,
1530    snapshots_to_verify: Q,
1531    storages_to_verify: R,
1532    archive_format: ArchiveFormat,
1533) where
1534    P: AsRef<Path>,
1535    Q: AsRef<Path>,
1536    R: AsRef<Path>,
1537{
1538    let temp_dir = tempfile::TempDir::new().unwrap();
1539    let unpack_dir = temp_dir.path();
1540    untar_snapshot_in(
1541        snapshot_archive,
1542        unpack_dir,
1543        &[unpack_dir.to_path_buf()],
1544        archive_format,
1545        1,
1546    )
1547    .unwrap();
1548
1549    // Check snapshots are the same
1550    let unpacked_snapshots = unpack_dir.join("snapshots");
1551    assert!(!dir_diff::is_different(&snapshots_to_verify, unpacked_snapshots).unwrap());
1552
1553    // Check the account entries are the same
1554    let unpacked_accounts = unpack_dir.join("accounts");
1555    assert!(!dir_diff::is_different(&storages_to_verify, unpacked_accounts).unwrap());
1556}
1557
1558/// Remove outdated bank snapshots
1559pub fn purge_old_bank_snapshots<P>(bank_snapshots_dir: P)
1560where
1561    P: AsRef<Path>,
1562{
1563    let mut bank_snapshot_infos = get_bank_snapshots(&bank_snapshots_dir);
1564    bank_snapshot_infos.sort_unstable();
1565    bank_snapshot_infos
1566        .into_iter()
1567        .rev()
1568        .skip(MAX_BANK_SNAPSHOTS_TO_RETAIN)
1569        .for_each(|bank_snapshot_info| {
1570            let r = remove_bank_snapshot(bank_snapshot_info.slot, &bank_snapshots_dir);
1571            if r.is_err() {
1572                warn!(
1573                    "Couldn't remove bank snapshot at: {}",
1574                    bank_snapshot_info.snapshot_path.display()
1575                );
1576            }
1577        })
1578}
1579
1580/// Gather the necessary elements for a snapshot of the given `root_bank`.
1581///
1582/// **DEVELOPER NOTE** Any error that is returned from this function may bring down the node!  This
1583/// function is called from AccountsBackgroundService to handle snapshot requests.  Since taking a
1584/// snapshot is not permitted to fail, any errors returned here will trigger the node to shutdown.
1585/// So, be careful whenever adding new code that may return errors.
1586pub fn snapshot_bank(
1587    root_bank: &Bank,
1588    status_cache_slot_deltas: Vec<BankSlotDelta>,
1589    accounts_package_sender: &AccountsPackageSender,
1590    bank_snapshots_dir: impl AsRef<Path>,
1591    snapshot_archives_dir: impl AsRef<Path>,
1592    snapshot_version: SnapshotVersion,
1593    archive_format: ArchiveFormat,
1594    hash_for_testing: Option<Hash>,
1595    snapshot_type: Option<SnapshotType>,
1596) -> Result<()> {
1597    let snapshot_storages = snapshot_type.map_or_else(SnapshotStorages::default, |snapshot_type| {
1598        let incremental_snapshot_base_slot = match snapshot_type {
1599            SnapshotType::IncrementalSnapshot(incremental_snapshot_base_slot) => {
1600                Some(incremental_snapshot_base_slot)
1601            }
1602            _ => None,
1603        };
1604        root_bank.get_snapshot_storages(incremental_snapshot_base_slot)
1605    });
1606    let mut add_snapshot_time = Measure::start("add-snapshot-ms");
1607    let bank_snapshot_info = add_bank_snapshot(
1608        &bank_snapshots_dir,
1609        root_bank,
1610        &snapshot_storages,
1611        snapshot_version,
1612    )?;
1613    add_snapshot_time.stop();
1614    inc_new_counter_info!("add-snapshot-ms", add_snapshot_time.as_ms() as usize);
1615
1616    let accounts_package = AccountsPackage::new(
1617        root_bank,
1618        &bank_snapshot_info,
1619        bank_snapshots_dir,
1620        status_cache_slot_deltas,
1621        snapshot_archives_dir,
1622        snapshot_storages,
1623        archive_format,
1624        snapshot_version,
1625        hash_for_testing,
1626        snapshot_type,
1627    )
1628    .expect("failed to hard link bank snapshot into a tmpdir");
1629
1630    accounts_package_sender.send(accounts_package)?;
1631
1632    Ok(())
1633}
1634
1635/// Convenience function to create a full snapshot archive out of any Bank, regardless of state.
1636/// The Bank will be frozen during the process.
1637///
1638/// Requires:
1639///     - `bank` is complete
1640pub fn bank_to_full_snapshot_archive(
1641    bank_snapshots_dir: impl AsRef<Path>,
1642    bank: &Bank,
1643    snapshot_version: Option<SnapshotVersion>,
1644    snapshot_archives_dir: impl AsRef<Path>,
1645    archive_format: ArchiveFormat,
1646    maximum_full_snapshot_archives_to_retain: usize,
1647    maximum_incremental_snapshot_archives_to_retain: usize,
1648) -> Result<FullSnapshotArchiveInfo> {
1649    let snapshot_version = snapshot_version.unwrap_or_default();
1650
1651    assert!(bank.is_complete());
1652    bank.squash(); // Bank may not be a root
1653    bank.force_flush_accounts_cache();
1654    bank.clean_accounts(true, false, Some(bank.slot()));
1655    bank.update_accounts_hash();
1656    bank.rehash(); // Bank accounts may have been manually modified by the caller
1657
1658    let temp_dir = tempfile::tempdir_in(bank_snapshots_dir)?;
1659    let snapshot_storages = bank.get_snapshot_storages(None);
1660    let bank_snapshot_info =
1661        add_bank_snapshot(&temp_dir, bank, &snapshot_storages, snapshot_version)?;
1662
1663    package_and_archive_full_snapshot(
1664        bank,
1665        &bank_snapshot_info,
1666        &temp_dir,
1667        snapshot_archives_dir,
1668        snapshot_storages,
1669        archive_format,
1670        snapshot_version,
1671        maximum_full_snapshot_archives_to_retain,
1672        maximum_incremental_snapshot_archives_to_retain,
1673    )
1674}
1675
1676/// Convenience function to create an incremental snapshot archive out of any Bank, regardless of
1677/// state.  The Bank will be frozen during the process.
1678///
1679/// Requires:
1680///     - `bank` is complete
1681///     - `bank`'s slot is greater than `full_snapshot_slot`
1682pub fn bank_to_incremental_snapshot_archive(
1683    bank_snapshots_dir: impl AsRef<Path>,
1684    bank: &Bank,
1685    full_snapshot_slot: Slot,
1686    snapshot_version: Option<SnapshotVersion>,
1687    snapshot_archives_dir: impl AsRef<Path>,
1688    archive_format: ArchiveFormat,
1689    maximum_full_snapshot_archives_to_retain: usize,
1690    maximum_incremental_snapshot_archives_to_retain: usize,
1691) -> Result<IncrementalSnapshotArchiveInfo> {
1692    let snapshot_version = snapshot_version.unwrap_or_default();
1693
1694    assert!(bank.is_complete());
1695    assert!(bank.slot() > full_snapshot_slot);
1696    bank.squash(); // Bank may not be a root
1697    bank.force_flush_accounts_cache();
1698    bank.clean_accounts(true, false, Some(full_snapshot_slot));
1699    bank.update_accounts_hash();
1700    bank.rehash(); // Bank accounts may have been manually modified by the caller
1701
1702    let temp_dir = tempfile::tempdir_in(bank_snapshots_dir)?;
1703    let snapshot_storages = bank.get_snapshot_storages(Some(full_snapshot_slot));
1704    let bank_snapshot_info =
1705        add_bank_snapshot(&temp_dir, bank, &snapshot_storages, snapshot_version)?;
1706
1707    package_and_archive_incremental_snapshot(
1708        bank,
1709        full_snapshot_slot,
1710        &bank_snapshot_info,
1711        &temp_dir,
1712        snapshot_archives_dir,
1713        snapshot_storages,
1714        archive_format,
1715        snapshot_version,
1716        maximum_full_snapshot_archives_to_retain,
1717        maximum_incremental_snapshot_archives_to_retain,
1718    )
1719}
1720
1721/// Helper function to hold shared code to package, process, and archive full snapshots
1722pub fn package_and_archive_full_snapshot(
1723    bank: &Bank,
1724    bank_snapshot_info: &BankSnapshotInfo,
1725    bank_snapshots_dir: impl AsRef<Path>,
1726    snapshot_archives_dir: impl AsRef<Path>,
1727    snapshot_storages: SnapshotStorages,
1728    archive_format: ArchiveFormat,
1729    snapshot_version: SnapshotVersion,
1730    maximum_full_snapshot_archives_to_retain: usize,
1731    maximum_incremental_snapshot_archives_to_retain: usize,
1732) -> Result<FullSnapshotArchiveInfo> {
1733    let accounts_package = AccountsPackage::new(
1734        bank,
1735        bank_snapshot_info,
1736        bank_snapshots_dir,
1737        bank.src.slot_deltas(&bank.src.roots()),
1738        snapshot_archives_dir,
1739        snapshot_storages,
1740        archive_format,
1741        snapshot_version,
1742        None,
1743        Some(SnapshotType::FullSnapshot),
1744    )?;
1745
1746    let snapshot_package = SnapshotPackage::from(accounts_package);
1747    archive_snapshot_package(
1748        &snapshot_package,
1749        maximum_full_snapshot_archives_to_retain,
1750        maximum_incremental_snapshot_archives_to_retain,
1751    )?;
1752
1753    Ok(FullSnapshotArchiveInfo::new(
1754        snapshot_package.snapshot_archive_info,
1755    ))
1756}
1757
1758/// Helper function to hold shared code to package, process, and archive incremental snapshots
1759#[allow(clippy::too_many_arguments)]
1760pub fn package_and_archive_incremental_snapshot(
1761    bank: &Bank,
1762    incremental_snapshot_base_slot: Slot,
1763    bank_snapshot_info: &BankSnapshotInfo,
1764    bank_snapshots_dir: impl AsRef<Path>,
1765    snapshot_archives_dir: impl AsRef<Path>,
1766    snapshot_storages: SnapshotStorages,
1767    archive_format: ArchiveFormat,
1768    snapshot_version: SnapshotVersion,
1769    maximum_full_snapshot_archives_to_retain: usize,
1770    maximum_incremental_snapshot_archives_to_retain: usize,
1771) -> Result<IncrementalSnapshotArchiveInfo> {
1772    let accounts_package = AccountsPackage::new(
1773        bank,
1774        bank_snapshot_info,
1775        bank_snapshots_dir,
1776        bank.src.slot_deltas(&bank.src.roots()),
1777        snapshot_archives_dir,
1778        snapshot_storages,
1779        archive_format,
1780        snapshot_version,
1781        None,
1782        Some(SnapshotType::IncrementalSnapshot(
1783            incremental_snapshot_base_slot,
1784        )),
1785    )?;
1786
1787    let snapshot_package = SnapshotPackage::from(accounts_package);
1788    archive_snapshot_package(
1789        &snapshot_package,
1790        maximum_full_snapshot_archives_to_retain,
1791        maximum_incremental_snapshot_archives_to_retain,
1792    )?;
1793
1794    Ok(IncrementalSnapshotArchiveInfo::new(
1795        incremental_snapshot_base_slot,
1796        snapshot_package.snapshot_archive_info,
1797    ))
1798}
1799
1800pub fn should_take_full_snapshot(
1801    block_height: Slot,
1802    full_snapshot_archive_interval_slots: Slot,
1803) -> bool {
1804    block_height % full_snapshot_archive_interval_slots == 0
1805}
1806
1807pub fn should_take_incremental_snapshot(
1808    block_height: Slot,
1809    incremental_snapshot_archive_interval_slots: Slot,
1810    last_full_snapshot_slot: Option<Slot>,
1811) -> bool {
1812    block_height % incremental_snapshot_archive_interval_slots == 0
1813        && last_full_snapshot_slot.is_some()
1814}
1815
1816#[cfg(test)]
1817mod tests {
1818    use super::*;
1819    use crate::accounts_db::ACCOUNTS_DB_CONFIG_FOR_TESTING;
1820    use assert_matches::assert_matches;
1821    use bincode::{deserialize_from, serialize_into};
1822    use gemachain_sdk::{
1823        genesis_config::create_genesis_config,
1824        signature::{Keypair, Signer},
1825        system_transaction,
1826        transaction::SanitizedTransaction,
1827    };
1828    use std::{convert::TryFrom, mem::size_of};
1829
1830    #[test]
1831    fn test_serialize_snapshot_data_file_under_limit() {
1832        let temp_dir = tempfile::TempDir::new().unwrap();
1833        let expected_consumed_size = size_of::<u32>() as u64;
1834        let consumed_size = serialize_snapshot_data_file_capped(
1835            &temp_dir.path().join("data-file"),
1836            expected_consumed_size,
1837            |stream| {
1838                serialize_into(stream, &2323_u32)?;
1839                Ok(())
1840            },
1841        )
1842        .unwrap();
1843        assert_eq!(consumed_size, expected_consumed_size);
1844    }
1845
1846    #[test]
1847    fn test_serialize_snapshot_data_file_over_limit() {
1848        let temp_dir = tempfile::TempDir::new().unwrap();
1849        let expected_consumed_size = size_of::<u32>() as u64;
1850        let result = serialize_snapshot_data_file_capped(
1851            &temp_dir.path().join("data-file"),
1852            expected_consumed_size - 1,
1853            |stream| {
1854                serialize_into(stream, &2323_u32)?;
1855                Ok(())
1856            },
1857        );
1858        assert_matches!(result, Err(SnapshotError::Io(ref message)) if message.to_string().starts_with("too large snapshot data file to serialize"));
1859    }
1860
1861    #[test]
1862    fn test_deserialize_snapshot_data_file_under_limit() {
1863        let expected_data = 2323_u32;
1864        let expected_consumed_size = size_of::<u32>() as u64;
1865
1866        let temp_dir = tempfile::TempDir::new().unwrap();
1867        serialize_snapshot_data_file_capped(
1868            &temp_dir.path().join("data-file"),
1869            expected_consumed_size,
1870            |stream| {
1871                serialize_into(stream, &expected_data)?;
1872                Ok(())
1873            },
1874        )
1875        .unwrap();
1876
1877        let snapshot_root_paths = SnapshotRootPaths {
1878            full_snapshot_root_file_path: temp_dir.path().join("data-file"),
1879            incremental_snapshot_root_file_path: None,
1880        };
1881
1882        let actual_data = deserialize_snapshot_data_files_capped(
1883            &snapshot_root_paths,
1884            expected_consumed_size,
1885            |stream| {
1886                Ok(deserialize_from::<_, u32>(
1887                    &mut stream.full_snapshot_stream,
1888                )?)
1889            },
1890        )
1891        .unwrap();
1892        assert_eq!(actual_data, expected_data);
1893    }
1894
1895    #[test]
1896    fn test_deserialize_snapshot_data_file_over_limit() {
1897        let expected_data = 2323_u32;
1898        let expected_consumed_size = size_of::<u32>() as u64;
1899
1900        let temp_dir = tempfile::TempDir::new().unwrap();
1901        serialize_snapshot_data_file_capped(
1902            &temp_dir.path().join("data-file"),
1903            expected_consumed_size,
1904            |stream| {
1905                serialize_into(stream, &expected_data)?;
1906                Ok(())
1907            },
1908        )
1909        .unwrap();
1910
1911        let snapshot_root_paths = SnapshotRootPaths {
1912            full_snapshot_root_file_path: temp_dir.path().join("data-file"),
1913            incremental_snapshot_root_file_path: None,
1914        };
1915
1916        let result = deserialize_snapshot_data_files_capped(
1917            &snapshot_root_paths,
1918            expected_consumed_size - 1,
1919            |stream| {
1920                Ok(deserialize_from::<_, u32>(
1921                    &mut stream.full_snapshot_stream,
1922                )?)
1923            },
1924        );
1925        assert_matches!(result, Err(SnapshotError::Io(ref message)) if message.to_string().starts_with("too large snapshot data file to deserialize"));
1926    }
1927
1928    #[test]
1929    fn test_deserialize_snapshot_data_file_extra_data() {
1930        let expected_data = 2323_u32;
1931        let expected_consumed_size = size_of::<u32>() as u64;
1932
1933        let temp_dir = tempfile::TempDir::new().unwrap();
1934        serialize_snapshot_data_file_capped(
1935            &temp_dir.path().join("data-file"),
1936            expected_consumed_size * 2,
1937            |stream| {
1938                serialize_into(stream.by_ref(), &expected_data)?;
1939                serialize_into(stream.by_ref(), &expected_data)?;
1940                Ok(())
1941            },
1942        )
1943        .unwrap();
1944
1945        let snapshot_root_paths = SnapshotRootPaths {
1946            full_snapshot_root_file_path: temp_dir.path().join("data-file"),
1947            incremental_snapshot_root_file_path: None,
1948        };
1949
1950        let result = deserialize_snapshot_data_files_capped(
1951            &snapshot_root_paths,
1952            expected_consumed_size * 2,
1953            |stream| {
1954                Ok(deserialize_from::<_, u32>(
1955                    &mut stream.full_snapshot_stream,
1956                )?)
1957            },
1958        );
1959        assert_matches!(result, Err(SnapshotError::Io(ref message)) if message.to_string().starts_with("invalid snapshot data file"));
1960    }
1961
1962    #[test]
1963    fn test_parse_full_snapshot_archive_filename() {
1964        assert_eq!(
1965            parse_full_snapshot_archive_filename(&format!(
1966                "snapshot-42-{}.tar.bz2",
1967                Hash::default()
1968            ))
1969            .unwrap(),
1970            (42, Hash::default(), ArchiveFormat::TarBzip2)
1971        );
1972        assert_eq!(
1973            parse_full_snapshot_archive_filename(&format!(
1974                "snapshot-43-{}.tar.zst",
1975                Hash::default()
1976            ))
1977            .unwrap(),
1978            (43, Hash::default(), ArchiveFormat::TarZstd)
1979        );
1980        assert_eq!(
1981            parse_full_snapshot_archive_filename(&format!("snapshot-44-{}.tar", Hash::default()))
1982                .unwrap(),
1983            (44, Hash::default(), ArchiveFormat::Tar)
1984        );
1985
1986        assert!(parse_full_snapshot_archive_filename("invalid").is_err());
1987        assert!(
1988            parse_full_snapshot_archive_filename("snapshot-bad!slot-bad!hash.bad!ext").is_err()
1989        );
1990
1991        assert!(
1992            parse_full_snapshot_archive_filename("snapshot-12345678-bad!hash.bad!ext").is_err()
1993        );
1994        assert!(parse_full_snapshot_archive_filename(&format!(
1995            "snapshot-12345678-{}.bad!ext",
1996            Hash::new_unique()
1997        ))
1998        .is_err());
1999        assert!(parse_full_snapshot_archive_filename("snapshot-12345678-bad!hash.tar").is_err());
2000
2001        assert!(parse_full_snapshot_archive_filename(&format!(
2002            "snapshot-bad!slot-{}.bad!ext",
2003            Hash::new_unique()
2004        ))
2005        .is_err());
2006        assert!(parse_full_snapshot_archive_filename(&format!(
2007            "snapshot-12345678-{}.bad!ext",
2008            Hash::new_unique()
2009        ))
2010        .is_err());
2011        assert!(parse_full_snapshot_archive_filename(&format!(
2012            "snapshot-bad!slot-{}.tar",
2013            Hash::new_unique()
2014        ))
2015        .is_err());
2016
2017        assert!(parse_full_snapshot_archive_filename("snapshot-bad!slot-bad!hash.tar").is_err());
2018        assert!(parse_full_snapshot_archive_filename("snapshot-12345678-bad!hash.tar").is_err());
2019        assert!(parse_full_snapshot_archive_filename(&format!(
2020            "snapshot-bad!slot-{}.tar",
2021            Hash::new_unique()
2022        ))
2023        .is_err());
2024    }
2025
2026    #[test]
2027    fn test_parse_incremental_snapshot_archive_filename() {
2028        gemachain_logger::setup();
2029        assert_eq!(
2030            parse_incremental_snapshot_archive_filename(&format!(
2031                "incremental-snapshot-42-123-{}.tar.bz2",
2032                Hash::default()
2033            ))
2034            .unwrap(),
2035            (42, 123, Hash::default(), ArchiveFormat::TarBzip2)
2036        );
2037        assert_eq!(
2038            parse_incremental_snapshot_archive_filename(&format!(
2039                "incremental-snapshot-43-234-{}.tar.zst",
2040                Hash::default()
2041            ))
2042            .unwrap(),
2043            (43, 234, Hash::default(), ArchiveFormat::TarZstd)
2044        );
2045        assert_eq!(
2046            parse_incremental_snapshot_archive_filename(&format!(
2047                "incremental-snapshot-44-345-{}.tar",
2048                Hash::default()
2049            ))
2050            .unwrap(),
2051            (44, 345, Hash::default(), ArchiveFormat::Tar)
2052        );
2053
2054        assert!(parse_incremental_snapshot_archive_filename("invalid").is_err());
2055        assert!(parse_incremental_snapshot_archive_filename(&format!(
2056            "snapshot-42-{}.tar",
2057            Hash::new_unique()
2058        ))
2059        .is_err());
2060        assert!(parse_incremental_snapshot_archive_filename(
2061            "incremental-snapshot-bad!slot-bad!slot-bad!hash.bad!ext"
2062        )
2063        .is_err());
2064
2065        assert!(parse_incremental_snapshot_archive_filename(&format!(
2066            "incremental-snapshot-bad!slot-56785678-{}.tar",
2067            Hash::new_unique()
2068        ))
2069        .is_err());
2070
2071        assert!(parse_incremental_snapshot_archive_filename(&format!(
2072            "incremental-snapshot-12345678-bad!slot-{}.tar",
2073            Hash::new_unique()
2074        ))
2075        .is_err());
2076
2077        assert!(parse_incremental_snapshot_archive_filename(
2078            "incremental-snapshot-12341234-56785678-bad!HASH.tar"
2079        )
2080        .is_err());
2081
2082        assert!(parse_incremental_snapshot_archive_filename(&format!(
2083            "incremental-snapshot-12341234-56785678-{}.bad!ext",
2084            Hash::new_unique()
2085        ))
2086        .is_err());
2087    }
2088
2089    #[test]
2090    fn test_check_are_snapshots_compatible() {
2091        gemachain_logger::setup();
2092        let slot1: Slot = 1234;
2093        let slot2: Slot = 5678;
2094        let slot3: Slot = 999_999;
2095
2096        let full_snapshot_archive_info = FullSnapshotArchiveInfo::new_from_path(PathBuf::from(
2097            format!("/dir/snapshot-{}-{}.tar", slot1, Hash::new_unique()),
2098        ))
2099        .unwrap();
2100
2101        assert!(check_are_snapshots_compatible(&full_snapshot_archive_info, None,).is_ok());
2102
2103        let incremental_snapshot_archive_info =
2104            IncrementalSnapshotArchiveInfo::new_from_path(PathBuf::from(format!(
2105                "/dir/incremental-snapshot-{}-{}-{}.tar",
2106                slot1,
2107                slot2,
2108                Hash::new_unique()
2109            )))
2110            .unwrap();
2111
2112        assert!(check_are_snapshots_compatible(
2113            &full_snapshot_archive_info,
2114            Some(&incremental_snapshot_archive_info)
2115        )
2116        .is_ok());
2117
2118        let incremental_snapshot_archive_info =
2119            IncrementalSnapshotArchiveInfo::new_from_path(PathBuf::from(format!(
2120                "/dir/incremental-snapshot-{}-{}-{}.tar",
2121                slot2,
2122                slot3,
2123                Hash::new_unique()
2124            )))
2125            .unwrap();
2126
2127        assert!(check_are_snapshots_compatible(
2128            &full_snapshot_archive_info,
2129            Some(&incremental_snapshot_archive_info)
2130        )
2131        .is_err());
2132    }
2133
2134    /// A test heler function that creates bank snapshot files
2135    fn common_create_bank_snapshot_files(
2136        bank_snapshots_dir: &Path,
2137        min_slot: Slot,
2138        max_slot: Slot,
2139    ) {
2140        for slot in min_slot..max_slot {
2141            let snapshot_dir = get_bank_snapshots_dir(bank_snapshots_dir, slot);
2142            fs::create_dir_all(&snapshot_dir).unwrap();
2143
2144            let snapshot_filename = get_snapshot_file_name(slot);
2145            let snapshot_path = snapshot_dir.join(snapshot_filename);
2146            File::create(snapshot_path).unwrap();
2147        }
2148    }
2149
2150    #[test]
2151    fn test_get_bank_snapshot_infos() {
2152        gemachain_logger::setup();
2153        let temp_snapshots_dir = tempfile::TempDir::new().unwrap();
2154        let min_slot = 10;
2155        let max_slot = 20;
2156        common_create_bank_snapshot_files(temp_snapshots_dir.path(), min_slot, max_slot);
2157
2158        let bank_snapshot_infos = get_bank_snapshots(temp_snapshots_dir.path());
2159        assert_eq!(bank_snapshot_infos.len() as Slot, max_slot - min_slot);
2160    }
2161
2162    #[test]
2163    fn test_get_highest_bank_snapshot_info() {
2164        gemachain_logger::setup();
2165        let temp_snapshots_dir = tempfile::TempDir::new().unwrap();
2166        let min_slot = 99;
2167        let max_slot = 123;
2168        common_create_bank_snapshot_files(temp_snapshots_dir.path(), min_slot, max_slot);
2169
2170        let highest_bank_snapshot_info = get_highest_bank_snapshot_info(temp_snapshots_dir.path());
2171        assert!(highest_bank_snapshot_info.is_some());
2172        assert_eq!(highest_bank_snapshot_info.unwrap().slot, max_slot - 1);
2173    }
2174
2175    /// A test helper function that creates full and incremental snapshot archive files.  Creates
2176    /// full snapshot files in the range (`min_full_snapshot_slot`, `max_full_snapshot_slot`], and
2177    /// incremental snapshot files in the range (`min_incremental_snapshot_slot`,
2178    /// `max_incremental_snapshot_slot`].  Additionally, "bad" files are created for both full and
2179    /// incremental snapshots to ensure the tests properly filter them out.
2180    fn common_create_snapshot_archive_files(
2181        snapshot_archives_dir: &Path,
2182        min_full_snapshot_slot: Slot,
2183        max_full_snapshot_slot: Slot,
2184        min_incremental_snapshot_slot: Slot,
2185        max_incremental_snapshot_slot: Slot,
2186    ) {
2187        for full_snapshot_slot in min_full_snapshot_slot..max_full_snapshot_slot {
2188            for incremental_snapshot_slot in
2189                min_incremental_snapshot_slot..max_incremental_snapshot_slot
2190            {
2191                let snapshot_filename = format!(
2192                    "incremental-snapshot-{}-{}-{}.tar",
2193                    full_snapshot_slot,
2194                    incremental_snapshot_slot,
2195                    Hash::default()
2196                );
2197                let snapshot_filepath = snapshot_archives_dir.join(snapshot_filename);
2198                File::create(snapshot_filepath).unwrap();
2199            }
2200
2201            let snapshot_filename =
2202                format!("snapshot-{}-{}.tar", full_snapshot_slot, Hash::default());
2203            let snapshot_filepath = snapshot_archives_dir.join(snapshot_filename);
2204            File::create(snapshot_filepath).unwrap();
2205
2206            // Add in an incremental snapshot with a bad filename and high slot to ensure filename are filtered and sorted correctly
2207            let bad_filename = format!(
2208                "incremental-snapshot-{}-{}-bad!hash.tar",
2209                full_snapshot_slot,
2210                max_incremental_snapshot_slot + 1,
2211            );
2212            let bad_filepath = snapshot_archives_dir.join(bad_filename);
2213            File::create(bad_filepath).unwrap();
2214        }
2215
2216        // Add in a snapshot with a bad filename and high slot to ensure filename are filtered and
2217        // sorted correctly
2218        let bad_filename = format!("snapshot-{}-bad!hash.tar", max_full_snapshot_slot + 1);
2219        let bad_filepath = snapshot_archives_dir.join(bad_filename);
2220        File::create(bad_filepath).unwrap();
2221    }
2222
2223    #[test]
2224    fn test_get_full_snapshot_archives() {
2225        gemachain_logger::setup();
2226        let temp_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2227        let min_slot = 123;
2228        let max_slot = 456;
2229        common_create_snapshot_archive_files(
2230            temp_snapshot_archives_dir.path(),
2231            min_slot,
2232            max_slot,
2233            0,
2234            0,
2235        );
2236
2237        let snapshot_archives = get_full_snapshot_archives(temp_snapshot_archives_dir);
2238        assert_eq!(snapshot_archives.len() as Slot, max_slot - min_slot);
2239    }
2240
2241    #[test]
2242    fn test_get_incremental_snapshot_archives() {
2243        gemachain_logger::setup();
2244        let temp_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2245        let min_full_snapshot_slot = 12;
2246        let max_full_snapshot_slot = 23;
2247        let min_incremental_snapshot_slot = 34;
2248        let max_incremental_snapshot_slot = 45;
2249        common_create_snapshot_archive_files(
2250            temp_snapshot_archives_dir.path(),
2251            min_full_snapshot_slot,
2252            max_full_snapshot_slot,
2253            min_incremental_snapshot_slot,
2254            max_incremental_snapshot_slot,
2255        );
2256
2257        let incremental_snapshot_archives =
2258            get_incremental_snapshot_archives(temp_snapshot_archives_dir);
2259        assert_eq!(
2260            incremental_snapshot_archives.len() as Slot,
2261            (max_full_snapshot_slot - min_full_snapshot_slot)
2262                * (max_incremental_snapshot_slot - min_incremental_snapshot_slot)
2263        );
2264    }
2265
2266    #[test]
2267    fn test_get_highest_full_snapshot_archive_slot() {
2268        gemachain_logger::setup();
2269        let temp_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2270        let min_slot = 123;
2271        let max_slot = 456;
2272        common_create_snapshot_archive_files(
2273            temp_snapshot_archives_dir.path(),
2274            min_slot,
2275            max_slot,
2276            0,
2277            0,
2278        );
2279
2280        assert_eq!(
2281            get_highest_full_snapshot_archive_slot(temp_snapshot_archives_dir.path()),
2282            Some(max_slot - 1)
2283        );
2284    }
2285
2286    #[test]
2287    fn test_get_highest_incremental_snapshot_slot() {
2288        gemachain_logger::setup();
2289        let temp_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2290        let min_full_snapshot_slot = 12;
2291        let max_full_snapshot_slot = 23;
2292        let min_incremental_snapshot_slot = 34;
2293        let max_incremental_snapshot_slot = 45;
2294        common_create_snapshot_archive_files(
2295            temp_snapshot_archives_dir.path(),
2296            min_full_snapshot_slot,
2297            max_full_snapshot_slot,
2298            min_incremental_snapshot_slot,
2299            max_incremental_snapshot_slot,
2300        );
2301
2302        for full_snapshot_slot in min_full_snapshot_slot..max_full_snapshot_slot {
2303            assert_eq!(
2304                get_highest_incremental_snapshot_archive_slot(
2305                    temp_snapshot_archives_dir.path(),
2306                    full_snapshot_slot
2307                ),
2308                Some(max_incremental_snapshot_slot - 1)
2309            );
2310        }
2311
2312        assert_eq!(
2313            get_highest_incremental_snapshot_archive_slot(
2314                temp_snapshot_archives_dir.path(),
2315                max_full_snapshot_slot
2316            ),
2317            None
2318        );
2319    }
2320
2321    fn common_test_purge_old_snapshot_archives(
2322        snapshot_names: &[&String],
2323        maximum_full_snapshot_archives_to_retain: usize,
2324        maximum_incremental_snapshot_archives_to_retain: usize,
2325        expected_snapshots: &[&String],
2326    ) {
2327        let temp_snap_dir = tempfile::TempDir::new().unwrap();
2328
2329        for snap_name in snapshot_names {
2330            let snap_path = temp_snap_dir.path().join(&snap_name);
2331            let mut _snap_file = File::create(snap_path);
2332        }
2333        purge_old_snapshot_archives(
2334            temp_snap_dir.path(),
2335            maximum_full_snapshot_archives_to_retain,
2336            maximum_incremental_snapshot_archives_to_retain,
2337        );
2338
2339        let mut retained_snaps = HashSet::new();
2340        for entry in fs::read_dir(temp_snap_dir.path()).unwrap() {
2341            let entry_path_buf = entry.unwrap().path();
2342            let entry_path = entry_path_buf.as_path();
2343            let snapshot_name = entry_path
2344                .file_name()
2345                .unwrap()
2346                .to_str()
2347                .unwrap()
2348                .to_string();
2349            retained_snaps.insert(snapshot_name);
2350        }
2351
2352        for snap_name in expected_snapshots {
2353            assert!(retained_snaps.contains(snap_name.as_str()));
2354        }
2355        assert!(retained_snaps.len() == expected_snapshots.len());
2356    }
2357
2358    #[test]
2359    fn test_purge_old_full_snapshot_archives() {
2360        // Create 3 snapshots, retaining 1,
2361        // expecting the oldest 1 and the newest 1 are retained
2362        let snap1_name = format!("snapshot-1-{}.tar.zst", Hash::default());
2363        let snap2_name = format!("snapshot-3-{}.tar.zst", Hash::default());
2364        let snap3_name = format!("snapshot-50-{}.tar.zst", Hash::default());
2365        let snapshot_names = vec![&snap1_name, &snap2_name, &snap3_name];
2366        let expected_snapshots = vec![&snap1_name, &snap3_name];
2367        common_test_purge_old_snapshot_archives(
2368            &snapshot_names,
2369            1,
2370            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2371            &expected_snapshots,
2372        );
2373
2374        // retaining 0, the expectation is the same as for 1, as at least 1 newest is expected to be retained
2375        common_test_purge_old_snapshot_archives(
2376            &snapshot_names,
2377            0,
2378            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2379            &expected_snapshots,
2380        );
2381
2382        // retaining 2, all three should be retained
2383        let expected_snapshots = vec![&snap1_name, &snap2_name, &snap3_name];
2384        common_test_purge_old_snapshot_archives(
2385            &snapshot_names,
2386            2,
2387            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2388            &expected_snapshots,
2389        );
2390    }
2391
2392    /// Mimic a running node's behavior w.r.t. purging old snapshot archives.  Take snapshots in a
2393    /// loop, and periodically purge old snapshot archives.  After purging, check to make sure the
2394    /// snapshot archives on disk are correct.
2395    #[test]
2396    fn test_purge_old_full_snapshot_archives_in_the_loop() {
2397        let snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2398        let maximum_snapshots_to_retain = 5;
2399        let starting_slot: Slot = 42;
2400
2401        for slot in (starting_slot..).take(100) {
2402            let full_snapshot_archive_file_name =
2403                format!("snapshot-{}-{}.tar", slot, Hash::default());
2404            let full_snapshot_archive_path = snapshot_archives_dir
2405                .as_ref()
2406                .join(full_snapshot_archive_file_name);
2407            File::create(full_snapshot_archive_path).unwrap();
2408
2409            // don't purge-and-check until enough snapshot archives have been created
2410            if slot < starting_slot + maximum_snapshots_to_retain as Slot {
2411                continue;
2412            }
2413
2414            // purge infrequently, so there will always be snapshot archives to purge
2415            if slot % (maximum_snapshots_to_retain as Slot * 2) != 0 {
2416                continue;
2417            }
2418
2419            purge_old_snapshot_archives(
2420                &snapshot_archives_dir,
2421                maximum_snapshots_to_retain,
2422                usize::MAX,
2423            );
2424            let mut full_snapshot_archives = get_full_snapshot_archives(&snapshot_archives_dir);
2425            full_snapshot_archives.sort_unstable();
2426            assert_eq!(
2427                full_snapshot_archives.len(),
2428                maximum_snapshots_to_retain + 1
2429            );
2430            assert_eq!(
2431                full_snapshot_archives.first().unwrap().slot(),
2432                starting_slot
2433            );
2434            assert_eq!(full_snapshot_archives.last().unwrap().slot(), slot);
2435            for (i, full_snapshot_archive) in
2436                full_snapshot_archives.iter().skip(1).rev().enumerate()
2437            {
2438                assert_eq!(full_snapshot_archive.slot(), slot - i as Slot);
2439            }
2440        }
2441    }
2442
2443    #[test]
2444    fn test_purge_old_incremental_snapshot_archives() {
2445        let snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2446        let starting_slot = 100_000;
2447
2448        let maximum_incremental_snapshot_archives_to_retain =
2449            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN;
2450        let maximum_full_snapshot_archives_to_retain = DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN;
2451
2452        let incremental_snapshot_interval = 100;
2453        let num_incremental_snapshots_per_full_snapshot =
2454            maximum_incremental_snapshot_archives_to_retain * 2;
2455        let full_snapshot_interval =
2456            incremental_snapshot_interval * num_incremental_snapshots_per_full_snapshot;
2457
2458        let mut snapshot_filenames = vec![];
2459        (starting_slot..)
2460            .step_by(full_snapshot_interval)
2461            .take(maximum_full_snapshot_archives_to_retain * 2)
2462            .for_each(|full_snapshot_slot| {
2463                let snapshot_filename =
2464                    format!("snapshot-{}-{}.tar", full_snapshot_slot, Hash::default());
2465                let snapshot_path = snapshot_archives_dir.path().join(&snapshot_filename);
2466                File::create(snapshot_path).unwrap();
2467                snapshot_filenames.push(snapshot_filename);
2468
2469                (full_snapshot_slot..)
2470                    .step_by(incremental_snapshot_interval)
2471                    .take(num_incremental_snapshots_per_full_snapshot)
2472                    .skip(1)
2473                    .for_each(|incremental_snapshot_slot| {
2474                        let snapshot_filename = format!(
2475                            "incremental-snapshot-{}-{}-{}.tar",
2476                            full_snapshot_slot,
2477                            incremental_snapshot_slot,
2478                            Hash::default()
2479                        );
2480                        let snapshot_path = snapshot_archives_dir.path().join(&snapshot_filename);
2481                        File::create(snapshot_path).unwrap();
2482                        snapshot_filenames.push(snapshot_filename);
2483                    });
2484            });
2485
2486        purge_old_snapshot_archives(
2487            snapshot_archives_dir.path(),
2488            maximum_full_snapshot_archives_to_retain,
2489            maximum_incremental_snapshot_archives_to_retain,
2490        );
2491
2492        // Ensure correct number of full snapshot archives are purged/retained
2493        // NOTE: One extra full snapshot is always kept (the oldest), hence the `+1`
2494        let mut remaining_full_snapshot_archives =
2495            get_full_snapshot_archives(snapshot_archives_dir.path());
2496        assert_eq!(
2497            remaining_full_snapshot_archives.len(),
2498            maximum_full_snapshot_archives_to_retain + 1,
2499        );
2500        remaining_full_snapshot_archives.sort_unstable();
2501
2502        // Ensure correct number of incremental snapshot archives are purged/retained
2503        let mut remaining_incremental_snapshot_archives =
2504            get_incremental_snapshot_archives(snapshot_archives_dir.path());
2505        assert_eq!(
2506            remaining_incremental_snapshot_archives.len(),
2507            maximum_incremental_snapshot_archives_to_retain
2508        );
2509        remaining_incremental_snapshot_archives.sort_unstable();
2510
2511        // Ensure all remaining incremental snapshots are only for the latest full snapshot
2512        let latest_full_snapshot_archive_slot =
2513            remaining_full_snapshot_archives.last().unwrap().slot();
2514        for incremental_snapshot_archive in &remaining_incremental_snapshot_archives {
2515            assert_eq!(
2516                incremental_snapshot_archive.base_slot(),
2517                latest_full_snapshot_archive_slot
2518            );
2519        }
2520
2521        // Ensure the remaining incremental snapshots are at the right slot
2522        let expected_remaing_incremental_snapshot_archive_slots =
2523            (latest_full_snapshot_archive_slot..)
2524                .step_by(incremental_snapshot_interval)
2525                .take(num_incremental_snapshots_per_full_snapshot)
2526                .skip(
2527                    num_incremental_snapshots_per_full_snapshot
2528                        - maximum_incremental_snapshot_archives_to_retain,
2529                )
2530                .collect::<Vec<_>>();
2531
2532        let actual_remaining_incremental_snapshot_archive_slots =
2533            remaining_incremental_snapshot_archives
2534                .iter()
2535                .map(|snapshot| snapshot.slot())
2536                .collect::<Vec<_>>();
2537        assert_eq!(
2538            actual_remaining_incremental_snapshot_archive_slots,
2539            expected_remaing_incremental_snapshot_archive_slots
2540        );
2541    }
2542
2543    #[test]
2544    fn test_purge_all_incremental_snapshot_archives_when_no_full_snapshot_archives() {
2545        let snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2546
2547        for snapshot_filenames in [
2548            format!("incremental-snapshot-100-120-{}.tar", Hash::default()),
2549            format!("incremental-snapshot-100-140-{}.tar", Hash::default()),
2550            format!("incremental-snapshot-100-160-{}.tar", Hash::default()),
2551            format!("incremental-snapshot-100-180-{}.tar", Hash::default()),
2552            format!("incremental-snapshot-200-220-{}.tar", Hash::default()),
2553            format!("incremental-snapshot-200-240-{}.tar", Hash::default()),
2554            format!("incremental-snapshot-200-260-{}.tar", Hash::default()),
2555            format!("incremental-snapshot-200-280-{}.tar", Hash::default()),
2556        ] {
2557            let snapshot_path = snapshot_archives_dir.path().join(&snapshot_filenames);
2558            File::create(snapshot_path).unwrap();
2559        }
2560
2561        purge_old_snapshot_archives(snapshot_archives_dir.path(), usize::MAX, usize::MAX);
2562
2563        let remaining_incremental_snapshot_archives =
2564            get_incremental_snapshot_archives(snapshot_archives_dir.path());
2565        assert!(remaining_incremental_snapshot_archives.is_empty());
2566    }
2567
2568    /// Test roundtrip of bank to a full snapshot, then back again.  This test creates the simplest
2569    /// bank possible, so the contents of the snapshot archive will be quite minimal.
2570    #[test]
2571    fn test_roundtrip_bank_to_and_from_full_snapshot_simple() {
2572        gemachain_logger::setup();
2573        let genesis_config = GenesisConfig::default();
2574        let original_bank = Bank::new_for_tests(&genesis_config);
2575
2576        while !original_bank.is_complete() {
2577            original_bank.register_tick(&Hash::new_unique());
2578        }
2579
2580        let accounts_dir = tempfile::TempDir::new().unwrap();
2581        let bank_snapshots_dir = tempfile::TempDir::new().unwrap();
2582        let snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2583        let snapshot_archive_format = ArchiveFormat::Tar;
2584
2585        let snapshot_archive_info = bank_to_full_snapshot_archive(
2586            &bank_snapshots_dir,
2587            &original_bank,
2588            None,
2589            snapshot_archives_dir.path(),
2590            snapshot_archive_format,
2591            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2592            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2593        )
2594        .unwrap();
2595
2596        let (roundtrip_bank, _) = bank_from_snapshot_archives(
2597            &[PathBuf::from(accounts_dir.path())],
2598            &[],
2599            bank_snapshots_dir.path(),
2600            &snapshot_archive_info,
2601            None,
2602            &genesis_config,
2603            None,
2604            None,
2605            AccountSecondaryIndexes::default(),
2606            false,
2607            None,
2608            AccountShrinkThreshold::default(),
2609            false,
2610            false,
2611            false,
2612            Some(ACCOUNTS_DB_CONFIG_FOR_TESTING),
2613        )
2614        .unwrap();
2615
2616        assert_eq!(original_bank, roundtrip_bank);
2617    }
2618
2619    /// Test roundtrip of bank to a full snapshot, then back again.  This test is more involved
2620    /// than the simple version above; creating multiple banks over multiple slots and doing
2621    /// multiple transfers.  So this full snapshot should contain more data.
2622    #[test]
2623    fn test_roundtrip_bank_to_and_from_snapshot_complex() {
2624        gemachain_logger::setup();
2625        let collector = Pubkey::new_unique();
2626        let key1 = Keypair::new();
2627        let key2 = Keypair::new();
2628        let key3 = Keypair::new();
2629        let key4 = Keypair::new();
2630        let key5 = Keypair::new();
2631
2632        let (genesis_config, mint_keypair) = create_genesis_config(1_000_000);
2633        let bank0 = Arc::new(Bank::new_for_tests(&genesis_config));
2634        bank0.transfer(1, &mint_keypair, &key1.pubkey()).unwrap();
2635        bank0.transfer(2, &mint_keypair, &key2.pubkey()).unwrap();
2636        bank0.transfer(3, &mint_keypair, &key3.pubkey()).unwrap();
2637        while !bank0.is_complete() {
2638            bank0.register_tick(&Hash::new_unique());
2639        }
2640
2641        let slot = 1;
2642        let bank1 = Arc::new(Bank::new_from_parent(&bank0, &collector, slot));
2643        bank1.transfer(3, &mint_keypair, &key3.pubkey()).unwrap();
2644        bank1.transfer(4, &mint_keypair, &key4.pubkey()).unwrap();
2645        bank1.transfer(5, &mint_keypair, &key5.pubkey()).unwrap();
2646        while !bank1.is_complete() {
2647            bank1.register_tick(&Hash::new_unique());
2648        }
2649
2650        let slot = slot + 1;
2651        let bank2 = Arc::new(Bank::new_from_parent(&bank1, &collector, slot));
2652        bank2.transfer(1, &mint_keypair, &key1.pubkey()).unwrap();
2653        while !bank2.is_complete() {
2654            bank2.register_tick(&Hash::new_unique());
2655        }
2656
2657        let slot = slot + 1;
2658        let bank3 = Arc::new(Bank::new_from_parent(&bank2, &collector, slot));
2659        bank3.transfer(1, &mint_keypair, &key1.pubkey()).unwrap();
2660        while !bank3.is_complete() {
2661            bank3.register_tick(&Hash::new_unique());
2662        }
2663
2664        let slot = slot + 1;
2665        let bank4 = Arc::new(Bank::new_from_parent(&bank3, &collector, slot));
2666        bank4.transfer(1, &mint_keypair, &key1.pubkey()).unwrap();
2667        while !bank4.is_complete() {
2668            bank4.register_tick(&Hash::new_unique());
2669        }
2670
2671        let accounts_dir = tempfile::TempDir::new().unwrap();
2672        let bank_snapshots_dir = tempfile::TempDir::new().unwrap();
2673        let snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2674        let snapshot_archive_format = ArchiveFormat::TarGzip;
2675
2676        let full_snapshot_archive_info = bank_to_full_snapshot_archive(
2677            bank_snapshots_dir.path(),
2678            &bank4,
2679            None,
2680            snapshot_archives_dir.path(),
2681            snapshot_archive_format,
2682            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2683            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2684        )
2685        .unwrap();
2686
2687        let (roundtrip_bank, _) = bank_from_snapshot_archives(
2688            &[PathBuf::from(accounts_dir.path())],
2689            &[],
2690            bank_snapshots_dir.path(),
2691            &full_snapshot_archive_info,
2692            None,
2693            &genesis_config,
2694            None,
2695            None,
2696            AccountSecondaryIndexes::default(),
2697            false,
2698            None,
2699            AccountShrinkThreshold::default(),
2700            false,
2701            false,
2702            false,
2703            Some(ACCOUNTS_DB_CONFIG_FOR_TESTING),
2704        )
2705        .unwrap();
2706
2707        assert_eq!(*bank4, roundtrip_bank);
2708    }
2709
2710    /// Test roundtrip of bank to snapshots, then back again, with incremental snapshots.  In this
2711    /// version, build up a few slots and take a full snapshot.  Continue on a few more slots and
2712    /// take an incremental snapshot.  Rebuild the bank from both the incremental snapshot and full
2713    /// snapshot.
2714    ///
2715    /// For the full snapshot, touch all the accounts, but only one for the incremental snapshot.
2716    /// This is intended to mimic the real behavior of transactions, where only a small number of
2717    /// accounts are modified often, which are captured by the incremental snapshot.  The majority
2718    /// of the accounts are not modified often, and are captured by the full snapshot.
2719    #[test]
2720    fn test_roundtrip_bank_to_and_from_incremental_snapshot() {
2721        gemachain_logger::setup();
2722        let collector = Pubkey::new_unique();
2723        let key1 = Keypair::new();
2724        let key2 = Keypair::new();
2725        let key3 = Keypair::new();
2726        let key4 = Keypair::new();
2727        let key5 = Keypair::new();
2728
2729        let (genesis_config, mint_keypair) = create_genesis_config(1_000_000);
2730        let bank0 = Arc::new(Bank::new_for_tests(&genesis_config));
2731        bank0.transfer(1, &mint_keypair, &key1.pubkey()).unwrap();
2732        bank0.transfer(2, &mint_keypair, &key2.pubkey()).unwrap();
2733        bank0.transfer(3, &mint_keypair, &key3.pubkey()).unwrap();
2734        while !bank0.is_complete() {
2735            bank0.register_tick(&Hash::new_unique());
2736        }
2737
2738        let slot = 1;
2739        let bank1 = Arc::new(Bank::new_from_parent(&bank0, &collector, slot));
2740        bank1.transfer(3, &mint_keypair, &key3.pubkey()).unwrap();
2741        bank1.transfer(4, &mint_keypair, &key4.pubkey()).unwrap();
2742        bank1.transfer(5, &mint_keypair, &key5.pubkey()).unwrap();
2743        while !bank1.is_complete() {
2744            bank1.register_tick(&Hash::new_unique());
2745        }
2746
2747        let accounts_dir = tempfile::TempDir::new().unwrap();
2748        let bank_snapshots_dir = tempfile::TempDir::new().unwrap();
2749        let snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2750        let snapshot_archive_format = ArchiveFormat::TarZstd;
2751
2752        let full_snapshot_slot = slot;
2753        let full_snapshot_archive_info = bank_to_full_snapshot_archive(
2754            bank_snapshots_dir.path(),
2755            &bank1,
2756            None,
2757            snapshot_archives_dir.path(),
2758            snapshot_archive_format,
2759            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2760            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2761        )
2762        .unwrap();
2763
2764        let slot = slot + 1;
2765        let bank2 = Arc::new(Bank::new_from_parent(&bank1, &collector, slot));
2766        bank2.transfer(1, &mint_keypair, &key1.pubkey()).unwrap();
2767        while !bank2.is_complete() {
2768            bank2.register_tick(&Hash::new_unique());
2769        }
2770
2771        let slot = slot + 1;
2772        let bank3 = Arc::new(Bank::new_from_parent(&bank2, &collector, slot));
2773        bank3.transfer(1, &mint_keypair, &key1.pubkey()).unwrap();
2774        while !bank3.is_complete() {
2775            bank3.register_tick(&Hash::new_unique());
2776        }
2777
2778        let slot = slot + 1;
2779        let bank4 = Arc::new(Bank::new_from_parent(&bank3, &collector, slot));
2780        bank4.transfer(1, &mint_keypair, &key1.pubkey()).unwrap();
2781        while !bank4.is_complete() {
2782            bank4.register_tick(&Hash::new_unique());
2783        }
2784
2785        let incremental_snapshot_archive_info = bank_to_incremental_snapshot_archive(
2786            bank_snapshots_dir.path(),
2787            &bank4,
2788            full_snapshot_slot,
2789            None,
2790            snapshot_archives_dir.path(),
2791            snapshot_archive_format,
2792            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2793            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2794        )
2795        .unwrap();
2796
2797        let (roundtrip_bank, _) = bank_from_snapshot_archives(
2798            &[PathBuf::from(accounts_dir.path())],
2799            &[],
2800            bank_snapshots_dir.path(),
2801            &full_snapshot_archive_info,
2802            Some(&incremental_snapshot_archive_info),
2803            &genesis_config,
2804            None,
2805            None,
2806            AccountSecondaryIndexes::default(),
2807            false,
2808            None,
2809            AccountShrinkThreshold::default(),
2810            false,
2811            false,
2812            false,
2813            Some(ACCOUNTS_DB_CONFIG_FOR_TESTING),
2814        )
2815        .unwrap();
2816
2817        assert_eq!(*bank4, roundtrip_bank);
2818    }
2819
2820    /// Test rebuilding bank from the latest snapshot archives
2821    #[test]
2822    fn test_bank_from_latest_snapshot_archives() {
2823        gemachain_logger::setup();
2824        let collector = Pubkey::new_unique();
2825        let key1 = Keypair::new();
2826        let key2 = Keypair::new();
2827        let key3 = Keypair::new();
2828
2829        let (genesis_config, mint_keypair) = create_genesis_config(1_000_000);
2830        let bank0 = Arc::new(Bank::new_for_tests(&genesis_config));
2831        bank0.transfer(1, &mint_keypair, &key1.pubkey()).unwrap();
2832        bank0.transfer(2, &mint_keypair, &key2.pubkey()).unwrap();
2833        bank0.transfer(3, &mint_keypair, &key3.pubkey()).unwrap();
2834        while !bank0.is_complete() {
2835            bank0.register_tick(&Hash::new_unique());
2836        }
2837
2838        let slot = 1;
2839        let bank1 = Arc::new(Bank::new_from_parent(&bank0, &collector, slot));
2840        bank1.transfer(1, &mint_keypair, &key1.pubkey()).unwrap();
2841        bank1.transfer(2, &mint_keypair, &key2.pubkey()).unwrap();
2842        bank1.transfer(3, &mint_keypair, &key3.pubkey()).unwrap();
2843        while !bank1.is_complete() {
2844            bank1.register_tick(&Hash::new_unique());
2845        }
2846
2847        let accounts_dir = tempfile::TempDir::new().unwrap();
2848        let bank_snapshots_dir = tempfile::TempDir::new().unwrap();
2849        let snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2850        let snapshot_archive_format = ArchiveFormat::Tar;
2851
2852        let full_snapshot_slot = slot;
2853        bank_to_full_snapshot_archive(
2854            &bank_snapshots_dir,
2855            &bank1,
2856            None,
2857            &snapshot_archives_dir,
2858            snapshot_archive_format,
2859            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2860            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2861        )
2862        .unwrap();
2863
2864        let slot = slot + 1;
2865        let bank2 = Arc::new(Bank::new_from_parent(&bank1, &collector, slot));
2866        bank2.transfer(1, &mint_keypair, &key1.pubkey()).unwrap();
2867        while !bank2.is_complete() {
2868            bank2.register_tick(&Hash::new_unique());
2869        }
2870
2871        let slot = slot + 1;
2872        let bank3 = Arc::new(Bank::new_from_parent(&bank2, &collector, slot));
2873        bank3.transfer(2, &mint_keypair, &key2.pubkey()).unwrap();
2874        while !bank3.is_complete() {
2875            bank3.register_tick(&Hash::new_unique());
2876        }
2877
2878        let slot = slot + 1;
2879        let bank4 = Arc::new(Bank::new_from_parent(&bank3, &collector, slot));
2880        bank4.transfer(3, &mint_keypair, &key3.pubkey()).unwrap();
2881        while !bank4.is_complete() {
2882            bank4.register_tick(&Hash::new_unique());
2883        }
2884
2885        bank_to_incremental_snapshot_archive(
2886            &bank_snapshots_dir,
2887            &bank4,
2888            full_snapshot_slot,
2889            None,
2890            &snapshot_archives_dir,
2891            snapshot_archive_format,
2892            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2893            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2894        )
2895        .unwrap();
2896
2897        let (deserialized_bank, ..) = bank_from_latest_snapshot_archives(
2898            &bank_snapshots_dir,
2899            &snapshot_archives_dir,
2900            &[accounts_dir.as_ref().to_path_buf()],
2901            &[],
2902            &genesis_config,
2903            None,
2904            None,
2905            AccountSecondaryIndexes::default(),
2906            false,
2907            None,
2908            AccountShrinkThreshold::default(),
2909            false,
2910            false,
2911            false,
2912            Some(ACCOUNTS_DB_CONFIG_FOR_TESTING),
2913        )
2914        .unwrap();
2915
2916        assert_eq!(deserialized_bank, *bank4);
2917    }
2918
2919    /// Test that cleaning works well in the edge cases of zero-carat accounts and snapshots.
2920    /// Here's the scenario:
2921    ///
2922    /// slot 1:
2923    ///     - send some carats to Account1 (from Account2) to bring it to life
2924    ///     - take a full snapshot
2925    /// slot 2:
2926    ///     - make Account1 have zero carats (send back to Account2)
2927    ///     - take an incremental snapshot
2928    ///     - ensure deserializing from this snapshot is equal to this bank
2929    /// slot 3:
2930    ///     - remove Account2's reference back to slot 2 by transfering from the mint to Account2
2931    /// slot 4:
2932    ///     - ensure `clean_accounts()` has run and that Account1 is gone
2933    ///     - take another incremental snapshot
2934    ///     - ensure deserializing from this snapshots is equal to this bank
2935    ///     - ensure Account1 hasn't come back from the dead
2936    ///
2937    /// The check at slot 4 will fail with the pre-incremental-snapshot cleaning logic.  Because
2938    /// of the cleaning/purging at slot 4, the incremental snapshot at slot 4 will no longer have
2939    /// information about Account1, but the full snapshost _does_ have info for Account1, which is
2940    /// no longer correct!
2941    #[test]
2942    fn test_incremental_snapshots_handle_zero_carat_accounts() {
2943        gemachain_logger::setup();
2944
2945        let collector = Pubkey::new_unique();
2946        let key1 = Keypair::new();
2947        let key2 = Keypair::new();
2948
2949        let accounts_dir = tempfile::TempDir::new().unwrap();
2950        let bank_snapshots_dir = tempfile::TempDir::new().unwrap();
2951        let snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2952        let snapshot_archive_format = ArchiveFormat::Tar;
2953
2954        let (genesis_config, mint_keypair) = create_genesis_config(1_000_000);
2955
2956        let carats_to_transfer = 123_456;
2957        let bank0 = Arc::new(Bank::new_with_paths_for_tests(
2958            &genesis_config,
2959            vec![accounts_dir.path().to_path_buf()],
2960            &[],
2961            None,
2962            None,
2963            AccountSecondaryIndexes::default(),
2964            false,
2965            AccountShrinkThreshold::default(),
2966            false,
2967        ));
2968        bank0
2969            .transfer(carats_to_transfer, &mint_keypair, &key2.pubkey())
2970            .unwrap();
2971        while !bank0.is_complete() {
2972            bank0.register_tick(&Hash::new_unique());
2973        }
2974
2975        let slot = 1;
2976        let bank1 = Arc::new(Bank::new_from_parent(&bank0, &collector, slot));
2977        bank1
2978            .transfer(carats_to_transfer, &key2, &key1.pubkey())
2979            .unwrap();
2980        while !bank1.is_complete() {
2981            bank1.register_tick(&Hash::new_unique());
2982        }
2983
2984        let full_snapshot_slot = slot;
2985        let full_snapshot_archive_info = bank_to_full_snapshot_archive(
2986            bank_snapshots_dir.path(),
2987            &bank1,
2988            None,
2989            snapshot_archives_dir.path(),
2990            snapshot_archive_format,
2991            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2992            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2993        )
2994        .unwrap();
2995
2996        let slot = slot + 1;
2997        let bank2 = Arc::new(Bank::new_from_parent(&bank1, &collector, slot));
2998        let tx = SanitizedTransaction::try_from(system_transaction::transfer(
2999            &key1,
3000            &key2.pubkey(),
3001            carats_to_transfer,
3002            bank2.last_blockhash(),
3003        ))
3004        .unwrap();
3005        let fee = bank2
3006            .get_fee_for_message(&bank2.last_blockhash(), tx.message())
3007            .unwrap();
3008        let tx = system_transaction::transfer(
3009            &key1,
3010            &key2.pubkey(),
3011            carats_to_transfer - fee,
3012            bank2.last_blockhash(),
3013        );
3014        bank2.process_transaction(&tx).unwrap();
3015        assert_eq!(
3016            bank2.get_balance(&key1.pubkey()),
3017            0,
3018            "Ensure Account1's balance is zero"
3019        );
3020        while !bank2.is_complete() {
3021            bank2.register_tick(&Hash::new_unique());
3022        }
3023
3024        // Take an incremental snapshot and then do a roundtrip on the bank and ensure it
3025        // deserializes correctly.
3026        let incremental_snapshot_archive_info = bank_to_incremental_snapshot_archive(
3027            bank_snapshots_dir.path(),
3028            &bank2,
3029            full_snapshot_slot,
3030            None,
3031            snapshot_archives_dir.path(),
3032            snapshot_archive_format,
3033            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3034            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3035        )
3036        .unwrap();
3037        let (deserialized_bank, _) = bank_from_snapshot_archives(
3038            &[accounts_dir.path().to_path_buf()],
3039            &[],
3040            bank_snapshots_dir.path(),
3041            &full_snapshot_archive_info,
3042            Some(&incremental_snapshot_archive_info),
3043            &genesis_config,
3044            None,
3045            None,
3046            AccountSecondaryIndexes::default(),
3047            false,
3048            None,
3049            AccountShrinkThreshold::default(),
3050            false,
3051            false,
3052            false,
3053            Some(ACCOUNTS_DB_CONFIG_FOR_TESTING),
3054        )
3055        .unwrap();
3056        assert_eq!(
3057            deserialized_bank, *bank2,
3058            "Ensure rebuilding from an incremental snapshot works"
3059        );
3060
3061        let slot = slot + 1;
3062        let bank3 = Arc::new(Bank::new_from_parent(&bank2, &collector, slot));
3063        // Update Account2 so that it no longer holds a reference to slot2
3064        bank3
3065            .transfer(carats_to_transfer, &mint_keypair, &key2.pubkey())
3066            .unwrap();
3067        while !bank3.is_complete() {
3068            bank3.register_tick(&Hash::new_unique());
3069        }
3070
3071        let slot = slot + 1;
3072        let bank4 = Arc::new(Bank::new_from_parent(&bank3, &collector, slot));
3073        while !bank4.is_complete() {
3074            bank4.register_tick(&Hash::new_unique());
3075        }
3076
3077        // Ensure account1 has been cleaned/purged from everywhere
3078        bank4.squash();
3079        bank4.clean_accounts(true, false, Some(full_snapshot_slot));
3080        assert!(
3081            bank4.get_account_modified_slot(&key1.pubkey()).is_none(),
3082            "Ensure Account1 has been cleaned and purged from AccountsDb"
3083        );
3084
3085        // Take an incremental snapshot and then do a roundtrip on the bank and ensure it
3086        // deserializes correctly
3087        let incremental_snapshot_archive_info = bank_to_incremental_snapshot_archive(
3088            bank_snapshots_dir.path(),
3089            &bank4,
3090            full_snapshot_slot,
3091            None,
3092            snapshot_archives_dir.path(),
3093            snapshot_archive_format,
3094            DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3095            DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
3096        )
3097        .unwrap();
3098
3099        let (deserialized_bank, _) = bank_from_snapshot_archives(
3100            &[accounts_dir.path().to_path_buf()],
3101            &[],
3102            bank_snapshots_dir.path(),
3103            &full_snapshot_archive_info,
3104            Some(&incremental_snapshot_archive_info),
3105            &genesis_config,
3106            None,
3107            None,
3108            AccountSecondaryIndexes::default(),
3109            false,
3110            None,
3111            AccountShrinkThreshold::default(),
3112            false,
3113            false,
3114            false,
3115            Some(ACCOUNTS_DB_CONFIG_FOR_TESTING),
3116        )
3117        .unwrap();
3118        assert_eq!(
3119            deserialized_bank, *bank4,
3120            "Ensure rebuilding from an incremental snapshot works",
3121        );
3122        assert!(
3123            deserialized_bank
3124                .get_account_modified_slot(&key1.pubkey())
3125                .is_none(),
3126            "Ensure Account1 has not been brought back from the dead"
3127        );
3128    }
3129}