solana_runtime/
snapshot_package.rs

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