1#[cfg(feature = "dev-context-only-utils")]
2use solana_accounts_db::utils::create_accounts_run_and_snapshot_dirs;
3use {
4 crate::{
5 bank::BankFieldsToDeserialize,
6 serde_snapshot::{
7 self, AccountsDbFields, ExtraFieldsToSerialize, SerdeObsoleteAccountsMap,
8 SerializableAccountStorageEntry, SnapshotAccountsDbFields, SnapshotBankFields,
9 SnapshotStreams, StorageListItem, StoragesList,
10 },
11 snapshot_package::BankSnapshotPackage,
12 snapshot_utils::snapshot_storage_rebuilder::{
13 SnapshotStorageRebuilder, get_slot_and_append_vec_id,
14 },
15 },
16 agave_fs::{
17 FileInfo, FileSize,
18 buffered_reader::large_file_buf_reader,
19 buffered_writer::{SizeLimitedWriter, large_file_buf_writer},
20 io_setup::IoSetupState,
21 },
22 agave_snapshots::{
23 ArchiveFormat, Result, SnapshotArchiveKind, SnapshotVersion, archive_snapshot,
24 error::{
25 AddBankSnapshotError, SnapshotError, SnapshotFastbootError, SnapshotNewFromDirError,
26 },
27 paths::{self as snapshot_paths, incremental_snapshot_archives_iter},
28 snapshot_archive_info::{
29 FullSnapshotArchiveInfo, IncrementalSnapshotArchiveInfo, SnapshotArchiveInfo,
30 SnapshotArchiveInfoGetter,
31 },
32 snapshot_config::SnapshotConfig,
33 snapshot_hash::SnapshotHash,
34 streaming_unarchive_snapshot,
35 },
36 crossbeam_channel::Receiver,
37 log::*,
38 regex::Regex,
39 semver::Version,
40 solana_accounts_db::{
41 account_storage::AccountStorageMap,
42 account_storage_entry::AccountStorageEntry,
43 accounts_db::{AccountsFileId, AtomicAccountsFileId},
44 utils::{
45 ACCOUNTS_RUN_DIR, ACCOUNTS_SNAPSHOT_DIR, move_and_async_delete_path,
46 move_and_async_delete_path_contents,
47 },
48 },
49 solana_clock::Slot,
50 solana_measure::{measure::Measure, measure_time, measure_us},
51 std::{
52 cmp::Ordering,
53 collections::{HashMap, HashSet},
54 fs,
55 io::{self, BufReader, Error as IoError, Read, Seek, Write},
56 mem,
57 num::NonZeroUsize,
58 path::{Path, PathBuf},
59 str::FromStr,
60 sync::{Arc, LazyLock},
61 thread,
62 },
63 tempfile::TempDir,
64 wincode::io::std_read::ReadAdapter,
65};
66
67pub mod snapshot_storage_rebuilder;
68
69pub const MAX_OBSOLETE_ACCOUNTS_FILE_SIZE: u64 = 1024 * 1024 * 1024 * 12; pub const MAX_STORAGES_LIST_FILE_SIZE: u64 = 100 * 1024 * 1024; pub const MAX_SNAPSHOT_DATA_FILE_SIZE: u64 = 32 * 1024 * 1024 * 1024; const MAX_SNAPSHOT_VERSION_FILE_SIZE: u64 = 8; const AUX_SNAPSHOT_FILE_READ_BUF_SIZE: usize = 4 * 1024 * 1024;
83
84const SNAPSHOT_FASTBOOT_VERSION: Version = Version::new(3, 0, 0);
97
98#[derive(PartialEq, Eq, Debug)]
101pub struct BankSnapshotInfo {
102 pub slot: Slot,
104 pub snapshot_dir: PathBuf,
106 pub snapshot_version: SnapshotVersion,
108 pub fastboot_version: Option<Version>,
110}
111
112impl PartialOrd for BankSnapshotInfo {
113 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
114 Some(self.cmp(other))
115 }
116}
117
118impl Ord for BankSnapshotInfo {
120 fn cmp(&self, other: &Self) -> Ordering {
121 self.slot.cmp(&other.slot)
122 }
123}
124
125impl BankSnapshotInfo {
126 pub fn new_from_dir(
127 bank_snapshots_dir: impl AsRef<Path>,
128 slot: Slot,
129 ) -> std::result::Result<BankSnapshotInfo, SnapshotNewFromDirError> {
130 let bank_snapshot_dir = snapshot_paths::get_bank_snapshot_dir(&bank_snapshots_dir, slot);
133
134 if !bank_snapshot_dir.is_dir() {
135 return Err(SnapshotNewFromDirError::InvalidBankSnapshotDir(
136 bank_snapshot_dir,
137 ));
138 }
139
140 let version_path = bank_snapshot_dir.join(snapshot_paths::SNAPSHOT_VERSION_FILENAME);
147 let version_file_info = FileInfo::new_from_path(&version_path)
148 .map_err(|err| SnapshotNewFromDirError::IncompleteDir(err, version_path))?;
149 let version_str = snapshot_version_from_file(version_file_info).map_err(|err| {
150 SnapshotNewFromDirError::IncompleteDir(err, bank_snapshot_dir.clone())
151 })?;
152
153 let snapshot_version = SnapshotVersion::from_str(version_str.as_str())
154 .or(Err(SnapshotNewFromDirError::InvalidVersion(version_str)))?;
155
156 let status_cache_file =
157 bank_snapshot_dir.join(snapshot_paths::SNAPSHOT_STATUS_CACHE_FILENAME);
158 if !status_cache_file.is_file() {
159 return Err(SnapshotNewFromDirError::MissingStatusCacheFile(
160 status_cache_file,
161 ));
162 }
163
164 let bank_snapshot_path =
165 bank_snapshot_dir.join(snapshot_paths::get_snapshot_file_name(slot));
166 if !bank_snapshot_path.is_file() {
167 return Err(SnapshotNewFromDirError::MissingSnapshotFile(
168 bank_snapshot_dir,
169 ));
170 };
171
172 let snapshot_fastboot_version_path =
173 bank_snapshot_dir.join(snapshot_paths::SNAPSHOT_FASTBOOT_VERSION_FILENAME);
174
175 let fastboot_version = fs::read_to_string(&snapshot_fastboot_version_path)
179 .ok()
180 .map(|version_string| {
181 Version::from_str(version_string.trim())
182 .map_err(|_| SnapshotNewFromDirError::InvalidFastbootVersion(version_string))
183 })
184 .transpose()?;
185
186 Ok(BankSnapshotInfo {
187 slot,
188 snapshot_dir: bank_snapshot_dir,
189 snapshot_version,
190 fastboot_version,
191 })
192 }
193
194 pub fn snapshot_path(&self) -> PathBuf {
195 self.snapshot_dir
196 .join(snapshot_paths::get_snapshot_file_name(self.slot))
197 }
198}
199
200#[derive(Clone, Copy, Debug, Eq, PartialEq)]
204pub enum SnapshotFrom {
205 Archive,
207 Dir,
209}
210
211#[derive(Debug)]
214pub struct SnapshotRootPaths {
215 pub full_snapshot_root_file_path: PathBuf,
216 pub incremental_snapshot_root_file_path: Option<PathBuf>,
217}
218
219#[derive(Debug)]
221pub struct UnarchivedSnapshot {
222 unpack_dir: TempDir,
223 pub storage: AccountStorageMap,
224 pub bank_fields: BankFieldsToDeserialize,
225 pub(crate) accounts_db_fields: AccountsDbFields<SerializableAccountStorageEntry>,
226 pub unpacked_snapshots_dir_and_version: UnpackedSnapshotsDirAndVersion,
227 pub measure_untar: Measure,
228}
229
230#[derive(Debug)]
232pub struct UnarchivedSnapshots {
233 pub full_storage: AccountStorageMap,
234 pub incremental_storage: Option<AccountStorageMap>,
235 pub bank_fields: SnapshotBankFields,
236 pub accounts_db_fields: SnapshotAccountsDbFields<SerializableAccountStorageEntry>,
237 pub full_unpacked_snapshots_dir_and_version: UnpackedSnapshotsDirAndVersion,
238 pub incremental_unpacked_snapshots_dir_and_version: Option<UnpackedSnapshotsDirAndVersion>,
239 pub full_measure_untar: Measure,
240 pub incremental_measure_untar: Option<Measure>,
241 pub next_append_vec_id: AtomicAccountsFileId,
242}
243
244#[expect(dead_code)]
247#[derive(Debug)]
248pub struct UnarchivedSnapshotsGuard {
249 full_unpack_dir: TempDir,
250 incremental_unpack_dir: Option<TempDir>,
251}
252#[derive(Debug)]
254pub struct UnpackedSnapshotsDirAndVersion {
255 pub unpacked_snapshots_dir: PathBuf,
256 pub snapshot_version: SnapshotVersion,
257}
258
259pub(crate) struct StorageAndNextAccountsFileId {
262 pub storage: AccountStorageMap,
263 pub next_append_vec_id: AtomicAccountsFileId,
264}
265
266pub fn purge_incomplete_bank_snapshots(bank_snapshots_dir: impl AsRef<Path>) {
268 let Ok(read_dir_iter) = std::fs::read_dir(&bank_snapshots_dir) else {
269 return;
271 };
272
273 let is_incomplete = |dir: &PathBuf| !is_bank_snapshot_complete(dir);
274
275 let incomplete_dirs: Vec<_> = read_dir_iter
276 .filter_map(|entry| entry.ok())
277 .map(|entry| entry.path())
278 .filter(|path| path.is_dir())
279 .filter(is_incomplete)
280 .collect();
281
282 for incomplete_dir in incomplete_dirs {
284 let result = purge_bank_snapshot(&incomplete_dir);
285 match result {
286 Ok(_) => info!(
287 "Purged incomplete snapshot dir: {}",
288 incomplete_dir.display()
289 ),
290 Err(err) => warn!("Failed to purge incomplete snapshot dir: {err}"),
291 }
292 }
293}
294
295fn is_bank_snapshot_complete(bank_snapshot_dir: impl AsRef<Path>) -> bool {
297 let version_path = bank_snapshot_dir
298 .as_ref()
299 .join(snapshot_paths::SNAPSHOT_VERSION_FILENAME);
300
301 let Ok(version_file_info) = FileInfo::new_from_path(&version_path) else {
302 return false;
304 };
305
306 let Ok(version_str) = snapshot_version_from_file(version_file_info) else {
307 return false;
309 };
310
311 let Ok(_snapshot_version) = SnapshotVersion::from_str(version_str.as_str()) else {
312 return false;
314 };
315
316 let Some(slot) = bank_snapshot_dir.as_ref().file_name() else {
318 return false;
319 };
320 let Some(slot) = slot.to_str() else {
321 return false;
322 };
323 for file_name in [slot, snapshot_paths::SNAPSHOT_STATUS_CACHE_FILENAME] {
324 let file_path = bank_snapshot_dir.as_ref().join(file_name);
325 let Ok(file_info) = FileInfo::new_from_path(file_path) else {
326 return false;
328 };
329 if file_info.size == 0 {
330 return false;
332 }
333 }
334
335 true
336}
337
338pub fn mark_bank_snapshot_as_loadable(bank_snapshot_dir: impl AsRef<Path>) -> io::Result<()> {
340 let snapshot_fastboot_version_path = bank_snapshot_dir
341 .as_ref()
342 .join(snapshot_paths::SNAPSHOT_FASTBOOT_VERSION_FILENAME);
343 fs::write(
344 &snapshot_fastboot_version_path,
345 SNAPSHOT_FASTBOOT_VERSION.to_string(),
346 )
347 .map_err(|err| {
348 IoError::other(format!(
349 "failed to write fastboot version file '{}': {err}",
350 snapshot_fastboot_version_path.display(),
351 ))
352 })?;
353 Ok(())
354}
355
356fn is_bank_snapshot_loadable(
358 fastboot_version: Option<&Version>,
359) -> std::result::Result<bool, SnapshotFastbootError> {
360 if let Some(fastboot_version) = fastboot_version {
361 is_snapshot_fastboot_compatible(fastboot_version)
362 } else {
363 Ok(false)
365 }
366}
367
368fn is_snapshot_fastboot_compatible(
370 version: &Version,
371) -> std::result::Result<bool, SnapshotFastbootError> {
372 match version.major {
373 3 => Ok(true),
375 2 => Ok(true),
378 v if v > SNAPSHOT_FASTBOOT_VERSION.major => {
379 Err(SnapshotFastbootError::IncompatibleVersion(version.clone()))
380 }
381 _ => Ok(false),
383 }
384}
385
386pub fn get_highest_loadable_bank_snapshot(
390 snapshot_config: &SnapshotConfig,
391) -> Option<BankSnapshotInfo> {
392 let highest_bank_snapshot = get_highest_bank_snapshot(&snapshot_config.bank_snapshots_dir)?;
393
394 let is_bank_snapshot_loadable =
395 is_bank_snapshot_loadable(highest_bank_snapshot.fastboot_version.as_ref());
396
397 match is_bank_snapshot_loadable {
398 Ok(true) => Some(highest_bank_snapshot),
399 Ok(false) => None,
400 Err(err) => {
401 warn!(
402 "Bank snapshot is not loadable '{}': {err}",
403 highest_bank_snapshot.snapshot_dir.display()
404 );
405 None
406 }
407 }
408}
409
410pub fn remove_tmp_snapshot_archives(snapshot_archives_dir: impl AsRef<Path>) {
413 if let Ok(entries) = std::fs::read_dir(snapshot_archives_dir) {
414 for entry in entries.flatten() {
415 if entry
416 .file_name()
417 .to_str()
418 .map(|file_name| file_name.starts_with(snapshot_paths::TMP_SNAPSHOT_ARCHIVE_PREFIX))
419 .unwrap_or(false)
420 {
421 let path = entry.path();
422 let result = if path.is_dir() {
423 fs::remove_dir_all(&path)
424 } else {
425 fs::remove_file(&path)
426 };
427 if let Err(err) = result {
428 warn!(
429 "Failed to remove temporary snapshot archive '{}': {err}",
430 path.display(),
431 );
432 }
433 }
434 }
435 }
436}
437
438pub fn archive_snapshot_package(
440 snapshot_archive_kind: SnapshotArchiveKind,
441 snapshot_slot: Slot,
442 snapshot_hash: SnapshotHash,
443 bank_snapshot_dir: impl AsRef<Path>,
444 mut snapshot_storages: Vec<Arc<AccountStorageEntry>>,
445 snapshot_config: &SnapshotConfig,
446 io_setup: &IoSetupState,
447) -> Result<SnapshotArchiveInfo> {
448 let snapshot_archive_path = match snapshot_archive_kind {
449 SnapshotArchiveKind::Full => snapshot_paths::build_full_snapshot_archive_path(
450 &snapshot_config.full_snapshot_archives_dir,
451 snapshot_slot,
452 &snapshot_hash,
453 snapshot_config.archive_format,
454 ),
455 SnapshotArchiveKind::Incremental(incremental_snapshot_base_slot) => {
456 snapshot_storages.retain(|storage| storage.slot() > incremental_snapshot_base_slot);
459 snapshot_paths::build_incremental_snapshot_archive_path(
460 &snapshot_config.incremental_snapshot_archives_dir,
461 incremental_snapshot_base_slot,
462 snapshot_slot,
463 &snapshot_hash,
464 snapshot_config.archive_format,
465 )
466 }
467 };
468
469 let snapshot_archive_info = archive_snapshot(
470 snapshot_archive_kind,
471 snapshot_slot,
472 snapshot_hash,
473 snapshot_storages.as_slice(),
474 &bank_snapshot_dir,
475 snapshot_archive_path,
476 snapshot_config.archive_format,
477 io_setup,
478 )?;
479
480 Ok(snapshot_archive_info)
481}
482
483pub fn serialize_snapshot(
485 bank_snapshots_dir: impl AsRef<Path>,
486 snapshot_version: SnapshotVersion,
487 bank_snapshot_package: BankSnapshotPackage,
488 snapshot_storages: &[Arc<AccountStorageEntry>],
489 should_finalize: bool,
490 io_setup: &IoSetupState,
491) -> Result<BankSnapshotInfo> {
492 let BankSnapshotPackage {
493 mut bank_fields,
494 bank_hash_stats,
495 status_cache_slot_deltas,
496 } = bank_snapshot_package;
497 let status_cache_slot_deltas = status_cache_slot_deltas.as_slice();
498 let slot = bank_fields.slot;
499
500 let do_serialize_snapshot = || {
503 let mut measure_everything = Measure::start("");
504 let bank_snapshot_dir = snapshot_paths::get_bank_snapshot_dir(&bank_snapshots_dir, slot);
505 if bank_snapshot_dir.exists() {
506 return Err(AddBankSnapshotError::SnapshotDirAlreadyExists(
507 bank_snapshot_dir,
508 ));
509 }
510 fs::create_dir_all(&bank_snapshot_dir).map_err(|err| {
511 AddBankSnapshotError::CreateSnapshotDir(err, bank_snapshot_dir.clone())
512 })?;
513
514 let bank_snapshot_path =
516 bank_snapshot_dir.join(snapshot_paths::get_snapshot_file_name(slot));
517 info!(
518 "Creating bank snapshot for slot {slot} at '{}'",
519 bank_snapshot_path.display(),
520 );
521
522 let bank_snapshot_serializer = move |stream: &mut dyn Write| -> Result<()> {
523 let versioned_epoch_stakes = mem::take(&mut bank_fields.versioned_epoch_stakes);
524 let extra_fields = ExtraFieldsToSerialize {
525 lamports_per_signature: bank_fields.fee_rate_governor.lamports_per_signature,
526 unused_incremental_snapshot_persistence: None,
527 unused_epoch_accounts_hash: None,
528 versioned_epoch_stakes,
529 accounts_lt_hash: Some(bank_fields.accounts_lt_hash.clone().into()),
530 block_id: Some(bank_fields.block_id),
531 };
532 serde_snapshot::serialize_bank_snapshot_into(
533 stream,
534 bank_fields,
535 bank_hash_stats,
536 snapshot_storages,
537 extra_fields,
538 )?;
539 Ok(())
540 };
541 let (bank_snapshot_consumed_size, bank_serialize) = measure_time!(
542 serialize_snapshot_data_file(&bank_snapshot_path, io_setup, bank_snapshot_serializer)
543 .map_err(|err| AddBankSnapshotError::SerializeBank(Box::new(err)))?,
544 "bank serialize"
545 );
546
547 let status_cache_path =
548 bank_snapshot_dir.join(snapshot_paths::SNAPSHOT_STATUS_CACHE_FILENAME);
549 let (status_cache_consumed_size, status_cache_serialize_us) = measure_us!(
550 serde_snapshot::serialize_status_cache(
551 status_cache_slot_deltas,
552 &status_cache_path,
553 io_setup,
554 )
555 .map_err(|err| AddBankSnapshotError::SerializeStatusCache(Box::new(err)))?
556 );
557
558 let version_path = bank_snapshot_dir.join(snapshot_paths::SNAPSHOT_VERSION_FILENAME);
559 let (_, write_version_file_us) = measure_us!(
560 fs::write(&version_path, snapshot_version.as_str().as_bytes(),)
561 .map_err(|err| AddBankSnapshotError::WriteSnapshotVersionFile(err, version_path))?
562 );
563
564 let (flush_storages_us, serialize_obsolete_accounts_us, write_storages_list_us) =
565 if should_finalize {
566 let flush_measure = Measure::start("");
567 for storage in snapshot_storages {
568 storage.flush().map_err(|err| {
569 AddBankSnapshotError::FlushStorage(err, storage.path().to_path_buf())
570 })?;
571 storage.disable_remove_on_drop();
574 }
575 let flush_us = flush_measure.end_as_us();
576
577 let (_, serialize_obsolete_accounts_us) = measure_us!({
578 write_obsolete_accounts_to_snapshot(
579 &bank_snapshot_dir,
580 snapshot_storages,
581 slot,
582 io_setup,
583 )
584 .map_err(|err| AddBankSnapshotError::SerializeObsoleteAccounts(Box::new(err)))?
585 });
586
587 let (_, write_storages_list_us) = measure_us!(
588 write_storages_list_to_snapshot(
589 &bank_snapshot_dir,
590 snapshot_storages,
591 io_setup,
592 )
593 .map_err(|err| AddBankSnapshotError::WriteStoragesList(Box::new(err)))?
594 );
595
596 mark_bank_snapshot_as_loadable(&bank_snapshot_dir)
597 .map_err(AddBankSnapshotError::MarkSnapshotLoadable)?;
598
599 (
600 Some(flush_us),
601 Some(serialize_obsolete_accounts_us),
602 Some(write_storages_list_us),
603 )
604 } else {
605 (None, None, None)
606 };
607
608 measure_everything.stop();
609
610 datapoint_info!(
612 "snapshot_bank",
613 ("slot", slot, i64),
614 ("bank_size", bank_snapshot_consumed_size, i64),
615 ("status_cache_size", status_cache_consumed_size, i64),
616 ("flush_storages_us", flush_storages_us, Option<i64>),
617 ("serialize_obsolete_accounts_us", serialize_obsolete_accounts_us, Option<i64>),
618 ("write_storages_list_us", write_storages_list_us, Option<i64>),
619 ("bank_serialize_us", bank_serialize.as_us(), i64),
620 ("status_cache_serialize_us", status_cache_serialize_us, i64),
621 ("write_version_file_us", write_version_file_us, i64),
622 ("total_us", measure_everything.as_us(), i64),
623 );
624
625 info!(
626 "{} for slot {} at {}",
627 bank_serialize,
628 slot,
629 bank_snapshot_path.display(),
630 );
631
632 Ok(BankSnapshotInfo {
633 slot,
634 snapshot_dir: bank_snapshot_dir,
635 snapshot_version,
636 fastboot_version: None,
637 })
638 };
639
640 do_serialize_snapshot().map_err(|err| SnapshotError::AddBankSnapshot(err, slot))
641}
642
643pub fn get_bank_snapshots(bank_snapshots_dir: impl AsRef<Path>) -> Vec<BankSnapshotInfo> {
645 let mut bank_snapshots = Vec::default();
646 match fs::read_dir(&bank_snapshots_dir) {
647 Err(err) => {
648 info!(
649 "Unable to read bank snapshots directory '{}': {err}",
650 bank_snapshots_dir.as_ref().display(),
651 );
652 }
653 Ok(paths) => paths
654 .filter_map(|entry| {
655 entry
658 .ok()
659 .filter(|entry| entry.path().is_dir())
660 .and_then(|entry| {
661 entry
662 .path()
663 .file_name()
664 .and_then(|file_name| file_name.to_str())
665 .and_then(|file_name| file_name.parse::<Slot>().ok())
666 })
667 })
668 .for_each(
669 |slot| match BankSnapshotInfo::new_from_dir(&bank_snapshots_dir, slot) {
670 Ok(snapshot_info) => bank_snapshots.push(snapshot_info),
671 Err(err) => debug!("Unable to read bank snapshot for slot {slot}: {err}"),
674 },
675 ),
676 }
677 bank_snapshots
678}
679
680pub fn get_highest_bank_snapshot(bank_snapshots_dir: impl AsRef<Path>) -> Option<BankSnapshotInfo> {
684 do_get_highest_bank_snapshot(get_bank_snapshots(&bank_snapshots_dir))
685}
686
687fn do_get_highest_bank_snapshot(
688 mut bank_snapshots: Vec<BankSnapshotInfo>,
689) -> Option<BankSnapshotInfo> {
690 bank_snapshots.sort_unstable();
691 bank_snapshots.into_iter().next_back()
692}
693
694pub fn write_obsolete_accounts_to_snapshot(
695 bank_snapshot_dir: impl AsRef<Path>,
696 snapshot_storages: &[Arc<AccountStorageEntry>],
697 snapshot_slot: Slot,
698 io_setup: &IoSetupState,
699) -> Result<u64> {
700 let obsolete_accounts =
701 SerdeObsoleteAccountsMap::new_from_storages(snapshot_storages, snapshot_slot);
702 serialize_obsolete_accounts(
703 bank_snapshot_dir,
704 &obsolete_accounts,
705 MAX_OBSOLETE_ACCOUNTS_FILE_SIZE,
706 io_setup,
707 )
708}
709
710fn serialize_obsolete_accounts(
711 bank_snapshot_dir: impl AsRef<Path>,
712 obsolete_accounts_map: &SerdeObsoleteAccountsMap,
713 maximum_obsolete_accounts_file_size: u64,
714 io_setup: &IoSetupState,
715) -> Result<u64> {
716 let obsolete_accounts_path = bank_snapshot_dir
717 .as_ref()
718 .join(snapshot_paths::SNAPSHOT_OBSOLETE_ACCOUNTS_FILENAME);
719 let mut file_stream = SizeLimitedWriter::new(
720 large_file_buf_writer(&obsolete_accounts_path, io_setup)?,
721 maximum_obsolete_accounts_file_size,
722 );
723
724 serde_snapshot::serialize_into(&mut file_stream, obsolete_accounts_map).map_err(|err| {
725 IoError::other(format!(
726 "unable to serialize obsolete accounts to file '{}': {err}",
727 obsolete_accounts_path.display(),
728 ))
729 })?;
730
731 Ok(file_stream.bytes_written())
732}
733
734fn deserialize_obsolete_accounts(
735 bank_snapshot_dir: impl AsRef<Path>,
736 maximum_obsolete_accounts_file_size: u64,
737) -> Result<SerdeObsoleteAccountsMap> {
738 let obsolete_accounts_path = bank_snapshot_dir
739 .as_ref()
740 .join(snapshot_paths::SNAPSHOT_OBSOLETE_ACCOUNTS_FILENAME);
741 let obsolete_accounts_reader = ReadAdapter::new(large_file_buf_reader(
742 &obsolete_accounts_path,
743 AUX_SNAPSHOT_FILE_READ_BUF_SIZE,
744 &IoSetupState::default(),
745 )?);
746 let obsolete_accounts_file_metadata = fs::metadata(&obsolete_accounts_path)?;
748 if obsolete_accounts_file_metadata.len() > maximum_obsolete_accounts_file_size {
749 let error_message = format!(
750 "too large obsolete accounts file to deserialize: '{}' has {} bytes (max size is \
751 {maximum_obsolete_accounts_file_size} bytes)",
752 obsolete_accounts_path.display(),
753 obsolete_accounts_file_metadata.len(),
754 );
755 return Err(IoError::other(error_message).into());
756 }
757
758 Ok(serde_snapshot::deserialize_wincode_from(
759 obsolete_accounts_reader,
760 )?)
761}
762
763pub fn write_storages_list_to_snapshot(
764 bank_snapshot_dir: impl AsRef<Path>,
765 snapshot_storages: &[Arc<AccountStorageEntry>],
766 io_setup: &IoSetupState,
767) -> Result<FileSize> {
768 let storages_list = StoragesList::new_from_storages(snapshot_storages);
769 serialize_storages_list_to_snapshot(bank_snapshot_dir, storages_list, io_setup)
770}
771
772fn serialize_storages_list_to_snapshot(
773 bank_snapshot_dir: impl AsRef<Path>,
774 storages_list: StoragesList,
775 io_setup: &IoSetupState,
776) -> Result<FileSize> {
777 let storages_list_path = bank_snapshot_dir
778 .as_ref()
779 .join(snapshot_paths::SNAPSHOT_STORAGES_LIST_FILENAME);
780 let mut file_stream = SizeLimitedWriter::new(
781 large_file_buf_writer(&storages_list_path, io_setup)?,
782 MAX_STORAGES_LIST_FILE_SIZE,
783 );
784 serde_snapshot::serialize_into(&mut file_stream, &storages_list).map_err(|err| {
785 IoError::other(format!(
786 "unable to serialize storages list to file '{}': {err}",
787 storages_list_path.display(),
788 ))
789 })?;
790 Ok(file_stream.bytes_written())
791}
792
793fn deserialize_storages_list(
794 storages_list_path: &Path,
795 maximum_storages_list_file_size: u64,
796) -> Result<StoragesList> {
797 let storages_list_reader = ReadAdapter::new(large_file_buf_reader(
798 storages_list_path,
799 AUX_SNAPSHOT_FILE_READ_BUF_SIZE,
800 &IoSetupState::default(),
801 )?);
802 let storages_list_file_metadata = fs::metadata(storages_list_path)?;
804 if storages_list_file_metadata.len() > maximum_storages_list_file_size {
805 let error_message = format!(
806 "too large storages list file to deserialize: '{}' has {} bytes (max size is \
807 {maximum_storages_list_file_size} bytes)",
808 storages_list_path.display(),
809 storages_list_file_metadata.len(),
810 );
811 return Err(IoError::other(error_message).into());
812 }
813
814 Ok(serde_snapshot::deserialize_wincode_from(
815 storages_list_reader,
816 )?)
817}
818
819pub fn serialize_snapshot_data_file<F>(
820 data_file_path: &Path,
821 io_setup: &IoSetupState,
822 serializer: F,
823) -> Result<u64>
824where
825 F: FnOnce(&mut dyn Write) -> Result<()>,
826{
827 serialize_snapshot_data_file_capped::<F>(
828 data_file_path,
829 MAX_SNAPSHOT_DATA_FILE_SIZE,
830 io_setup,
831 serializer,
832 )
833}
834
835pub fn deserialize_snapshot_data_file<T: Sized>(
836 data_file_path: &Path,
837 deserializer: impl FnOnce(&mut BufReader<std::fs::File>) -> Result<T>,
838) -> Result<T> {
839 let wrapped_deserializer = move |streams: &mut SnapshotStreams<std::fs::File>| -> Result<T> {
840 deserializer(streams.full_snapshot_stream)
841 };
842
843 let wrapped_data_file_path = SnapshotRootPaths {
844 full_snapshot_root_file_path: data_file_path.to_path_buf(),
845 incremental_snapshot_root_file_path: None,
846 };
847
848 deserialize_snapshot_data_files_capped(
849 &wrapped_data_file_path,
850 MAX_SNAPSHOT_DATA_FILE_SIZE,
851 wrapped_deserializer,
852 )
853}
854
855pub fn deserialize_snapshot_data_files<T: Sized>(
856 snapshot_root_paths: &SnapshotRootPaths,
857 deserializer: impl FnOnce(&mut SnapshotStreams<std::fs::File>) -> Result<T>,
858) -> Result<T> {
859 deserialize_snapshot_data_files_capped(
860 snapshot_root_paths,
861 MAX_SNAPSHOT_DATA_FILE_SIZE,
862 deserializer,
863 )
864}
865
866fn serialize_snapshot_data_file_capped<F>(
867 data_file_path: &Path,
868 maximum_file_size: u64,
869 io_setup: &IoSetupState,
870 serializer: F,
871) -> Result<u64>
872where
873 F: FnOnce(&mut dyn Write) -> Result<()>,
874{
875 let mut data_file_stream = SizeLimitedWriter::new(
876 large_file_buf_writer(data_file_path, io_setup)?,
877 maximum_file_size,
878 );
879 serializer(&mut data_file_stream).map_err(|err| {
880 IoError::other(format!(
881 "unable to serialize snapshot data to file '{}': {err}",
882 data_file_path.display(),
883 ))
884 })?;
885 data_file_stream.flush()?;
886 Ok(data_file_stream.bytes_written())
887}
888
889fn deserialize_snapshot_data_files_capped<T: Sized>(
890 snapshot_root_paths: &SnapshotRootPaths,
891 maximum_file_size: u64,
892 deserializer: impl FnOnce(&mut SnapshotStreams<std::fs::File>) -> Result<T>,
893) -> Result<T> {
894 let (full_snapshot_file_size, mut full_snapshot_data_file_stream) =
895 create_snapshot_data_file_stream(
896 &snapshot_root_paths.full_snapshot_root_file_path,
897 maximum_file_size,
898 )?;
899
900 let (incremental_snapshot_file_size, mut incremental_snapshot_data_file_stream) =
901 if let Some(ref incremental_snapshot_root_file_path) =
902 snapshot_root_paths.incremental_snapshot_root_file_path
903 {
904 Some(create_snapshot_data_file_stream(
905 incremental_snapshot_root_file_path,
906 maximum_file_size,
907 )?)
908 } else {
909 None
910 }
911 .unzip();
912
913 let mut snapshot_streams = SnapshotStreams {
914 full_snapshot_stream: &mut full_snapshot_data_file_stream,
915 incremental_snapshot_stream: incremental_snapshot_data_file_stream.as_mut(),
916 };
917 let ret = deserializer(&mut snapshot_streams)?;
918
919 check_deserialize_file_consumed(
920 full_snapshot_file_size,
921 &snapshot_root_paths.full_snapshot_root_file_path,
922 &mut full_snapshot_data_file_stream,
923 )?;
924
925 if let Some(ref incremental_snapshot_root_file_path) =
926 snapshot_root_paths.incremental_snapshot_root_file_path
927 {
928 check_deserialize_file_consumed(
929 incremental_snapshot_file_size.unwrap(),
930 incremental_snapshot_root_file_path,
931 incremental_snapshot_data_file_stream.as_mut().unwrap(),
932 )?;
933 }
934
935 Ok(ret)
936}
937
938fn create_snapshot_data_file_stream(
941 snapshot_root_file_path: impl AsRef<Path>,
942 maximum_file_size: u64,
943) -> Result<(u64, BufReader<std::fs::File>)> {
944 let snapshot_file_size = fs::metadata(&snapshot_root_file_path)?.len();
945
946 if snapshot_file_size > maximum_file_size {
947 let error_message = format!(
948 "too large snapshot data file to deserialize: '{}' has {} bytes (max size is {} bytes)",
949 snapshot_root_file_path.as_ref().display(),
950 snapshot_file_size,
951 maximum_file_size,
952 );
953 return Err(IoError::other(error_message).into());
954 }
955
956 let snapshot_data_file = fs::File::open(snapshot_root_file_path)?;
957 let snapshot_data_file_stream = BufReader::new(snapshot_data_file);
958
959 Ok((snapshot_file_size, snapshot_data_file_stream))
960}
961
962fn check_deserialize_file_consumed(
965 file_size: u64,
966 file_path: impl AsRef<Path>,
967 file_stream: &mut BufReader<std::fs::File>,
968) -> Result<()> {
969 let consumed_size = file_stream.stream_position()?;
970
971 if consumed_size != file_size {
972 let error_message = format!(
973 "invalid snapshot data file: '{}' has {} bytes, however consumed {} bytes to \
974 deserialize",
975 file_path.as_ref().display(),
976 file_size,
977 consumed_size,
978 );
979 return Err(IoError::other(error_message).into());
980 }
981
982 Ok(())
983}
984
985pub fn verify_and_unarchive_snapshots(
987 bank_snapshots_dir: impl AsRef<Path>,
988 full_snapshot_archive_info: &FullSnapshotArchiveInfo,
989 incremental_snapshot_archive_info: Option<&IncrementalSnapshotArchiveInfo>,
990 account_paths: &[PathBuf],
991 io_setup: &IoSetupState,
992) -> Result<(UnarchivedSnapshots, UnarchivedSnapshotsGuard)> {
993 check_are_snapshots_compatible(
994 full_snapshot_archive_info,
995 incremental_snapshot_archive_info,
996 )?;
997
998 let next_append_vec_id = Arc::new(AtomicAccountsFileId::new(0));
999 let UnarchivedSnapshot {
1000 unpack_dir: full_unpack_dir,
1001 storage: full_storage,
1002 bank_fields: full_bank_fields,
1003 accounts_db_fields: full_accounts_db_fields,
1004 unpacked_snapshots_dir_and_version: full_unpacked_snapshots_dir_and_version,
1005 measure_untar: full_measure_untar,
1006 } = unarchive_snapshot(
1007 &bank_snapshots_dir,
1008 snapshot_paths::TMP_SNAPSHOT_ARCHIVE_PREFIX,
1009 full_snapshot_archive_info.path(),
1010 "snapshot untar",
1011 account_paths,
1012 full_snapshot_archive_info.archive_format(),
1013 next_append_vec_id.clone(),
1014 io_setup,
1015 )?;
1016
1017 let (
1018 incremental_unpack_dir,
1019 incremental_storage,
1020 incremental_bank_fields,
1021 incremental_accounts_db_fields,
1022 incremental_unpacked_snapshots_dir_and_version,
1023 incremental_measure_untar,
1024 ) = if let Some(incremental_snapshot_archive_info) = incremental_snapshot_archive_info {
1025 let UnarchivedSnapshot {
1026 unpack_dir,
1027 storage,
1028 bank_fields,
1029 accounts_db_fields,
1030 unpacked_snapshots_dir_and_version,
1031 measure_untar,
1032 } = unarchive_snapshot(
1033 &bank_snapshots_dir,
1034 snapshot_paths::TMP_SNAPSHOT_ARCHIVE_PREFIX,
1035 incremental_snapshot_archive_info.path(),
1036 "incremental snapshot untar",
1037 account_paths,
1038 incremental_snapshot_archive_info.archive_format(),
1039 next_append_vec_id.clone(),
1040 io_setup,
1041 )?;
1042 (
1043 Some(unpack_dir),
1044 Some(storage),
1045 Some(bank_fields),
1046 Some(accounts_db_fields),
1047 Some(unpacked_snapshots_dir_and_version),
1048 Some(measure_untar),
1049 )
1050 } else {
1051 (None, None, None, None, None, None)
1052 };
1053
1054 let bank_fields = SnapshotBankFields::new(full_bank_fields, incremental_bank_fields);
1055 let accounts_db_fields =
1056 SnapshotAccountsDbFields::new(full_accounts_db_fields, incremental_accounts_db_fields);
1057 let next_append_vec_id = Arc::try_unwrap(next_append_vec_id).unwrap();
1058
1059 Ok((
1060 UnarchivedSnapshots {
1061 full_storage,
1062 incremental_storage,
1063 bank_fields,
1064 accounts_db_fields,
1065 full_unpacked_snapshots_dir_and_version,
1066 incremental_unpacked_snapshots_dir_and_version,
1067 full_measure_untar,
1068 incremental_measure_untar,
1069 next_append_vec_id,
1070 },
1071 UnarchivedSnapshotsGuard {
1072 full_unpack_dir,
1073 incremental_unpack_dir,
1074 },
1075 ))
1076}
1077
1078#[derive(PartialEq, Debug)]
1080enum SnapshotFileKind {
1081 Version,
1082 BankFields,
1083 Storage,
1084}
1085
1086fn get_snapshot_file_kind(filename: &str) -> Option<SnapshotFileKind> {
1088 static VERSION_FILE_REGEX: LazyLock<Regex> =
1089 LazyLock::new(|| Regex::new(r"^version$").unwrap());
1090 static BANK_FIELDS_FILE_REGEX: LazyLock<Regex> =
1091 LazyLock::new(|| Regex::new(r"^[0-9]+(\.pre)?$").unwrap());
1092
1093 if VERSION_FILE_REGEX.is_match(filename) {
1094 Some(SnapshotFileKind::Version)
1095 } else if BANK_FIELDS_FILE_REGEX.is_match(filename) {
1096 Some(SnapshotFileKind::BankFields)
1097 } else if get_slot_and_append_vec_id(filename).is_ok() {
1098 Some(SnapshotFileKind::Storage)
1099 } else {
1100 None
1101 }
1102}
1103
1104fn get_version_and_snapshot_files(
1108 file_receiver: &Receiver<FileInfo>,
1109) -> Result<(FileInfo, FileInfo, Vec<FileInfo>)> {
1110 let mut append_vec_files = Vec::with_capacity(1024);
1111 let mut snapshot_version = None;
1112 let mut snapshot_bank = None;
1113
1114 loop {
1115 if let Ok(file_info) = file_receiver.recv() {
1116 let filename = file_info.path.file_name().unwrap().to_str().unwrap();
1117 match get_snapshot_file_kind(filename) {
1118 Some(SnapshotFileKind::Version) => {
1119 snapshot_version = Some(file_info);
1120
1121 if snapshot_bank.is_some() {
1123 break;
1124 }
1125 }
1126 Some(SnapshotFileKind::BankFields) => {
1127 snapshot_bank = Some(file_info);
1128
1129 if snapshot_version.is_some() {
1131 break;
1132 }
1133 }
1134 Some(SnapshotFileKind::Storage) => {
1135 append_vec_files.push(file_info);
1136 }
1137 None => {} }
1139 } else {
1140 return Err(SnapshotError::RebuildStorages(
1141 "did not receive snapshot file from unpacking threads".to_string(),
1142 ));
1143 }
1144 }
1145 let snapshot_version = snapshot_version.unwrap();
1146 let snapshot_bank = snapshot_bank.unwrap();
1147
1148 Ok((snapshot_version, snapshot_bank, append_vec_files))
1149}
1150
1151struct SnapshotFieldsBundle {
1153 snapshot_version: SnapshotVersion,
1154 bank_fields: BankFieldsToDeserialize,
1155 accounts_db_fields: AccountsDbFields<SerializableAccountStorageEntry>,
1156 append_vec_files: Vec<FileInfo>,
1157}
1158
1159fn snapshot_fields_from_files(file_receiver: &Receiver<FileInfo>) -> Result<SnapshotFieldsBundle> {
1162 let (snapshot_version, snapshot_bank, append_vec_files) =
1163 get_version_and_snapshot_files(file_receiver)?;
1164 let snapshot_version_str = snapshot_version_from_file(snapshot_version)?;
1165 let snapshot_version = snapshot_version_str.parse().map_err(|err| {
1166 IoError::other(format!(
1167 "unsupported snapshot version '{snapshot_version_str}': {err}",
1168 ))
1169 })?;
1170
1171 let mut snapshot_stream = BufReader::new(snapshot_bank.file);
1172 let (bank_fields, accounts_db_fields) = match snapshot_version {
1173 SnapshotVersion::V1_2_0 => serde_snapshot::fields_from_stream(&mut snapshot_stream)?,
1174 };
1175
1176 Ok(SnapshotFieldsBundle {
1177 snapshot_version,
1178 bank_fields,
1179 accounts_db_fields,
1180 append_vec_files,
1181 })
1182}
1183
1184fn create_snapshot_meta_files_for_unarchived_snapshot(unpack_dir: impl AsRef<Path>) -> Result<()> {
1189 let snapshots_dir = unpack_dir.as_ref().join(snapshot_paths::BANK_SNAPSHOTS_DIR);
1190 if !snapshots_dir.is_dir() {
1191 return Err(SnapshotError::NoSnapshotSlotDir(snapshots_dir));
1192 }
1193
1194 let slot_dir = std::fs::read_dir(&snapshots_dir)
1196 .map_err(|_| SnapshotError::NoSnapshotSlotDir(snapshots_dir.clone()))?
1197 .find(|entry| entry.as_ref().unwrap().path().is_dir())
1198 .ok_or_else(|| SnapshotError::NoSnapshotSlotDir(snapshots_dir.clone()))?
1199 .map_err(|_| SnapshotError::NoSnapshotSlotDir(snapshots_dir.clone()))?
1200 .path();
1201
1202 let version_file = unpack_dir
1203 .as_ref()
1204 .join(snapshot_paths::SNAPSHOT_VERSION_FILENAME);
1205 fs::hard_link(
1206 version_file,
1207 slot_dir.join(snapshot_paths::SNAPSHOT_VERSION_FILENAME),
1208 )?;
1209
1210 let status_cache_file = snapshots_dir.join(snapshot_paths::SNAPSHOT_STATUS_CACHE_FILENAME);
1211 fs::hard_link(
1212 status_cache_file,
1213 slot_dir.join(snapshot_paths::SNAPSHOT_STATUS_CACHE_FILENAME),
1214 )?;
1215
1216 Ok(())
1217}
1218
1219#[allow(clippy::too_many_arguments)]
1223fn unarchive_snapshot(
1224 bank_snapshots_dir: impl AsRef<Path>,
1225 unpacked_snapshots_dir_prefix: &'static str,
1226 snapshot_archive_path: impl AsRef<Path>,
1227 measure_name: &'static str,
1228 account_paths: &[PathBuf],
1229 archive_format: ArchiveFormat,
1230 next_append_vec_id: Arc<AtomicAccountsFileId>,
1231 io_setup: &IoSetupState,
1232) -> Result<UnarchivedSnapshot> {
1233 let unpack_dir = tempfile::Builder::new()
1234 .prefix(unpacked_snapshots_dir_prefix)
1235 .tempdir_in(bank_snapshots_dir)?;
1236 let unpacked_snapshots_dir = unpack_dir.path().join(snapshot_paths::BANK_SNAPSHOTS_DIR);
1237
1238 let (file_sender, file_receiver) = crossbeam_channel::unbounded();
1239 thread::scope(|scope| {
1240 let unarchive_handle = streaming_unarchive_snapshot(
1241 scope,
1242 file_sender,
1243 account_paths.to_vec(),
1244 unpack_dir.path().to_path_buf(),
1245 snapshot_archive_path.as_ref().to_path_buf(),
1246 archive_format,
1247 io_setup,
1248 );
1249
1250 let snapshot_result = snapshot_fields_from_files(&file_receiver).and_then(
1251 |SnapshotFieldsBundle {
1252 snapshot_version,
1253 bank_fields,
1254 accounts_db_fields,
1255 append_vec_files,
1256 ..
1257 }| {
1258 let (storage, measure_untar) = measure_time!(
1259 SnapshotStorageRebuilder::rebuild_storages(
1260 append_vec_files.into_iter().chain(file_receiver),
1261 next_append_vec_id,
1262 SnapshotFrom::Archive,
1263 None,
1264 )?,
1265 measure_name
1266 );
1267 info!("{measure_untar}");
1268 create_snapshot_meta_files_for_unarchived_snapshot(&unpack_dir)?;
1269
1270 Ok(UnarchivedSnapshot {
1271 unpack_dir,
1272 storage,
1273 bank_fields,
1274 accounts_db_fields,
1275 unpacked_snapshots_dir_and_version: UnpackedSnapshotsDirAndVersion {
1276 unpacked_snapshots_dir,
1277 snapshot_version,
1278 },
1279 measure_untar,
1280 })
1281 },
1282 );
1283 let unarchive_result = unarchive_handle.join().expect("must join unarchive thread");
1285 match (unarchive_result, snapshot_result) {
1286 (Err(SnapshotError::CrossbeamSend(_)), snap @ Err(_)) => snap,
1289 (Err(err), _) => Err(err),
1290 (Ok(()), snap) => snap,
1291 }
1292 })
1293}
1294
1295fn spawn_streaming_snapshot_dir_files(
1299 snapshot_file_path: PathBuf,
1300 snapshot_version_path: PathBuf,
1301 account_paths: &[PathBuf],
1302) -> (Receiver<FileInfo>, thread::JoinHandle<Result<()>>) {
1303 let (file_sender, file_receiver) = crossbeam_channel::unbounded();
1304 let account_paths = account_paths.to_vec();
1305
1306 let handle = thread::Builder::new()
1307 .name("solSnapDirFiles".to_string())
1308 .spawn(move || {
1309 let snapshot_bank_file_info = FileInfo::new_from_path(snapshot_file_path)?;
1310 file_sender.send(snapshot_bank_file_info)?;
1311 let snapshot_version_file_info = FileInfo::new_from_path(snapshot_version_path)?;
1312 file_sender.send(snapshot_version_file_info)?;
1313
1314 for account_path in account_paths {
1315 for dir_entry_result in fs::read_dir(account_path)? {
1316 let dir_entry = dir_entry_result?;
1317 let path = dir_entry.path();
1318 let file_info = FileInfo::new_from_path(path)?;
1319 file_sender.send(file_info)?;
1320 }
1321 }
1322 Ok::<_, SnapshotError>(())
1323 })
1324 .expect("should spawn thread");
1325
1326 (file_receiver, handle)
1327}
1328
1329fn migrate_legacy_hardlinks(bank_snapshot_dir: &Path, account_run_paths: &[PathBuf]) -> Result<()> {
1338 let accounts_hardlinks_dir =
1339 bank_snapshot_dir.join(snapshot_paths::SNAPSHOT_ACCOUNTS_HARDLINKS);
1340 let mut items: Vec<StorageListItem> = Vec::new();
1341
1342 for entry in fs::read_dir(&accounts_hardlinks_dir).map_err(|err| {
1343 IoError::other(format!(
1344 "failed to read legacy accounts hardlinks dir '{}': {err}",
1345 accounts_hardlinks_dir.display(),
1346 ))
1347 })? {
1348 let symlink_path = entry?.path();
1349 let snapshot_slot_dir = fs::read_link(&symlink_path).map_err(|err| {
1350 IoError::other(format!(
1351 "failed to read symlink '{}': {err}",
1352 symlink_path.display(),
1353 ))
1354 })?;
1355 let run_dir = snapshot_slot_dir
1358 .parent()
1359 .and_then(Path::parent)
1360 .ok_or_else(|| {
1361 IoError::other(format!(
1362 "invalid legacy hardlink target '{}'",
1363 snapshot_slot_dir.display(),
1364 ))
1365 })?
1366 .join(ACCOUNTS_RUN_DIR);
1367 if !account_run_paths.contains(&run_dir) {
1372 return Err(IoError::other(format!(
1373 "legacy hardlink target '{}' points to run dir '{}' which is not in the current \
1374 account paths ({:?}); the account paths configuration has changed since this \
1375 snapshot was taken — load from a snapshot archive instead",
1376 snapshot_slot_dir.display(),
1377 run_dir.display(),
1378 account_run_paths,
1379 ))
1380 .into());
1381 }
1382
1383 for file_entry in fs::read_dir(&snapshot_slot_dir).map_err(|err| {
1384 IoError::other(format!(
1385 "failed to read legacy hardlink dir '{}': {err}",
1386 snapshot_slot_dir.display(),
1387 ))
1388 })? {
1389 let src = file_entry?.path();
1390 let Some(name) = src.file_name().and_then(|n| n.to_str()) else {
1391 continue;
1392 };
1393 let (slot, id) = get_slot_and_append_vec_id(name)?;
1394 let dest = run_dir.join(name);
1395 fs::rename(&src, &dest).map_err(|err| {
1396 IoError::other(format!(
1397 "failed to migrate legacy storage from '{}' to '{}': {err}",
1398 src.display(),
1399 dest.display(),
1400 ))
1401 })?;
1402 items.push(StorageListItem {
1403 slot,
1404 id: id as AccountsFileId,
1405 });
1406 }
1407 }
1408
1409 serialize_storages_list_to_snapshot(
1414 bank_snapshot_dir,
1415 StoragesList::from_items(items),
1416 &IoSetupState::default(),
1417 )?;
1418
1419 fs::remove_dir_all(&accounts_hardlinks_dir).map_err(|err| {
1423 IoError::other(format!(
1424 "failed to remove legacy accounts hardlinks dir '{}': {err}",
1425 accounts_hardlinks_dir.display(),
1426 ))
1427 })?;
1428 wipe_account_snapshot_dirs(account_run_paths);
1429
1430 mark_bank_snapshot_as_loadable(bank_snapshot_dir)?;
1433
1434 Ok(())
1435}
1436
1437fn prune_stale_storages(account_paths: &[PathBuf], storages_list: StoragesList) -> Result<()> {
1441 let expected_storages = storages_list.into_slot_file_id_set();
1442 for account_path in account_paths {
1443 let read_dir = fs::read_dir(account_path).map_err(|err| {
1444 IoError::other(format!(
1445 "failed to read account path '{}': {err}",
1446 account_path.display(),
1447 ))
1448 })?;
1449 for entry in read_dir {
1450 let path = entry?.path();
1451 let Some(name) = path.file_name().and_then(|n| n.to_str()) else {
1452 continue;
1453 };
1454 let Ok((slot, id)) = get_slot_and_append_vec_id(name) else {
1455 continue;
1457 };
1458 if !expected_storages.contains(&(slot, id as AccountsFileId)) {
1459 info!(
1460 "Removing stale storage file '{}' not in storages list",
1461 path.display(),
1462 );
1463 fs::remove_file(&path)?
1464 }
1465 }
1466 }
1467 Ok(())
1468}
1469
1470pub(crate) fn rebuild_storages_from_snapshot_dir(
1475 snapshot_info: &BankSnapshotInfo,
1476 account_paths: &[PathBuf],
1477 next_append_vec_id: Arc<AtomicAccountsFileId>,
1478) -> Result<(
1479 AccountStorageMap,
1480 BankFieldsToDeserialize,
1481 AccountsDbFields<SerializableAccountStorageEntry>,
1482)> {
1483 let bank_snapshot_dir = &snapshot_info.snapshot_dir;
1484
1485 let obsolete_accounts = snapshot_info
1489 .fastboot_version
1490 .as_ref()
1491 .is_some_and(|fastboot_version| fastboot_version.major >= 2)
1492 .then(|| deserialize_obsolete_accounts(bank_snapshot_dir, MAX_OBSOLETE_ACCOUNTS_FILE_SIZE))
1493 .transpose()
1494 .map_err(|err| {
1495 IoError::other(format!(
1496 "failed to read obsolete accounts file '{}': {err}",
1497 bank_snapshot_dir.display()
1498 ))
1499 })?;
1500
1501 let storages_list_path =
1505 bank_snapshot_dir.join(snapshot_paths::SNAPSHOT_STORAGES_LIST_FILENAME);
1506 if !storages_list_path.exists() {
1507 migrate_legacy_hardlinks(bank_snapshot_dir, account_paths)?;
1513 }
1514 let storages_list =
1515 deserialize_storages_list(&storages_list_path, MAX_STORAGES_LIST_FILE_SIZE)?;
1516 prune_stale_storages(account_paths, storages_list)?;
1517
1518 let snapshot_file_path = snapshot_info.snapshot_path();
1519 let snapshot_version_path = bank_snapshot_dir.join(snapshot_paths::SNAPSHOT_VERSION_FILENAME);
1520 let (file_receiver, stream_files_handle) = spawn_streaming_snapshot_dir_files(
1521 snapshot_file_path,
1522 snapshot_version_path,
1523 account_paths,
1524 );
1525
1526 let snapshot_result = snapshot_fields_from_files(&file_receiver).and_then(
1527 |SnapshotFieldsBundle {
1528 bank_fields,
1529 accounts_db_fields,
1530 append_vec_files,
1531 ..
1532 }| {
1533 let storage = SnapshotStorageRebuilder::rebuild_storages(
1534 append_vec_files.into_iter().chain(file_receiver),
1535 next_append_vec_id,
1536 SnapshotFrom::Dir,
1537 obsolete_accounts,
1538 )?;
1539 Ok((storage, bank_fields, accounts_db_fields))
1540 },
1541 );
1542
1543 let stream_files_result = stream_files_handle.join().expect("must join dir thread");
1545 match (stream_files_result, snapshot_result) {
1546 (Err(SnapshotError::CrossbeamSend(_)), snap @ Err(_)) => snap,
1549 (Err(err), _) => Err(err),
1550 (Ok(()), snap) => snap,
1551 }
1552}
1553
1554fn snapshot_version_from_file(mut file_info: FileInfo) -> io::Result<String> {
1558 let file_size = file_info.size;
1559 if file_size > MAX_SNAPSHOT_VERSION_FILE_SIZE {
1560 let error_message = format!(
1561 "snapshot version file too large: '{}' has {} bytes (max size is {} bytes)",
1562 file_info.path.display(),
1563 file_size,
1564 MAX_SNAPSHOT_VERSION_FILE_SIZE,
1565 );
1566 return Err(IoError::other(error_message));
1567 }
1568
1569 let mut snapshot_version = String::new();
1571 file_info
1572 .file
1573 .read_to_string(&mut snapshot_version)
1574 .map_err(|err| {
1575 IoError::other(format!(
1576 "failed to read snapshot version from file '{}': {err}",
1577 file_info.path.display()
1578 ))
1579 })?;
1580
1581 Ok(snapshot_version.trim().to_string())
1582}
1583
1584fn check_are_snapshots_compatible(
1587 full_snapshot_archive_info: &FullSnapshotArchiveInfo,
1588 incremental_snapshot_archive_info: Option<&IncrementalSnapshotArchiveInfo>,
1589) -> Result<()> {
1590 if incremental_snapshot_archive_info.is_none() {
1591 return Ok(());
1592 }
1593
1594 let incremental_snapshot_archive_info = incremental_snapshot_archive_info.unwrap();
1595
1596 (full_snapshot_archive_info.slot() == incremental_snapshot_archive_info.base_slot())
1597 .then_some(())
1598 .ok_or_else(|| {
1599 SnapshotError::MismatchedBaseSlot(
1600 full_snapshot_archive_info.slot(),
1601 incremental_snapshot_archive_info.base_slot(),
1602 )
1603 })
1604}
1605
1606pub fn purge_old_snapshot_archives(
1607 full_snapshot_archives_dir: impl AsRef<Path>,
1608 incremental_snapshot_archives_dir: impl AsRef<Path>,
1609 maximum_full_snapshot_archives_to_retain: NonZeroUsize,
1610 maximum_incremental_snapshot_archives_to_retain: NonZeroUsize,
1611) {
1612 info!(
1613 "Purging old full snapshot archives in {}, retaining up to {} full snapshots",
1614 full_snapshot_archives_dir.as_ref().display(),
1615 maximum_full_snapshot_archives_to_retain
1616 );
1617
1618 let mut full_snapshot_archives =
1619 snapshot_paths::full_snapshot_archives_iter(full_snapshot_archives_dir.as_ref())
1620 .collect::<Vec<_>>();
1621 full_snapshot_archives.sort_unstable();
1622 full_snapshot_archives.reverse();
1623
1624 let num_to_retain = full_snapshot_archives
1625 .len()
1626 .min(maximum_full_snapshot_archives_to_retain.get());
1627 trace!(
1628 "There are {} full snapshot archives, retaining {}",
1629 full_snapshot_archives.len(),
1630 num_to_retain,
1631 );
1632
1633 let (full_snapshot_archives_to_retain, full_snapshot_archives_to_remove) =
1634 if full_snapshot_archives.is_empty() {
1635 None
1636 } else {
1637 Some(full_snapshot_archives.split_at(num_to_retain))
1638 }
1639 .unwrap_or_default();
1640
1641 let retained_full_snapshot_slots = full_snapshot_archives_to_retain
1642 .iter()
1643 .map(|ai| ai.slot())
1644 .collect::<HashSet<_>>();
1645
1646 fn remove_archives<T: SnapshotArchiveInfoGetter>(archives: &[T]) {
1647 for path in archives.iter().map(|a| a.path()) {
1648 trace!("Removing snapshot archive: {}", path.display());
1649 let result = fs::remove_file(path);
1650 if let Err(err) = result {
1651 info!(
1652 "Failed to remove snapshot archive '{}': {err}",
1653 path.display()
1654 );
1655 }
1656 }
1657 }
1658 remove_archives(full_snapshot_archives_to_remove);
1659
1660 info!(
1661 "Purging old incremental snapshot archives in {}, retaining up to {} incremental snapshots",
1662 incremental_snapshot_archives_dir.as_ref().display(),
1663 maximum_incremental_snapshot_archives_to_retain
1664 );
1665 let mut incremental_snapshot_archives_by_base_slot = HashMap::<Slot, Vec<_>>::new();
1666 for incremental_snapshot_archive in
1667 incremental_snapshot_archives_iter(incremental_snapshot_archives_dir.as_ref())
1668 {
1669 incremental_snapshot_archives_by_base_slot
1670 .entry(incremental_snapshot_archive.base_slot())
1671 .or_default()
1672 .push(incremental_snapshot_archive)
1673 }
1674
1675 let highest_full_snapshot_slot = retained_full_snapshot_slots.iter().max().copied();
1676 for (base_slot, mut incremental_snapshot_archives) in incremental_snapshot_archives_by_base_slot
1677 {
1678 incremental_snapshot_archives.sort_unstable();
1679 let num_to_retain = if Some(base_slot) == highest_full_snapshot_slot {
1680 maximum_incremental_snapshot_archives_to_retain.get()
1681 } else {
1682 usize::from(retained_full_snapshot_slots.contains(&base_slot))
1683 };
1684 trace!(
1685 "There are {} incremental snapshot archives for base slot {}, removing {} of them",
1686 incremental_snapshot_archives.len(),
1687 base_slot,
1688 incremental_snapshot_archives
1689 .len()
1690 .saturating_sub(num_to_retain),
1691 );
1692
1693 incremental_snapshot_archives.truncate(
1694 incremental_snapshot_archives
1695 .len()
1696 .saturating_sub(num_to_retain),
1697 );
1698 remove_archives(&incremental_snapshot_archives);
1699 }
1700}
1701
1702pub fn verify_unpacked_snapshots_dir_and_version(
1703 unpacked_snapshots_dir_and_version: &UnpackedSnapshotsDirAndVersion,
1704) -> Result<(SnapshotVersion, BankSnapshotInfo)> {
1705 info!(
1706 "snapshot version: {}",
1707 unpacked_snapshots_dir_and_version.snapshot_version
1708 );
1709
1710 let snapshot_version = unpacked_snapshots_dir_and_version.snapshot_version;
1711 let mut bank_snapshots =
1712 get_bank_snapshots(&unpacked_snapshots_dir_and_version.unpacked_snapshots_dir);
1713 if bank_snapshots.len() > 1 {
1714 return Err(IoError::other(format!(
1715 "invalid snapshot format: only one snapshot allowed, but found {}",
1716 bank_snapshots.len(),
1717 ))
1718 .into());
1719 }
1720 let root_paths = bank_snapshots.pop().ok_or_else(|| {
1721 IoError::other(format!(
1722 "no snapshots found in snapshots directory '{}'",
1723 unpacked_snapshots_dir_and_version
1724 .unpacked_snapshots_dir
1725 .display(),
1726 ))
1727 })?;
1728 Ok((snapshot_version, root_paths))
1729}
1730
1731#[derive(Debug, Copy, Clone)]
1732pub enum VerifyBank {
1734 Deterministic,
1736 NonDeterministic,
1739}
1740
1741pub fn wipe_account_snapshot_dirs(account_run_paths: &[PathBuf]) {
1751 for account_run_path in account_run_paths {
1752 if let Some(parent) = account_run_path.parent() {
1753 move_and_async_delete_path_contents(parent.join(ACCOUNTS_SNAPSHOT_DIR));
1754 }
1755 }
1756}
1757
1758pub fn purge_all_bank_snapshots(bank_snapshots_dir: impl AsRef<Path>) {
1760 let bank_snapshots = get_bank_snapshots(&bank_snapshots_dir);
1761 purge_bank_snapshots(&bank_snapshots);
1762}
1763
1764pub fn purge_old_bank_snapshots(
1766 bank_snapshots_dir: impl AsRef<Path>,
1767 num_bank_snapshots_to_retain: usize,
1768) {
1769 let mut bank_snapshots = get_bank_snapshots(&bank_snapshots_dir);
1770
1771 bank_snapshots.sort_unstable();
1772 purge_bank_snapshots(
1773 bank_snapshots
1774 .iter()
1775 .rev()
1776 .skip(num_bank_snapshots_to_retain),
1777 );
1778}
1779
1780pub fn purge_old_bank_snapshots_at_startup(bank_snapshots_dir: impl AsRef<Path>) {
1782 purge_old_bank_snapshots(&bank_snapshots_dir, 1);
1783
1784 let highest_bank_snapshot = get_highest_bank_snapshot(&bank_snapshots_dir);
1785 if let Some(highest_bank_snapshot) = highest_bank_snapshot {
1786 debug!(
1787 "Retained bank snapshot for slot {}, and purged the rest.",
1788 highest_bank_snapshot.slot
1789 );
1790 }
1791}
1792
1793pub fn purge_bank_snapshots_older_than_slot(bank_snapshots_dir: impl AsRef<Path>, slot: Slot) {
1795 let mut bank_snapshots = get_bank_snapshots(&bank_snapshots_dir);
1796 bank_snapshots.retain(|bank_snapshot| bank_snapshot.slot < slot);
1797 purge_bank_snapshots(&bank_snapshots);
1798}
1799
1800fn purge_bank_snapshots<'a>(bank_snapshots: impl IntoIterator<Item = &'a BankSnapshotInfo>) {
1804 for snapshot_dir in bank_snapshots.into_iter().map(|s| &s.snapshot_dir) {
1805 if purge_bank_snapshot(snapshot_dir).is_err() {
1806 warn!("Failed to purge bank snapshot: {}", snapshot_dir.display());
1807 }
1808 }
1809}
1810
1811pub fn purge_bank_snapshot(bank_snapshot_dir: impl AsRef<Path>) -> Result<()> {
1813 const FN_ERR: &str = "failed to purge bank snapshot";
1814 let accounts_hardlinks_dir = bank_snapshot_dir
1819 .as_ref()
1820 .join(snapshot_paths::SNAPSHOT_ACCOUNTS_HARDLINKS);
1821 if accounts_hardlinks_dir.is_dir() {
1822 let read_dir = fs::read_dir(&accounts_hardlinks_dir).map_err(|err| {
1823 IoError::other(format!(
1824 "{FN_ERR}: failed to read accounts hardlinks dir '{}': {err}",
1825 accounts_hardlinks_dir.display(),
1826 ))
1827 })?;
1828 for entry in read_dir {
1829 let accounts_hardlink_dir = entry?.path();
1830 let accounts_hardlink_dir = fs::read_link(&accounts_hardlink_dir).map_err(|err| {
1831 IoError::other(format!(
1832 "{FN_ERR}: failed to read symlink '{}': {err}",
1833 accounts_hardlink_dir.display(),
1834 ))
1835 })?;
1836 move_and_async_delete_path(&accounts_hardlink_dir);
1837 }
1838 }
1839 fs::remove_dir_all(&bank_snapshot_dir).map_err(|err| {
1840 IoError::other(format!(
1841 "{FN_ERR}: failed to remove dir '{}': {err}",
1842 bank_snapshot_dir.as_ref().display(),
1843 ))
1844 })?;
1845 Ok(())
1846}
1847
1848pub fn should_take_full_snapshot(
1849 block_height: Slot,
1850 full_snapshot_archive_interval_slots: Slot,
1851) -> bool {
1852 block_height.is_multiple_of(full_snapshot_archive_interval_slots)
1853}
1854
1855pub fn should_take_incremental_snapshot(
1856 block_height: Slot,
1857 incremental_snapshot_archive_interval_slots: Slot,
1858 latest_full_snapshot_slot: Option<Slot>,
1859) -> bool {
1860 block_height.is_multiple_of(incremental_snapshot_archive_interval_slots)
1861 && latest_full_snapshot_slot.is_some()
1862}
1863
1864#[cfg(feature = "dev-context-only-utils")]
1869pub fn create_tmp_accounts_dir_for_tests() -> (TempDir, PathBuf) {
1870 let tmp_dir = tempfile::TempDir::new().unwrap();
1871 let account_dir = create_accounts_run_and_snapshot_dirs(&tmp_dir).unwrap().0;
1872 (tmp_dir, account_dir)
1873}
1874
1875#[cfg(test)]
1876mod tests {
1877 use {
1878 super::*,
1879 agave_snapshots::{
1880 paths::{
1881 full_snapshot_archives_iter, get_highest_full_snapshot_archive_slot,
1882 get_highest_incremental_snapshot_archive_slot,
1883 },
1884 snapshot_config::{
1885 DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
1886 DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
1887 },
1888 },
1889 assert_matches::assert_matches,
1890 bincode::{deserialize_from, serialize_into},
1891 solana_accounts_db::accounts_file::{AccountsFile, AccountsFileProvider},
1892 solana_hash::Hash,
1893 std::{convert::TryFrom, mem::size_of},
1894 tempfile::NamedTempFile,
1895 test_case::test_case,
1896 };
1897
1898 #[test]
1899 fn test_serialize_snapshot_data_file_under_limit() {
1900 let temp_dir = tempfile::TempDir::new().unwrap();
1901 let expected_consumed_size = size_of::<u32>() as u64;
1902 let consumed_size = serialize_snapshot_data_file_capped(
1903 &temp_dir.path().join("data-file"),
1904 expected_consumed_size,
1905 &IoSetupState::default(),
1906 |stream| {
1907 serialize_into(stream, &2323_u32)?;
1908 Ok(())
1909 },
1910 )
1911 .unwrap();
1912 assert_eq!(consumed_size, expected_consumed_size);
1913 }
1914
1915 #[test]
1916 fn test_serialize_snapshot_data_file_over_limit() {
1917 let temp_dir = tempfile::TempDir::new().unwrap();
1918 let expected_consumed_size = size_of::<u32>() as u64;
1919 let result = serialize_snapshot_data_file_capped(
1920 &temp_dir.path().join("data-file"),
1921 expected_consumed_size - 1,
1922 &IoSetupState::default(),
1923 |stream| {
1924 serialize_into(stream, &2323_u32)?;
1925 Ok(())
1926 },
1927 );
1928 assert_matches!(result, Err(SnapshotError::Io(ref message)) if message.to_string().contains("bytes would exceed limit of"));
1929 }
1930
1931 #[test]
1932 fn test_deserialize_snapshot_data_file_under_limit() {
1933 let expected_data = 2323_u32;
1934 let expected_consumed_size = size_of::<u32>() as u64;
1935
1936 let temp_dir = tempfile::TempDir::new().unwrap();
1937 serialize_snapshot_data_file_capped(
1938 &temp_dir.path().join("data-file"),
1939 expected_consumed_size,
1940 &IoSetupState::default(),
1941 |stream| {
1942 serialize_into(stream, &expected_data)?;
1943 Ok(())
1944 },
1945 )
1946 .unwrap();
1947
1948 let snapshot_root_paths = SnapshotRootPaths {
1949 full_snapshot_root_file_path: temp_dir.path().join("data-file"),
1950 incremental_snapshot_root_file_path: None,
1951 };
1952
1953 let actual_data = deserialize_snapshot_data_files_capped(
1954 &snapshot_root_paths,
1955 expected_consumed_size,
1956 |stream| {
1957 Ok(deserialize_from::<_, u32>(
1958 &mut stream.full_snapshot_stream,
1959 )?)
1960 },
1961 )
1962 .unwrap();
1963 assert_eq!(actual_data, expected_data);
1964 }
1965
1966 #[test]
1967 fn test_deserialize_snapshot_data_file_over_limit() {
1968 let expected_data = 2323_u32;
1969 let expected_consumed_size = size_of::<u32>() as u64;
1970
1971 let temp_dir = tempfile::TempDir::new().unwrap();
1972 serialize_snapshot_data_file_capped(
1973 &temp_dir.path().join("data-file"),
1974 expected_consumed_size,
1975 &IoSetupState::default(),
1976 |stream| {
1977 serialize_into(stream, &expected_data)?;
1978 Ok(())
1979 },
1980 )
1981 .unwrap();
1982
1983 let snapshot_root_paths = SnapshotRootPaths {
1984 full_snapshot_root_file_path: temp_dir.path().join("data-file"),
1985 incremental_snapshot_root_file_path: None,
1986 };
1987
1988 let result = deserialize_snapshot_data_files_capped(
1989 &snapshot_root_paths,
1990 expected_consumed_size - 1,
1991 |stream| {
1992 Ok(deserialize_from::<_, u32>(
1993 &mut stream.full_snapshot_stream,
1994 )?)
1995 },
1996 );
1997 assert_matches!(result, Err(SnapshotError::Io(ref message)) if message.to_string().starts_with("too large snapshot data file to deserialize"));
1998 }
1999
2000 #[test]
2001 fn test_deserialize_snapshot_data_file_extra_data() {
2002 let expected_data = 2323_u32;
2003 let expected_consumed_size = size_of::<u32>() as u64;
2004
2005 let temp_dir = tempfile::TempDir::new().unwrap();
2006 serialize_snapshot_data_file_capped(
2007 &temp_dir.path().join("data-file"),
2008 expected_consumed_size * 2,
2009 &IoSetupState::default(),
2010 |stream| {
2011 serialize_into(&mut *stream, &expected_data)?;
2012 serialize_into(&mut *stream, &expected_data)?;
2013 Ok(())
2014 },
2015 )
2016 .unwrap();
2017
2018 let snapshot_root_paths = SnapshotRootPaths {
2019 full_snapshot_root_file_path: temp_dir.path().join("data-file"),
2020 incremental_snapshot_root_file_path: None,
2021 };
2022
2023 let result = deserialize_snapshot_data_files_capped(
2024 &snapshot_root_paths,
2025 expected_consumed_size * 2,
2026 |stream| {
2027 Ok(deserialize_from::<_, u32>(
2028 &mut stream.full_snapshot_stream,
2029 )?)
2030 },
2031 );
2032 assert_matches!(result, Err(SnapshotError::Io(ref message)) if message.to_string().starts_with("invalid snapshot data file"));
2033 }
2034
2035 #[test]
2036 fn test_snapshot_version_from_file_under_limit() {
2037 let file_content = SnapshotVersion::default().as_str();
2038 let mut file = NamedTempFile::new().unwrap();
2039 file.write_all(file_content.as_bytes()).unwrap();
2040 let file_info = FileInfo::new_from_path(file.path()).unwrap();
2041 let version_from_file = snapshot_version_from_file(file_info).unwrap();
2042 assert_eq!(version_from_file, file_content);
2043 }
2044
2045 #[test]
2046 fn test_snapshot_version_from_file_over_limit() {
2047 let over_limit_size = usize::try_from(MAX_SNAPSHOT_VERSION_FILE_SIZE + 1).unwrap();
2048 let file_content = vec![7u8; over_limit_size];
2049 let mut file = NamedTempFile::new().unwrap();
2050 file.write_all(&file_content).unwrap();
2051 let file_info = FileInfo::new_from_path(file.path()).unwrap();
2052 assert_matches!(
2053 snapshot_version_from_file(file_info),
2054 Err(ref message) if message.to_string().starts_with("snapshot version file too large")
2055 );
2056 }
2057
2058 #[test]
2059 fn test_check_are_snapshots_compatible() {
2060 let slot1: Slot = 1234;
2061 let slot2: Slot = 5678;
2062 let slot3: Slot = 999_999;
2063
2064 let full_snapshot_archive_info = FullSnapshotArchiveInfo::new_from_path(PathBuf::from(
2065 format!("/dir/snapshot-{}-{}.tar.zst", slot1, Hash::new_unique()),
2066 ))
2067 .unwrap();
2068
2069 assert!(check_are_snapshots_compatible(&full_snapshot_archive_info, None,).is_ok());
2070
2071 let incremental_snapshot_archive_info =
2072 IncrementalSnapshotArchiveInfo::new_from_path(PathBuf::from(format!(
2073 "/dir/incremental-snapshot-{}-{}-{}.tar.zst",
2074 slot1,
2075 slot2,
2076 Hash::new_unique()
2077 )))
2078 .unwrap();
2079
2080 assert!(
2081 check_are_snapshots_compatible(
2082 &full_snapshot_archive_info,
2083 Some(&incremental_snapshot_archive_info)
2084 )
2085 .is_ok()
2086 );
2087
2088 let incremental_snapshot_archive_info =
2089 IncrementalSnapshotArchiveInfo::new_from_path(PathBuf::from(format!(
2090 "/dir/incremental-snapshot-{}-{}-{}.tar.zst",
2091 slot2,
2092 slot3,
2093 Hash::new_unique()
2094 )))
2095 .unwrap();
2096
2097 assert!(
2098 check_are_snapshots_compatible(
2099 &full_snapshot_archive_info,
2100 Some(&incremental_snapshot_archive_info)
2101 )
2102 .is_err()
2103 );
2104 }
2105
2106 fn common_create_bank_snapshot_files(
2108 bank_snapshots_dir: &Path,
2109 min_slot: Slot,
2110 max_slot: Slot,
2111 ) {
2112 for slot in min_slot..max_slot {
2113 let snapshot_dir = snapshot_paths::get_bank_snapshot_dir(bank_snapshots_dir, slot);
2114 fs::create_dir_all(&snapshot_dir).unwrap();
2115
2116 let snapshot_filename = snapshot_paths::get_snapshot_file_name(slot);
2117 let snapshot_path = snapshot_dir.join(snapshot_filename);
2118 fs::File::create(snapshot_path).unwrap();
2119
2120 let status_cache_file =
2121 snapshot_dir.join(snapshot_paths::SNAPSHOT_STATUS_CACHE_FILENAME);
2122 fs::File::create(status_cache_file).unwrap();
2123
2124 let version_path = snapshot_dir.join(snapshot_paths::SNAPSHOT_VERSION_FILENAME);
2125 fs::write(version_path, SnapshotVersion::default().as_str().as_bytes()).unwrap();
2126 }
2127 }
2128
2129 #[test]
2130 fn test_get_bank_snapshots() {
2131 let temp_snapshots_dir = tempfile::TempDir::new().unwrap();
2132 let min_slot = 10;
2133 let max_slot = 20;
2134 common_create_bank_snapshot_files(temp_snapshots_dir.path(), min_slot, max_slot);
2135
2136 let bank_snapshots = get_bank_snapshots(temp_snapshots_dir.path());
2137 assert_eq!(bank_snapshots.len() as Slot, max_slot - min_slot);
2138 }
2139
2140 #[test]
2141 fn test_get_highest_bank_snapshot() {
2142 let temp_snapshots_dir = tempfile::TempDir::new().unwrap();
2143 let min_slot = 99;
2144 let max_slot = 123;
2145 common_create_bank_snapshot_files(temp_snapshots_dir.path(), min_slot, max_slot);
2146
2147 let highest_bank_snapshot = get_highest_bank_snapshot(temp_snapshots_dir.path());
2148 assert!(highest_bank_snapshot.is_some());
2149 assert_eq!(highest_bank_snapshot.unwrap().slot, max_slot - 1);
2150 }
2151
2152 fn common_create_snapshot_archive_files(
2158 full_snapshot_archives_dir: &Path,
2159 incremental_snapshot_archives_dir: &Path,
2160 min_full_snapshot_slot: Slot,
2161 max_full_snapshot_slot: Slot,
2162 min_incremental_snapshot_slot: Slot,
2163 max_incremental_snapshot_slot: Slot,
2164 ) {
2165 fs::create_dir_all(full_snapshot_archives_dir).unwrap();
2166 fs::create_dir_all(incremental_snapshot_archives_dir).unwrap();
2167 for full_snapshot_slot in min_full_snapshot_slot..max_full_snapshot_slot {
2168 for incremental_snapshot_slot in
2169 min_incremental_snapshot_slot..max_incremental_snapshot_slot
2170 {
2171 let snapshot_filename = format!(
2172 "incremental-snapshot-{}-{}-{}.tar.zst",
2173 full_snapshot_slot,
2174 incremental_snapshot_slot,
2175 Hash::default()
2176 );
2177 let snapshot_filepath = incremental_snapshot_archives_dir.join(snapshot_filename);
2178 fs::File::create(snapshot_filepath).unwrap();
2179 }
2180
2181 let snapshot_filename = format!(
2182 "snapshot-{}-{}.tar.zst",
2183 full_snapshot_slot,
2184 Hash::default()
2185 );
2186 let snapshot_filepath = full_snapshot_archives_dir.join(snapshot_filename);
2187 fs::File::create(snapshot_filepath).unwrap();
2188
2189 let bad_filename = format!(
2191 "incremental-snapshot-{}-{}-bad!hash.tar.zst",
2192 full_snapshot_slot,
2193 max_incremental_snapshot_slot + 1,
2194 );
2195 let bad_filepath = incremental_snapshot_archives_dir.join(bad_filename);
2196 fs::File::create(bad_filepath).unwrap();
2197 }
2198
2199 let bad_filename = format!("snapshot-{}-bad!hash.tar.zst", max_full_snapshot_slot + 1);
2202 let bad_filepath = full_snapshot_archives_dir.join(bad_filename);
2203 fs::File::create(bad_filepath).unwrap();
2204 }
2205
2206 #[test]
2207 fn test_get_full_snapshot_archives() {
2208 let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2209 let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2210 let min_slot = 123;
2211 let max_slot = 456;
2212 common_create_snapshot_archive_files(
2213 full_snapshot_archives_dir.path(),
2214 incremental_snapshot_archives_dir.path(),
2215 min_slot,
2216 max_slot,
2217 0,
2218 0,
2219 );
2220
2221 let snapshot_archives =
2222 full_snapshot_archives_iter(full_snapshot_archives_dir.path()).collect::<Vec<_>>();
2223 assert_eq!(snapshot_archives.len() as Slot, max_slot - min_slot);
2224 }
2225
2226 #[test]
2227 fn test_get_full_snapshot_archives_remote() {
2228 let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2229 let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2230 let min_slot = 123;
2231 let max_slot = 456;
2232 common_create_snapshot_archive_files(
2233 &full_snapshot_archives_dir
2234 .path()
2235 .join(snapshot_paths::SNAPSHOT_ARCHIVE_DOWNLOAD_DIR),
2236 &incremental_snapshot_archives_dir
2237 .path()
2238 .join(snapshot_paths::SNAPSHOT_ARCHIVE_DOWNLOAD_DIR),
2239 min_slot,
2240 max_slot,
2241 0,
2242 0,
2243 );
2244
2245 let snapshot_archives =
2246 full_snapshot_archives_iter(full_snapshot_archives_dir.path()).collect::<Vec<_>>();
2247 assert_eq!(snapshot_archives.len() as Slot, max_slot - min_slot);
2248 assert!(snapshot_archives.iter().all(|info| info.is_remote()));
2249 }
2250
2251 #[test]
2252 fn test_get_incremental_snapshot_archives() {
2253 let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2254 let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2255 let min_full_snapshot_slot = 12;
2256 let max_full_snapshot_slot = 23;
2257 let min_incremental_snapshot_slot = 34;
2258 let max_incremental_snapshot_slot = 45;
2259 common_create_snapshot_archive_files(
2260 full_snapshot_archives_dir.path(),
2261 incremental_snapshot_archives_dir.path(),
2262 min_full_snapshot_slot,
2263 max_full_snapshot_slot,
2264 min_incremental_snapshot_slot,
2265 max_incremental_snapshot_slot,
2266 );
2267
2268 let incremental_snapshot_archives =
2269 incremental_snapshot_archives_iter(incremental_snapshot_archives_dir.path())
2270 .collect::<Vec<_>>();
2271 assert_eq!(
2272 incremental_snapshot_archives.len() as Slot,
2273 (max_full_snapshot_slot - min_full_snapshot_slot)
2274 * (max_incremental_snapshot_slot - min_incremental_snapshot_slot)
2275 );
2276 }
2277
2278 #[test]
2279 fn test_get_incremental_snapshot_archives_remote() {
2280 let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2281 let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2282 let min_full_snapshot_slot = 12;
2283 let max_full_snapshot_slot = 23;
2284 let min_incremental_snapshot_slot = 34;
2285 let max_incremental_snapshot_slot = 45;
2286 common_create_snapshot_archive_files(
2287 &full_snapshot_archives_dir
2288 .path()
2289 .join(snapshot_paths::SNAPSHOT_ARCHIVE_DOWNLOAD_DIR),
2290 &incremental_snapshot_archives_dir
2291 .path()
2292 .join(snapshot_paths::SNAPSHOT_ARCHIVE_DOWNLOAD_DIR),
2293 min_full_snapshot_slot,
2294 max_full_snapshot_slot,
2295 min_incremental_snapshot_slot,
2296 max_incremental_snapshot_slot,
2297 );
2298
2299 let incremental_snapshot_archives =
2300 incremental_snapshot_archives_iter(incremental_snapshot_archives_dir.path())
2301 .collect::<Vec<_>>();
2302 assert_eq!(
2303 incremental_snapshot_archives.len() as Slot,
2304 (max_full_snapshot_slot - min_full_snapshot_slot)
2305 * (max_incremental_snapshot_slot - min_incremental_snapshot_slot)
2306 );
2307 assert!(
2308 incremental_snapshot_archives
2309 .iter()
2310 .all(|info| info.is_remote())
2311 );
2312 }
2313
2314 #[test]
2315 fn test_get_highest_full_snapshot_archive_slot() {
2316 let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2317 let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2318 let min_slot = 123;
2319 let max_slot = 456;
2320 common_create_snapshot_archive_files(
2321 full_snapshot_archives_dir.path(),
2322 incremental_snapshot_archives_dir.path(),
2323 min_slot,
2324 max_slot,
2325 0,
2326 0,
2327 );
2328
2329 assert_eq!(
2330 get_highest_full_snapshot_archive_slot(full_snapshot_archives_dir.path()),
2331 Some(max_slot - 1)
2332 );
2333 }
2334
2335 #[test]
2336 fn test_get_highest_incremental_snapshot_slot() {
2337 let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2338 let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2339 let min_full_snapshot_slot = 12;
2340 let max_full_snapshot_slot = 23;
2341 let min_incremental_snapshot_slot = 34;
2342 let max_incremental_snapshot_slot = 45;
2343 common_create_snapshot_archive_files(
2344 full_snapshot_archives_dir.path(),
2345 incremental_snapshot_archives_dir.path(),
2346 min_full_snapshot_slot,
2347 max_full_snapshot_slot,
2348 min_incremental_snapshot_slot,
2349 max_incremental_snapshot_slot,
2350 );
2351
2352 for full_snapshot_slot in min_full_snapshot_slot..max_full_snapshot_slot {
2353 assert_eq!(
2354 get_highest_incremental_snapshot_archive_slot(
2355 incremental_snapshot_archives_dir.path(),
2356 full_snapshot_slot
2357 ),
2358 Some(max_incremental_snapshot_slot - 1)
2359 );
2360 }
2361
2362 assert_eq!(
2363 get_highest_incremental_snapshot_archive_slot(
2364 incremental_snapshot_archives_dir.path(),
2365 max_full_snapshot_slot
2366 ),
2367 None
2368 );
2369 }
2370
2371 fn common_test_purge_old_snapshot_archives(
2372 snapshot_names: &[&String],
2373 maximum_full_snapshot_archives_to_retain: NonZeroUsize,
2374 maximum_incremental_snapshot_archives_to_retain: NonZeroUsize,
2375 expected_snapshots: &[&String],
2376 ) {
2377 let temp_snap_dir = tempfile::TempDir::new().unwrap();
2378
2379 for snap_name in snapshot_names {
2380 let snap_path = temp_snap_dir.path().join(snap_name);
2381 let mut _snap_file = fs::File::create(snap_path);
2382 }
2383 purge_old_snapshot_archives(
2384 temp_snap_dir.path(),
2385 temp_snap_dir.path(),
2386 maximum_full_snapshot_archives_to_retain,
2387 maximum_incremental_snapshot_archives_to_retain,
2388 );
2389
2390 let mut retained_snaps = HashSet::new();
2391 for entry in fs::read_dir(temp_snap_dir.path()).unwrap() {
2392 let entry_path_buf = entry.unwrap().path();
2393 let entry_path = entry_path_buf.as_path();
2394 let snapshot_name = entry_path
2395 .file_name()
2396 .unwrap()
2397 .to_str()
2398 .unwrap()
2399 .to_string();
2400 retained_snaps.insert(snapshot_name);
2401 }
2402
2403 for snap_name in expected_snapshots {
2404 assert!(
2405 retained_snaps.contains(snap_name.as_str()),
2406 "{snap_name} not found"
2407 );
2408 }
2409 assert_eq!(retained_snaps.len(), expected_snapshots.len());
2410 }
2411
2412 #[test]
2413 fn test_purge_old_full_snapshot_archives() {
2414 let snap1_name = format!("snapshot-1-{}.tar.zst", Hash::default());
2415 let snap2_name = format!("snapshot-3-{}.tar.zst", Hash::default());
2416 let snap3_name = format!("snapshot-50-{}.tar.zst", Hash::default());
2417 let snapshot_names = vec![&snap1_name, &snap2_name, &snap3_name];
2418
2419 let expected_snapshots = vec![&snap3_name];
2421 common_test_purge_old_snapshot_archives(
2422 &snapshot_names,
2423 NonZeroUsize::new(1).unwrap(),
2424 DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2425 &expected_snapshots,
2426 );
2427
2428 let expected_snapshots = vec![&snap2_name, &snap3_name];
2430 common_test_purge_old_snapshot_archives(
2431 &snapshot_names,
2432 NonZeroUsize::new(2).unwrap(),
2433 DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2434 &expected_snapshots,
2435 );
2436
2437 let expected_snapshots = vec![&snap1_name, &snap2_name, &snap3_name];
2439 common_test_purge_old_snapshot_archives(
2440 &snapshot_names,
2441 NonZeroUsize::new(3).unwrap(),
2442 DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
2443 &expected_snapshots,
2444 );
2445 }
2446
2447 #[test]
2451 fn test_purge_old_full_snapshot_archives_in_the_loop() {
2452 let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2453 let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2454 let maximum_snapshots_to_retain = NonZeroUsize::new(5).unwrap();
2455 let starting_slot: Slot = 42;
2456
2457 for slot in (starting_slot..).take(100) {
2458 let full_snapshot_archive_file_name =
2459 format!("snapshot-{}-{}.tar.zst", slot, Hash::default());
2460 let full_snapshot_archive_path = full_snapshot_archives_dir
2461 .as_ref()
2462 .join(full_snapshot_archive_file_name);
2463 fs::File::create(full_snapshot_archive_path).unwrap();
2464
2465 if slot < starting_slot + maximum_snapshots_to_retain.get() as Slot {
2467 continue;
2468 }
2469
2470 if slot % (maximum_snapshots_to_retain.get() as Slot * 2) != 0 {
2472 continue;
2473 }
2474
2475 purge_old_snapshot_archives(
2476 &full_snapshot_archives_dir,
2477 &incremental_snapshot_archives_dir,
2478 maximum_snapshots_to_retain,
2479 NonZeroUsize::new(usize::MAX).unwrap(),
2480 );
2481 let mut full_snapshot_archives =
2482 full_snapshot_archives_iter(full_snapshot_archives_dir.path()).collect::<Vec<_>>();
2483 full_snapshot_archives.sort_unstable();
2484 assert_eq!(
2485 full_snapshot_archives.len(),
2486 maximum_snapshots_to_retain.get()
2487 );
2488 assert_eq!(full_snapshot_archives.last().unwrap().slot(), slot);
2489 for (i, full_snapshot_archive) in full_snapshot_archives.iter().rev().enumerate() {
2490 assert_eq!(full_snapshot_archive.slot(), slot - i as Slot);
2491 }
2492 }
2493 }
2494
2495 #[test]
2496 fn test_purge_old_incremental_snapshot_archives() {
2497 let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2498 let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2499 let starting_slot = 100_000;
2500
2501 let maximum_incremental_snapshot_archives_to_retain =
2502 DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN;
2503 let maximum_full_snapshot_archives_to_retain = DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN;
2504
2505 let incremental_snapshot_interval = 100;
2506 let num_incremental_snapshots_per_full_snapshot =
2507 maximum_incremental_snapshot_archives_to_retain.get() * 2;
2508 let full_snapshot_interval =
2509 incremental_snapshot_interval * num_incremental_snapshots_per_full_snapshot;
2510
2511 let mut snapshot_filenames = vec![];
2512 (starting_slot..)
2513 .step_by(full_snapshot_interval)
2514 .take(
2515 maximum_full_snapshot_archives_to_retain
2516 .checked_mul(NonZeroUsize::new(2).unwrap())
2517 .unwrap()
2518 .get(),
2519 )
2520 .for_each(|full_snapshot_slot| {
2521 let snapshot_filename = format!(
2522 "snapshot-{}-{}.tar.zst",
2523 full_snapshot_slot,
2524 Hash::default()
2525 );
2526 let snapshot_path = full_snapshot_archives_dir.path().join(&snapshot_filename);
2527 fs::File::create(snapshot_path).unwrap();
2528 snapshot_filenames.push(snapshot_filename);
2529
2530 (full_snapshot_slot..)
2531 .step_by(incremental_snapshot_interval)
2532 .take(num_incremental_snapshots_per_full_snapshot)
2533 .skip(1)
2534 .for_each(|incremental_snapshot_slot| {
2535 let snapshot_filename = format!(
2536 "incremental-snapshot-{}-{}-{}.tar.zst",
2537 full_snapshot_slot,
2538 incremental_snapshot_slot,
2539 Hash::default()
2540 );
2541 let snapshot_path = incremental_snapshot_archives_dir
2542 .path()
2543 .join(&snapshot_filename);
2544 fs::File::create(snapshot_path).unwrap();
2545 snapshot_filenames.push(snapshot_filename);
2546 });
2547 });
2548
2549 purge_old_snapshot_archives(
2550 full_snapshot_archives_dir.path(),
2551 incremental_snapshot_archives_dir.path(),
2552 maximum_full_snapshot_archives_to_retain,
2553 maximum_incremental_snapshot_archives_to_retain,
2554 );
2555
2556 let mut remaining_full_snapshot_archives =
2558 full_snapshot_archives_iter(full_snapshot_archives_dir.path()).collect::<Vec<_>>();
2559 assert_eq!(
2560 remaining_full_snapshot_archives.len(),
2561 maximum_full_snapshot_archives_to_retain.get(),
2562 );
2563 remaining_full_snapshot_archives.sort_unstable();
2564 let latest_full_snapshot_archive_slot =
2565 remaining_full_snapshot_archives.last().unwrap().slot();
2566
2567 let mut remaining_incremental_snapshot_archives =
2572 incremental_snapshot_archives_iter(incremental_snapshot_archives_dir.path())
2573 .collect::<Vec<_>>();
2574 assert_eq!(
2575 remaining_incremental_snapshot_archives.len(),
2576 maximum_incremental_snapshot_archives_to_retain
2577 .get()
2578 .saturating_add(
2579 maximum_full_snapshot_archives_to_retain
2580 .get()
2581 .saturating_sub(1)
2582 )
2583 );
2584 remaining_incremental_snapshot_archives.sort_unstable();
2585 remaining_incremental_snapshot_archives.reverse();
2586
2587 for i in (1..maximum_full_snapshot_archives_to_retain.get()).rev() {
2589 let incremental_snapshot_archive =
2590 remaining_incremental_snapshot_archives.pop().unwrap();
2591
2592 let expected_base_slot =
2593 latest_full_snapshot_archive_slot - (i * full_snapshot_interval) as u64;
2594 assert_eq!(incremental_snapshot_archive.base_slot(), expected_base_slot);
2595 let expected_slot = expected_base_slot
2596 + (full_snapshot_interval - incremental_snapshot_interval) as u64;
2597 assert_eq!(incremental_snapshot_archive.slot(), expected_slot);
2598 }
2599
2600 for incremental_snapshot_archive in &remaining_incremental_snapshot_archives {
2602 assert_eq!(
2603 incremental_snapshot_archive.base_slot(),
2604 latest_full_snapshot_archive_slot
2605 );
2606 }
2607
2608 let expected_remaining_incremental_snapshot_archive_slots =
2610 (latest_full_snapshot_archive_slot..)
2611 .step_by(incremental_snapshot_interval)
2612 .take(num_incremental_snapshots_per_full_snapshot)
2613 .skip(
2614 num_incremental_snapshots_per_full_snapshot
2615 - maximum_incremental_snapshot_archives_to_retain.get(),
2616 )
2617 .collect::<HashSet<_>>();
2618
2619 let actual_remaining_incremental_snapshot_archive_slots =
2620 remaining_incremental_snapshot_archives
2621 .iter()
2622 .map(|snapshot| snapshot.slot())
2623 .collect::<HashSet<_>>();
2624 assert_eq!(
2625 actual_remaining_incremental_snapshot_archive_slots,
2626 expected_remaining_incremental_snapshot_archive_slots
2627 );
2628 }
2629
2630 #[test]
2631 fn test_purge_all_incremental_snapshot_archives_when_no_full_snapshot_archives() {
2632 let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2633 let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
2634
2635 for snapshot_filenames in [
2636 format!("incremental-snapshot-100-120-{}.tar.zst", Hash::default()),
2637 format!("incremental-snapshot-100-140-{}.tar.zst", Hash::default()),
2638 format!("incremental-snapshot-100-160-{}.tar.zst", Hash::default()),
2639 format!("incremental-snapshot-100-180-{}.tar.zst", Hash::default()),
2640 format!("incremental-snapshot-200-220-{}.tar.zst", Hash::default()),
2641 format!("incremental-snapshot-200-240-{}.tar.zst", Hash::default()),
2642 format!("incremental-snapshot-200-260-{}.tar.zst", Hash::default()),
2643 format!("incremental-snapshot-200-280-{}.tar.zst", Hash::default()),
2644 ] {
2645 let snapshot_path = incremental_snapshot_archives_dir
2646 .path()
2647 .join(snapshot_filenames);
2648 fs::File::create(snapshot_path).unwrap();
2649 }
2650
2651 purge_old_snapshot_archives(
2652 full_snapshot_archives_dir.path(),
2653 incremental_snapshot_archives_dir.path(),
2654 NonZeroUsize::new(usize::MAX).unwrap(),
2655 NonZeroUsize::new(usize::MAX).unwrap(),
2656 );
2657
2658 let remaining_incremental_snapshot_archives =
2659 incremental_snapshot_archives_iter(incremental_snapshot_archives_dir.path())
2660 .collect::<Vec<_>>();
2661 assert!(remaining_incremental_snapshot_archives.is_empty());
2662 }
2663
2664 #[test]
2665 fn test_get_snapshot_file_kind() {
2666 assert_eq!(None, get_snapshot_file_kind("file.txt"));
2667 assert_eq!(
2668 Some(SnapshotFileKind::Version),
2669 get_snapshot_file_kind(snapshot_paths::SNAPSHOT_VERSION_FILENAME)
2670 );
2671 assert_eq!(
2672 Some(SnapshotFileKind::BankFields),
2673 get_snapshot_file_kind("1234")
2674 );
2675 assert_eq!(
2676 Some(SnapshotFileKind::Storage),
2677 get_snapshot_file_kind("1000.999")
2678 );
2679 }
2680
2681 #[test_case(0)]
2682 #[test_case(1)]
2683 #[test_case(10)]
2684 fn test_serialize_deserialize_account_storage_entries(num_storages: u64) {
2685 let temp_dir = tempfile::tempdir().unwrap();
2686 let bank_snapshot_dir = temp_dir.path();
2687 let snapshot_slot = num_storages + 1 as Slot;
2688
2689 let mut snapshot_storages = Vec::new();
2691 for i in 0..num_storages {
2692 let storage = Arc::new(AccountStorageEntry::new(
2693 &PathBuf::new(),
2694 i, i as u32, 1024,
2697 AccountsFileProvider::AppendVec,
2698 ));
2699 snapshot_storages.push(storage);
2700 }
2701
2702 write_obsolete_accounts_to_snapshot(
2704 bank_snapshot_dir,
2705 &snapshot_storages,
2706 snapshot_slot,
2707 &IoSetupState::default(),
2708 )
2709 .unwrap();
2710
2711 let mut deserialized_accounts =
2713 deserialize_obsolete_accounts(bank_snapshot_dir, MAX_OBSOLETE_ACCOUNTS_FILE_SIZE)
2714 .unwrap()
2715 .into_hashmap();
2716
2717 for storage in &snapshot_storages {
2719 let obsolete_accounts = deserialized_accounts.remove(&storage.slot()).unwrap();
2720 assert!(obsolete_accounts.into_tuple().2 == 0);
2721 }
2722 }
2723
2724 #[test]
2725 #[should_panic(expected = "bytes would exceed limit of 100")]
2726 fn test_serialize_obsolete_accounts_too_large_file() {
2727 let temp_dir = tempfile::tempdir().unwrap();
2728 let bank_snapshot_dir = temp_dir.path();
2729 let num_storages = 10;
2730 let snapshot_slot = num_storages + 1 as Slot;
2731
2732 let mut snapshot_storages = Vec::new();
2734 for i in 0..num_storages {
2735 let storage = Arc::new(AccountStorageEntry::new(
2736 &PathBuf::new(),
2737 i, i as u32, 1024,
2740 AccountsFileProvider::AppendVec,
2741 ));
2742 snapshot_storages.push(storage);
2743 }
2744
2745 let obsolete_accounts =
2747 SerdeObsoleteAccountsMap::new_from_storages(&snapshot_storages, snapshot_slot);
2748
2749 serialize_obsolete_accounts(
2751 bank_snapshot_dir,
2752 &obsolete_accounts,
2753 100,
2754 &IoSetupState::default(),
2755 )
2756 .unwrap();
2757 }
2758
2759 #[test]
2760 #[should_panic(expected = "too large obsolete accounts file to deserialize")]
2761 fn test_deserialize_obsolete_accounts_too_large_file() {
2762 let temp_dir = tempfile::tempdir().unwrap();
2763 let bank_snapshot_dir = temp_dir.path();
2764 let num_storages = 10;
2765 let snapshot_slot = num_storages + 1 as Slot;
2766
2767 let mut snapshot_storages = Vec::new();
2769 for i in 0..num_storages {
2770 let storage = Arc::new(AccountStorageEntry::new(
2771 &PathBuf::new(),
2772 i, i as u32, 1024,
2775 AccountsFileProvider::AppendVec,
2776 ));
2777 snapshot_storages.push(storage);
2778 }
2779
2780 write_obsolete_accounts_to_snapshot(
2782 bank_snapshot_dir,
2783 &snapshot_storages,
2784 snapshot_slot,
2785 &IoSetupState::default(),
2786 )
2787 .unwrap();
2788
2789 deserialize_obsolete_accounts(bank_snapshot_dir, 100).unwrap();
2792 }
2793
2794 #[test]
2795 fn test_is_bank_snapshot_complete() {
2796 let temp_dir = TempDir::new().unwrap();
2797 let slot = 123;
2798 let bank_snapshot_dir = temp_dir.as_ref().join(slot.to_string());
2799 fs::create_dir(&bank_snapshot_dir).unwrap();
2800
2801 let version_path = bank_snapshot_dir.join(snapshot_paths::SNAPSHOT_VERSION_FILENAME);
2802 let serialized_bank_path = bank_snapshot_dir.join(slot.to_string());
2803 let status_cache_path =
2804 bank_snapshot_dir.join(snapshot_paths::SNAPSHOT_STATUS_CACHE_FILENAME);
2805
2806 assert!(!is_bank_snapshot_complete(&bank_snapshot_dir));
2808
2809 let too_large = format!(
2811 "{:v>width$}",
2812 "hi",
2813 width = (MAX_SNAPSHOT_VERSION_FILE_SIZE + 1) as usize,
2814 );
2815 fs::write(&version_path, too_large).unwrap();
2816 assert!(!is_bank_snapshot_complete(&bank_snapshot_dir));
2817
2818 fs::remove_file(&version_path).unwrap();
2820 let bad_version = String::from("v0.0.0");
2821 fs::write(&version_path, bad_version).unwrap();
2822 assert!(!is_bank_snapshot_complete(&bank_snapshot_dir));
2823
2824 fs::remove_file(&version_path).unwrap();
2826 fs::File::create_new(&version_path).unwrap();
2827 assert!(!is_bank_snapshot_complete(&bank_snapshot_dir));
2828
2829 fs::remove_file(&version_path).unwrap();
2831 fs::write(&version_path, SnapshotVersion::default().as_str()).unwrap();
2832
2833 assert!(!is_bank_snapshot_complete(&bank_snapshot_dir));
2835
2836 fs::File::create_new(&serialized_bank_path).unwrap();
2838 assert!(!is_bank_snapshot_complete(&bank_snapshot_dir));
2839
2840 fs::remove_file(&serialized_bank_path).unwrap();
2842 fs::write(&serialized_bank_path, "serialized bank").unwrap();
2843
2844 assert!(!is_bank_snapshot_complete(&bank_snapshot_dir));
2846
2847 fs::File::create_new(&status_cache_path).unwrap();
2849 assert!(!is_bank_snapshot_complete(&bank_snapshot_dir));
2850
2851 fs::remove_file(&status_cache_path).unwrap();
2853 fs::write(&status_cache_path, "status cache").unwrap();
2854
2855 assert!(is_bank_snapshot_complete(bank_snapshot_dir));
2857 }
2858
2859 #[test]
2860 fn test_prune_stale_storages() {
2861 let account_path = tempfile::TempDir::new().unwrap();
2862 let keep_a = account_path.path().join(AccountsFile::file_name(100, 1));
2864 let keep_b = account_path.path().join(AccountsFile::file_name(200, 2));
2865 let stale = account_path.path().join(AccountsFile::file_name(300, 3));
2867 let untouched = account_path.path().join("something_else.txt");
2869 for path in [&keep_a, &keep_b, &stale, &untouched] {
2870 fs::write(path, b"x").unwrap();
2871 }
2872
2873 let storages_list = StoragesList::from_items(vec![
2874 StorageListItem { slot: 100, id: 1 },
2875 StorageListItem { slot: 200, id: 2 },
2876 ]);
2877 prune_stale_storages(
2878 std::slice::from_ref(&account_path.path().to_path_buf()),
2879 storages_list,
2880 )
2881 .unwrap();
2882
2883 assert!(keep_a.exists(), "expected storage file was deleted");
2884 assert!(keep_b.exists(), "expected storage file was deleted");
2885 assert!(!stale.exists(), "stale storage file was not removed");
2886 assert!(untouched.exists(), "non-storage file was wrongly removed");
2887 }
2888}