solana_runtime/
snapshot_package.rs

1use {
2    crate::{
3        bank::{Bank, BankFieldsToSerialize, BankSlotDelta},
4        serde_snapshot::BankIncrementalSnapshotPersistence,
5        snapshot_hash::SnapshotHash,
6    },
7    log::*,
8    solana_accounts_db::{
9        account_storage::meta::StoredMetaWriteVersion,
10        accounts::Accounts,
11        accounts_db::{stats::BankHashStats, AccountStorageEntry},
12        accounts_hash::{AccountsDeltaHash, AccountsHash, AccountsHashKind},
13        epoch_accounts_hash::EpochAccountsHash,
14    },
15    solana_sdk::{
16        clock::Slot, hash::Hash, rent_collector::RentCollector,
17        sysvar::epoch_schedule::EpochSchedule,
18    },
19    std::{
20        sync::{atomic::Ordering, Arc},
21        time::Instant,
22    },
23};
24
25mod compare;
26pub use compare::*;
27
28/// This struct packages up fields to send from AccountsBackgroundService to AccountsHashVerifier
29pub struct AccountsPackage {
30    pub package_kind: AccountsPackageKind,
31    pub slot: Slot,
32    pub block_height: Slot,
33    pub snapshot_storages: Vec<Arc<AccountStorageEntry>>,
34    pub expected_capitalization: u64,
35    pub accounts_hash_for_testing: Option<AccountsHash>,
36    pub accounts: Arc<Accounts>,
37    pub epoch_schedule: EpochSchedule,
38    pub rent_collector: RentCollector,
39
40    /// Supplemental information needed for snapshots
41    pub snapshot_info: Option<SupplementalSnapshotInfo>,
42
43    /// The instant this accounts package was send to the queue.
44    /// Used to track how long accounts packages wait before processing.
45    pub enqueued: Instant,
46}
47
48impl AccountsPackage {
49    /// Package up bank files, storages, and slot deltas for a snapshot
50    pub fn new_for_snapshot(
51        package_kind: AccountsPackageKind,
52        bank: &Bank,
53        snapshot_storages: Vec<Arc<AccountStorageEntry>>,
54        status_cache_slot_deltas: Vec<BankSlotDelta>,
55        accounts_hash_for_testing: Option<AccountsHash>,
56    ) -> Self {
57        let slot = bank.slot();
58        if let AccountsPackageKind::Snapshot(snapshot_kind) = package_kind {
59            info!(
60                "Package snapshot for bank {} has {} account storage entries (snapshot kind: {:?})",
61                slot,
62                snapshot_storages.len(),
63                snapshot_kind,
64            );
65            if let SnapshotKind::IncrementalSnapshot(incremental_snapshot_base_slot) = snapshot_kind
66            {
67                assert!(
68                    slot > incremental_snapshot_base_slot,
69                    "Incremental snapshot base slot must be less than the bank being snapshotted!"
70                );
71            }
72        }
73
74        let snapshot_info = {
75            let accounts_db = &bank.rc.accounts.accounts_db;
76            let write_version = accounts_db.write_version.load(Ordering::Acquire);
77            // SAFETY: There *must* be an accounts delta hash for this slot.
78            // Since we only snapshot rooted slots, and we know rooted slots must be frozen,
79            // that guarantees this slot will have an accounts delta hash.
80            let accounts_delta_hash = accounts_db.get_accounts_delta_hash(slot).unwrap();
81            // SAFETY: Every slot *must* have a BankHashStats entry in AccountsDb.
82            let bank_hash_stats = accounts_db.get_bank_hash_stats(slot).unwrap();
83            let bank_fields_to_serialize = bank.get_fields_to_serialize();
84            SupplementalSnapshotInfo {
85                status_cache_slot_deltas,
86                bank_fields_to_serialize,
87                bank_hash_stats,
88                accounts_delta_hash,
89                must_include_epoch_accounts_hash: bank
90                    .must_include_epoch_accounts_hash_in_snapshot(),
91                write_version,
92            }
93        };
94
95        Self::_new(
96            package_kind,
97            bank,
98            snapshot_storages,
99            accounts_hash_for_testing,
100            Some(snapshot_info),
101        )
102    }
103
104    /// Package up fields needed to verify an accounts hash
105    #[must_use]
106    pub fn new_for_accounts_hash_verifier(
107        package_kind: AccountsPackageKind,
108        bank: &Bank,
109        snapshot_storages: Vec<Arc<AccountStorageEntry>>,
110        accounts_hash_for_testing: Option<AccountsHash>,
111    ) -> Self {
112        assert_eq!(package_kind, AccountsPackageKind::AccountsHashVerifier);
113        Self::_new(
114            package_kind,
115            bank,
116            snapshot_storages,
117            accounts_hash_for_testing,
118            None,
119        )
120    }
121
122    /// Package up fields needed to compute an EpochAccountsHash
123    #[must_use]
124    pub fn new_for_epoch_accounts_hash(
125        package_kind: AccountsPackageKind,
126        bank: &Bank,
127        snapshot_storages: Vec<Arc<AccountStorageEntry>>,
128        accounts_hash_for_testing: Option<AccountsHash>,
129    ) -> Self {
130        assert_eq!(package_kind, AccountsPackageKind::EpochAccountsHash);
131        Self::_new(
132            package_kind,
133            bank,
134            snapshot_storages,
135            accounts_hash_for_testing,
136            None,
137        )
138    }
139
140    fn _new(
141        package_kind: AccountsPackageKind,
142        bank: &Bank,
143        snapshot_storages: Vec<Arc<AccountStorageEntry>>,
144        accounts_hash_for_testing: Option<AccountsHash>,
145        snapshot_info: Option<SupplementalSnapshotInfo>,
146    ) -> Self {
147        Self {
148            package_kind,
149            slot: bank.slot(),
150            block_height: bank.block_height(),
151            snapshot_storages,
152            expected_capitalization: bank.capitalization(),
153            accounts_hash_for_testing,
154            accounts: bank.accounts(),
155            epoch_schedule: bank.epoch_schedule().clone(),
156            rent_collector: bank.rent_collector().clone(),
157            snapshot_info,
158            enqueued: Instant::now(),
159        }
160    }
161
162    /// Create a new Accounts Package where basically every field is defaulted.
163    /// Only use for tests; many of the fields are invalid!
164    #[cfg(feature = "dev-context-only-utils")]
165    pub fn default_for_tests() -> Self {
166        use solana_accounts_db::accounts_db::AccountsDb;
167        let accounts_db = AccountsDb::default_for_tests();
168        let accounts = Accounts::new(Arc::new(accounts_db));
169        Self {
170            package_kind: AccountsPackageKind::AccountsHashVerifier,
171            slot: Slot::default(),
172            block_height: Slot::default(),
173            snapshot_storages: Vec::default(),
174            expected_capitalization: u64::default(),
175            accounts_hash_for_testing: Option::default(),
176            accounts: Arc::new(accounts),
177            epoch_schedule: EpochSchedule::default(),
178            rent_collector: RentCollector::default(),
179            snapshot_info: Some(SupplementalSnapshotInfo {
180                status_cache_slot_deltas: Vec::default(),
181                bank_fields_to_serialize: BankFieldsToSerialize::default_for_tests(),
182                bank_hash_stats: BankHashStats::default(),
183                accounts_delta_hash: AccountsDeltaHash(Hash::default()),
184                must_include_epoch_accounts_hash: false,
185                write_version: StoredMetaWriteVersion::default(),
186            }),
187            enqueued: Instant::now(),
188        }
189    }
190}
191
192impl std::fmt::Debug for AccountsPackage {
193    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194        f.debug_struct("AccountsPackage")
195            .field("kind", &self.package_kind)
196            .field("slot", &self.slot)
197            .field("block_height", &self.block_height)
198            .finish_non_exhaustive()
199    }
200}
201
202/// Supplemental information needed for snapshots
203pub struct SupplementalSnapshotInfo {
204    pub status_cache_slot_deltas: Vec<BankSlotDelta>,
205    pub bank_fields_to_serialize: BankFieldsToSerialize,
206    pub bank_hash_stats: BankHashStats,
207    pub accounts_delta_hash: AccountsDeltaHash,
208    pub must_include_epoch_accounts_hash: bool,
209    pub write_version: StoredMetaWriteVersion,
210}
211
212/// Accounts packages are sent to the Accounts Hash Verifier for processing.  There are multiple
213/// types of accounts packages, which are specified as variants in this enum.  All accounts
214/// packages do share some processing: such as calculating the accounts hash.
215#[derive(Debug, Copy, Clone, Eq, PartialEq)]
216pub enum AccountsPackageKind {
217    AccountsHashVerifier,
218    Snapshot(SnapshotKind),
219    EpochAccountsHash,
220}
221
222/// This struct packages up fields to send from AccountsHashVerifier to SnapshotPackagerService
223pub struct SnapshotPackage {
224    pub snapshot_kind: SnapshotKind,
225    pub slot: Slot,
226    pub block_height: Slot,
227    pub hash: SnapshotHash,
228    pub snapshot_storages: Vec<Arc<AccountStorageEntry>>,
229    pub status_cache_slot_deltas: Vec<BankSlotDelta>,
230    pub bank_fields_to_serialize: BankFieldsToSerialize,
231    pub bank_hash_stats: BankHashStats,
232    pub accounts_delta_hash: AccountsDeltaHash,
233    pub accounts_hash: AccountsHash,
234    pub epoch_accounts_hash: Option<EpochAccountsHash>,
235    pub write_version: StoredMetaWriteVersion,
236    pub bank_incremental_snapshot_persistence: Option<BankIncrementalSnapshotPersistence>,
237
238    /// The instant this snapshot package was sent to the queue.
239    /// Used to track how long snapshot packages wait before handling.
240    pub enqueued: Instant,
241}
242
243impl SnapshotPackage {
244    pub fn new(
245        accounts_package: AccountsPackage,
246        accounts_hash_kind: AccountsHashKind,
247        bank_incremental_snapshot_persistence: Option<BankIncrementalSnapshotPersistence>,
248    ) -> Self {
249        let AccountsPackageKind::Snapshot(kind) = accounts_package.package_kind else {
250            panic!(
251                "The AccountsPackage must be of kind Snapshot in order to make a SnapshotPackage!"
252            );
253        };
254        let Some(snapshot_info) = accounts_package.snapshot_info else {
255            panic!(
256                "The AccountsPackage must have snapshot info in order to make a SnapshotPackage!"
257            );
258        };
259
260        let accounts_hash = match accounts_hash_kind {
261            AccountsHashKind::Full(accounts_hash) => accounts_hash,
262            AccountsHashKind::Incremental(_) => {
263                // The accounts hash is only needed when serializing a full snapshot.
264                // When serializing an incremental snapshot, there will not be a full accounts hash
265                // at `slot`.  In that case, use the default, because it doesn't actually get used.
266                // The incremental snapshot will use the BankIncrementalSnapshotPersistence
267                // field, so ensure it is Some.
268                assert!(bank_incremental_snapshot_persistence.is_some());
269                AccountsHash(Hash::default())
270            }
271        };
272
273        let epoch_accounts_hash = snapshot_info.must_include_epoch_accounts_hash.then(|| {
274            // If we were told we must include the EAH in the snapshot, go retrieve it now.
275            // SAFETY: Snapshot handling happens sequentially, and EAH requests must be handled
276            // prior to snapshot requests for higher slots.  Therefore, a snapshot for a slot
277            // in the EAH calculation window is guaranteed to have been handled by AHV after the
278            // EAH request.  This guarantees the EAH calc has completed prior to here.
279            accounts_package
280                .accounts
281                .accounts_db
282                .epoch_accounts_hash_manager
283                .try_get_epoch_accounts_hash()
284                .unwrap()
285        });
286
287        Self {
288            snapshot_kind: kind,
289            slot: accounts_package.slot,
290            block_height: accounts_package.block_height,
291            hash: SnapshotHash::new(&accounts_hash_kind, epoch_accounts_hash.as_ref()),
292            snapshot_storages: accounts_package.snapshot_storages,
293            status_cache_slot_deltas: snapshot_info.status_cache_slot_deltas,
294            bank_fields_to_serialize: snapshot_info.bank_fields_to_serialize,
295            accounts_delta_hash: snapshot_info.accounts_delta_hash,
296            bank_hash_stats: snapshot_info.bank_hash_stats,
297            accounts_hash,
298            epoch_accounts_hash,
299            bank_incremental_snapshot_persistence,
300            write_version: snapshot_info.write_version,
301            enqueued: Instant::now(),
302        }
303    }
304}
305
306#[cfg(feature = "dev-context-only-utils")]
307impl SnapshotPackage {
308    /// Create a new SnapshotPackage where basically every field is defaulted.
309    /// Only use for tests; many of the fields are invalid!
310    pub fn default_for_tests() -> Self {
311        Self {
312            snapshot_kind: SnapshotKind::FullSnapshot,
313            slot: Slot::default(),
314            block_height: Slot::default(),
315            hash: SnapshotHash(Hash::default()),
316            snapshot_storages: Vec::default(),
317            status_cache_slot_deltas: Vec::default(),
318            bank_fields_to_serialize: BankFieldsToSerialize::default_for_tests(),
319            accounts_delta_hash: AccountsDeltaHash(Hash::default()),
320            bank_hash_stats: BankHashStats::default(),
321            accounts_hash: AccountsHash(Hash::default()),
322            epoch_accounts_hash: None,
323            bank_incremental_snapshot_persistence: None,
324            write_version: StoredMetaWriteVersion::default(),
325            enqueued: Instant::now(),
326        }
327    }
328}
329
330impl std::fmt::Debug for SnapshotPackage {
331    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
332        f.debug_struct("SnapshotPackage")
333            .field("kind", &self.snapshot_kind)
334            .field("slot", &self.slot)
335            .field("block_height", &self.block_height)
336            .finish_non_exhaustive()
337    }
338}
339
340/// Snapshots come in two kinds, Full and Incremental.  The IncrementalSnapshot has a Slot field,
341/// which is the incremental snapshot base slot.
342#[derive(Clone, Copy, Debug, Eq, PartialEq)]
343pub enum SnapshotKind {
344    FullSnapshot,
345    IncrementalSnapshot(Slot),
346}
347
348impl SnapshotKind {
349    pub fn is_full_snapshot(&self) -> bool {
350        matches!(self, SnapshotKind::FullSnapshot)
351    }
352    pub fn is_incremental_snapshot(&self) -> bool {
353        matches!(self, SnapshotKind::IncrementalSnapshot(_))
354    }
355}