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        accounts::Accounts,
11        accounts_db::AccountStorageEntry,
12        accounts_hash::{
13            AccountsDeltaHash, AccountsHash, AccountsHashKind, MerkleOrLatticeAccountsHash,
14        },
15        epoch_accounts_hash::EpochAccountsHash,
16    },
17    solana_clock::Slot,
18    solana_epoch_schedule::EpochSchedule,
19    solana_hash::Hash,
20    solana_rent_collector::RentCollector,
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                must_include_epoch_accounts_hash: bank
99                    .must_include_epoch_accounts_hash_in_snapshot(),
100                write_version,
101            }
102        };
103
104        let accounts_hash_algorithm = if bank.is_snapshots_lt_hash_enabled() {
105            AccountsHashAlgorithm::Lattice
106        } else {
107            AccountsHashAlgorithm::Merkle
108        };
109        Self::_new(
110            package_kind,
111            bank,
112            snapshot_storages,
113            accounts_hash_for_testing,
114            accounts_hash_algorithm,
115            Some(snapshot_info),
116        )
117    }
118
119    /// Package up fields needed to compute an EpochAccountsHash
120    #[must_use]
121    pub fn new_for_epoch_accounts_hash(
122        package_kind: AccountsPackageKind,
123        bank: &Bank,
124        snapshot_storages: Vec<Arc<AccountStorageEntry>>,
125        accounts_hash_for_testing: Option<AccountsHash>,
126    ) -> Self {
127        assert_eq!(package_kind, AccountsPackageKind::EpochAccountsHash);
128        Self::_new(
129            package_kind,
130            bank,
131            snapshot_storages,
132            accounts_hash_for_testing,
133            AccountsHashAlgorithm::Merkle,
134            None,
135        )
136    }
137
138    fn _new(
139        package_kind: AccountsPackageKind,
140        bank: &Bank,
141        snapshot_storages: Vec<Arc<AccountStorageEntry>>,
142        accounts_hash_for_testing: Option<AccountsHash>,
143        accounts_hash_algorithm: AccountsHashAlgorithm,
144        snapshot_info: Option<SupplementalSnapshotInfo>,
145    ) -> Self {
146        Self {
147            package_kind,
148            slot: bank.slot(),
149            block_height: bank.block_height(),
150            snapshot_storages,
151            expected_capitalization: bank.capitalization(),
152            accounts_hash_for_testing,
153            accounts: bank.accounts(),
154            epoch_schedule: bank.epoch_schedule().clone(),
155            rent_collector: bank.rent_collector().clone(),
156            accounts_hash_algorithm,
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::EpochAccountsHash,
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            accounts_hash_algorithm: AccountsHashAlgorithm::Merkle,
180            snapshot_info: Some(SupplementalSnapshotInfo {
181                status_cache_slot_deltas: Vec::default(),
182                bank_fields_to_serialize: BankFieldsToSerialize::default_for_tests(),
183                bank_hash_stats: BankHashStats::default(),
184                accounts_delta_hash: AccountsDeltaHash(Hash::default()),
185                must_include_epoch_accounts_hash: false,
186                write_version: u64::default(),
187            }),
188            enqueued: Instant::now(),
189        }
190    }
191}
192
193impl std::fmt::Debug for AccountsPackage {
194    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195        f.debug_struct("AccountsPackage")
196            .field("kind", &self.package_kind)
197            .field("slot", &self.slot)
198            .field("block_height", &self.block_height)
199            .field("accounts_hash_algorithm", &self.accounts_hash_algorithm)
200            .finish_non_exhaustive()
201    }
202}
203
204/// Supplemental information needed for snapshots
205pub struct SupplementalSnapshotInfo {
206    pub status_cache_slot_deltas: Vec<BankSlotDelta>,
207    pub bank_fields_to_serialize: BankFieldsToSerialize,
208    pub bank_hash_stats: BankHashStats,
209    pub accounts_delta_hash: AccountsDeltaHash,
210    pub must_include_epoch_accounts_hash: bool,
211    pub write_version: u64,
212}
213
214/// Accounts packages are sent to the Accounts Hash Verifier for processing.  There are multiple
215/// types of accounts packages, which are specified as variants in this enum.  All accounts
216/// packages do share some processing: such as calculating the accounts hash.
217#[derive(Debug, Copy, Clone, Eq, PartialEq)]
218pub enum AccountsPackageKind {
219    Snapshot(SnapshotKind),
220    EpochAccountsHash,
221}
222
223/// This struct packages up fields to send from AccountsHashVerifier to SnapshotPackagerService
224pub struct SnapshotPackage {
225    pub snapshot_kind: SnapshotKind,
226    pub slot: Slot,
227    pub block_height: Slot,
228    pub hash: SnapshotHash,
229    pub snapshot_storages: Vec<Arc<AccountStorageEntry>>,
230    pub status_cache_slot_deltas: Vec<BankSlotDelta>,
231    pub bank_fields_to_serialize: BankFieldsToSerialize,
232    pub bank_hash_stats: BankHashStats,
233    pub accounts_delta_hash: AccountsDeltaHash,
234    pub accounts_hash: AccountsHash,
235    pub epoch_accounts_hash: Option<EpochAccountsHash>,
236    pub write_version: u64,
237    pub bank_incremental_snapshot_persistence: Option<BankIncrementalSnapshotPersistence>,
238
239    /// The instant this snapshot package was sent to the queue.
240    /// Used to track how long snapshot packages wait before handling.
241    pub enqueued: Instant,
242}
243
244impl SnapshotPackage {
245    pub fn new(
246        accounts_package: AccountsPackage,
247        merkle_or_lattice_accounts_hash: MerkleOrLatticeAccountsHash,
248        bank_incremental_snapshot_persistence: Option<BankIncrementalSnapshotPersistence>,
249    ) -> Self {
250        let AccountsPackageKind::Snapshot(kind) = accounts_package.package_kind else {
251            panic!(
252                "The AccountsPackage must be of kind Snapshot in order to make a SnapshotPackage!"
253            );
254        };
255        let Some(snapshot_info) = accounts_package.snapshot_info else {
256            panic!(
257                "The AccountsPackage must have snapshot info in order to make a SnapshotPackage!"
258            );
259        };
260
261        let accounts_hash = match merkle_or_lattice_accounts_hash {
262            MerkleOrLatticeAccountsHash::Merkle(accounts_hash_kind) => {
263                match accounts_hash_kind {
264                    AccountsHashKind::Full(accounts_hash) => accounts_hash,
265                    AccountsHashKind::Incremental(_) => {
266                        // The accounts hash is only needed when serializing a full snapshot.
267                        // When serializing an incremental snapshot, there will not be a full accounts hash
268                        // at `slot`.  In that case, use the default, because it doesn't actually get used.
269                        // The incremental snapshot will use the BankIncrementalSnapshotPersistence
270                        // field, so ensure it is Some.
271                        assert!(bank_incremental_snapshot_persistence.is_some());
272                        AccountsHash(Hash::default())
273                    }
274                }
275            }
276            MerkleOrLatticeAccountsHash::Lattice => {
277                // This is the merkle-based accounts hash, which isn't used in the Lattice case,
278                // so any value is fine here.
279                AccountsHash(Hash::default())
280            }
281        };
282
283        let epoch_accounts_hash = snapshot_info.must_include_epoch_accounts_hash.then(|| {
284            // If we were told we must include the EAH in the snapshot, go retrieve it now.
285            // SAFETY: Snapshot handling happens sequentially, and EAH requests must be handled
286            // prior to snapshot requests for higher slots.  Therefore, a snapshot for a slot
287            // in the EAH calculation window is guaranteed to have been handled by AHV after the
288            // EAH request.  This guarantees the EAH calc has completed prior to here.
289            accounts_package
290                .accounts
291                .accounts_db
292                .epoch_accounts_hash_manager
293                .try_get_epoch_accounts_hash()
294                .unwrap()
295        });
296
297        Self {
298            snapshot_kind: kind,
299            slot: accounts_package.slot,
300            block_height: accounts_package.block_height,
301            hash: SnapshotHash::new(
302                &merkle_or_lattice_accounts_hash,
303                epoch_accounts_hash.as_ref(),
304                snapshot_info
305                    .bank_fields_to_serialize
306                    .accounts_lt_hash
307                    .as_ref()
308                    .map(|accounts_lt_hash| accounts_lt_hash.0.checksum()),
309            ),
310            snapshot_storages: accounts_package.snapshot_storages,
311            status_cache_slot_deltas: snapshot_info.status_cache_slot_deltas,
312            bank_fields_to_serialize: snapshot_info.bank_fields_to_serialize,
313            accounts_delta_hash: snapshot_info.accounts_delta_hash,
314            bank_hash_stats: snapshot_info.bank_hash_stats,
315            accounts_hash,
316            epoch_accounts_hash,
317            bank_incremental_snapshot_persistence,
318            write_version: snapshot_info.write_version,
319            enqueued: Instant::now(),
320        }
321    }
322}
323
324#[cfg(feature = "dev-context-only-utils")]
325impl SnapshotPackage {
326    /// Create a new SnapshotPackage where basically every field is defaulted.
327    /// Only use for tests; many of the fields are invalid!
328    pub fn default_for_tests() -> Self {
329        Self {
330            snapshot_kind: SnapshotKind::FullSnapshot,
331            slot: Slot::default(),
332            block_height: Slot::default(),
333            hash: SnapshotHash(Hash::default()),
334            snapshot_storages: Vec::default(),
335            status_cache_slot_deltas: Vec::default(),
336            bank_fields_to_serialize: BankFieldsToSerialize::default_for_tests(),
337            accounts_delta_hash: AccountsDeltaHash(Hash::default()),
338            bank_hash_stats: BankHashStats::default(),
339            accounts_hash: AccountsHash(Hash::default()),
340            epoch_accounts_hash: None,
341            bank_incremental_snapshot_persistence: None,
342            write_version: u64::default(),
343            enqueued: Instant::now(),
344        }
345    }
346}
347
348impl std::fmt::Debug for SnapshotPackage {
349    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
350        f.debug_struct("SnapshotPackage")
351            .field("kind", &self.snapshot_kind)
352            .field("slot", &self.slot)
353            .field("block_height", &self.block_height)
354            .finish_non_exhaustive()
355    }
356}
357
358/// Snapshots come in two kinds, Full and Incremental.  The IncrementalSnapshot has a Slot field,
359/// which is the incremental snapshot base slot.
360#[derive(Clone, Copy, Debug, Eq, PartialEq)]
361pub enum SnapshotKind {
362    FullSnapshot,
363    IncrementalSnapshot(Slot),
364}
365
366impl SnapshotKind {
367    pub fn is_full_snapshot(&self) -> bool {
368        matches!(self, SnapshotKind::FullSnapshot)
369    }
370    pub fn is_incremental_snapshot(&self) -> bool {
371        matches!(self, SnapshotKind::IncrementalSnapshot(_))
372    }
373}
374
375/// Which algorithm should be used to calculate the accounts hash?
376#[derive(Debug, Copy, Clone, Eq, PartialEq)]
377pub enum AccountsHashAlgorithm {
378    /// Merkle-based accounts hash algorithm
379    Merkle,
380    /// Lattice-based accounts hash algorithm
381    Lattice,
382}