solana_runtime/
snapshot_package.rs

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